public inbox for [email protected]  
help / color / mirror / Atom feed
From: Gilles Darold <[email protected]>
To: PostgreSQL Hackers <[email protected]>
Subject: [Proposal] Allow pg_dump to include all child tables with the root table
Date: Wed, 11 Jan 2023 17:59:59 +0100
Message-ID: <[email protected]> (raw)

Hi all,


I would like to propose a new pg_dump option called --with-child to 
include or exclude from a dump all child and partition tables when a 
parent table is specified using option -t/--table or -T/--exclude-table. 
The whole tree is dumped with the root table.


To include all partitions or child tables with inheritance in a table 
dump we usually use the wildcard, for example:


     pg_dump -d mydb -t "root_tbname*" > out.sql


This suppose that all child/partition tables use the prefix root_tbname 
in their object name. This is often the case but, if you are as lucky as 
me, the partitions could have a total different name. No need to say 
that for inheritance this is rarely the case. The other problem is that 
with the wildcard you can also dump relations that are not concerned at 
all by what you want to dump. Using the --with-child option will allow 
to just specify the root relation and all child/partition definitions 
and/or data will be parts of dump.


     pg_dump -d mydb --table "root_tbname" --with-childs > out.sql


To exclude a whole inheritance tree from a dump:


     pg_dump -d mydb --exclude-table "root_tbname" --with-childs > out.sql


Here in attachment the patch that adds this feature to pg_dump.


Is there is any interest for this feature?


Best regards,

-- 
Gilles Darold
https://www.migops.com/


Attachments:

  [text/x-patch] 0001-dump-with-child-v1.patch (11.5K, ../[email protected]/2-0001-dump-with-child-v1.patch)
  download | inline diff:
diff --git a/doc/src/sgml/ref/pg_dump.sgml b/doc/src/sgml/ref/pg_dump.sgml
index 2c938cd7e1..f9635442f9 100644
--- a/doc/src/sgml/ref/pg_dump.sgml
+++ b/doc/src/sgml/ref/pg_dump.sgml
@@ -1172,6 +1172,17 @@ PostgreSQL documentation
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><option>--with-childs</option></term>
+      <listitem>
+       <para>
+        Include or exclude from a dump all child and partition tables when a parent
+        table is specified using option <option>-t</option>/<option>--table</option>
+        or <option>-T</option>/<option>--exclude-table</option>.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
        <term><option>-?</option></term>
        <term><option>--help</option></term>
diff --git a/src/bin/pg_dump/pg_backup.h b/src/bin/pg_dump/pg_backup.h
index aba780ef4b..09284c82be 100644
--- a/src/bin/pg_dump/pg_backup.h
+++ b/src/bin/pg_dump/pg_backup.h
@@ -200,6 +200,7 @@ typedef struct _dumpOptions
 
 	int			sequence_data;	/* dump sequence data even in schema-only mode */
 	int			do_nothing;
+	bool			with_childs;
 } DumpOptions;
 
 /*
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 5e800dc79a..83c092080e 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -421,6 +421,7 @@ main(int argc, char **argv)
 		{"on-conflict-do-nothing", no_argument, &dopt.do_nothing, 1},
 		{"rows-per-insert", required_argument, NULL, 10},
 		{"include-foreign-data", required_argument, NULL, 11},
+		{"with-childs", no_argument, NULL, 12},
 
 		{NULL, 0, NULL, 0}
 	};
@@ -631,6 +632,10 @@ main(int argc, char **argv)
 										  optarg);
 				break;
 
+			case 12:				/* dump child table too */
+				dopt.with_childs = true;
+				break;
+
 			default:
 				/* getopt_long already emitted a complaint */
 				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
@@ -810,6 +815,14 @@ main(int argc, char **argv)
 								false);
 	/* non-matching exclusion patterns aren't an error */
 
