Skip to content

Commit c96d92a

Browse files
akshay-joshihackorum
authored andcommitted
Add pg_get_table_ddl() to reconstruct CREATE TABLE statements
The function reconstructs the CREATE TABLE statement for an ordinary or partitioned table, followed by the ALTER TABLE / CREATE INDEX / CREATE RULE / CREATE STATISTICS statements needed to restore its full definition. Each statement is returned as a separate row. Supported per-column features: data type with type modifiers, COLLATE, STORAGE, COMPRESSION (pglz / lz4), GENERATED ALWAYS AS (expr) STORED/VIRTUAL, GENERATED ALWAYS|BY DEFAULT AS IDENTITY (with sequence options), DEFAULT, NOT NULL (including named NOT NULL constraints), and per-column attoptions emitted as ALTER COLUMN SET (...). Supported table-level features: UNLOGGED, INHERITS, PARTITION BY (RANGE / LIST / HASH parents), PARTITION OF parent FOR VALUES (FROM/TO, WITH modulus/remainder, DEFAULT), USING table access method, WITH (reloptions), TABLESPACE, and inline CHECK constraints in the CREATE TABLE body. Supported sub-objects (re-using existing deparse helpers from ruleutils.c): indexes (including partial and functional) via pg_get_indexdef_ddl; constraints (PRIMARY KEY with WITHOUT OVERLAPS for temporal keys, UNIQUE with NULLS NOT DISTINCT and INCLUDE columns, FOREIGN KEY with ON DELETE/UPDATE referential actions and MATCH clause, NOT ENFORCED foreign keys, EXCLUDE, named NOT NULL) via pg_get_constraintdef_body; rules via pg_get_ruledef_ddl; extended statistics via pg_get_statisticsobjdef_ddl; REPLICA IDENTITY NOTHING/FULL/USING INDEX; ALTER TABLE ENABLE/FORCE ROW LEVEL SECURITY; and child-local DEFAULT overrides on inheritance/partition children. DDL for partition children of a partitioned-table parent is appended after the parent by default. The function signature follows the named-parameter convention established by pg_get_role_ddl(), pg_get_tablespace_ddl(), and pg_get_database_ddl(): pg_get_table_ddl(relation regclass, pretty boolean DEFAULT false, owner boolean DEFAULT true, tablespace boolean DEFAULT true, schema_qualified boolean DEFAULT true, only_kinds text[] DEFAULT NULL, except_kinds text[] DEFAULT NULL) pretty controls pretty-printed output. owner controls emission of the ALTER TABLE ... OWNER TO statement. tablespace controls the TABLESPACE clause on CREATE TABLE. schema_qualified controls whether object names are emitted with their schema prefix: when true (the default) the active search_path is temporarily narrowed to pg_catalog so every deparse helper produces fully-qualified names; when false it is narrowed to the target table's own schema so same-schema references come out unqualified while cross-schema references remain qualified for correctness. Temporary tables are never schema-qualified regardless of this setting: the TEMPORARY keyword already places them in pg_temp, and emitting pg_temp_NN.relname would produce non-replayable DDL. Object-class filtering uses two mutually-exclusive text-array parameters, only_kinds and except_kinds. When only_kinds is set, only the listed kinds are emitted; when except_kinds is set, every kind except the listed ones is emitted; when neither is set every kind is emitted. Each array element is matched case-insensitively with leading/trailing whitespace trimmed; an unrecognized name raises an error. The kind vocabulary is: table, index, primary_key, unique, check, foreign_key, exclusion, rule, statistics, rls, replica_identity, partition trigger and policy are accepted in the vocabulary but currently produce no output; they are reserved for when standalone pg_get_trigger_ddl() and pg_get_policy_ddl() helpers become available. NOT NULL is not part of the vocabulary: it is always emitted to prevent producing schemas that silently accept NULLs the source would reject. When replica_identity is in the active filter and the table's REPLICA IDENTITY USING INDEX references an index that the filter would suppress, the function raises an error before emitting any output, so the generated DDL never references an index it did not produce. Default omission convention: every optional clause is omitted when its value matches what the server would reapply on round-trip, including type-default COLLATE, per-type STORAGE, the auto-generated identity sequence name and parameter defaults, heap access method, default REPLICA IDENTITY, disabled RLS toggles, empty reloptions, and the default tablespace. Author: Akshay Joshi <akshay.joshi@enterprisedb.com> Reviewed-by: Marcos Pegoraro <marcos@f10.com.br> Reviewed-by: Zsolt Parragi <zsolt.parragi@percona.com> Reviewed-by: Kyotaro Horiguchi <horikyota.ntt@gmail.com> Reviewed-by: Chao Li <li.evan.chao@gmail.com> Reviewed-by: Rui Zhao <zhaorui126@gmail.com>
1 parent d34f950 commit c96d92a

