Hi

Ășt 13. 7. 2021 v 1:16 odesĂ­latel Tom Lane <t...@sss.pgh.pa.us> napsal:

> Alvaro Herrera <alvhe...@2ndquadrant.com> writes:
> > [1] your proposal of "[+-] OBJTYPE OBJIDENT" plus empty lines allowed
> >     plus lines starting with # are comments, seems plenty.  Any line not
> >     following that format would cause an error to be thrown.
>
> I'd like to see some kind of keyword on each line, so that we could extend
> the command set by adding new keywords.  As this stands, I fear we'd end
> up using random punctuation characters in place of [+-], which seems
> pretty horrid from a readability standpoint.
>
> I think that this file format should be designed with an eye to allowing
> every, or at least most, pg_dump options to be written in the file rather
> than on the command line.  I don't say we have to *implement* that right
> now; but if the format spec is incapable of being extended to meet
> requests like that one, I think we'll regret it.  This line of thought
> suggests that the initial commands ought to match the existing
> include/exclude switches, at least approximately.
>
> Hence I suggest
>
>         include table PATTERN
>         exclude table PATTERN
>
> which ends up being the above but with words not [+-].
>

Here is an updated implementation of filter's file, that implements syntax
proposed by you.

Regards

Pavel



>                         regards, tom lane
>
diff --git a/doc/src/sgml/ref/pg_dump.sgml b/doc/src/sgml/ref/pg_dump.sgml
index 7682226b99..d0459b385e 100644
--- a/doc/src/sgml/ref/pg_dump.sgml
+++ b/doc/src/sgml/ref/pg_dump.sgml
@@ -789,6 +789,56 @@ PostgreSQL documentation
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><option>--filter=<replaceable class="parameter">filename</replaceable></option></term>
+      <listitem>
+       <para>
+        Read objects filters from the specified file.
+        If you use "-" as a filename, the filters are read from stdin.
+        The lines of this file must have the following format:
+<synopsis>
+(include|exclude)[table|schema|foreign_data|data] <replaceable class="parameter">objectname</replaceable>
+</synopsis>
+       </para>
+
+       <para>
+        The first keyword specifies whether the object is to be included
+        or excluded, and the second keyword specifies the type of object
+        to be filtered:
+        <literal>table</literal> (table),
+        <literal>schema</literal> (schema),
+        <literal>foreign_data</literal> (foreign server),
+        <literal>data</literal> (table data).
+       </para>
+
+       <para>
+        With the following filter file, the dump would include table
+        <literal>mytable1</literal> and data from foreign tables of
+        <literal>some_foreign_server</literal> foreign server, but exclude data
+        from table <literal>mytable2</literal>.
+<programlisting>
+include table mytable1
+include foreign_data some_foreign_server
+exclude table mytable2
+</programlisting>
+       </para>
+
+       <para>
+        The lines starting with symbol <literal>#</literal> are ignored.
+        Previous white chars (spaces, tabs) are not allowed. These
+        lines can be used for comments, notes. Empty lines are ignored too.
+       </para>
+
+       <para>
+        The <option>--filter</option> option works just like the other
+        options to include or exclude tables, schemas, table data, or foreign
+        tables, and both forms may be combined.  Note that there are no options
+        to exclude a specific foreign table or to include a specific table's
+        data.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><option>--if-exists</option></term>
       <listitem>
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 90ac445bcd..ba4c425ee6 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -55,10 +55,12 @@
 #include "catalog/pg_trigger_d.h"
 #include "catalog/pg_type_d.h"
 #include "common/connect.h"
+#include "common/string.h"
 #include "dumputils.h"
 #include "fe_utils/option_utils.h"
 #include "fe_utils/string_utils.h"
 #include "getopt_long.h"
+#include "lib/stringinfo.h"
 #include "libpq/libpq-fs.h"
 #include "parallel.h"
 #include "pg_backup_db.h"