+	/*
+	 * The include child option require that there is
+	 * at least one table inclusion
+	 */
+	if (dopt.with_childs && table_include_patterns.head == NULL
+			&& table_exclude_patterns.head == NULL)
+		pg_fatal("option --with-childs requires option -t/--table or -T/--exclude-table");
+
 	/* Expand table selection patterns into OID lists */
 	if (table_include_patterns.head != NULL)
 	{
@@ -1088,6 +1101,9 @@ help(const char *progname)
 	printf(_("  --use-set-session-authorization\n"
 			 "                               use SET SESSION AUTHORIZATION commands instead of\n"
 			 "                               ALTER OWNER commands to set ownership\n"));
+	printf(_("  --with-childs                include or exclude from a dump all child and partition\n"
+			 "                               tables when a parent table is specified using\n"
+			 "                               -t/--table or -T/--exclude-table\n"));
 
 	printf(_("\nConnection options:\n"));
 	printf(_("  -d, --dbname=DBNAME      database to dump\n"));
@@ -1520,6 +1536,15 @@ expand_table_name_patterns(Archive *fout,
 		PQExpBufferData dbbuf;
 		int			dotcnt;
 
+		/*
+		 * With --include_child we look recursively to the inheritance
+		 * tree to find the childs tables of the matching include filter
+		 */
+		if (fout->dopt->with_childs)
+		{
+			appendPQExpBuffer(query, "WITH RECURSIVE child_tree (relid) AS (\n");
+		}
+
 		/*
 		 * Query must remain ABSOLUTELY devoid of unqualified names.  This
 		 * would be unnecessary given a pg_table_is_visible() variant taking a
@@ -1547,6 +1572,20 @@ expand_table_name_patterns(Archive *fout,
 			prohibit_crossdb_refs(GetConnection(fout), dbbuf.data, cell->val);
 		termPQExpBuffer(&dbbuf);
 
+		if (fout->dopt->with_childs)
+		{
+			appendPQExpBuffer(query, "\n  UNION ALL"
+						 "\n    SELECT c.oid AS relid"
+						 "\n    FROM child_tree AS p"
+						 "\n      JOIN pg_catalog.pg_inherits AS i"
+						 "\n        ON (p.relid OPERATOR(pg_catalog.=) i.inhparent)"
+						 "\n      JOIN pg_catalog.pg_class AS c"
+						 "\n        ON (c.oid OPERATOR(pg_catalog.=) i.inhrelid)"
+						 "\n)"
+						 "\nSELECT relid FROM child_tree");
+
+		}
+
 		ExecuteSqlStatement(fout, "RESET search_path");
 		res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
 		PQclear(ExecuteSqlQueryForSingleRow(fout,
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index 2eeef2a478..6f4ecc4210 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -319,6 +319,17 @@ my %pgdump_runs = (
 			'--exclude-table=dump_test.test_table', 'postgres',
 		],
 	},
+	exclude_child_table => {
+		dump_cmd => [
+			'pg_dump',
+			'--no-sync',
+			'--with-childs',
+			"--file=$tempdir/exclude_child_table.sql",
+			'--exclude-table=dump_test.measurement',
+			'postgres',
+		],
+	},
+
 	exclude_test_table_data => {
 		dump_cmd => [
 			'pg_dump',
@@ -413,6 +424,18 @@ my %pgdump_runs = (
 			'postgres',
 		],
 	},
+	include_child_table => {
+		dump_cmd => [
+			'pg_dump',
+			'--no-sync',
+			'--with-childs',
+			"--file=$tempdir/include_child_table.sql",
+			'--table=dump_test.measurement',
+			'--lock-wait-timeout='
+			  . (1000 * $PostgreSQL::Test::Utils::timeout_default),
+			'postgres',
+		],
+	},
 	role => {
 		dump_cmd => [
 			'pg_dump',
@@ -541,6 +564,7 @@ my %full_runs = (
 	compression              => 1,
 	createdb                 => 1,
 	defaults                 => 1,
+	exclude_child_table      => 1,
 	exclude_dump_test_schema => 1,
 	exclude_test_table       => 1,
 	exclude_test_table_data  => 1,
@@ -936,6 +960,10 @@ my %tests = (
 			role             => 1,
 			section_pre_data => 1,
 			binary_upgrade   => 1,
+			include_child_table => 1,
+		},
+		unlike => {
+			exclude_child_table => 1,
 		},
 	  },
 
@@ -1025,11 +1053,16 @@ my %tests = (
 
 	'ALTER TABLE measurement OWNER TO' => {
 		regexp => qr/^\QALTER TABLE dump_test.measurement OWNER TO \E.+;/m,
-		like =>
-		  { %full_runs, %dump_test_schema_runs, section_pre_data => 1, },
+		like => {
+			%full_runs,
+			%dump_test_schema_runs,
+			section_pre_data => 1,
+			include_child_table => 1,
+		},
 		unlike => {
 			exclude_dump_test_schema => 1,
 			no_owner                 => 1,
+			exclude_child_table      => 1,
 		},
 	},
 
@@ -1040,8 +1073,12 @@ my %tests = (
 			%full_runs,
 			role             => 1,
 			section_pre_data => 1,
+			include_child_table => 1,
+		},
+		unlike => {
+			no_owner => 1,
+			exclude_child_table => 1,
 		},
-		unlike => { no_owner => 1, },
 	},
 
 	'ALTER FOREIGN TABLE foreign_table OWNER TO' => {
@@ -2793,11 +2830,16 @@ my %tests = (
 			\)\n
 			\QPARTITION BY RANGE (logdate);\E\n
 			/xm,
-		like =>
-		  { %full_runs, %dump_test_schema_runs, section_pre_data => 1, },
+		like => {
+			%full_runs,
+			%dump_test_schema_runs,
+			section_pre_data => 1,
+			include_child_table => 1,
+		},
 		unlike => {
 			binary_upgrade           => 1,
 			exclude_dump_test_schema => 1,
+			exclude_child_table      => 1,
 		},
 	},
 
@@ -2824,6 +2866,10 @@ my %tests = (
 			section_pre_data => 1,
 			role             => 1,
 			binary_upgrade   => 1,
+			include_child_table => 1,
+		},
+		unlike => {
+			exclude_child_table => 1,
 		},
 	},
 
@@ -2838,10 +2884,14 @@ my %tests = (
 			\QEXECUTE FUNCTION dump_test.trigger_func();\E
 			/xm,
 		like => {
-			%full_runs, %dump_test_schema_runs, section_post_data => 1,
+			%full_runs,
+			%dump_test_schema_runs,
+			section_post_data => 1,
+			include_child_table => 1,
 		},
 		unlike => {
 			exclude_dump_test_schema => 1,
+			exclude_child_table => 1,
 		},
 	},
 
@@ -2869,6 +2919,10 @@ my %tests = (
 			section_post_data => 1,
 			role              => 1,
 			binary_upgrade    => 1,
+			include_child_table => 1,
+		},
+		unlike => {
+			exclude_child_table => 1,
 		},
 	},
 
@@ -2881,6 +2935,10 @@ my %tests = (
 			section_post_data => 1,
 			role              => 1,
 			binary_upgrade    => 1,
+			include_child_table => 1,
+		},
+		unlike => {
+			exclude_child_table => 1,
 		},
 	},
 
@@ -2893,6 +2951,10 @@ my %tests = (
 			section_post_data => 1,
 			role              => 1,
 			binary_upgrade    => 1,
+			include_child_table => 1,
+		},
+		unlike => {
+			exclude_child_table => 1,
 		},
 	},
 
@@ -3250,6 +3312,7 @@ my %tests = (
 			schema_only             => 1,
 			section_post_data       => 1,
 			test_schema_plus_large_objects => 1,
+			include_child_table => 1,
 		},
 		unlike => {
 			exclude_dump_test_schema => 1,
@@ -3258,6 +3321,7 @@ my %tests = (
 			pg_dumpall_globals_clean => 1,
 			role                     => 1,
 			section_pre_data         => 1,
+			exclude_child_table      => 1,
 		},
 	},
 
@@ -3271,9 +3335,16 @@ my %tests = (
 			\QALTER TABLE ONLY dump_test.measurement\E \n^\s+
 			\QADD CONSTRAINT measurement_pkey PRIMARY KEY (city_id, logdate);\E
 		/xm,
-		like =>
-		  { %full_runs, %dump_test_schema_runs, section_post_data => 1, },
-		unlike => { exclude_dump_test_schema => 1, },
+		like => {
+			%full_runs,
+			%dump_test_schema_runs,
+			section_post_data => 1,
+			include_child_table => 1,
+		},
+		unlike => {
+			exclude_dump_test_schema => 1,
+			exclude_child_table => 1,
+		},
 	},
 
 	'CREATE INDEX ... ON measurement_y2006_m2' => {
@@ -3284,6 +3355,10 @@ my %tests = (
 			%full_runs,
 			role              => 1,
 			section_post_data => 1,
+			include_child_table => 1,
+		},
+		unlike => {
+			exclude_child_table => 1,
 		},
 	},
 
@@ -3295,6 +3370,10 @@ my %tests = (
 			%full_runs,
 			role              => 1,
 			section_post_data => 1,
+			include_child_table => 1,
+		},
+		unlike => {
+			exclude_child_table => 1,
 		},
 	},
 
@@ -3324,6 +3403,7 @@ my %tests = (
 			role                     => 1,
 			schema_only              => 1,
 			section_post_data        => 1,
+			include_child_table      => 1,
 		},
 		unlike => {
 			only_dump_test_schema    => 1,
@@ -3332,6 +3412,7 @@ my %tests = (
 			pg_dumpall_globals_clean => 1,
 			section_pre_data         => 1,
 			test_schema_plus_large_objects => 1,
+			exclude_child_table      => 1,
 		},
 	},
 
@@ -3614,11 +3695,16 @@ my %tests = (
 						   TO regress_dump_test_role;',
 		regexp =>
 		  qr/^\QGRANT SELECT ON TABLE dump_test.measurement TO regress_dump_test_role;\E/m,
-		like =>
-		  { %full_runs, %dump_test_schema_runs, section_pre_data => 1, },
+		like => {
+			%full_runs,
+			%dump_test_schema_runs,
+			section_pre_data => 1,
+			include_child_table => 1,
+		},
 		unlike => {
 			exclude_dump_test_schema => 1,
 			no_privs                 => 1,
+			exclude_child_table      => 1,
 		},
 	},
 
@@ -3636,8 +3722,12 @@ my %tests = (
 			%full_runs,
 			role             => 1,
 			section_pre_data => 1,
+			include_child_table => 1,
+		},
+		unlike => {
+			no_privs => 1,
+			exclude_child_table => 1,
 		},
-		unlike => { no_privs => 1, },
 	},
 
 	'GRANT ALL ON LARGE OBJECT ...' => {
@@ -3884,6 +3974,7 @@ my %tests = (
 			only_dump_test_table => 1,
 			role                 => 1,
 			section_pre_data     => 1,
+			include_child_table  => 1,
 		},
 		unlike => { no_privs => 1, },
 	},
@@ -4106,6 +4197,13 @@ $node->command_fails_like(
 	qr/pg_dumpall: error: improper qualified name \(too many dotted names\): myhost\.mydb/,
 	'pg_dumpall: option --exclude-database rejects multipart database names');
 
+#########################################
+# Test invalid use of --with-childs
+$node->command_fails_like(
+	[ 'pg_dump', '-p', "$port", '--with-childs' ],
+	qr/pg_dump: error: option --with-childs requires option -t\/--table or -T\/--exclude-table/,
+	'pg_dump: option --with-childs require inclusion or exclusion of tables');
+
 #########################################
 # Test valid database exclusion patterns
 


view thread (2+ messages)

reply

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Reply to all the recipients using the --to and --cc options:
  reply via email

  To: [email protected]
  Cc: [email protected]
  Subject: Re: [Proposal] Allow pg_dump to include all child tables with the root table
  In-Reply-To: <[email protected]>

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox