Skip to content

Commit 0f43020

Browse files
committed
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, 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 via pg_get_indexdef_string, constraints (PRIMARY KEY, UNIQUE, FOREIGN KEY, EXCLUDE, named NOT NULL) via pg_get_constraintdef_command, rules via pg_get_ruledef, extended statistics via pg_get_statisticsobjdef_string, REPLICA IDENTITY NOTHING/FULL/USING INDEX, ALTER TABLE ENABLE/FORCE ROW LEVEL SECURITY, and child-local DEFAULT overrides on inheritance/partition children. Default omission convention: every optional clause is dropped when its value equals what the system 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. A regression test under src/test/regress covers ordinary tables, identity (default and custom sequence options), generated columns, STORAGE/COMPRESSION, constraints (CHECK/UNIQUE/FK with deferrable), functional and partial indexes, inheritance and partitioning, partition children with FOR VALUES FROM/TO, WITH modulus/remainder, and DEFAULT, rules, extended statistics, RLS toggles, REPLICA IDENTITY, UNLOGGED with reloptions, per-column attoptions, child DEFAULT overrides, pretty mode, owner=false, and the error paths for views, sequences, NULL, unknown options, and odd-variadic argument counts. 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>
1 parent dc51167 commit 0f43020

10 files changed

Lines changed: 3118 additions & 3 deletions

File tree

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

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3947,6 +3947,85 @@ acl | {postgres=arwdDxtm/postgres,foo=r/postgres}
39473947
<literal>TABLESPACE</literal>.
39483948
</para></entry>
39493949
</row>
3950+
<row>
3951+
<entry role="func_table_entry"><para role="func_signature">
3952+
<indexterm>
3953+
<primary>pg_get_table_ddl</primary>
3954+
</indexterm>
3955+
<function>pg_get_table_ddl</function>
3956+
( <parameter>table</parameter> <type>regclass</type>
3957+
<optional>, <literal>VARIADIC</literal> <parameter>options</parameter>
3958+
<type>text</type> </optional> )
3959+
<returnvalue>setof text</returnvalue>
3960+
</para>
3961+
<para>
3962+
Reconstructs the <command>CREATE TABLE</command> statement for the
3963+
specified ordinary or partitioned table, followed by the
3964+
<command>ALTER TABLE</command>, <command>CREATE INDEX</command>,
3965+
<command>CREATE RULE</command>, and <command>CREATE STATISTICS</command>
3966+
statements needed to recreate the table's columns, constraints,
3967+
indexes, rules, extended statistics, and row-level security flags.
3968+
Inherited columns and constraints are emitted by the parent table's
3969+
DDL and are not duplicated on inheritance children or partitions.
3970+
Each statement is returned as a separate row.
3971+
The following options are supported:
3972+
<literal>pretty</literal> (boolean) for formatted output,
3973+
<literal>owner</literal> (boolean) to include the
3974+
<command>ALTER TABLE ... OWNER TO</command> statement,
3975+
<literal>tablespace</literal> (boolean) to include the
3976+
<literal>TABLESPACE</literal> clause on the
3977+
<command>CREATE TABLE</command> statement, and a family of
3978+
<literal>includes_<replaceable>category</replaceable></literal>
3979+
booleans that gate emission of optional sub-objects:
3980+
<literal>includes_indexes</literal>,
3981+
<literal>includes_constraints</literal>,
3982+
<literal>includes_rules</literal>,
3983+
<literal>includes_statistics</literal>,
3984+
<literal>includes_triggers</literal>,
3985+
<literal>includes_policies</literal>,
3986+
<literal>includes_rls</literal> (the
3987+
<command>ENABLE</command>/<command>FORCE ROW LEVEL SECURITY</command>
3988+
toggles), and
3989+
<literal>includes_replica_identity</literal>. Each of these
3990+
defaults to <literal>true</literal>. The
3991+
<literal>includes_partition</literal> option, which defaults to
3992+
<literal>false</literal>, controls whether the DDL for partition
3993+
children is appended after a partitioned-table parent.
3994+
</para>
3995+
<para>
3996+
The <literal>schema_qualified</literal> option (boolean, default
3997+
<literal>true</literal>) controls whether the target table's own
3998+
schema is included in the generated DDL. When set to
3999+
<literal>false</literal>, the table name is emitted unqualified
4000+
in the <command>CREATE TABLE</command> and every subsequent
4001+
<command>ALTER TABLE</command>, <command>CREATE INDEX</command>,
4002+
<command>CREATE RULE</command>, and
4003+
<command>CREATE STATISTICS</command> statement. References to
4004+
objects in the same schema as the target table (inheritance
4005+
parents, partition parents, identity sequences, and any
4006+
same-schema object the deparse helpers happen to mention) are
4007+
also emitted unqualified, so the script can be replayed under a
4008+
different <varname>search_path</varname> to recreate the table
4009+
in another schema. Cross-schema references (for example a
4010+
foreign key target in a different schema) remain qualified for
4011+
correctness.
4012+
</para>
4013+
<para>
4014+
All three forms of <command>CREATE TABLE</command> are supported:
4015+
the ordinary column-list form, the typed-table form
4016+
(<literal>OF <replaceable>type_name</replaceable></literal>, with
4017+
per-column <literal>WITH OPTIONS</literal> overrides for local
4018+
defaults, <literal>NOT NULL</literal>, and <literal>CHECK</literal>
4019+
constraints), and the <literal>PARTITION OF</literal> form.
4020+
<literal>TEMPORARY</literal> and <literal>UNLOGGED</literal>
4021+
persistence modes are emitted from
4022+
<structfield>relpersistence</structfield>. For temporary tables
4023+
registered in the current session, the
4024+
<literal>ON COMMIT DELETE ROWS</literal> and
4025+
<literal>ON COMMIT DROP</literal> clauses are emitted; the default
4026+
<literal>ON COMMIT PRESERVE ROWS</literal> is omitted.
4027+
</para></entry>
4028+
</row>
39504029
</tbody>
39514030
</tgroup>
39524031
</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
@@ -739,7 +739,6 @@ static ObjectAddress ATExecSetCompression(Relation rel,
739739
const char *column, Node *newValue, LOCKMODE lockmode);
740740

