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 1wi2yR-000YNk-04 for pgsql-hackers@arkaria.postgresql.org; Fri, 10 Jul 2026 04:36:27 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.96) (envelope-from ) id 1wi2yP-00FnU0-0O for pgsql-hackers@arkaria.postgresql.org; Fri, 10 Jul 2026 04:36:25 +0000 Received: from magus.postgresql.org ([2a02:c0:301:0:ffff::29]) by malur.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.96) (envelope-from ) id 1wi2yO-00FnTN-2X for pgsql-hackers@lists.postgresql.org; Fri, 10 Jul 2026 04:36:25 +0000 Received: from mail-ej1-x62b.google.com ([2a00:1450:4864:20::62b]) by magus.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (Exim 4.98.2) (envelope-from ) id 1wi2yI-00000000arz-3c7y for pgsql-hackers@lists.postgresql.org; Fri, 10 Jul 2026 04:36:25 +0000 Received: by mail-ej1-x62b.google.com with SMTP id a640c23a62f3a-c15f360851aso82142666b.2 for ; Thu, 09 Jul 2026 21:36:19 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1783658178; cv=none; d=google.com; s=arc-20260327; b=EyylvlaJgE1zppcOngk8N9+sV757R8QtJRjqyjRe/Eow52fo87qcbCEt4uEzIU/Ac0 SDYTITsiwAmslCG3azCOaJh4vOjCgcPblrDkiHYI0zkHQ1KF/y18EKRM3lL94uXz9U8X T3ml4VeOfdWz5MSluvthanZQQ82ndTFdAvRqGWNw83rYy+0B71gkkk96u5lD/Rl+TbdU NBNiO8r01vuDmj5SvI/SbXFxWc9qDVNKKppHP3TN6Wr29pxTPB9u3x5EJWO773qmNfQR J+QAqDoqC5xo3f/KULSxhtU/SD10hWneQxeH1l1k/5MX5M3yrWsy3cWckO9cfpz02q+k Oy1w== 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=tnJ3NH4S3oy3t7dmRnpDl6fD8zV7TN1/w67OoXJg2g4=; fh=0tkAeTtdD2S5+vxes2IOMiqa0EKYvU3YLD7Va3d2FGA=; b=cse82XfoK/qPmuLKNkJtKLkNZUZ500efidnYCfmA2ARlPGICB0sbZ86WuwnpnbHyMU j0/aZJyR1SALTRzrN+1iBV7b5eQvtL6G4sVwnPTaq20khkiAQqvAfMC/FOW18ZKCK+92 KodEvyRJpFfwN3mRACXodsumA26GnR4fLQDm4S9lQ/jqSZJphWWXvDJX5sua3qH15GGd NBiJ9msCeQuIygylbHaTQibkKKRHE4y7JFEpVVRBWYKXpYz6uh7t21VeNewnYY20J1LF 2G//kviyiePXhU1Oy1dmrN0Bxo1waflAKNZJ5KB5xzNlciYWu5j3UsRvtC4xcg3XmEWQ OYtA==; 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=1783658178; x=1784262978; 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=tnJ3NH4S3oy3t7dmRnpDl6fD8zV7TN1/w67OoXJg2g4=; b=A9QqWzA/4t5yN+KCnSYYq0Jq4HfugwKLkHqQ9L8nyEMqnJnJ9qssIZaixprRMB18dW rGJKalwwk6H5psaqFeqG0cZR8roaSakhoHRA/RT4ePNcBJz2ZQAHySRBSAbal1B8PcbC TG7ekbil9J09dJOKE5McEN4xQmcd7GA24rOn0QBCPExknQ0xv34TeDl1Hs4wRHWprjUV tThuU7hY9CbA0CYnq1e/kbx2GzS9qT/o3tCzLijeHT4TClj4PYSGApKHotbGDztsfRsR EJQaEBqgjEYPaBn6F82uQgxzhE7KTZ0gg85jYV9r4GMcyfodx1KKzRhD5mAroPdiOYXJ Em6A== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1783658178; x=1784262978; 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=tnJ3NH4S3oy3t7dmRnpDl6fD8zV7TN1/w67OoXJg2g4=; b=J29giOmXcZZiKtB7US2DcTMSOCLy4B/m255sReGqVOoADQxLr9R8o5zw/GlH50TpUg hJ2I747/hHZA+EquaSp1TINZ0GncnPVHgebkI+gTi3gxwl0I+CO+8ja03ZSYcAhEcvCc PHPqbheiaHJmrmyqSKWqwa+FO0ykCe4vdT1q+CjUN3mUS2bQrEkwARBpw59U6rGfq4aJ 7ZUFJWCk/J//+D9B0QZ3zYw672/NwNelBQJDpZ/DXlkgd2fQ3C2wZWTkbsSDoBccR42g HRDG0U4YWhuMr8is83hOCC0SiZ8HYSDEusppQVZ6Xx+99+6qOGS+m0S0OKU4rRQ6k/K8 otyQ== X-Forwarded-Encrypted: i=1; AHgh+Rpa0C9Ii1g8YNUrNmzNgvEkHG+qQzNGrALqg6YqqgVYrJXMTuivFmvD7srAG+STnv4pUnBVdy6dOFphJckH@lists.postgresql.org X-Gm-Message-State: AOJu0YyPPRiSox/0P1ft8CETYnZux2Z6i29+Vu5PrX6wNNQV4grp6k4m M7wHrFHEiIfYX257uTteYjzMdXRJcHkIYULT/u/bAtvCIRDCMGYrvafKsExNM2uIjXp35PBVdI6 KOtR+/MaJ9BSn+JbIfEwMDJRI05WmIJY= X-Gm-Gg: AfdE7ck5FLYaGGPAwl2FHbYoAk0td3FBDCcTG181HG9fuhDdwA4TlCIa9eTYSSgZgZr 4/G+N8/XgRZj5XPRyOcJ0CGX8mlPSPGA2qYNNwbWgv12KiYKeM3yRsb4JisDq7qCrNKQoopXsJQ DGWs4cd2dpUUokXfuFDaoihHN8MZ4yp3xrlC9/LIdTssPOh5yLfvSddNGrahNw5MDBFSvAaiUa9 BWme4Hgt9nj6Y1IryQpC8hbA+mgsEQLRdeYKT+AJJCSA9WtbSUnftIN6Kz0gkPwrW+TgUtVvaaX Zdo3RnBDQDSc1LS5CfSjYtcssXFEjrDm9a9vRSU= X-Received: by 2002:a17:907:6096:b0:c12:7e9f:5ae3 with SMTP id a640c23a62f3a-c15ce0004fbmr505866066b.15.1783658177661; Thu, 09 Jul 2026 21:36:17 -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: Fri, 10 Jul 2026 12:36:05 +0800 X-Gm-Features: AVVi8CduW1Ecaw7oWml4KKld4xe7rnwFb4dOA0vXRWBo_pr5BC43gHJIV6Z3skQ Message-ID: Subject: Re: [PATCH] Add pg_get_table_ddl() to reconstruct CREATE TABLE statements To: Akshay Joshi Cc: Marcos Pegoraro , 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, Thanks for v19. I re-ran the pg_dump round-trip check (the 002_pg_dump.pl scenarios plus the whole regression database) on current master (2e6578292a). The five fixes hold up: SET STORAGE/COMPRESSION now use ONLY, invalid indexes are skipped, SET STATISTICS / CLUSTER ON / DISABLE RULE are emitted, and the child-default and named-NOT NULL cases I sent all round-trip now. 494 of 562 tables match pg_dump exactly (was 483/560 on v17). Three things came out of the run below; the third is a side effect of that invalid-index skip. 1) The named-NOT NULL handling for inheritance/partition children still isn't complete. It works when the child constraint has a user-defined name, but the corpus turns up two ways it still goes wrong. 1a) For an auto-named inherited NOT NULL it emits an out-of-line ADD CONSTRAINT that collides with the one INHERITS/PARTITION OF already propagates, giving non-replayable DDL: CREATE TABLE nt (a int PRIMARY KEY); CREATE TABLE ntc (PRIMARY KEY (a) DEFERRABLE) INHERITS (nt); SELECT d FROM pg_get_table_ddl('ntc'::regclass, owner => false) d; -- CREATE TABLE public.ntc () INHERITS (public.nt); -- ALTER TABLE public.ntc ADD CONSTRAINT ntc_a_not_null NOT NULL a; -- ALTER TABLE public.ntc ADD CONSTRAINT ntc_pkey PRIMARY KEY (a) DEFERRABLE; -- replaying that, with nt already restored first so INHERITS propagates -- its nt_a_not_null down to ntc: ERROR: cannot create not-null constraint "ntc_a_not_null" on column "a" of table "ntc" DETAIL: A not-null constraint named "nt_a_not_null" already exists for this column. (notnull_tbl4_cld2 in the regression suite fails the same way.) 1b) Even where it does replay, a partition attached from a standalone table keeps its name but comes back with the wrong constraint locality, because the constraint is folded into PARTITION OF instead of a standalone CREATE + ATTACH: CREATE TABLE mp (a int NOT NULL) PARTITION BY LIST (a); CREATE TABLE mp1 (a int CONSTRAINT mp1_nn NOT NULL); ALTER TABLE mp ATTACH PARTITION mp1 FOR VALUES IN (1); -- source mp1.mp1_nn: conislocal = false (the ATTACH merged it into the parent's) SELECT d FROM pg_get_table_ddl('mp'::regclass, owner => false) d; -- CREATE TABLE public.mp (a integer NOT NULL) PARTITION BY LIST (a); -- CREATE TABLE public.mp1 PARTITION OF public.mp (CONSTRAINT mp1_nn NOT NULL a) FOR VALUES IN (1); Declaring the constraint inline makes it local, so mp1.mp1_nn comes back with conislocal = true, and the behavior diverges once the parent's NOT NULL is dropped: ALTER TABLE mp ALTER a DROP NOT NULL; -- source: mp1.a is no longer NOT NULL; reconstructed: mp1.a stays NOT NULL pg_dump reconstructs both faithfully. For reference, this is where it puts each constraint: - In the CREATE TABLE body: NOT NULL, and validated CHECK. - As a separate ALTER TABLE ... ADD CONSTRAINT: PRIMARY KEY, UNIQUE, EXCLUDE, FOREIGN KEY, and NOT VALID CHECK. The split is the same on plain tables, inheritance children and partitions; only the table shape differs: - A plain-inheritance child keeps CREATE ... INHERITS and emits only its locally-owned constraints (purely-inherited NOT NULL/CHECK are left to INHERITS); the NOT NULL goes in the body unnamed so it auto-names, e.g. for the 1a case: CREATE TABLE ntc (NOT NULL a) INHERITS (nt); ... - A partition is never dumped with CREATE ... PARTITION OF; it is a standalone CREATE TABLE plus ALTER TABLE ONLY parent ATTACH PARTITION, so the child's column order, constraint names and conislocal come from its own definition plus the merge ATTACH performs. That partition path is the same standalone+ATTACH that the reordered-column case you deferred needs, so 1b likely folds into that follow-up. I'll leave the exact approach to you. 2) A NOT VALID CHECK constraint loses its NOT VALID flag on round-trip. The constraint is emitted inline in CREATE TABLE, where it is validated against the (empty) table, so the reconstructed constraint is marked validated: CREATE TABLE nv (a int); ALTER TABLE nv ADD CONSTRAINT chk CHECK (a < 50) NOT VALID; SELECT d FROM pg_get_table_ddl('nv'::regclass, owner => false) d; -- CREATE TABLE public.nv (a integer, CONSTRAINT chk CHECK ((a < 50)) NOT VALID); -- source nv.chk: convalidated = false -- reconstructed nv.chk: convalidated = true pg_dump emits NOT VALID constraints as a separate ALTER TABLE ... ADD CONSTRAINT ... NOT VALID for this reason. 3) The invalid-index skip added for the earlier finding also drops legitimate partitioned indexes. emit_indexes now has: /* Skip invalid indexes; they may be left over from a failed CREATE INDEX CONCURRENTLY. */ if (!idxform->indisvalid) { ReleaseSysCache(indTup); continue; } but a partitioned index on the parent is normally indisvalid = false (e.g. one created with ON ONLY, or before all child indexes are attached), so the whole index is lost: CREATE TABLE p (a int) PARTITION BY RANGE (a); CREATE TABLE p1 PARTITION OF p FOR VALUES FROM (0) TO (100); CREATE INDEX ON ONLY p (a); -- p_a_idx.indisvalid = false SELECT d FROM pg_get_table_ddl('p'::regclass, owner => false) d; -- CREATE TABLE public.p (a integer) PARTITION BY RANGE (a); -- CREATE TABLE public.p1 PARTITION OF public.p FOR VALUES FROM (0) TO (100); No CREATE INDEX at all; pg_dump keeps it as "CREATE INDEX p_a_idx ON ONLY public.p USING btree (a)". That's the pg_dump condition I quoted earlier -- "i.indisvalid OR t2.relkind = 'p'"; the relkind = 'p' half needs to stay so partitioned indexes aren't filtered out along with the failed-CIC ones. Thanks, Rui