13 files changed

Lines changed: 7087 additions & 35 deletions

File tree

doc/src/sgml/func/func-info.sgml

Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3965,6 +3965,217 @@ acl | {postgres=arwdDxtm/postgres,foo=r/postgres}
39653965
is false, the <literal>OWNER</literal> clause is omitted.
39663966
</para></entry>
39673967
</row>
3968+
<row>
3969+
<entry role="func_table_entry"><para role="func_signature">
3970+
<indexterm>
3971+
<primary>pg_get_table_ddl</primary>
3972+
</indexterm>
3973+
<function>pg_get_table_ddl</function>
3974+
( <parameter>relation</parameter> <type>regclass</type>
3975+
<optional>, <parameter>pretty</parameter> <type>boolean</type>
3976+
<literal>DEFAULT</literal> false</optional>
3977+
<optional>, <parameter>owner</parameter> <type>boolean</type>
3978+
<literal>DEFAULT</literal> true</optional>
3979+
<optional>, <parameter>tablespace</parameter> <type>boolean</type>
3980+
<literal>DEFAULT</literal> true</optional>
3981+
<optional>, <parameter>schema_qualified</parameter> <type>boolean</type>
3982+
<literal>DEFAULT</literal> true</optional>
3983+
<optional>, <parameter>only_kinds</parameter> <type>text[]</type>
3984+
<literal>DEFAULT</literal> NULL</optional>
3985+
<optional>, <parameter>except_kinds</parameter> <type>text[]</type>
3986+
<literal>DEFAULT</literal> NULL</optional> )
3987+
<returnvalue>setof text</returnvalue>
3988+
</para>
3989+
<para>
3990+
Reconstructs the <command>CREATE TABLE</command> statement for the
3991+
specified ordinary or partitioned table, followed by the
3992+
<command>ALTER TABLE</command>, <command>CREATE INDEX</command>,
3993+
<command>CREATE RULE</command>, and <command>CREATE STATISTICS</command>
3994+
statements needed to recreate the table's columns, constraints,
3995+
indexes, rules, extended statistics, and row-level security flags.
3996+
This includes per-index statistics targets
3997+
(<command>ALTER INDEX ... SET STATISTICS</command>),
3998+
the clustering index (<command>ALTER TABLE ... CLUSTER ON</command>),
3999+
and non-default rule firing states
4000+
(<command>ALTER TABLE ... DISABLE/ENABLE RULE</command>).
4001+
Inherited columns and constraints are emitted by the parent table's
4002+
DDL and are not duplicated on inheritance children or partitions.
4003+
Each statement is returned as a separate row.
4004+
Foreign-key constraints are always emitted after all other
4005+
constraints so that self-referencing foreign keys (where the
4006+
referenced primary or unique key is on the same table) can be
4007+
added once that key already exists.
4008+
When <parameter>pretty</parameter> is true, the output is
4009+
pretty-printed. When <parameter>owner</parameter> is false, the
4010+
<command>ALTER TABLE ... OWNER TO</command> statement is omitted.
4011+
When <parameter>tablespace</parameter> is false, the
4012+
<literal>TABLESPACE</literal> clause is omitted from the
4013+
<command>CREATE TABLE</command> statement. The
4014+
<parameter>only_kinds</parameter> and <parameter>except_kinds</parameter>
4015+
parameters described below control which object classes are emitted.
4016+
</para>
4017+
<para>
4018+
The <parameter>schema_qualified</parameter> parameter (default
4019+
<literal>true</literal>) controls whether the target table's own
4020+
schema is included in the generated DDL. When set to
4021+
<literal>false</literal>, the table name is emitted unqualified
4022+
in the <command>CREATE TABLE</command> and every subsequent
4023+
<command>ALTER TABLE</command>, <command>CREATE INDEX</command>,
4024+
<command>CREATE RULE</command>, and
4025+
<command>CREATE STATISTICS</command> statement. References to
4026+
objects in the same schema as the target table (inheritance
4027+
parents, partition parents, identity sequences, and any
4028+
same-schema object the deparse helpers happen to mention) are
4029+
also emitted unqualified, so the script can be replayed under a
4030+
different <varname>search_path</varname> to recreate the table
4031+
in another schema. Cross-schema references (for example a
4032+
foreign key target in a different schema) remain qualified for
4033+
correctness. Partition children that reside in a different
4034+
schema than the target table are always emitted with full schema
4035+
qualification regardless of this parameter, because the output
4036+
would otherwise place the child in the wrong schema when
4037+
replayed.
4038+
</para>
4039+
<para>
4040+
The <parameter>only_kinds</parameter> and <parameter>except_kinds</parameter>
4041+
parameters each take a text array of object-class kind names
4042+
and are mutually exclusive (specifying both raises an
4043+
error). When <parameter>only_kinds</parameter> is set, only the
4044+
listed kinds are emitted; when <parameter>except_kinds</parameter> is
4045+
set, every kind <emphasis>except</emphasis> the listed ones is
4046+
emitted. When neither is set (the default), every kind is
4047+
emitted. Leading and trailing whitespace in each array element
4048+
is ignored and matching is case-insensitive; an unrecognized
4049+
kind name raises an error.
4050+
The kind vocabulary is
4051+
<literal>table</literal>,
4052+
<literal>index</literal>,
4053+
<literal>primary_key</literal>,
4054+
<literal>unique</literal>,
4055+
<literal>check</literal>,
4056+
<literal>foreign_key</literal>,
4057+
<literal>exclusion</literal>,
4058+
<literal>rule</literal>,
4059+
<literal>statistics</literal>,
4060+
<literal>trigger</literal>,
4061+
<literal>policy</literal>,
4062+
<literal>rls</literal> (the
4063+
<command>ENABLE</command>/<command>FORCE ROW LEVEL SECURITY</command>
4064+
toggles),
4065+
<literal>replica_identity</literal>, and
4066+
<literal>partition</literal> (the DDL for each direct
4067+
partition child of a partitioned-table parent).
4068+
Note that <literal>trigger</literal> and <literal>policy</literal>
4069+
are accepted in the vocabulary but currently produce no output;
4070+
they are reserved for future use when standalone
4071+
<function>pg_get_trigger_ddl</function> and
4072+
<function>pg_get_policy_ddl</function> helpers become available. The
4073+
<literal>table</literal> kind groups the
4074+
<command>CREATE TABLE</command> statement together with the
4075+
related per-table <command>ALTER TABLE</command> passes:
4076+
<command>OWNER TO</command>, child-default
4077+
<command>SET DEFAULT</command>, and per-column
4078+
<command>SET (<replaceable>attoptions</replaceable>)</command>.
4079+
<literal>NOT NULL</literal> constraints are not part of the
4080+
vocabulary; they are always emitted as part of the table to
4081+
avoid producing schemas that silently accept
4082+
<literal>NULL</literal> values the source would have rejected.
4083+
For example, the second pass of a two-pass schema clone that
4084+
adds cross-table foreign keys after data has loaded can be
4085+
written as
4086+
<literal>only_kinds => ARRAY['foreign_key']</literal>, and the
4087+
pub/sub-style clone that keeps a primary key but drops every
4088+
other constraint can be written as
4089+
<literal>except_kinds => ARRAY['unique','check','foreign_key','exclusion']</literal>.
4090+
</para>
4091+
<para>
4092+
When the table has
4093+
<literal>REPLICA IDENTITY USING INDEX</literal> and the
4094+
<literal>replica_identity</literal> kind is in the active
4095+
filter, the kind that emits the referenced index
4096+
(<literal>primary_key</literal>, <literal>unique</literal>,
4097+
<literal>exclusion</literal>, or <literal>index</literal>) must
4098+
also be in the filter. Otherwise the emitted
4099+
<command>ALTER TABLE ... REPLICA IDENTITY USING INDEX</command>
4100+
would reference an index the same DDL never produced, and the
4101+
function reports an error before emitting any statements.
4102+
</para>
4103+
<para>
4104+
All three forms of <command>CREATE TABLE</command> are supported:
4105+
the ordinary column-list form, the typed-table form
4106+
(<literal>OF <replaceable>type_name</replaceable></literal>, with
4107+
per-column <literal>WITH OPTIONS</literal> overrides for local
4108+
defaults, <literal>NOT NULL</literal>, and <literal>CHECK</literal>
4109+
constraints), and the <literal>PARTITION OF</literal> form.
4110+
<literal>TEMPORARY</literal> and <literal>UNLOGGED</literal>
4111+
persistence modes are emitted from
4112+
<structfield>relpersistence</structfield>. For temporary tables
4113+
registered in the current session, the
4114+
<literal>ON COMMIT DELETE ROWS</literal> and
4115+
<literal>ON COMMIT DROP</literal> clauses are emitted; the default
4116+
<literal>ON COMMIT PRESERVE ROWS</literal> is omitted.
4117+
</para>
4118+
<para>
4119+
The following table-related objects are intentionally outside the
4120+
scope of this function and are not emitted:
4121+
<itemizedlist>
4122+
<listitem>
4123+
<para>
4124+
<emphasis>Sequences</emphasis> owned by serial columns
4125+
(<type>serial</type>, <type>bigserial</type>,
4126+
<type>smallserial</type>) &mdash;sequences are independent catalog
4127+
objects. Use <function>pg_get_sequence_ddl</function> (if
4128+
available) or <productname>pg_dump</productname> to capture them
4129+
alongside the table.
4130+
</para>
4131+
</listitem>
4132+
<listitem>
4133+
<para>
4134+
<emphasis>Partition children whose column order differs from the
4135+
parent</emphasis> &mdash;the <literal>PARTITION OF</literal> syntax
4136+
can only reproduce the parent's column order. Such partitions
4137+
require a standalone <command>CREATE TABLE</command> followed by
4138+
<command>ALTER TABLE ... ATTACH PARTITION</command>, which
4139+
<productname>pg_dump</productname> performs automatically.
4140+
</para>
4141+
</listitem>
4142+
<listitem>
4143+
<para>
4144+
<emphasis>Triggers and row-level security policies</emphasis> &mdash;
4145+
reserved for future use when standalone
4146+
<function>pg_get_trigger_ddl</function> and
4147+
<function>pg_get_policy_ddl</function> helpers are available.
4148+
</para>
4149+
</listitem>
4150+
<listitem>
4151+
<para>
4152+
<emphasis>Security labels</emphasis> &mdash; labels applied with
4153+
<link linkend="sql-security-label"><command>SECURITY LABEL</command></link>
4154+
are not emitted.
4155+
</para>
4156+
</listitem>
4157+
<listitem>
4158+
<para>
4159+
<emphasis>Constraint names on partition children</emphasis> &mdash;
4160+
when called on a partition child in isolation (not as part of emitting
4161+
the whole partitioned tree), <productname>PostgreSQL</productname>
4162+
auto-generates names for inherited PK, UNIQUE, and EXCLUSION
4163+
constraints, which may differ from the names in the source catalog.
4164+
Round-trip fidelity for these names requires emitting the whole tree
4165+
via the parent table.
4166+
</para>
4167+
</listitem>
4168+
<listitem>
4169+
<para>
4170+
<emphasis><command>COMMENT ON</command> and
4171+
<command>GRANT</command>/<command>REVOKE</command></emphasis> &mdash;
4172+
these are separate from the table's structural DDL and are not
4173+
emitted.
4174+
</para>
4175+
</listitem>
4176+
</itemizedlist>
4177+
</para></entry>
4178+
</row>
39684179
</tbody>
39694180
</tgroup>
39704181
</table>