@@ -308,7 +310,7 @@ static void appendReloptionsArrayAH(PQExpBuffer buffer, const char *reloptions,
 static char *get_synchronized_snapshot(Archive *fout);
 static void setupDumpWorker(Archive *AHX);
 static TableInfo *getRootTableInfo(const TableInfo *tbinfo);
-
+static void read_patterns_from_file(char *filename, DumpOptions *dopt);
 
 int
 main(int argc, char **argv)
@@ -380,6 +382,7 @@ main(int argc, char **argv)
 		{"enable-row-security", no_argument, &dopt.enable_row_security, 1},
 		{"exclude-table-data", required_argument, NULL, 4},
 		{"extra-float-digits", required_argument, NULL, 8},
+		{"filter", required_argument, NULL, 12},
 		{"if-exists", no_argument, &dopt.if_exists, 1},
 		{"inserts", no_argument, NULL, 9},
 		{"lock-wait-timeout", required_argument, NULL, 2},
@@ -613,6 +616,10 @@ main(int argc, char **argv)
 										  optarg);
 				break;
 
+			case 12:			/* filter implementation */
+				read_patterns_from_file(optarg, &dopt);
+				break;
+
 			default:
 				fprintf(stderr, _("Try \"%s --help\" for more information.\n"), progname);
 				exit_nicely(1);
@@ -1038,6 +1045,8 @@ help(const char *progname)
 			 "                               access to)\n"));
 	printf(_("  --exclude-table-data=PATTERN do NOT dump data for the specified table(s)\n"));
 	printf(_("  --extra-float-digits=NUM     override default setting for extra_float_digits\n"));
