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 1wg3LZ-005t2p-2E for pgsql-hackers@arkaria.postgresql.org; Sat, 04 Jul 2026 16:36:05 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.96) (envelope-from ) id 1wg3LV-00Adte-33 for pgsql-hackers@arkaria.postgresql.org; Sat, 04 Jul 2026 16:36:01 +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 1wg3LV-00AdtV-1m for pgsql-hackers@lists.postgresql.org; Sat, 04 Jul 2026 16:36:01 +0000 Received: from mail-ed1-x531.google.com ([2a00:1450:4864:20::531]) by magus.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (Exim 4.98.2) (envelope-from ) id 1wg3LS-00000001ieZ-3IiZ for pgsql-hackers@lists.postgresql.org; Sat, 04 Jul 2026 16:36:00 +0000 Received: by mail-ed1-x531.google.com with SMTP id 4fb4d7f45d1cf-698c0ff45b5so3382364a12.1 for ; Sat, 04 Jul 2026 09:35:58 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1783182956; cv=none; d=google.com; s=arc-20260327; b=OQ/Er/YXTzckpJM8rSEDH9sIyYxip5OgxGEjy6sUBLF+TwWI3aEJW7QLjfMZ9F8to6 Vz5jZAGijKkr8M+ZPCp5ALoc6RJM0hmYmgpZhFX8EG+63C6FCj7e7dgrx7eqBIMZ6q4J 7b48/c0yUKBuScVQTYRIXTbdMQ3SCHWhEtvzMRGG6V91wMPU9zPoqybhpwhwGOEXUvZ2 rgOxAwJIBDkjI1Okzg+pzjl44xr5RWDHIgjSLPGcl9pYokNRzADkYRdLFPGW3ZzPtvRO YesnXy/Do1IGl9+jtuWmsKASXZS/s9trDqWZ/Sb8yhnsc4zzOkDpn8W7Pqj0yK5zvOS6 HqqA== 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=n6U0VFHyLMJHxtBxQzLh5T1JlnAe7Tb6/SUtT8yziEg=; fh=sBEPr5nsW/PbR2cEtMMO56wtuKYDDhx2HmFORe10taw=; b=QvVANo+lC6Pu8OXqTR/8s4HQKDcIy5+q+pdg5wAGWrC7NvIjdOH2ZPvVfwmX+vfsh0 vBoWacBIRDFZPhjYmIxqnaJRYg4FhvSeT7aSnb/5ILzKfcQfVxzYEZS0lQcCI+Wabscd Ctl2nlR2mq1LxS76PCi+lI8virbXdeAaZ5ASu2wAMOWG3KpHrRHIhbmqCFD+x820R4wz a7OQPYyBEflRYsAfhg7xqQ3bg+jMr+W9uIRJLHdvIvF6aMfMOnisB0ElnibB3R7gTrIR Cdk7Of2lrYKt9IT5tH4ohnfPcRX3jDxms8dTvRouo7pHehR/Ce+mFAZdxIphyvrZ5aH4 T0fA==; 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=1783182956; x=1783787756; darn=lists.postgresql.org; h=cc:to:subject:message-id:date:from:in-reply-to:references :mime-version:from:to:cc:subject:date:message-id:reply-to; bh=n6U0VFHyLMJHxtBxQzLh5T1JlnAe7Tb6/SUtT8yziEg=; b=TdzRMCxmhOUuE9XyAEP/tu5WVo+4Kd3byL/bYXWef9gN/UNRft3Wno8PxKv676wIS2 e7yCnCmbLUD2m2Umz2Z5xoA+gGVTS6SqO5FFWZ2lq4XCwfdXa/xGSyZwo38V8lY5BFDK KB3GVzc9rRYOw52gqEcFiqOC4n8XqxbvnL2UjD+lmeJyMsyiZ6zunN3VKH5HWt3a99sn 7WKpdPB8j2+g1Poyfl28CnURsJFOvQjZ6FD1Loy8n1f/AaY9Dn2CMayjTNYoSIJWMHVT nNyt4mqoJWsTSzvV9nS98gaV1k7fPy+nMUoVYSwhnjC3rl8IOAIqPnkDhZap8zMPJ/cr a+zw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1783182956; x=1783787756; h=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; bh=n6U0VFHyLMJHxtBxQzLh5T1JlnAe7Tb6/SUtT8yziEg=; b=Xffdtb86c+eTO3T+hkHr9Dr69MDtE+VkrxMwmuoaruHrmybwWug1AFTtBQcruVOd/v WElHymxcRruBKvJBJ0cW6XHqgyL8T9LLPZzXwgeM6sL52ngbH4FTM02+LQvbZNsAZHs/ cAJI5ypiAmDIBRmjkqcEEoMZNpPrKYsw/OMlHDrD3O27ieWGG/lTNdsv5z8utpMZf1NX pwlVjvR/ne7qggcZ8KcZCExeAYVHI9l8rNOPvnKk3XWYMcRaimaKK+NGlOZrdv9A9AtY Hl8DepFhgGldMNw32VEsyEl60ya6fILnNtNAN+AmE/iuSayTy824V4b6giFWRYDvGV58 /9Gw== X-Forwarded-Encrypted: i=1; AHgh+RpTMIK1jr3BqGBD4MHeVwej8MwdAHez5n2p+j/LIOuZJ304mFnyCmZ7CV8quHG+TgVJefH+d6+UceSGaziL@lists.postgresql.org X-Gm-Message-State: AOJu0YwtoNqlr6be1/eXcD86M1zUj0CyZy/E3prnah+rNpcWPaSSd8Hl rVk6d7HpXTai+MM7OWvFrEz/fEfLNyKCMGffGkST580p7IQAZSONfJmoWJLGOcGxbqquPZyByUM YjhcW+u8wUqELXGN8rJySD3ne0/KrCV4= X-Gm-Gg: AfdE7cmIkzsoltrfayTtJuXKnKTxEHgt36AWVZSDtQX+Lf8bzu6pkvZb8CkgzPVPqLq NuRCPsOT2/2el9csGS1omsdebMNXMw/vxjkLxxNWl7rhQEojG4/lN/VCsQRNPUn9/mI4XZWH1QT m/aSpHxppFprn01RPaLDvnZ0oEMrf/Gfpw1hbLtHiojf23JrnDaN2whGxH7lb3d6EDDefDRBk1V rwCSiQFvY1BCqouFchBW7uKYSkmqoiAUBwzvfCwmaWzy0iS1g1zIZY3w7cA9eYvl/Bz1LK77BNU uDCH0J4H5wA8u9dPfLDUWJdNxaz3VjPXZ/Hagoc= X-Received: by 2002:a17:907:3fa6:b0:c12:3cbf:9f6d with SMTP id a640c23a62f3a-c12e624eb96mr144196966b.1.1783182955990; Sat, 04 Jul 2026 09:35:55 -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: Sun, 5 Jul 2026 00:35:44 +0800 X-Gm-Features: AVVi8Cco0BX9rjZi9FiR1exdV5Uf61LmbeqbqQzmA8hhX1n3KKFx4nNCu9AH9Bo 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 v16 on current master -- builds clean and the recent fixes hold up (self-ref FK after PK, no duplicate partition-child index, cross-schema partition child qualified, schema_qualified => true consistent). A few issues, in severity order; several are places where pg_dump already does the right thing. 1. Clause order: TABLESPACE is emitted before ON COMMIT (ddlutils.c:2042 vs 2054), but the grammar is "... OptWith OnCommitOption OptTableSpace" -- ON COMMIT must come first, so a temp table with both clauses is non-replayable: CREATE TABLESPACE ts1 LOCATION '/path/to/dir'; CREATE TEMP TABLE tt (a int) ON COMMIT DROP TABLESPACE ts1; SELECT d FROM pg_get_table_ddl('tt'::regclass, owner => false) d; -- CREATE TEMPORARY TABLE tt (a integer) TABLESPACE ts1 ON COMMIT DROP; CREATE TEMPORARY TABLE tt (a integer) TABLESPACE ts1 ON COMMIT DROP; -- ERROR: syntax error at or near "ON" Swapping the two blocks so ON COMMIT precedes TABLESPACE fixes it. 2. Child-default override recurses (missing ONLY). emit_child_default_overrides emits the inherited-column default without ONLY (ddlutils.c:2118), so SET DEFAULT recurses into the table's children and reconstructing one table silently rewrites another: CREATE TABLE dpar (x int); CREATE TABLE dch () INHERITS (dpar); CREATE TABLE dgc () INHERITS (dch); ALTER TABLE ONLY dch ALTER COLUMN x SET DEFAULT 5; ALTER TABLE ONLY dgc ALTER COLUMN x SET DEFAULT 10; SELECT d FROM pg_get_table_ddl('dch'::regclass, owner => false) d; -- ALTER TABLE dch ALTER COLUMN x SET DEFAULT 5; -- no ONLY ALTER TABLE dch ALTER COLUMN x SET DEFAULT 5; -- replay this line -- => dgc's default is now 5, not 10 pg_dump uses ALTER TABLE ONLY here for exactly this reason. Partitioned tables hit this especially easily -- any partitioned table with a column default emits a redundant, ONLY-less SET DEFAULT for every partition that merely inherits it: CREATE TABLE p (id int, amt int DEFAULT 5) PARTITION BY LIST (id); CREATE TABLE p_a PARTITION OF p FOR VALUES IN (1); SELECT d FROM pg_get_table_ddl('p'::regclass, owner => false) d; -- CREATE TABLE public.p (id integer, amt integer DEFAULT 5) PARTITION BY LIST (id); -- CREATE TABLE public.p_a PARTITION OF public.p FOR VALUES IN (1); -- ALTER TABLE public.p_a ALTER COLUMN amt SET DEFAULT 5; -- redundant, no ONLY p_a already inherits amt's default from the PARTITION OF, so the third line is redundant; pg_dump instead keeps the default inline on the child and attaches with ALTER TABLE ONLY ... ATTACH PARTITION. A full-hierarchy replay self-corrects (each child's own SET DEFAULT runs last), but a partial replay or a lone emitted statement does not. The commit message lists "child-local DEFAULT overrides on inheritance/partition children" as supported, so this is in scope. (More generally the patch never emits ONLY anywhere; ADD CONSTRAINT, where CHECK / NOT NULL also recurse to children, is worth the same audit.) 3. Inherited-only NOT NULL emitted as local. When a child redeclares an inherited column (attislocal) without restating NOT NULL, the constraint is inherited-only (conislocal = false); collect_local_not_null skips it, but append_column_defs keys off att->attnotnull and emits a bare NOT NULL anyway: CREATE TABLE par (a int NOT NULL); CREATE TABLE chld (a int) INHERITS (par); -- 'a' redeclared, no NOT NULL SELECT d FROM pg_get_table_ddl('chld'::regclass, owner => false) d; -- CREATE TABLE public.chld (a integer NOT NULL) INHERITS (public.par); On replay the child now owns the constraint (conislocal flips false -> true, name regenerates par_a_not_null -> chld_a_not_null), so a later "ALTER TABLE par ALTER a DROP NOT NULL" cascades to the original child but not the reconstructed one. pg_dump emits "a integer" with no NOT NULL here, suppressing it via notnull_islocal (pg_dump.c ~9916). The docs describe this as the intended behavior -- "Inherited columns and constraints ... are not duplicated on inheritance children or partitions" -- so it reads as a documented contract the code doesn't quite meet. The inline CHECK path in this patch already filters on conislocal; the NOT NULL path could do the same. 4. Typed-table STORAGE / COMPRESSION not emitted -- intended? append_column_defs emits per-column STORAGE for ordinary tables, but the typed-table path (append_typed_column_overrides) only handles DEFAULT / NOT NULL / CHECK, so a storage override on a typed table is not reproduced: CREATE TYPE mytype AS (a int, b text); CREATE TABLE typed_t OF mytype; ALTER TABLE typed_t ALTER COLUMN b SET STORAGE external; SELECT d FROM pg_get_table_ddl('typed_t'::regclass, owner => false) d; -- CREATE TABLE public.typed_t OF public.mytype; -- STORAGE not emitted The docs scope the typed-table form to overrides for "defaults, NOT NULL, and CHECK", so this may well be deliberate. But STORAGE is listed in the general per-column coverage, and pg_dump does emit it (ALTER TABLE ONLY ... ALTER COLUMN b SET STORAGE EXTERNAL) -- so it seems worth confirming the omission is intentional rather than an oversight. 5. Minor: is_auto (ddlutils.c:1381) and the identity SEQUENCE NAME check (1681) rebuild the expected auto-name with snprintf, but the backend uses makeObjectName(), which truncates name1/name2 to fit NAMEDATALEN and never the label -- so for long names the "_not_null" / "_seq" suffix is dropped and the checks misfire, emitting a name a short-named table would omit: CREATE TABLE aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa (bbbbbbbbbbbbbbbbbbbb int GENERATED ALWAYS AS IDENTITY); SELECT d FROM pg_get_table_ddl( 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'::regclass, owner => false) d; -- CREATE TABLE public.aaaa...(50) (bbbb...(20) integer -- GENERATED ALWAYS AS IDENTITY (SEQUENCE NAME public.aaaa..._seq) -- CONSTRAINT aaaa..._not_null NOT NULL); The default-omission convention in the commit message lists "the auto-generated identity sequence name" among the clauses meant to be dropped, which is exactly what misfires here for long names. It still replays (the names are real), so this one is cosmetic; comparing against makeObjectName(relname, colname, "not_null" / "seq") makes both sides agree. Everything else looks good. Thanks, Rui