src/backend/catalog/pg_inherits.c

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,99 @@ find_inheritance_children_extended(Oid parentrelId, bool omit_detached,
236236
}
237237

238238

239+
/*
240+
* find_inheritance_parents
241+
*
242+
* Returns a list containing the OIDs of all relations that the relation with
243+
* OID 'relid' inherits *directly* from, in inhseqno order (the order in which
244+
* they should appear in an INHERITS clause).
245+
*
246+
* The specified lock type is acquired on each parent relation (but not on the
247+
* given rel; caller should already have locked it). If lockmode is NoLock
248+
* then no locks are acquired, but caller must beware of race conditions
249+
* against possible DROPs of parent relations.
250+
*
251+
* Partition children also have a pg_inherits entry pointing to the
252+
* partitioned parent; callers that distinguish INHERITS from PARTITION OF
253+
* must check relispartition themselves.
254+
*/
255+
List *
256+
find_inheritance_parents(Oid relid, LOCKMODE lockmode)
257+
{
258+
List *list = NIL;
259+
Relation relation;
260+
SysScanDesc scan;
261+
ScanKeyData key[1];
262+
HeapTuple inheritsTuple;
263+
Oid inhparent;
264+
Oid *oidarr;
265+
int maxoids,
266+
numoids,
267+
i;
268+
269+
/*
270+
* Scan pg_inherits via the (inhrelid, inhseqno) index so that the rows
271+
* come out in inhseqno order, which is the order required for the
272+
* INHERITS clause.
273+
*/
274+
maxoids = 8;
275+
oidarr = (Oid *) palloc(maxoids * sizeof(Oid));
276+
numoids = 0;
277+
278+
relation = table_open(InheritsRelationId, AccessShareLock);
279+
280+
ScanKeyInit(&key[0],
281+
Anum_pg_inherits_inhrelid,
282+
BTEqualStrategyNumber, F_OIDEQ,
283+
ObjectIdGetDatum(relid));
284+
285+
scan = systable_beginscan(relation, InheritsRelidSeqnoIndexId, true,
286+
NULL, 1, key);
287+
288+
while ((inheritsTuple = systable_getnext(scan)) != NULL)
289+
{
290+
inhparent = ((Form_pg_inherits) GETSTRUCT(inheritsTuple))->inhparent;
291+
if (numoids >= maxoids)
292+
{
293+
maxoids *= 2;
294+
oidarr = (Oid *) repalloc(oidarr, maxoids * sizeof(Oid));
295+
}
296+
oidarr[numoids++] = inhparent;
297+
}
298+
299+
systable_endscan(scan);
300+
301+
table_close(relation, AccessShareLock);
302+
303+
/*
304+
* Acquire locks and build the result list. Unlike
305+
* find_inheritance_children we do *not* sort by OID: callers need the
306+
* seqno-ordered traversal.
307+
*/
308+
for (i = 0; i < numoids; i++)
309+
{
310+
inhparent = oidarr[i];
311+
312+
if (lockmode != NoLock)
313+
{
314+
LockRelationOid(inhparent, lockmode);
315+
316+
if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(inhparent)))
317+
{
318+
UnlockRelationOid(inhparent, lockmode);
319+
continue;
320+
}
321+
}
322+
323+
list = lappend_oid(list, inhparent);
324+
}
325+
326+
pfree(oidarr);
327+
328+
return list;
329+
}
330+
331+
239332
/*
240333
* find_all_inheritors -
241334
* Returns a list of relation OIDs including the given rel plus

src/backend/commands/tablecmds.c

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -747,7 +747,6 @@ static ObjectAddress ATExecSetCompression(Relation rel,
747747
const char *column, Node *newValue, LOCKMODE lockmode);
748748

749749
static void index_copy_data(Relation rel, RelFileLocator newrlocator);
750-
static const char *storage_name(char c);
751750

752751
static void RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid,
753752
Oid oldRelOid, void *arg);
@@ -2525,7 +2524,7 @@ truncate_check_activity(Relation rel)
25252524
* storage_name
25262525
* returns the name corresponding to a typstorage/attstorage enum value
25272526
*/
2528-
static const char *
2527+
const char *
25292528
storage_name(char c)
25302529
{
25312530
switch (c)
@@ -20003,6 +20002,33 @@ remove_on_commit_action(Oid relid)
2000320002
}
2000420003
}
2000520004

20005+
/*
20006+
* Look up the registered ON COMMIT action for a relation.
20007+
*
20008+
* Returns ONCOMMIT_NOOP when nothing was registered, which also covers
20009+
* temporary tables created with the default ON COMMIT PRESERVE ROWS
20010+
* behavior (register_on_commit_action() skips those, since no action is
20011+
* needed at commit). Entries marked for deletion in the current
20012+
* transaction are ignored.
20013+
*/
20014+
OnCommitAction
20015+
get_on_commit_action(Oid relid)
20016+
{
20017+
ListCell *l;
20018+
20019+
foreach(l, on_commits)
20020+
{
20021+
OnCommitItem *oc = (OnCommitItem *) lfirst(l);
20022+
20023+
if (oc->relid != relid)
20024+
continue;
20025+
if (oc->deleting_subid != InvalidSubTransactionId)
20026+
continue;
20027+
return oc->oncommit;
20028+
}
20029+
return ONCOMMIT_NOOP;
20030+
}
20031+
2000620032
/*
2000720033
* Perform ON COMMIT actions.
2000820034
*

0 commit comments

Comments
 (0)