+	printf(_("  --filter=FILENAME            dump objects and data based on the filter expressions\n"
+			 "                               from the filter file\n"));
 	printf(_("  --if-exists                  use IF EXISTS when dropping objects\n"));
 	printf(_("  --include-foreign-data=PATTERN\n"
 			 "                               include data of foreign tables on foreign\n"
@@ -18940,3 +18949,281 @@ appendReloptionsArrayAH(PQExpBuffer buffer, const char *reloptions,
 	if (!res)
 		pg_log_warning("could not parse reloptions array");
 }
+
+/*
+ * Print error message and exit.
+ */
+static void
+exit_invalid_filter_format(FILE *fp, char *filename, char *message, char *line, int lineno)
+{
+	pg_log_error("invalid format of filter file \"%s\": %s",
+				 filename,
+				 message);
+
+	fprintf(stderr, "%d: %s\n", lineno, line);
+
+	if (fp != stdin)
+		fclose(fp);
+
+	exit_nicely(-1);
+}
+
+/*
+ * Search keyword (can contains only ascii alphabetic characters) on line.
+ * Returns NULL, when the line is empty or first char is not alpha
+ */
+static const char *
+get_keyword(const char **line, int *size)
+{
+	const char	   *ptr = *line;
+	const char	   *result = NULL;
+
+	/* skip initial white spaces */
+	while (isblank(*ptr))
+		ptr += 1;
+
+	if (isascii(*ptr) && isalpha(*ptr))
+	{
+		result = ptr++;
+
+		while (isascii(*ptr) && (isalpha(*ptr) || *ptr == '_'))
+			ptr += 1;
+
+		*size = ptr - result;
+	}
+
+	*line = ptr;
+
+	return result;
+}
+
+static bool
+is_keyword(const char *keyword, int size, const char *str)
+{
+	if (strlen(str) != size)
+		return false;
+
+	return pg_strncasecmp(keyword, str, size) == 0;
+}
+
+static bool
+isblank_line(const char *line)
+{
+	while (*line)
+	{
+		if (!isblank(*line++))
+			return false;
+	}
+
+	return true;
+}
+
+/*
+ * Read dumped object specification from file
+ */
+static void
+read_patterns_from_file(char *filename, DumpOptions *dopt)
+{
+	FILE	   *fp;
+	int			lineno = 0;
+	StringInfoData line;
+	PQExpBuffer quoted_name = NULL;
+
+	/* use "-" as symbol for stdin */
+	if (strcmp(filename, "-") != 0)
+	{
+		fp = fopen(filename, "r");
+		if (!fp)
+			fatal("could not open the input file \"%s\": %m",
+				  filename);
+	}
+	else
+		fp = stdin;
+
+	initStringInfo(&line);
+
+	while (pg_get_line_buf(fp, &line))
+	{
+		bool		is_include;
+		char		objecttype;
+		char	   *objectname;
+		char	   *str = line.data;
+		char	   *str_mark;
+		const char	   *keyword;
+		int			size;
+
+		lineno += 1;
+
+		(void) pg_strip_crlf(str);
+
+		/* ignore blank lines */
+		if (isblank_line(str))
+			continue;
+
+		/* when first char is hash, ignore whole line */
+		if (*str == '#')
+			continue;
+
+		keyword = get_keyword((const char **) &str, &size);
+
+		/* Now we expect sequence of two keywords */
+		if (keyword && is_keyword(keyword, size, "include"))
+			is_include = true;
+		else if (keyword && is_keyword(keyword, size, "exclude"))
+			is_include = false;
+		else
+			exit_invalid_filter_format(fp,
+									   filename,
+									   "expected keyword \"include\" or \"exclude\"",
+									   line.data,
+									   lineno);
+
+		/*
+		 * Save current position in parsed line. Can be used later
+		 * in error message.
+		 */
+		str_mark = str;
+
+		keyword = get_keyword((const char **) &str, &size);
+
+		if (keyword && is_keyword(keyword, size, "table"))
+			objecttype = 't';
+		else if (keyword && is_keyword(keyword, size, "schema"))
+			objecttype = 's';
+		else if (keyword && is_keyword(keyword, size, "foreign_data"))
+			objecttype = 'f';
+		else if (keyword && is_keyword(keyword, size, "data"))
+			objecttype = 'd';
+		else
+			exit_invalid_filter_format(fp,
+									   filename,
+									   "expected keyword \"table\", \"schema\", \"foreign_data\" or \"data\"",
+									   str_mark,
+									   lineno);
+
+		objectname = str;
+
+		/* skip initial spaces */
+		while (isspace(*objectname))
+			objectname++;
+
+		if (*objectname == '\0')
+			exit_invalid_filter_format(fp,
+									   filename,
+									   "missing object name",
+									   str,
+									   lineno);
+
+		if (*objectname == '"')
+		{
+			PQExpBuffer		quoted_name;
+			char	   *ptr = objectname + 1;
+
+			quoted_name = createPQExpBuffer();
+
+			appendPQExpBufferChar(quoted_name, '"');
+
+			while (1)
+			{
+				if (*ptr == '\0')
+				{
+					if (!pg_get_line_buf(fp, &line))
+						exit_invalid_filter_format(fp,
+												   filename,
+												   "unexpected end of file",
+												   "",
+												   lineno);
+
+					if (ferror(fp))
+						fatal("could not read from file \"%s\": %m", filename);
+
+					appendPQExpBufferChar(quoted_name, '\n');
+					ptr = line.data;
+					lineno += 1;
+				}
+
+				appendPQExpBufferChar(quoted_name, *ptr);
+				if (*ptr++ == '"')
+				{
+					if (*ptr == '"')
+						appendPQExpBufferChar(quoted_name, *ptr++);
+					else
+						break;
+				}
+			}
+
+			/* check garbage after identifier */
+			if (!isblank_line(ptr))
+				exit_invalid_filter_format(fp,
+										   filename,
+										   "unexpected chars after object name",
+										   ptr,
+										   lineno);
+
+			objectname = quoted_name->data;
+		}
+
+		if (objecttype == 't')
+		{
+			if (is_include)
+			{
+				simple_string_list_append(&table_include_patterns,
+										  objectname);
+				dopt->include_everything = false;
+			}
+			else
+				simple_string_list_append(&table_exclude_patterns,
+										  objectname);
+		}
+		else if (objecttype == 's')
+		{
+			if (is_include)
+			{
+				simple_string_list_append(&schema_include_patterns,
+										  objectname);
+				dopt->include_everything = false;
+			}
+			else
+				simple_string_list_append(&schema_exclude_patterns,
+										  objectname);
+		}
+		else if (objecttype == 'd')
+		{
+			if (is_include)
+				exit_invalid_filter_format(fp,
+										   filename,
+										   "include filter is not supported for this type of object",
+										   str,
+										   lineno);
+			else
+				simple_string_list_append(&tabledata_exclude_patterns,
+										  objectname);
+		}
+		else if (objecttype == 'f')
+		{
+			if (is_include)
+				simple_string_list_append(&foreign_servers_include_patterns,
+										  objectname);
+			else
+				exit_invalid_filter_format(fp,
+										   filename,
+										   "exclude filter is not supported for this type of object",
+										   str,
+										   lineno);
+		}
+
+		if (quoted_name)
+		{
+			destroyPQExpBuffer(quoted_name);
+			quoted_name = NULL;
+		}
+	}
+
+	pfree(line.data);
+
+	if (ferror(fp))
+		fatal("could not read from file \"%s\": %m", filename);
+
+	if (fp != stdin)
+		fclose(fp);
+}

Reply via email to