741741
static void index_copy_data(Relation rel, RelFileLocator newrlocator);
742-
static const char *storage_name(char c);
743742

744743
static void RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid,
745744
Oid oldRelOid, void *arg);
@@ -2515,7 +2514,7 @@ truncate_check_activity(Relation rel)
25152514
* storage_name
25162515
* returns the name corresponding to a typstorage/attstorage enum value
25172516
*/
2518-
static const char *
2517+
const char *
25192518
storage_name(char c)
25202519
{
25212520
switch (c)
@@ -19558,6 +19557,33 @@ remove_on_commit_action(Oid relid)
1955819557
}
1955919558
}
1956019559

19560+
/*
19561+
* Look up the registered ON COMMIT action for a relation.
19562+
*
19563+
* Returns ONCOMMIT_NOOP when nothing was registered, which also covers
19564+
* temporary tables created with the default ON COMMIT PRESERVE ROWS
19565+
* behavior (register_on_commit_action() skips those, since no action is
19566+
* needed at commit). Entries marked for deletion in the current
19567+
* transaction are ignored.
19568+
*/
19569+
OnCommitAction
19570+
get_on_commit_action(Oid relid)
19571+
{
19572+
ListCell *l;
19573+
19574+
foreach(l, on_commits)
19575+
{
19576+
OnCommitItem *oc = (OnCommitItem *) lfirst(l);
19577+
19578+
if (oc->relid != relid)
19579+
continue;
19580+
if (oc->deleting_subid != InvalidSubTransactionId)
19581+
continue;
19582+
return oc->oncommit;
19583+
}
19584+
return ONCOMMIT_NOOP;
19585+
}
19586+
1956119587
/*
1956219588
* Perform ON COMMIT actions.
1956319589
*

0 commit comments

Comments
 (0)