Received: from malur.postgresql.org ([217.196.149.56]) by arkaria.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.96) (envelope-from ) id 1wh92z-00028d-0g for pgsql-hackers@arkaria.postgresql.org; Tue, 07 Jul 2026 16:53:25 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.96) (envelope-from ) id 1wh92x-001nGw-2P for pgsql-hackers@arkaria.postgresql.org; Tue, 07 Jul 2026 16:53:24 +0000 Received: from makus.postgresql.org ([2001:4800:3e1:1::229]) by malur.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.96) (envelope-from ) id 1wh92x-001nGi-0k for pgsql-hackers@lists.postgresql.org; Tue, 07 Jul 2026 16:53:24 +0000 Received: from mail-ej1-x62a.google.com ([2a00:1450:4864:20::62a]) by makus.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (Exim 4.98.2) (envelope-from ) id 1wh92t-00000000118-0rv0 for pgsql-hackers@lists.postgresql.org; Tue, 07 Jul 2026 16:53:23 +0000 Received: by mail-ej1-x62a.google.com with SMTP id a640c23a62f3a-c1214350188so469826666b.0 for ; Tue, 07 Jul 2026 09:53:19 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1783443197; cv=none; d=google.com; s=arc-20260327; b=DmB/I+kkVWrp+dgyEleGTx/nvkBAN4yscQHfD098BTM7VfracP3HPQMFnq5Eby2aXb 45Q2HNIMStUw7yl6qMO+5byy5jwoPyGbQEC+x3aT6jjS4aYeN+64I9pYJhaJTPNcuijb wo8jNh3VzzRKqtVYIEB4gl7Ry7s3LhTVZmvX991t9fwIUm4+xZCmC7ZufmNidPPBxJPl beJsDDIZ7X8qU3LSG4i60BcbnYZZO89nT+bt90+iPaNvwms6ZEunHJad868JRn5MgBk+ ztq26L7Ky6VtKx81ka205zUgJ4JpmKYHas/hRnM0H2cwrY6MTbyO/+imMIp0DgSQluXJ jlqA== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20260327; h=cc:to:subject:message-id:date:from:in-reply-to:references :mime-version:dkim-signature; bh=cB+giE2QIaWdJ8+RTlys+3SLvIKxbttjNFnqdtlPUDk=; fh=Do15ivUni5JgRtChBFN2IdfBcDfdT6Uyi5RQVvDmDfI=; b=foPwmTBI4Sk5XielaOF4u3qg0qJrtICDGT5njZzoi9yLso0PQKtGeaVJw3dap+AEIA fl4n/BYfwuKIm5cs/uM3NohF+rlXas5RRVJZvEKnxW4oeV2jMhAhALq2TWHjQlJlumpM nwIyz9IFh8KxztYI5NxqnXt/Acrl3OQ/PxDog0QD2F0Hx5RmjZtjI772TPDgjQf3jGPJ hSRvAjRcP5lbag/XZSEkl5Q/2Y+2ltJrLsRReHhkAUirEn37XhFiTOKLYQRNC5yLmVPr qbesE/SpAGxIuII9ihSHPh4IgWHVV+gEGuyd7prGlCdoEm18NFLkmD0m7vgj0t7ZF8bg pYsw==; darn=lists.postgresql.org ARC-Authentication-Results: i=1; mx.google.com; arc=none DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1783443197; x=1784047997; darn=lists.postgresql.org; h=content-type:cc:to:subject:message-id:date:from:in-reply-to :references:mime-version:from:to:cc:subject:date:message-id:reply-to :content-type; bh=cB+giE2QIaWdJ8+RTlys+3SLvIKxbttjNFnqdtlPUDk=; b=kQTK9W5Hu8j8Ct0Ng/Stu7iAjo3N7XNw7KHL/dobsMtvwdZSRRdlMkUQfBt92gScR8 Of/KNhP2jdezHu6zGRCWLbT4F0YG2qlZiwLzIUslOqv1ZUgQfg5+InS59opeSqO2gxOg G6EwyWDCFwWyNyCJHfVQsewLWeTCR4TxyzX8WCFJ5c4lPHmKBeVpIDqQ+g+S4ccE9v0+ Z3DYs53i1Y3y+ha2MpzJZJ07KqjkqZ6sZ7OJY6nHZOPuJqvp5DSgsBpYu/nLal+NkVlF /UzWum4iVxEjGwtfauvTWvTCGkMpez+4M+AX40pmsB24KUFuoxS6pxt9MAGG5Hib59aJ qKCw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1783443197; x=1784047997; h=content-type:cc:to:subject:message-id:date:from:in-reply-to :references:mime-version:x-gm-gg:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to:content-type; bh=cB+giE2QIaWdJ8+RTlys+3SLvIKxbttjNFnqdtlPUDk=; b=r5W5aqMSoc3bz5G2iHPa00JpjxsNaTZpCP1z89z3UmDUszBRIxKkp8FyP6AJSHg2yX LjdqAx1LTOcqEyRwCScTeSkg4zR3/cwvktkNXScYB/siD1MkR6u3iz9g2Jlcm0G0apVO ssqGAHVxG9936A9vNRA4ah5nR5x5qq6gfFBzFc2a1Rcfh2rwyynT1sbXO/rOwcguioOT HFL4nPpDVoKKADeTWmNjtPD5aokJBj0PbQl5yRtXzivWD6/BbhT9BfGjzNUI0vltGDs7 RTKOEdYC/i9FTRk7eDuOp2sRDmc+IIcPyHPCGgZWlps8QPEeKW6grzg0hwUMF4T2uUEV zofA== X-Forwarded-Encrypted: i=1; AHgh+RoMCbJlROOYoAGhp2oIMA42+GY25QQHPSKYg+wX3sTmt9OdNnqYCfwB/Jp53iQJXpY+21y4B+GY72GIm3Bx@lists.postgresql.org X-Gm-Message-State: AOJu0YzHvcHCCHOI0fM9uboUkku9zdHsz8DZgBP0cFh97wysXWWJd7nS bIYC7NtUZn96NGSXCkwWHlC/UXRD++Xc7KT/Lt/PZU2s71WZh14FxDQpj2QGzCNIcUL5MZBrO1R YVHqeDyiXWPCHlu0khYP+A5CzMoOZX0w= X-Gm-Gg: AfdE7ck6uub4uM95oXF47o8vlhUcbjjkFbB4hhnRZXtsK8LW3cQhyR/EqNBZ+s2Cus3 elW3+fNltVRFiP4q2UGM28o2db6cC0wOhzmTj2ztow74BQ0HSmpZ6F78tavjaKErLcNjIQaY919 QhJYhUptZK/8E2rVTiCjAU4P9EQdKgjr8vmmY/0Kwa6S5qnCLHojK4mn41AvEJwPhIl1JwMv2/u 4sJmz+rwcEX9oUEwui6V3VGwm/wNbDBv3U/oYyGGO5YGlleGrfN/xs1ihSxfdSyGa5v9ehO6bqL EGrfg6NrFNipKtoyc+UaRue8lO5q+q3jD5vht2bSVNihNNHHVA== X-Received: by 2002:a17:907:3c95:b0:c12:cb8a:cc60 with SMTP id a640c23a62f3a-c15a6896cdemr326622466b.58.1783443196308; Tue, 07 Jul 2026 09:53:16 -0700 (PDT) MIME-Version: 1.0 References: <983C7B03-D9C3-4F15-8913-7616EC5E6414@gmail.com> <20260623.162104.775158751399277188.horikyota.ntt@gmail.com> In-Reply-To: From: Rui Zhao Date: Wed, 8 Jul 2026 00:53:04 +0800 X-Gm-Features: AVVi8CfRLeDuQNFKeYARjxUsmifmrgkFboCAz3-bnA5QsZnPWtXAcb7tO3j0LJ8 Message-ID: Subject: Re: [PATCH] Add pg_get_table_ddl() to reconstruct CREATE TABLE statements To: Akshay Joshi Cc: Zsolt Parragi , pgsql-hackers@lists.postgresql.org Content-Type: text/plain; charset="UTF-8" List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Archived-At: Precedence: bulk Hi Akshay, Re-tested v17 on current master (73dfe79fd6) -- all five issues are fixed, and make check passes here. I then ran a round-trip check against pg_dump: for each table, replay the pg_get_table_ddl() output into a clone of the database and diff "pg_dump -t" of both sides (both sides being pg_dump output, any surviving diff is semantic, not formatting). Corpus: the create_sql scenarios from 002_pg_dump.pl, then the whole regression database. 483 of 560 tables round-trip identically; setting aside the documented no-output kinds (trigger/policy), the rest reduce to: 1) serial columns produce non-replayable DDL -- the output references the sequence but never creates it: CREATE TABLE ser (id serial, v text); SELECT d FROM pg_get_table_ddl('ser'::regclass, owner => false) d; -- CREATE TABLE public.ser (id integer DEFAULT nextval('public.ser_id_seq'::regclass) NOT NULL, v text); CREATE TABLE public.ser (id integer DEFAULT nextval('public.ser_id_seq'::regclass) NOT NULL, v text); -- ERROR: relation "public.ser_id_seq" does not exist (on an empty database) pg_dump emits CREATE SEQUENCE + OWNED BY + SET DEFAULT. This accounts for 14 regression failures, including whole partition trees. 2) Partition/inheritance children that diverged from their parent don't survive the PARTITION OF / INHERITS rebuild. Three variants: - a dropped default silently comes back: CREATE TABLE dp (a int DEFAULT 99) PARTITION BY LIST (a); CREATE TABLE dp1 PARTITION OF dp FOR VALUES IN (1); ALTER TABLE ONLY dp1 ALTER COLUMN a DROP DEFAULT; SELECT d FROM pg_get_table_ddl('dp'::regclass, owner => false) d; -- CREATE TABLE public.dp (a integer DEFAULT 99) PARTITION BY LIST (a); -- CREATE TABLE public.dp1 PARTITION OF public.dp FOR VALUES IN (1); SELECT pg_get_expr(adbin, adrelid) FROM pg_attrdef WHERE adrelid = 'dp1'::regclass; -- source: (0 rows) -- replayed: 99 => INSERTs into dp1 now get 99, not NULL - a child's own NOT NULL constraint name is lost: CREATE TABLE np (a int NOT NULL) PARTITION BY LIST (a); CREATE TABLE np1 (a int CONSTRAINT np1_nn NOT NULL); ALTER TABLE np ATTACH PARTITION np1 FOR VALUES IN (1); SELECT d FROM pg_get_table_ddl('np'::regclass, owner => false) d; -- CREATE TABLE public.np (a integer NOT NULL) PARTITION BY LIST (a); -- CREATE TABLE public.np1 PARTITION OF public.np FOR VALUES IN (1); SELECT conname FROM pg_constraint WHERE conrelid = 'np1'::regclass AND contype = 'n'; -- source: np1_nn -- replayed: np_a_not_null and when the column itself is inherited, the emitted constraint errors instead of merging: CREATE TABLE p5 (a int); CREATE TABLE c5 () INHERITS (p5); ALTER TABLE c5 ADD CONSTRAINT c5_nn NOT NULL a; ALTER TABLE p5 ADD CONSTRAINT p5_nn NOT NULL a; SELECT d FROM pg_get_table_ddl('c5'::regclass, owner => false) d; -- CREATE TABLE public.c5 () INHERITS (public.p5); -- ALTER TABLE public.c5 ADD CONSTRAINT c5_nn NOT NULL a; ALTER TABLE public.c5 ADD CONSTRAINT c5_nn NOT NULL a; -- replay, p5 recreated first -- ERROR: cannot create not-null constraint "c5_nn" on column "a" of table "c5" -- DETAIL: A not-null constraint named "p5_nn" already exists for this column. - a partition attached from a table with different column order is rebuilt in the parent's order: CREATE TABLE p (a int, b int, c int) PARTITION BY LIST (a); CREATE TABLE c1 (c int, b int, a int); ALTER TABLE p ATTACH PARTITION c1 FOR VALUES IN (1); SELECT d FROM pg_get_table_ddl('p'::regclass, owner => false) d; -- CREATE TABLE public.p (a integer, b integer, c integer) PARTITION BY LIST (a); -- CREATE TABLE public.c1 PARTITION OF public.p FOR VALUES IN (1); -- => replayed c1 columns are a, b, c (source: c, b, a), so -- SELECT * / COPY / positional INSERT all shift pg_dump handles all three: it emits the named constraint inline in the child's CREATE body (the merge with the inherited constraint at CREATE time keeps the local name, and attislocal / conislocal / coninhcount all survive), and falls back to standalone CREATE + ATTACH PARTITION for shapes a PARTITION OF / INHERITS clause can't express. The first two variants have lightweight fixes that keep the PARTITION OF / INHERITS shape -- CREATE TABLE c5 (CONSTRAINT c5_nn NOT NULL a) INHERITS (p5); CREATE TABLE np1 PARTITION OF np (CONSTRAINT np1_nn NOT NULL a) FOR VALUES IN (1); plus a counter-statement for the divergent-default case (ALTER TABLE ONLY dp1 ALTER COLUMN a DROP DEFAULT). Only the reordered-column case really needs the standalone shape. The commit message lists child-local DEFAULT overrides and named NOT NULL constraints as supported, so I'm treating these as bugs rather than scope cuts. 3) emit_typed_column_storage() (ddlutils.c:2331, 2342) emits ALTER TABLE without ONLY; SET STORAGE / SET COMPRESSION recurse: CREATE TYPE mytype AS (a int, b text); CREATE TABLE typed_t OF mytype; ALTER TABLE typed_t ALTER COLUMN b SET STORAGE external; CREATE TABLE tchild () INHERITS (typed_t); ALTER TABLE ONLY tchild ALTER COLUMN b SET STORAGE main; ALTER TABLE public.typed_t ALTER COLUMN b SET STORAGE EXTERNAL; -- replay v17's output -- => tchild's attstorage flips m -> e pg_dump uses ALTER TABLE ONLY here. (The other emitted ALTER TABLEs are fine: OWNER / REPLICA IDENTITY / RLS / SET (options) don't recurse, and ADD CONSTRAINT must stay ONLY-less since the dump relies on its recursion to rebuild the children's suppressed inherited copies.) 4) Some per-table state pg_dump preserves is missing. Index statistics targets: CREATE TABLE ist (c1 int); CREATE INDEX ist_idx ON ist ((c1 + 1)); ALTER INDEX ist_idx ALTER COLUMN 1 SET STATISTICS 400; SELECT d FROM pg_get_table_ddl('ist'::regclass, owner => false) d; -- CREATE TABLE public.ist (c1 integer); -- CREATE INDEX ist_idx ON public.ist USING btree (((c1 + 1))); -- no SET STATISTICS Same story for ALTER TABLE ... CLUSTER ON (the index comes back without the indisclustered marker) and for ALTER TABLE ... DISABLE RULE (the rule is emitted but comes back enabled, which changes behavior -- and rule is a covered kind). 5) emit_indexes doesn't check indisvalid, so an invalid index is emitted as a normal one: CREATE TABLE inv (x int); INSERT INTO inv VALUES (1), (1); CREATE UNIQUE INDEX CONCURRENTLY inv_uidx ON inv (x); -- ERROR: could not create unique index "inv_uidx" (leaves indisvalid = false) SELECT d FROM pg_get_table_ddl('inv'::regclass, owner => false) d; -- CREATE TABLE public.inv (x integer); -- CREATE UNIQUE INDEX inv_uidx ON public.inv USING btree (x); pg_dump skips those (getIndexes: "i.indisvalid OR t2.relkind = 'p'"). 6) Minor: COMMENT ON and GRANT/REVOKE are not emitted. If that's intentional -- like trigger/policy -- worth saying so in the doc. Thanks, Rui