Read include/exclude commands for dump/restore from file
authorDaniel Gustafsson <[email protected]>
Wed, 29 Nov 2023 13:56:24 +0000 (14:56 +0100)
committerDaniel Gustafsson <[email protected]>
Wed, 29 Nov 2023 13:56:24 +0000 (14:56 +0100)
When there is a need to filter multiple tables with include and/or exclude
options it's quite possible to run into the limitations of the commandline.
This adds a --filter=FILENAME feature to pg_dump, pg_dumpall and pg_restore
which is used to supply a file containing object exclude/include commands
which work just like their commandline counterparts. The format of the file
is one command per row like:

    <command> <object> <objectpattern>

<command> can be "include" or "exclude", <object> can be table_data, index
table_data_and_children, database, extension, foreign_data, function, table
schema, table_and_children or trigger.

This  has gone through many revisions and design changes over a long
period of time, the list of reviewers reflect reviewers of some version of
the , not necessarily the final version.

 by Pavel Stehule with some additional hacking by me.

Author: Pavel Stehule <[email protected]>
Reviewed-by: Justin Pryzby <[email protected]>
Reviewed-by: vignesh C <[email protected]>
Reviewed-by: Dean Rasheed <[email protected]>
Reviewed-by: Tomas Vondra <[email protected]>
Reviewed-by: Julien Rouhaud <[email protected]>
Reviewed-by: Erik Rijkers <[email protected]>
Discussion: https://postgr.es/m/CAFj8pRB10wvW0CC9Xq=1XDs=zCQxer3cbLcNZa+qiX4cUH-G_A@mail.gmail.com

13 files changed:
doc/src/sgml/ref/pg_dump.sgml
doc/src/sgml/ref/pg_dumpall.sgml
doc/src/sgml/ref/pg_restore.sgml
src/bin/pg_dump/Makefile
src/bin/pg_dump/filter.c[new file with mode: 0644]
src/bin/pg_dump/filter.h[new file with mode: 0644]
src/bin/pg_dump/meson.build
src/bin/pg_dump/pg_dump.c
src/bin/pg_dump/pg_dumpall.c
src/bin/pg_dump/pg_restore.c
src/bin/pg_dump/t/005_pg_dump_filterfile.pl[new file with mode: 0644]
src/tools/msvc/Mkvcbuild.pm
src/tools/pgindent/typedefs.list

index 8695571045b2d0aab5f21f9ee755d632ff5921fa..0e5ba4f7125e26f80257e7c5da31fa0ca443c80c 100644 (file)
@@ -836,6 +836,109 @@ PostgreSQL documentation
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><option>--filter=<replaceable class="parameter">filename</replaceable></option></term>
+      <listitem>
+       <para>
+        Specify a filename from which to read patterns for objects to include
+        or exclude from the dump. The patterns are interpreted according to the
+        same rules as the corresponding options:
+        <option>-t</option>/<option>--table</option>,
+        <option>--table-and-children</option>,
+        <option>--exclude-table-and-children</option> or
+        <option>-T</option> for tables,
+        <option>-n</option>/<option>--schema</option> for schemas,
+        <option>--include-foreign-data</option> for data on foreign servers and
+        <option>--exclude-table-data</option>,
+        <option>--exclude-table-data-and-children</option> for table data,
+        <option>-e</option>/<option>--extension</option> for extensions.
+        To read from <literal>STDIN</literal>, use <filename>-</filename> as the
+        filename.  The <option>--filter</option> option can be specified in
+        conjunction with the above listed options for including or excluding
+        objects, and can also be specified more than once for multiple filter
+        files.
+       </para>
+
+       <para>
+        The file lists one object pattern per row, with the following format:
+<synopsis>
+{ include | exclude } { extension | foreign_data | table | table_and_children | table_data | table_data_and_children | schema } <replaceable class="parameter">PATTERN</replaceable>
+</synopsis>
+       </para>
+
+       <para>
+        The first keyword specifies whether the objects matched by the pattern
+        are to be included or excluded. The second keyword specifies the type
+        of object to be filtered using the pattern:
+        <itemizedlist>
+         <listitem>
+          <para>
+           <literal>extension</literal>: extensions, works like the
+           <option>--extension</option> option. This keyword can only be
+           used with the <literal>include</literal> keyword.
+          </para>
+         </listitem>
+         <listitem>
+          <para>
+           <literal>foreign_data</literal>: data on foreign servers, works like
+           the <option>--include-foreign-data</option> option. This keyword can
+           only be used with the <literal>include</literal> keyword.
+          </para>
+         </listitem>
+         <listitem>
+          <para>
+           <literal>table</literal>: tables, works like the
+           <option>-t</option>/<option>--table</option> option.
+          </para>
+         </listitem>
+         <listitem>
+          <para>
+           <literal>table_and_children</literal>: tables including any partitions
+           or inheritance child tables, works like the
+           <option>--table-and-children</option> option.
+          </para>
+         </listitem>
+         <listitem>
+          <para>
+           <literal>table_data</literal>: table data of any tables matching
+           <replaceable>pattern</replaceable>, works like the
+           <option>--exclude-table-data</option> option. This keyword can only
+           be used with the <literal>exclude</literal> keyword.
+          </para>
+         </listitem>
+         <listitem>
+          <para>
+           <literal>table_data_and_children</literal>: table data of any tables
+           matching <replaceable>pattern</replaceable> as well as any partitions
+           or inheritance children of the table(s), works like the
+           <option>--exclude-table-data-and-children</option> option. This
+           keyword can only be used with the <literal>exclude</literal> keyword.
+          </para>
+         </listitem>
+         <listitem>
+          <para>
+           <literal>schema</literal>: schemas, works like the
+           <option>-n</option>/<option>--schema</option> option.
+          </para>
+         </listitem>
+        </itemizedlist>
+       </para>
+
+       <para>
+        Lines starting with <literal>#</literal> are considered comments and
+        ignored. Comments can be placed after an object pattern row as well.
+        Blank lines are also ignored. See <xref linkend="app-psql-patterns"/>
+        for how to perform quoting in patterns.
+       </para>
+
+       <para>
+        Example files are listed below in the <xref linkend="pg-dump-examples"/>
+        section.
+       </para>
+
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><option>--if-exists</option></term>
       <listitem>
@@ -1168,6 +1271,7 @@ PostgreSQL documentation
         schema (<option>-n</option>/<option>--schema</option>) and
         table (<option>-t</option>/<option>--table</option>) pattern
         match at least one extension/schema/table in the database to be dumped.
+        This also applies to filters used with <option>--filter</option>.
         Note that if none of the extension/schema/table patterns find
         matches, <application>pg_dump</application> will generate an error
         even without <option>--strict-names</option>.
@@ -1611,6 +1715,19 @@ CREATE DATABASE foo WITH TEMPLATE template0;
 
 <screen>
 <prompt>$</prompt> <userinput>pg_dump -t "\"MixedCaseName\"" mydb &gt; mytab.sql</userinput>
+</screen></para>
+
+  <para>
+   To dump all tables whose names start with <literal>mytable</literal>, except
+   for table <literal>mytable2</literal>, specify a filter file
+   <filename>filter.txt</filename> like:
+<programlisting>
+include table mytable*
+exclude table mytable2
+</programlisting>
+
+<screen>
+<prompt>$</prompt> <userinput>pg_dump --filter=filter.txt mydb &gt; db.sql</userinput>
 </screen></para>
 
  </refsect1>
index d31585216c60d27f659634390b35c9aaea23632b..4d7c0464687ef6e0e1443a588ba39b0ae38c4f03 100644 (file)
@@ -125,6 +125,37 @@ PostgreSQL documentation
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><option>--filter=<replaceable class="parameter">filename</replaceable></option></term>
+      <listitem>
+       <para>
+        Specify a filename from which to read patterns for databases excluded
+        from the dump. The patterns are interpreted according to the same rules
+        as <option>--exclude-database</option>.
+        To read from <literal>STDIN</literal>, use <filename>-</filename> as the
+        filename.  The <option>--filter</option> option can be specified in
+        conjunction with <option>--exclude-database</option> for excluding
+        databases, and can also be specified more than once for multiple filter
+        files.
+       </para>
+
+       <para>
+        The file lists one database pattern per row, with the following format:
+<synopsis>
+exclude database <replaceable class="parameter">PATTERN</replaceable>
+</synopsis>
+       </para>
+
+       <para>
+        Lines starting with <literal>#</literal> are considered comments and
+        ignored. Comments can be placed after an object pattern row as well.
+        Blank lines are also ignored. See <xref linkend="app-psql-patterns"/>
+        for how to perform quoting in patterns.
+       </para>
+
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><option>-g</option></term>
       <term><option>--globals-only</option></term>
index 374d8d8715c051d06905b20fb76809fd5ce1e5ae..1a23874da6824e6ca792728e39175d33e22a878c 100644 (file)
@@ -190,6 +190,86 @@ PostgreSQL documentation
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><option>--filter=<replaceable class="parameter">filename</replaceable></option></term>
+      <listitem>
+       <para>
+        Specify a filename from which to read patterns for objects excluded
+        or included from restore. The patterns are interpreted according to the
+        same rules as
+        <option>-n</option>/<option>--schema</option> for including objects in schemas,
+        <option>-N</option>/<option>--exclude-schema</option>for excluding objects in schemas,
+        <option>-P</option>/<option>--function</option> for restoring named functions,
+        <option>-I</option>/<option>--index</option> for restoring named indexes,
+        <option>-t</option>/<option>--table</option> for restoring named tables
+        or <option>-T</option>/<option>--trigger</option> for restoring triggers.
+        To read from <literal>STDIN</literal>, use <filename>-</filename> as the
+        filename.  The <option>--filter</option> option can be specified in
+        conjunction with the above listed options for including or excluding
+        objects, and can also be specified more than once for multiple filter
+        files.
+       </para>
+
+       <para>
+        The file lists one database pattern per row, with the following format:
+<synopsis>
+{ include | exclude } { function | index | schema | table | trigger } <replaceable class="parameter">PATTERN</replaceable>
+</synopsis>
+       </para>
+
+       <para>
+        The first keyword specifies whether the objects matched by the pattern
+        are to be included or excluded. The second keyword specifies the type
+        of object to be filtered using the pattern:
+        <itemizedlist>
+         <listitem>
+          <para>
+           <literal>function</literal>: functions, works like the
+           <option>-P</option>/<option>--function</option> option. This keyword
+           can only be used with the <literal>include</literal> keyword.
+          </para>
+         </listitem>
+         <listitem>
+          <para>
+           <literal>index</literal>: indexes, works like the
+           <option>-I</option>/<option>--indexes</option> option. This keyword
+           can only be used with the <literal>include</literal> keyword.
+          </para>
+         </listitem>
+         <listitem>
+          <para>
+           <literal>schema</literal>: schemas, works like the
+           <option>-n</option>/<option>--schema</option> and
+           <option>-N</option>/<option>--exclude-schema</option> options.
+          </para>
+         </listitem>
+         <listitem>
+          <para>
+           <literal>table</literal>: tables, works like the
+           <option>-t</option>/<option>--table</option> option. This keyword
+           can only be used with the <literal>include</literal> keyword.
+          </para>
+         </listitem>
+         <listitem>
+          <para>
+           <literal>trigger</literal>: triggers, works like the
+           <option>-T</option>/<option>--trigger</option> option. This keyword
+           can only be used with the <literal>include</literal> keyword.
+          </para>
+         </listitem>
+        </itemizedlist>
+       </para>
+
+       <para>
+        Lines starting with <literal>#</literal> are considered comments and
+        ignored. Comments can be placed after an object pattern row as well.
+        Blank lines are also ignored. See <xref linkend="app-psql-patterns"/>
+        for how to perform quoting in patterns.
+       </para>
+
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><option>-F <replaceable class="parameter">format</replaceable></option></term>
       <term><option>--format=<replaceable class="parameter">format</replaceable></option></term>
index 604cddb9972ca8c61c0a33df57fafb17affc0117..2bcf2a70028c2493de23bb969f6b9bed3aaa8cde 100644 (file)
@@ -32,6 +32,7 @@ OBJS = \
    compress_none.o \
    compress_zstd.o \
    dumputils.o \
+   filter.o \
    parallel.o \
    pg_backup_archiver.o \
    pg_backup_custom.o \
@@ -49,8 +50,8 @@ pg_dump: pg_dump.o common.o pg_dump_sort.o $(OBJS) | submake-libpq submake-libpg
 pg_restore: pg_restore.o $(OBJS) | submake-libpq submake-libpgport submake-libpgfeutils
    $(CC) $(CFLAGS) pg_restore.o $(OBJS) $(LDFLAGS) $(LDFLAGS_EX) $(LIBS) -o $@$(X)
 
-pg_dumpall: pg_dumpall.o dumputils.o $(WIN32RES) | submake-libpq submake-libpgport submake-libpgfeutils
-   $(CC) $(CFLAGS) pg_dumpall.o dumputils.o $(WIN32RES) $(LDFLAGS) $(LDFLAGS_EX) $(LIBS) -o $@$(X)
+pg_dumpall: pg_dumpall.o dumputils.o filter.o $(WIN32RES) | submake-libpq submake-libpgport submake-libpgfeutils
+   $(CC) $(CFLAGS) pg_dumpall.o dumputils.o filter.o $(WIN32RES) $(LDFLAGS) $(LDFLAGS_EX) $(LIBS) -o $@$(X)
 
 install: all installdirs
    $(INSTALL_PROGRAM) pg_dump$(X) '$(DESTDIR)$(bindir)'/pg_dump$(X)
diff --git a/src/bin/pg_dump/filter.c b/src/bin/pg_dump/filter.c
new file mode 100644 (file)
index 0000000..dff871c
--- /dev/null
@@ -0,0 +1,471 @@
+/*-------------------------------------------------------------------------
+ *
+ * filter.c
+ *     Implementation of simple filter file parser
+ *
+ * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ *   src/bin/pg_dump/filter.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres_fe.h"
+
+#include "common/fe_memutils.h"
+#include "common/logging.h"
+#include "common/string.h"
+#include "filter.h"
+#include "lib/stringinfo.h"
+#include "pqexpbuffer.h"
+
+#define        is_keyword_str(cstr, str, bytes) \
+   ((strlen(cstr) == (bytes)) && (pg_strncasecmp((cstr), (str), (bytes)) == 0))
+
+/*
+ * Following routines are called from pg_dump, pg_dumpall and pg_restore.
+ * Since the implementation of exit_nicely is application specific, each
+ * application need to pass a function pointer to the exit_nicely function to
+ * use for exiting on errors.
+ */
+
+/*
+ * Opens filter's file and initialize fstate structure.
+ */
+void
+filter_init(FilterStateData *fstate, const char *filename, exit_function f_exit)
+{
+   fstate->filename = filename;
+   fstate->lineno = 0;
+   fstate->exit_nicely = f_exit;
+   initStringInfo(&fstate->linebuff);
+
+   if (strcmp(filename, "-") != 0)
+   {
+       fstate->fp = fopen(filename, "r");
+       if (!fstate->fp)
+       {
+           pg_log_error("could not open filter file \"%s\": %m", filename);
+           fstate->exit_nicely(1);
+       }
+   }
+   else
+       fstate->fp = stdin;
+}
+
+/*
+ * Release allocated resources for the given filter.
+ */
+void
+filter_free(FilterStateData *fstate)
+{
+   if (!fstate)
+       return;
+
+   free(fstate->linebuff.data);
+   fstate->linebuff.data = NULL;
+
+   if (fstate->fp && fstate->fp != stdin)
+   {
+       if (fclose(fstate->fp) != 0)
+           pg_log_error("could not close filter file \"%s\": %m", fstate->filename);
+
+       fstate->fp = NULL;
+   }
+}
+
+/*
+ * Translate FilterObjectType enum to string. The main purpose is for error
+ * message formatting.
+ */
+const char *
+filter_object_type_name(FilterObjectType fot)
+{
+   switch (fot)
+   {
+       case FILTER_OBJECT_TYPE_NONE:
+           return "comment or empty line";
+       case FILTER_OBJECT_TYPE_TABLE_DATA:
+           return "table data";
+       case FILTER_OBJECT_TYPE_TABLE_DATA_AND_CHILDREN:
+           return "table data and children";
+       case FILTER_OBJECT_TYPE_DATABASE:
+           return "database";
+       case FILTER_OBJECT_TYPE_EXTENSION:
+           return "extension";
+       case FILTER_OBJECT_TYPE_FOREIGN_DATA:
+           return "foreign data";
+       case FILTER_OBJECT_TYPE_FUNCTION:
+           return "function";
+       case FILTER_OBJECT_TYPE_INDEX:
+           return "index";
+       case FILTER_OBJECT_TYPE_SCHEMA:
+           return "schema";
+       case FILTER_OBJECT_TYPE_TABLE:
+           return "table";
+       case FILTER_OBJECT_TYPE_TABLE_AND_CHILDREN:
+           return "table and children";
+       case FILTER_OBJECT_TYPE_TRIGGER:
+           return "trigger";
+   }
+
+   /* should never get here */
+   pg_unreachable();
+}
+
+/*
+ * Returns true when keyword is one of supported object types, and
+ * set related objtype. Returns false, when keyword is not assigned
+ * with known object type.
+ */
+static bool
+get_object_type(const char *keyword, int size, FilterObjectType *objtype)
+{
+   if (is_keyword_str("table_data", keyword, size))
+       *objtype = FILTER_OBJECT_TYPE_TABLE_DATA;
+   else if (is_keyword_str("table_data_and_children", keyword, size))
+       *objtype = FILTER_OBJECT_TYPE_TABLE_DATA_AND_CHILDREN;
+   else if (is_keyword_str("database", keyword, size))
+       *objtype = FILTER_OBJECT_TYPE_DATABASE;
+   else if (is_keyword_str("extension", keyword, size))
+       *objtype = FILTER_OBJECT_TYPE_EXTENSION;
+   else if (is_keyword_str("foreign_data", keyword, size))
+       *objtype = FILTER_OBJECT_TYPE_FOREIGN_DATA;
+   else if (is_keyword_str("function", keyword, size))
+       *objtype = FILTER_OBJECT_TYPE_FUNCTION;
+   else if (is_keyword_str("index", keyword, size))
+       *objtype = FILTER_OBJECT_TYPE_INDEX;
+   else if (is_keyword_str("schema", keyword, size))
+       *objtype = FILTER_OBJECT_TYPE_SCHEMA;
+   else if (is_keyword_str("table", keyword, size))
+       *objtype = FILTER_OBJECT_TYPE_TABLE;
+   else if (is_keyword_str("table_and_children", keyword, size))
+       *objtype = FILTER_OBJECT_TYPE_TABLE_AND_CHILDREN;
+   else if (is_keyword_str("trigger", keyword, size))
+       *objtype = FILTER_OBJECT_TYPE_TRIGGER;
+   else
+       return false;
+
+   return true;
+}
+
+
+void
+pg_log_filter_error(FilterStateData *fstate, const char *fmt,...)
+{
+   va_list     argp;
+   char        buf[256];
+
+   va_start(argp, fmt);
+   vsnprintf(buf, sizeof(buf), fmt, argp);
+   va_end(argp);
+
+   pg_log_error("invalid format in filter read from \"%s\" on line %d: %s",
+                (fstate->fp == stdin ? "stdin" : fstate->filename),
+                fstate->lineno,
+                buf);
+}
+
+/*
+ * filter_get_keyword - read the next filter keyword from buffer
+ *
+ * Search for keywords (limited to ascii alphabetic characters) in
+ * the passed in line buffer. Returns NULL when the buffer is empty or the first
+ * char is not alpha. The char '_' is allowed, except as the first character.
+ * The length of the found keyword is returned in the size parameter.
+ */
+static const char *
+filter_get_keyword(const char **line, int *size)
+{
+   const char *ptr = *line;
+   const char *result = NULL;
+
+   /* Set returned length preemptively in case no keyword is found */
+   *size = 0;
+
+   /* Skip initial whitespace */
+   while (isspace(*ptr))
+       ptr++;
+
+   if (isalpha(*ptr))
+   {
+       result = ptr++;
+
+       while (isalpha(*ptr) || *ptr == '_')
+           ptr++;
+
+       *size = ptr - result;
+   }
+
+   *line = ptr;
+
+   return result;
+}
+
+/*
+ * read_quoted_pattern - read quoted possibly multi line string
+ *
+ * Reads a quoted string which can span over multiple lines and returns a
+ * pointer to next char after ending double quotes; it will exit on errors.
+ */
+static const char *
+read_quoted_string(FilterStateData *fstate,
+                  const char *str,
+                  PQExpBuffer pattern)
+{
+   appendPQExpBufferChar(pattern, '"');
+   str++;
+
+   while (1)
+   {
+       /*
+        * We can ignore \r or \n chars because the string is read by
+        * pg_get_line_buf, so these chars should be just trailing chars.
+        */
+       if (*str == '\r' || *str == '\n')
+       {
+           str++;
+           continue;
+       }
+
+       if (*str == '\0')
+       {
+           Assert(fstate->linebuff.data);
+
+           if (!pg_get_line_buf(fstate->fp, &fstate->linebuff))
+           {
+               if (ferror(fstate->fp))
+                   pg_log_error("could not read from filter file \"%s\": %m",
+                                fstate->filename);
+               else
+                   pg_log_filter_error(fstate, _("unexpected end of file"));
+
+               fstate->exit_nicely(1);
+           }
+
+           str = fstate->linebuff.data;
+
+           appendPQExpBufferChar(pattern, '\n');
+           fstate->lineno++;
+       }
+
+       if (*str == '"')
+       {
+           appendPQExpBufferChar(pattern, '"');
+           str++;
+
+           if (*str == '"')
+           {
+               appendPQExpBufferChar(pattern, '"');
+               str++;
+           }
+           else
+               break;
+       }
+       else if (*str == '\\')
+       {
+           str++;
+           if (*str == 'n')
+               appendPQExpBufferChar(pattern, '\n');
+           else if (*str == '\\')
+               appendPQExpBufferChar(pattern, '\\');
+
+           str++;
+       }
+       else
+           appendPQExpBufferChar(pattern, *str++);
+   }
+
+   return str;
+}
+
+/*
+ * read_pattern - reads on object pattern from input
+ *
+ * This function will parse any valid identifier (quoted or not, qualified or
+ * not), which can also includes the full signature for routines.
+ * Note that this function takes special care to sanitize the detected
+ * identifier (removing extraneous whitespaces or other unnecessary
+ * characters).  This is necessary as most backup/restore filtering functions
+ * only recognize identifiers if they are written exactly the same way as
+ * they are output by the server.
+ *
+ * Returns a pointer to next character after the found identifier and exits
+ * on error.
+ */
+static const char *
+read_pattern(FilterStateData *fstate, const char *str, PQExpBuffer pattern)
+{
+   bool        skip_space = true;
+   bool        found_space = false;
+
+   /* Skip initial whitespace */
+   while (isspace(*str))
+       str++;
+
+   if (*str == '\0')
+   {
+       pg_log_filter_error(fstate, _("missing object name pattern"));
+       fstate->exit_nicely(1);
+   }
+
+   while (*str && *str != '#')
+   {
+       while (*str && !isspace(*str) && !strchr("#,.()\"", *str))
+       {
+           /*
+            * Append space only when it is allowed, and when it was found in
+            * original string.
+            */
+           if (!skip_space && found_space)
+           {
+               appendPQExpBufferChar(pattern, ' ');
+               skip_space = true;
+           }
+
+           appendPQExpBufferChar(pattern, *str++);
+       }
+
+       skip_space = false;
+
+       if (*str == '"')
+       {
+           if (found_space)
+               appendPQExpBufferChar(pattern, ' ');
+
+           str = read_quoted_string(fstate, str, pattern);
+       }
+       else if (*str == ',')
+       {
+           appendPQExpBufferStr(pattern, ", ");
+           skip_space = true;
+           str++;
+       }
+       else if (*str && strchr(".()", *str))
+       {
+           appendPQExpBufferChar(pattern, *str++);
+           skip_space = true;
+       }
+
+       found_space = false;
+
+       /* skip ending whitespaces */
+       while (isspace(*str))
+       {
+           found_space = true;
+           str++;
+       }
+   }
+
+   return str;
+}
+
+/*
+ * filter_read_item - Read command/type/pattern triplet from a filter file
+ *
+ * This will parse one filter item from the filter file, and while it is a
+ * row based format a pattern may span more than one line due to how object
+ * names can be constructed.  The expected format of the filter file is:
+ *
+ * <command> <object_type> <pattern>
+ *
+ * command can be "include" or "exclude".
+ *
+ * Supported object types are described by enum FilterObjectType
+ * (see function get_object_type).
+ *
+ * pattern can be any possibly-quoted and possibly-qualified identifier.  It
+ * follows the same rules as other object include and exclude functions so it
+ * can also use wildcards.
+ *
+ * Returns true when one filter item was successfully read and parsed.  When
+ * object name contains \n chars, then more than one line from input file can
+ * be processed.  Returns false when the filter file reaches EOF. In case of
+ * error, the function will emit an appropriate error message and exit.
+ */
+bool
+filter_read_item(FilterStateData *fstate,
+                char **objname,
+                FilterCommandType *comtype,
+                FilterObjectType *objtype)
+{
+   if (pg_get_line_buf(fstate->fp, &fstate->linebuff))
+   {
+       const char *str = fstate->linebuff.data;
+       const char *keyword;
+       int         size;
+       PQExpBufferData pattern;
+
+       fstate->lineno++;
+
+       /* Skip initial white spaces */
+       while (isspace(*str))
+           str++;
+
+       /*
+        * Skip empty lines or lines where the first non-whitespace character
+        * is a hash indicating a comment.
+        */
+       if (*str != '\0' && *str != '#')
+       {
+           /*
+            * First we expect sequence of two keywords, {include|exclude}
+            * followed by the object type to operate on.
+            */
+           keyword = filter_get_keyword(&str, &size);
+           if (!keyword)
+           {
+               pg_log_filter_error(fstate,
+                                   _("no filter command found (expected \"include\" or \"exclude\")"));
+               fstate->exit_nicely(1);
+           }
+
+           if (is_keyword_str("include", keyword, size))
+               *comtype = FILTER_COMMAND_TYPE_INCLUDE;
+           else if (is_keyword_str("exclude", keyword, size))
+               *comtype = FILTER_COMMAND_TYPE_EXCLUDE;
+           else
+           {
+               pg_log_filter_error(fstate,
+                                   _("invalid filter command (expected \"include\" or \"exclude\")"));
+               fstate->exit_nicely(1);
+           }
+
+           keyword = filter_get_keyword(&str, &size);
+           if (!keyword)
+           {
+               pg_log_filter_error(fstate, _("missing filter object type"));
+               fstate->exit_nicely(1);
+           }
+
+           if (!get_object_type(keyword, size, objtype))
+           {
+               pg_log_filter_error(fstate,
+                                   _("unsupported filter object type: \"%.*s\""), size, keyword);
+               fstate->exit_nicely(1);
+           }
+
+           initPQExpBuffer(&pattern);
+
+           str = read_pattern(fstate, str, &pattern);
+           *objname = pattern.data;
+       }
+       else
+       {
+           *objname = NULL;
+           *comtype = FILTER_COMMAND_TYPE_NONE;
+           *objtype = FILTER_OBJECT_TYPE_NONE;
+       }
+
+       return true;
+   }
+
+   if (ferror(fstate->fp))
+   {
+       pg_log_error("could not read from filter file \"%s\": %m", fstate->filename);
+       fstate->exit_nicely(1);
+   }
+
+   return false;
+}
diff --git a/src/bin/pg_dump/filter.h b/src/bin/pg_dump/filter.h
new file mode 100644 (file)
index 0000000..502407d
--- /dev/null
@@ -0,0 +1,71 @@
+/*-------------------------------------------------------------------------
+ *
+ * filter.h
+ *     Common header file for the parser of filter file
+ *
+ * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ *   src/bin/pg_dump/filter.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef FILTER_H
+#define FILTER_H
+
+#include "lib/stringinfo.h"
+
+/* Function signature for exit_nicely functions */
+typedef void (*exit_function) (int status);
+
+/*
+ * State data for reading filter items from stream
+ */
+typedef struct
+{
+   FILE       *fp;
+   const char *filename;
+   exit_function exit_nicely;
+   int         lineno;
+   StringInfoData linebuff;
+} FilterStateData;
+
+/*
+ * List of command types that can be specified in filter file
+ */
+typedef enum
+{
+   FILTER_COMMAND_TYPE_NONE,
+   FILTER_COMMAND_TYPE_INCLUDE,
+   FILTER_COMMAND_TYPE_EXCLUDE,
+} FilterCommandType;
+
+/*
+ * List of objects that can be specified in filter file
+ */
+typedef enum
+{
+   FILTER_OBJECT_TYPE_NONE,
+   FILTER_OBJECT_TYPE_TABLE_DATA,
+   FILTER_OBJECT_TYPE_TABLE_DATA_AND_CHILDREN,
+   FILTER_OBJECT_TYPE_DATABASE,
+   FILTER_OBJECT_TYPE_EXTENSION,
+   FILTER_OBJECT_TYPE_FOREIGN_DATA,
+   FILTER_OBJECT_TYPE_FUNCTION,
+   FILTER_OBJECT_TYPE_INDEX,
+   FILTER_OBJECT_TYPE_SCHEMA,
+   FILTER_OBJECT_TYPE_TABLE,
+   FILTER_OBJECT_TYPE_TABLE_AND_CHILDREN,
+   FILTER_OBJECT_TYPE_TRIGGER,
+} FilterObjectType;
+
+extern const char *filter_object_type_name(FilterObjectType fot);
+extern void filter_init(FilterStateData *fstate, const char *filename, exit_function f_exit);
+extern void filter_free(FilterStateData *fstate);
+extern void pg_log_filter_error(FilterStateData *fstate, const char *fmt,...)
+           pg_attribute_printf(2, 3);
+extern bool filter_read_item(FilterStateData *fstate, char **objname,
+                            FilterCommandType *comtype, FilterObjectType *objtype);
+
+#endif                         /* FILTER_H */
index 9d59a106f369aef93007651e67acf88a7ae54901..b6603e26a501b48e4a6ec0432d6ecb9a4c1520bb 100644 (file)
@@ -7,6 +7,7 @@ pg_dump_common_sources = files(
   'compress_none.c',
   'compress_zstd.c',
   'dumputils.c',
+  'filter.c',
   'parallel.c',
   'pg_backup_archiver.c',
   'pg_backup_custom.c',
@@ -99,6 +100,7 @@ tests += {
       't/002_pg_dump.pl',
       't/003_pg_dump_with_server.pl',
       't/004_pg_dump_parallel.pl',
+      't/005_pg_dump_filterfile.pl',
       't/010_dump_connstr.pl',
     ],
   },
index 34fd0a86e9c3f1e23f28cd98d9eb6877e5701d94..64e2d754d12c0ab5a25837aeafcf919ff620abe6 100644 (file)
@@ -60,6 +60,7 @@
 #include "dumputils.h"
 #include "fe_utils/option_utils.h"
 #include "fe_utils/string_utils.h"
+#include "filter.h"
 #include "getopt_long.h"
 #include "libpq/libpq-fs.h"
 #include "parallel.h"
@@ -327,6 +328,7 @@ static char *get_synchronized_snapshot(Archive *fout);
 static void setupDumpWorker(Archive *AH);
 static TableInfo *getRootTableInfo(const TableInfo *tbinfo);
 static bool forcePartitionRootLoad(const TableInfo *tbinfo);
+static void read_dump_filters(const char *filename, DumpOptions *dopt);
 
 
 int
@@ -433,6 +435,7 @@ main(int argc, char **argv)
        {"exclude-table-and-children", required_argument, NULL, 13},
        {"exclude-table-data-and-children", required_argument, NULL, 14},
        {"sync-method", required_argument, NULL, 15},
+       {"filter", required_argument, NULL, 16},
 
        {NULL, 0, NULL, 0}
    };
@@ -664,6 +667,10 @@ main(int argc, char **argv)
                    exit_nicely(1);
                break;
 
+           case 16:            /* read object filters from file */
+               read_dump_filters(optarg, &dopt);
+               break;
+
            default:
                /* getopt_long already emitted a complaint */
                pg_log_error_hint("Try \"%s --help\" for more information.", progname);
@@ -1111,6 +1118,8 @@ help(const char *progname)
             "                               do NOT dump data for the specified table(s),\n"
             "                               including child and partition tables\n"));
    printf(_("  --extra-float-digits=NUM     override default setting for extra_float_digits\n"));
+   printf(_("  --filter=FILENAME            include or exclude objects and data from dump\n"
+            "                               based expressions in FILENAME\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"
@@ -18771,3 +18780,112 @@ appendReloptionsArrayAH(PQExpBuffer buffer, const char *reloptions,
    if (!res)
        pg_log_warning("could not parse %s array", "reloptions");
 }
+
+/*
+ * read_dump_filters - retrieve object identifier patterns from file
+ *
+ * Parse the specified filter file for include and exclude patterns, and add
+ * them to the relevant lists.  If the filename is "-" then filters will be
+ * read from STDIN rather than a file.
+ */
+static void
+read_dump_filters(const char *filename, DumpOptions *dopt)
+{
+   FilterStateData fstate;
+   char       *objname;
+   FilterCommandType comtype;
+   FilterObjectType objtype;
+
+   filter_init(&fstate, filename, exit_nicely);
+
+   while (filter_read_item(&fstate, &objname, &comtype, &objtype))
+   {
+       if (comtype == FILTER_COMMAND_TYPE_INCLUDE)
+       {
+           switch (objtype)
+           {
+               case FILTER_OBJECT_TYPE_NONE:
+                   break;
+               case FILTER_OBJECT_TYPE_DATABASE:
+               case FILTER_OBJECT_TYPE_FUNCTION:
+               case FILTER_OBJECT_TYPE_INDEX:
+               case FILTER_OBJECT_TYPE_TABLE_DATA:
+               case FILTER_OBJECT_TYPE_TABLE_DATA_AND_CHILDREN:
+               case FILTER_OBJECT_TYPE_TRIGGER:
+                   pg_log_filter_error(&fstate, _("%s filter for \"%s\" is not allowed."),
+                                       "include",
+                                       filter_object_type_name(objtype));
+                   exit_nicely(1);
+                   break;      /* unreachable */
+
+               case FILTER_OBJECT_TYPE_EXTENSION:
+                   simple_string_list_append(&extension_include_patterns, objname);
+                   break;
+               case FILTER_OBJECT_TYPE_FOREIGN_DATA:
+                   simple_string_list_append(&foreign_servers_include_patterns, objname);
+                   break;
+               case FILTER_OBJECT_TYPE_SCHEMA:
+                   simple_string_list_append(&schema_include_patterns, objname);
+                   dopt->include_everything = false;
+                   break;
+               case FILTER_OBJECT_TYPE_TABLE:
+                   simple_string_list_append(&table_include_patterns, objname);
+                   dopt->include_everything = false;
+                   break;
+               case FILTER_OBJECT_TYPE_TABLE_AND_CHILDREN:
+                   simple_string_list_append(&table_include_patterns_and_children,
+                                             objname);
+                   dopt->include_everything = false;
+                   break;
+           }
+       }
+       else if (comtype == FILTER_COMMAND_TYPE_EXCLUDE)
+       {
+           switch (objtype)
+           {
+               case FILTER_OBJECT_TYPE_NONE:
+                   break;
+               case FILTER_OBJECT_TYPE_DATABASE:
+               case FILTER_OBJECT_TYPE_FUNCTION:
+               case FILTER_OBJECT_TYPE_INDEX:
+               case FILTER_OBJECT_TYPE_TRIGGER:
+               case FILTER_OBJECT_TYPE_EXTENSION:
+               case FILTER_OBJECT_TYPE_FOREIGN_DATA:
+                   pg_log_filter_error(&fstate, _("%s filter for \"%s\" is not allowed."),
+                                       "exclude",
+                                       filter_object_type_name(objtype));
+                   exit_nicely(1);
+                   break;
+
+               case FILTER_OBJECT_TYPE_TABLE_DATA:
+                   simple_string_list_append(&tabledata_exclude_patterns,
+                                             objname);
+                   break;
+               case FILTER_OBJECT_TYPE_TABLE_DATA_AND_CHILDREN:
+                   simple_string_list_append(&tabledata_exclude_patterns_and_children,
+                                             objname);
+                   break;
+               case FILTER_OBJECT_TYPE_SCHEMA:
+                   simple_string_list_append(&schema_exclude_patterns, objname);
+                   break;
+               case FILTER_OBJECT_TYPE_TABLE:
+                   simple_string_list_append(&table_exclude_patterns, objname);
+                   break;
+               case FILTER_OBJECT_TYPE_TABLE_AND_CHILDREN:
+                   simple_string_list_append(&table_exclude_patterns_and_children,
+                                             objname);
+                   break;
+           }
+       }
+       else
+       {
+           Assert(comtype == FILTER_COMMAND_TYPE_NONE);
+           Assert(objtype == FILTER_OBJECT_TYPE_NONE);
+       }
+
+       if (objname)
+           free(objname);
+   }
+
+   filter_free(&fstate);
+}
index e2a9733d34897b534dc10a6aa26acfecfa025eb3..1b974cf7e8e2524b3a538a18be1fca42a3aeb5e4 100644 (file)
@@ -26,6 +26,7 @@
 #include "common/string.h"
 #include "dumputils.h"
 #include "fe_utils/string_utils.h"
+#include "filter.h"
 #include "getopt_long.h"
 #include "pg_backup.h"
 
@@ -81,6 +82,7 @@ static PGresult *executeQuery(PGconn *conn, const char *query);
 static void executeCommand(PGconn *conn, const char *query);
 static void expand_dbname_patterns(PGconn *conn, SimpleStringList *patterns,
                                   SimpleStringList *names);
+static void read_dumpall_filters(const char *filename, SimpleStringList *patterns);
 
 static char pg_dump_bin[MAXPGPATH];
 static const char *progname;
@@ -177,6 +179,7 @@ main(int argc, char *argv[])
        {"no-unlogged-table-data", no_argument, &no_unlogged_table_data, 1},
        {"on-conflict-do-nothing", no_argument, &on_conflict_do_nothing, 1},
        {"rows-per-insert", required_argument, NULL, 7},
+       {"filter", required_argument, NULL, 8},
 
        {NULL, 0, NULL, 0}
    };
@@ -360,6 +363,10 @@ main(int argc, char *argv[])
                appendShellString(pgdumpopts, optarg);
                break;
 
+           case 8:
+               read_dumpall_filters(optarg, &database_exclude_patterns);
+               break;
+
            default:
                /* getopt_long already emitted a complaint */
                pg_log_error_hint("Try \"%s --help\" for more information.", progname);
@@ -653,6 +660,7 @@ help(void)
    printf(_("  --disable-triggers           disable triggers during data-only restore\n"));
    printf(_("  --exclude-database=PATTERN   exclude databases whose name matches PATTERN\n"));
    printf(_("  --extra-float-digits=NUM     override default setting for extra_float_digits\n"));
+   printf(_("  --filter=FILENAME            exclude databases specified in FILENAME\n"));
    printf(_("  --if-exists                  use IF EXISTS when dropping objects\n"));
    printf(_("  --inserts                    dump data as INSERT commands, rather than COPY\n"));
    printf(_("  --load-via-partition-root    load partitions via the root table\n"));
@@ -1937,3 +1945,62 @@ hash_string_pointer(char *s)
 
    return hash_bytes(ss, strlen(s));
 }
+
+/*
+ * read_dumpall_filters - retrieve database identifier patterns from file
+ *
+ * Parse the specified filter file for include and exclude patterns, and add
+ * them to the relevant lists.  If the filename is "-" then filters will be
+ * read from STDIN rather than a file.
+ *
+ * At the moment, the only allowed filter is for database exclusion.
+ */
+static void
+read_dumpall_filters(const char *filename, SimpleStringList *pattern)
+{
+   FilterStateData fstate;
+   char       *objname;
+   FilterCommandType comtype;
+   FilterObjectType objtype;
+
+   filter_init(&fstate, filename, exit);
+
+   while (filter_read_item(&fstate, &objname, &comtype, &objtype))
+   {
+       if (comtype == FILTER_COMMAND_TYPE_INCLUDE)
+       {
+           pg_log_filter_error(&fstate, _("%s filter for \"%s\" is not allowed."),
+                               "include",
+                               filter_object_type_name(objtype));
+           exit_nicely(1);
+       }
+
+       switch (objtype)
+       {
+           case FILTER_OBJECT_TYPE_NONE:
+               break;
+           case FILTER_OBJECT_TYPE_FUNCTION:
+           case FILTER_OBJECT_TYPE_INDEX:
+           case FILTER_OBJECT_TYPE_TABLE_DATA:
+           case FILTER_OBJECT_TYPE_TABLE_DATA_AND_CHILDREN:
+           case FILTER_OBJECT_TYPE_TRIGGER:
+           case FILTER_OBJECT_TYPE_EXTENSION:
+           case FILTER_OBJECT_TYPE_FOREIGN_DATA:
+           case FILTER_OBJECT_TYPE_SCHEMA:
+           case FILTER_OBJECT_TYPE_TABLE:
+           case FILTER_OBJECT_TYPE_TABLE_AND_CHILDREN:
+               pg_log_filter_error(&fstate, _("unsupported filter object."));
+               exit_nicely(1);
+               break;
+
+           case FILTER_OBJECT_TYPE_DATABASE:
+               simple_string_list_append(pattern, objname);
+               break;
+       }
+
+       if (objname)
+           free(objname);
+   }
+
+   filter_free(&fstate);
+}
index 049a100634734a6b31fb4632d5124d50931e905a..1459e02263f178dce7dab6ea50cc9c95d761f870 100644 (file)
 
 #include "dumputils.h"
 #include "fe_utils/option_utils.h"
+#include "filter.h"
 #include "getopt_long.h"
 #include "parallel.h"
 #include "pg_backup_utils.h"
 
 static void usage(const char *progname);
+static void read_restore_filters(const char *filename, RestoreOptions *dopt);
 
 int
 main(int argc, char **argv)
@@ -123,6 +125,7 @@ main(int argc, char **argv)
        {"no-publications", no_argument, &no_publications, 1},
        {"no-security-labels", no_argument, &no_security_labels, 1},
        {"no-subscriptions", no_argument, &no_subscriptions, 1},
+       {"filter", required_argument, NULL, 4},
 
        {NULL, 0, NULL, 0}
    };
@@ -286,6 +289,10 @@ main(int argc, char **argv)
                set_dump_section(optarg, &(opts->dumpSections));
                break;
 
+           case 4:
+               read_restore_filters(optarg, opts);
+               break;
+
            default:
                /* getopt_long already emitted a complaint */
                pg_log_error_hint("Try \"%s --help\" for more information.", progname);
@@ -463,6 +470,8 @@ usage(const char *progname)
    printf(_("  -1, --single-transaction     restore as a single transaction\n"));
    printf(_("  --disable-triggers           disable triggers during data-only restore\n"));
    printf(_("  --enable-row-security        enable row security\n"));
+   printf(_("  --filter=FILENAME            restore or skip objects based on expressions\n"
+            "                               in FILENAME\n"));
    printf(_("  --if-exists                  use IF EXISTS when dropping objects\n"));
    printf(_("  --no-comments                do not restore comments\n"));
    printf(_("  --no-data-for-failed-tables  do not restore data of tables that could not be\n"
@@ -494,3 +503,103 @@ usage(const char *progname)
    printf(_("Report bugs to <%s>.\n"), PACKAGE_BUGREPORT);
    printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 }
+
+/*
+ * read_restore_filters - retrieve object identifier patterns from file
+ *
+ * Parse the specified filter file for include and exclude patterns, and add
+ * them to the relevant lists.  If the filename is "-" then filters will be
+ * read from STDIN rather than a file.
+ */
+static void
+read_restore_filters(const char *filename, RestoreOptions *opts)
+{
+   FilterStateData fstate;
+   char       *objname;
+   FilterCommandType comtype;
+   FilterObjectType objtype;
+
+   filter_init(&fstate, filename, exit_nicely);
+
+   while (filter_read_item(&fstate, &objname, &comtype, &objtype))
+   {
+       if (comtype == FILTER_COMMAND_TYPE_INCLUDE)
+       {
+           switch (objtype)
+           {
+               case FILTER_OBJECT_TYPE_NONE:
+                   break;
+               case FILTER_OBJECT_TYPE_TABLE_DATA:
+               case FILTER_OBJECT_TYPE_TABLE_DATA_AND_CHILDREN:
+               case FILTER_OBJECT_TYPE_TABLE_AND_CHILDREN:
+               case FILTER_OBJECT_TYPE_DATABASE:
+               case FILTER_OBJECT_TYPE_EXTENSION:
+               case FILTER_OBJECT_TYPE_FOREIGN_DATA:
+                   pg_log_filter_error(&fstate, _("%s filter for \"%s\" is not allowed."),
+                                       "include",
+                                       filter_object_type_name(objtype));
+                   exit_nicely(1);
+
+               case FILTER_OBJECT_TYPE_FUNCTION:
+                   opts->selTypes = 1;
+                   opts->selFunction = 1;
+                   simple_string_list_append(&opts->functionNames, objname);
+                   break;
+               case FILTER_OBJECT_TYPE_INDEX:
+                   opts->selTypes = 1;
+                   opts->selIndex = 1;
+                   simple_string_list_append(&opts->indexNames, objname);
+                   break;
+               case FILTER_OBJECT_TYPE_SCHEMA:
+                   simple_string_list_append(&opts->schemaNames, objname);
+                   break;
+               case FILTER_OBJECT_TYPE_TABLE:
+                   opts->selTypes = 1;
+                   opts->selTable = 1;
+                   simple_string_list_append(&opts->tableNames, objname);
+                   break;
+               case FILTER_OBJECT_TYPE_TRIGGER:
+                   opts->selTypes = 1;
+                   opts->selTrigger = 1;
+                   simple_string_list_append(&opts->triggerNames, objname);
+                   break;
+           }
+       }
+       else if (comtype == FILTER_COMMAND_TYPE_EXCLUDE)
+       {
+           switch (objtype)
+           {
+               case FILTER_OBJECT_TYPE_NONE:
+                   break;
+               case FILTER_OBJECT_TYPE_TABLE_DATA:
+               case FILTER_OBJECT_TYPE_TABLE_DATA_AND_CHILDREN:
+               case FILTER_OBJECT_TYPE_DATABASE:
+               case FILTER_OBJECT_TYPE_EXTENSION:
+               case FILTER_OBJECT_TYPE_FOREIGN_DATA:
+               case FILTER_OBJECT_TYPE_FUNCTION:
+               case FILTER_OBJECT_TYPE_INDEX:
+               case FILTER_OBJECT_TYPE_TABLE:
+               case FILTER_OBJECT_TYPE_TABLE_AND_CHILDREN:
+               case FILTER_OBJECT_TYPE_TRIGGER:
+                   pg_log_filter_error(&fstate, _("%s filter for \"%s\" is not allowed."),
+                                       "exclude",
+                                       filter_object_type_name(objtype));
+                   exit_nicely(1);
+
+               case FILTER_OBJECT_TYPE_SCHEMA:
+                   simple_string_list_append(&opts->schemaExcludeNames, objname);
+                   break;
+           }
+       }
+       else
+       {
+           Assert(comtype == FILTER_COMMAND_TYPE_NONE);
+           Assert(objtype == FILTER_OBJECT_TYPE_NONE);
+       }
+
+       if (objname)
+           free(objname);
+   }
+
+   filter_free(&fstate);
+}
diff --git a/src/bin/pg_dump/t/005_pg_dump_filterfile.pl b/src/bin/pg_dump/t/005_pg_dump_filterfile.pl
new file mode 100644 (file)
index 0000000..d16f034
--- /dev/null
@@ -0,0 +1,799 @@
+
+# Copyright (c) 2023, PostgreSQL Global Development Group
+
+use strict;
+use warnings;
+
+use PostgreSQL::Test::Cluster;
+use PostgreSQL::Test::Utils;
+use Test::More;
+
+my $tempdir = PostgreSQL::Test::Utils::tempdir;
+my $inputfile;
+
+my $node = PostgreSQL::Test::Cluster->new('main');
+my $port = $node->port;
+my $backupdir = $node->backup_dir;
+my $plainfile = "$backupdir/plain.sql";
+
+$node->init;
+$node->start;
+
+# Generate test objects
+$node->safe_psql('postgres', 'CREATE FOREIGN DATA WRAPPER dummy;');
+$node->safe_psql('postgres',
+   'CREATE SERVER dummyserver FOREIGN DATA WRAPPER dummy;');
+
+$node->safe_psql('postgres', "CREATE TABLE table_one(a varchar)");
+$node->safe_psql('postgres', "CREATE TABLE table_two(a varchar)");
+$node->safe_psql('postgres', "CREATE TABLE table_three(a varchar)");
+$node->safe_psql('postgres', "CREATE TABLE table_three_one(a varchar)");
+$node->safe_psql('postgres', "CREATE TABLE footab(a varchar)");
+$node->safe_psql('postgres', "CREATE TABLE bootab() inherits (footab)");
+$node->safe_psql(
+   'postgres', "CREATE TABLE \"strange aaa
+name\"(a varchar)");
+$node->safe_psql(
+   'postgres', "CREATE TABLE \"
+t
+t
+\"(a int)");
+
+$node->safe_psql('postgres',
+   "INSERT INTO table_one VALUES('*** TABLE ONE ***')");
+$node->safe_psql('postgres',
+   "INSERT INTO table_two VALUES('*** TABLE TWO ***')");
+$node->safe_psql('postgres',
+   "INSERT INTO table_three VALUES('*** TABLE THREE ***')");
+$node->safe_psql('postgres',
+   "INSERT INTO table_three_one VALUES('*** TABLE THREE_ONE ***')");
+$node->safe_psql('postgres', "INSERT INTO bootab VALUES(10)");
+
+$node->safe_psql('postgres', "CREATE DATABASE sourcedb");
+$node->safe_psql('postgres', "CREATE DATABASE targetdb");
+
+$node->safe_psql('sourcedb',
+   'CREATE FUNCTION foo1(a int) RETURNS int AS $$ select $1 $$ LANGUAGE sql'
+);
+$node->safe_psql('sourcedb',
+   'CREATE FUNCTION foo2(a int) RETURNS int AS $$ select $1 $$ LANGUAGE sql'
+);
+$node->safe_psql('sourcedb',
+   'CREATE FUNCTION foo3(a double precision, b int) RETURNS double precision AS $$ select $1 + $2 $$ LANGUAGE sql'
+);
+$node->safe_psql('sourcedb',
+   'CREATE FUNCTION foo_trg() RETURNS trigger AS $$ BEGIN RETURN NEW; END $$ LANGUAGE plpgsql'
+);
+$node->safe_psql('sourcedb', 'CREATE SCHEMA s1');
+$node->safe_psql('sourcedb', 'CREATE SCHEMA s2');
+$node->safe_psql('sourcedb', 'CREATE TABLE s1.t1(a int)');
+$node->safe_psql('sourcedb', 'CREATE SEQUENCE s1.s1');
+$node->safe_psql('sourcedb', 'CREATE TABLE s2.t2(a int)');
+$node->safe_psql('sourcedb', 'CREATE TABLE t1(a int, b int)');
+$node->safe_psql('sourcedb', 'CREATE TABLE t2(a int, b int)');
+$node->safe_psql('sourcedb', 'CREATE INDEX t1_idx1 ON t1(a)');
+$node->safe_psql('sourcedb', 'CREATE INDEX t1_idx2 ON t1(b)');
+$node->safe_psql('sourcedb',
+   'CREATE TRIGGER trg1 BEFORE INSERT ON t1 EXECUTE FUNCTION foo_trg()');
+$node->safe_psql('sourcedb',
+   'CREATE TRIGGER trg2 BEFORE INSERT ON t1 EXECUTE FUNCTION foo_trg()');
+
+#
+# Test interaction of correctly specified filter file
+#
+my ($cmd, $stdout, $stderr, $result);
+
+# Empty filterfile
+open $inputfile, '>', "$tempdir/inputfile.txt"
+  or die "unable to open filterfile for writing";
+print $inputfile "\n # a comment and nothing more\n\n";
+close $inputfile;
+
+command_ok(
+   [
+       'pg_dump', '-p', $port, '-f', $plainfile,
+       "--filter=$tempdir/inputfile.txt", 'postgres'
+   ],
+   "filter file without patterns");
+
+my $dump = slurp_file($plainfile);
+
+ok($dump =~ qr/^CREATE TABLE public\.table_one/m, "table one dumped");
+ok($dump =~ qr/^CREATE TABLE public\.table_two/m, "table two dumped");
+ok($dump =~ qr/^CREATE TABLE public\.table_three/m, "table three dumped");
+ok($dump =~ qr/^CREATE TABLE public\.table_three_one/m,
+   "table three one dumped");
+
+# Test various combinations of whitespace, comments and correct filters
+open $inputfile, '>', "$tempdir/inputfile.txt"
+  or die "unable to open filterfile for writing";
+print $inputfile "  include   table table_one    #comment\n";
+print $inputfile "include table table_two\n";
+print $inputfile "# skip this line\n";
+print $inputfile "\n";
+print $inputfile "\t\n";
+print $inputfile "  \t# another comment\n";
+print $inputfile "exclude table_data table_one\n";
+close $inputfile;
+
+command_ok(
+   [
+       'pg_dump', '-p', $port, '-f', $plainfile,
+       "--filter=$tempdir/inputfile.txt", 'postgres'
+   ],
+   "dump tables with filter patterns as well as comments and whitespace");
+
+$dump = slurp_file($plainfile);
+
+ok($dump =~ qr/^CREATE TABLE public\.table_one/m, "dumped table one");
+ok($dump =~ qr/^CREATE TABLE public\.table_two/m, "dumped table two");
+ok($dump !~ qr/^CREATE TABLE public\.table_three/m, "table three not dumped");
+ok($dump !~ qr/^CREATE TABLE public\.table_three_one/m,
+   "table three_one not dumped");
+ok( $dump !~ qr/^COPY public\.table_one/m,
+   "content of table one is not included");
+ok($dump =~ qr/^COPY public\.table_two/m, "content of table two is included");
+
+# Test dumping tables specified by qualified names
+open $inputfile, '>', "$tempdir/inputfile.txt"
+  or die "unable to open filterfile for writing";
+print $inputfile "include table public.table_one\n";
+print $inputfile "include table \"public\".\"table_two\"\n";
+print $inputfile "include table \"public\". table_three\n";
+close $inputfile;
+
+command_ok(
+   [
+       'pg_dump', '-p', $port, '-f', $plainfile,
+       "--filter=$tempdir/inputfile.txt", 'postgres'
+   ],
+   "filter file without patterns");
+
+$dump = slurp_file($plainfile);
+
+ok($dump =~ qr/^CREATE TABLE public\.table_one/m, "dumped table one");
+ok($dump =~ qr/^CREATE TABLE public\.table_two/m, "dumped table two");
+ok($dump =~ qr/^CREATE TABLE public\.table_three/m, "dumped table three");
+
+# Test dumping all tables except one
+open $inputfile, '>', "$tempdir/inputfile.txt"
+  or die "unable to open filterfile for writing";
+print $inputfile "exclude table table_one\n";
+close $inputfile;
+
+command_ok(
+   [
+       'pg_dump', '-p', $port, '-f', $plainfile,
+       "--filter=$tempdir/inputfile.txt", 'postgres'
+   ],
+   "dump tables with exclusion of a single table");
+
+$dump = slurp_file($plainfile);
+
+ok($dump !~ qr/^CREATE TABLE public\.table_one/m, "table one not dumped");
+ok($dump =~ qr/^CREATE TABLE public\.table_two/m, "dumped table two");
+ok($dump =~ qr/^CREATE TABLE public\.table_three/m, "dumped table three");
+ok($dump =~ qr/^CREATE TABLE public\.table_three_one/m,
+   "dumped table three_one");
+
+# Test dumping tables with a wildcard pattern
+open $inputfile, '>', "$tempdir/inputfile.txt"
+  or die "unable to open filterfile for writing";
+print $inputfile "include table table_thre*\n";
+close $inputfile;
+
+command_ok(
+   [
+       'pg_dump', '-p', $port, '-f', $plainfile,
+       "--filter=$tempdir/inputfile.txt", 'postgres'
+   ],
+   "dump tables with wildcard in pattern");
+
+$dump = slurp_file($plainfile);
+
+ok($dump !~ qr/^CREATE TABLE public\.table_one/m, "table one not dumped");
+ok($dump !~ qr/^CREATE TABLE public\.table_two/m, "table two not dumped");
+ok($dump =~ qr/^CREATE TABLE public\.table_three/m, "dumped table three");
+ok($dump =~ qr/^CREATE TABLE public\.table_three_one/m,
+   "dumped table three_one");
+
+# Test dumping table with multiline quoted tablename
+open $inputfile, '>', "$tempdir/inputfile.txt"
+  or die "unable to open filterfile for writing";
+print $inputfile "include table \"strange aaa
+name\"";
+close $inputfile;
+
+command_ok(
+   [
+       'pg_dump', '-p', $port, '-f', $plainfile,
+       "--filter=$tempdir/inputfile.txt", 'postgres'
+   ],
+   "dump tables with multiline names requiring quoting");
+
+$dump = slurp_file($plainfile);
+
+ok($dump =~ qr/^CREATE TABLE public.\"strange aaa/m,
+   "dump table with new line in name");
+
+# Test excluding multiline quoted tablename from dump
+open $inputfile, '>', "$tempdir/inputfile.txt"
+  or die "unable to open filterfile for writing";
+print $inputfile "exclude table \"strange aaa\\nname\"";
+close $inputfile;
+
+command_ok(
+   [
+       'pg_dump', '-p', $port, '-f', $plainfile,
+       "--filter=$tempdir/inputfile.txt", 'postgres'
+   ],
+   "dump tables with filter");
+
+$dump = slurp_file($plainfile);
+
+ok($dump !~ qr/^CREATE TABLE public.\"strange aaa/m,
+   "dump table with new line in name");
+
+# Test excluding an entire schema
+open $inputfile, '>', "$tempdir/inputfile.txt"
+  or die "unable to open filterfile for writing";
+print $inputfile "exclude schema public\n";
+close $inputfile;
+
+command_ok(
+   [
+       'pg_dump', '-p', $port, '-f', $plainfile,
+       "--filter=$tempdir/inputfile.txt", 'postgres'
+   ],
+   "exclude the public schema");
+
+$dump = slurp_file($plainfile);
+
+ok($dump !~ qr/^CREATE TABLE/m, "no table dumped");
+
+# Test including and excluding an entire schema by multiple filterfiles
+open $inputfile, '>', "$tempdir/inputfile.txt"
+  or die "unable to open filterfile for writing";
+print $inputfile "include schema public\n";
+close $inputfile;
+
+open my $alt_inputfile, '>', "$tempdir/inputfile2.txt"
+  or die "unable to open filterfile for writing";
+print $alt_inputfile "exclude schema public\n";
+close $alt_inputfile;
+
+command_ok(
+   [
+       'pg_dump', '-p', $port, '-f', $plainfile,
+       "--filter=$tempdir/inputfile.txt",
+       "--filter=$tempdir/inputfile2.txt", 'postgres'
+   ],
+   "exclude the public schema with multiple filters");
+
+$dump = slurp_file($plainfile);
+
+ok($dump !~ qr/^CREATE TABLE/m, "no table dumped");
+
+# Test dumping a table with a single leading newline on a row
+open $inputfile, '>', "$tempdir/inputfile.txt"
+  or die "unable to open filterfile for writing";
+print $inputfile "include table \"
+t
+t
+\"";
+close $inputfile;
+
+command_ok(
+   [
+       'pg_dump', '-p', $port, '-f', $plainfile,
+       "--filter=$tempdir/inputfile.txt", 'postgres'
+   ],
+   "dump tables with filter");
+
+$dump = slurp_file($plainfile);
+
+ok($dump =~ qr/^CREATE TABLE public.\"\nt\nt\n\" \($/ms,
+   "dump table with multiline strange name");
+
+open $inputfile, '>', "$tempdir/inputfile.txt"
+  or die "unable to open filterfile for writing";
+print $inputfile "include table \"\\nt\\nt\\n\"";
+close $inputfile;
+
+command_ok(
+   [
+       'pg_dump', '-p', $port, '-f', $plainfile,
+       "--filter=$tempdir/inputfile.txt", 'postgres'
+   ],
+   "dump tables with filter");
+
+$dump = slurp_file($plainfile);
+
+ok($dump =~ qr/^CREATE TABLE public.\"\nt\nt\n\" \($/ms,
+   "dump table with multiline strange name");
+
+#########################################
+# Test foreign_data
+
+open $inputfile, '>', "$tempdir/inputfile.txt"
+  or die "unable to open filterfile for writing";
+print $inputfile "include foreign_data doesnt_exists\n";
+close $inputfile;
+
+command_fails_like(
+   [
+       'pg_dump', '-p', $port, '-f', $plainfile,
+       "--filter=$tempdir/inputfile.txt", 'postgres'
+   ],
+   qr/pg_dump: error: no matching foreign servers were found for pattern/,
+   "dump nonexisting foreign server");
+
+open $inputfile, '>', "$tempdir/inputfile.txt"
+  or die "unable to open filterfile for writing";
+print $inputfile, "include foreign_data dummyserver\n";
+close $inputfile;
+
+command_ok(
+   [
+       'pg_dump', '-p', $port, '-f', $plainfile,
+       "--filter=$tempdir/inputfile.txt", 'postgres'
+   ],
+   "dump foreign_data with filter");
+
+$dump = slurp_file($plainfile);
+
+ok($dump =~ qr/^CREATE SERVER dummyserver/m, "dump foreign server");
+
+open $inputfile, '>', "$tempdir/inputfile.txt"
+  or die "unable to open filterfile for writing";
+print $inputfile "exclude foreign_data dummy*\n";
+close $inputfile;
+
+command_fails_like(
+   [
+       'pg_dump', '-p', $port, '-f', $plainfile,
+       "--filter=$tempdir/inputfile.txt", 'postgres'
+   ],
+   qr/exclude filter for "foreign data" is not allowed/,
+   "erroneously exclude foreign server");
+
+#########################################
+# Test broken input format
+
+# Test invalid filter command
+open $inputfile, '>', "$tempdir/inputfile.txt"
+  or die "unable to open filterfile for writing";
+print $inputfile "k";
+close $inputfile;
+
+command_fails_like(
+   [
+       'pg_dump', '-p', $port, '-f', $plainfile,
+       "--filter=$tempdir/inputfile.txt", 'postgres'
+   ],
+   qr/invalid filter command/,
+   "invalid syntax: incorrect filter command");
+
+# Test invalid object type
+open $inputfile, '>', "$tempdir/inputfile.txt"
+  or die "unable to open filterfile for writing";
+print $inputfile "include xxx";
+close $inputfile;
+
+command_fails_like(
+   [
+       'pg_dump', '-p', $port, '-f', $plainfile,
+       "--filter=$tempdir/inputfile.txt", 'postgres'
+   ],
+   qr/unsupported filter object type: "xxx"/,
+   "invalid syntax: invalid object type specified, should be table, schema, foreign_data or data"
+);
+
+# Test missing object identifier pattern
+open $inputfile, '>', "$tempdir/inputfile.txt"
+  or die "unable to open filterfile for writing";
+print $inputfile "include table";
+close $inputfile;
+
+command_fails_like(
+   [
+       'pg_dump', '-p', $port, '-f', $plainfile,
+       "--filter=$tempdir/inputfile.txt", 'postgres'
+   ],
+   qr/missing object name/,
+   "invalid syntax: missing object identifier pattern");
+
+# Test adding extra content after the object identifier pattern
+open $inputfile, '>', "$tempdir/inputfile.txt"
+  or die "unable to open filterfile for writing";
+print $inputfile "include table table one";
+close $inputfile;
+
+command_fails_like(
+   [
+       'pg_dump', '-p', $port, '-f', $plainfile,
+       "--filter=$tempdir/inputfile.txt", 'postgres'
+   ],
+   qr/no matching tables were found/,
+   "invalid syntax: extra content after object identifier pattern");
+
+#########################################
+# Combined with --strict-names
+
+# First ensure that a matching filter works
+open $inputfile, '>', "$tempdir/inputfile.txt"
+  or die "unable to open filterfile for writing";
+print $inputfile "include table table_one\n";
+close $inputfile;
+
+command_ok(
+   [
+       'pg_dump', '-p', $port, '-f', $plainfile,
+       "--filter=$tempdir/inputfile.txt",
+       '--strict-names', 'postgres'
+   ],
+   "strict names with matching pattern");
+
+$dump = slurp_file($plainfile);
+
+ok($dump =~ qr/^CREATE TABLE public\.table_one/m, "no table dumped");
+
+# Now append a pattern to the filter file which doesn't resolve
+open $inputfile, '>>', "$tempdir/inputfile.txt"
+  or die "unable to open filterfile for writing";
+print $inputfile "include table table_nonexisting_name";
+close $inputfile;
+
+command_fails_like(
+   [
+       'pg_dump', '-p', $port, '-f', $plainfile,
+       "--filter=$tempdir/inputfile.txt",
+       '--strict-names', 'postgres'
+   ],
+   qr/no matching tables were found/,
+   "inclusion of non-existing objects with --strict names");
+
+#########################################
+# pg_dumpall tests
+
+###########################
+# Test dumping all tables except one
+open $inputfile, '>', "$tempdir/inputfile.txt"
+  or die "unable to open filterfile for writing";
+print $inputfile "exclude database postgres\n";
+close $inputfile;
+
+command_ok(
+   [
+       'pg_dumpall', '-p', $port, '-f', $plainfile,
+       "--filter=$tempdir/inputfile.txt"
+   ],
+   "dump tables with exclusion of a database");
+
+$dump = slurp_file($plainfile);
+
+ok($dump !~ qr/^\\connect postgres/m, "database postgres is not dumped");
+ok($dump =~ qr/^\\connect template1/m, "database template1 is dumped");
+
+# Make sure this option dont break the existing limitation of using
+# --globals-only with exclusions
+command_fails_like(
+   [
+       'pg_dumpall', '-p', $port, '-f', $plainfile,
+       "--filter=$tempdir/inputfile.txt",
+       '--globals-only'
+   ],
+   qr/\Qpg_dumpall: error: option --exclude-database cannot be used together with -g\/--globals-only\E/,
+   'pg_dumpall: option --exclude-database cannot be used together with -g/--globals-only'
+);
+
+# Test invalid filter command
+open $inputfile, '>', "$tempdir/inputfile.txt"
+  or die "unable to open filterfile for writing";
+print $inputfile "k";
+close $inputfile;
+
+command_fails_like(
+   [
+       'pg_dumpall', '-p', $port, '-f', $plainfile,
+       "--filter=$tempdir/inputfile.txt"
+   ],
+   qr/invalid filter command/,
+   "invalid syntax: incorrect filter command");
+
+# Test invalid object type
+open $inputfile, '>', "$tempdir/inputfile.txt"
+  or die "unable to open filterfile for writing";
+print $inputfile "exclude xxx";
+close $inputfile;
+
+command_fails_like(
+   [
+       'pg_dumpall', '-p', $port, '-f', $plainfile,
+       "--filter=$tempdir/inputfile.txt"
+   ],
+   qr/unsupported filter object type: "xxx"/,
+   "invalid syntax: exclusion of non-existing object type");
+
+open $inputfile, '>', "$tempdir/inputfile.txt"
+  or die "unable to open filterfile for writing";
+print $inputfile "exclude table foo";
+close $inputfile;
+
+command_fails_like(
+   [
+       'pg_dumpall', '-p', $port, '-f', $plainfile,
+       "--filter=$tempdir/inputfile.txt"
+   ],
+   qr/pg_dumpall: error: invalid format in filter/,
+   "invalid syntax: exclusion of unsupported object type");
+
+#########################################
+# pg_restore tests
+
+command_ok(
+   [
+       'pg_dump', '-p', $port, '-f', "$tempdir/filter_test.dump",
+       "-Fc", 'postgres'
+   ],
+   "dump all tables");
+
+open $inputfile, '>', "$tempdir/inputfile.txt"
+  or die "unable to open filterfile for writing";
+print $inputfile "include table table_two";
+close $inputfile;
+
+command_ok(
+   [
+       'pg_restore', '-p', $port, '-f', $plainfile,
+       "--filter=$tempdir/inputfile.txt",
+       "-Fc", "$tempdir/filter_test.dump"
+   ],
+   "restore tables with filter");
+
+$dump = slurp_file($plainfile);
+
+ok($dump =~ qr/^CREATE TABLE public\.table_two/m, "wanted table restored");
+ok($dump !~ qr/^CREATE TABLE public\.table_one/m,
+   "unwanted table is not restored");
+
+open $inputfile, '>', "$tempdir/inputfile.txt"
+  or die "unable to open filterfile for writing";
+print $inputfile "include table_data xxx";
+close $inputfile;
+
+command_fails_like(
+   [
+       'pg_restore', '-p', $port, '-f', $plainfile,
+       "--filter=$tempdir/inputfile.txt"
+   ],
+   qr/include filter for "table data" is not allowed/,
+   "invalid syntax: inclusion of unallowed object");
+
+open $inputfile, '>', "$tempdir/inputfile.txt"
+  or die "unable to open filterfile for writing";
+print $inputfile "include extension xxx";
+close $inputfile;
+
+command_fails_like(
+   [
+       'pg_restore', '-p', $port, '-f', $plainfile,
+       "--filter=$tempdir/inputfile.txt"
+   ],
+   qr/include filter for "extension" is not allowed/,
+   "invalid syntax: inclusion of unallowed object");
+
+open $inputfile, '>', "$tempdir/inputfile.txt"
+  or die "unable to open filterfile for writing";
+print $inputfile "exclude extension xxx";
+close $inputfile;
+
+command_fails_like(
+   [
+       'pg_restore', '-p', $port, '-f', $plainfile,
+       "--filter=$tempdir/inputfile.txt"
+   ],
+   qr/exclude filter for "extension" is not allowed/,
+   "invalid syntax: exclusion of unallowed object");
+
+open $inputfile, '>', "$tempdir/inputfile.txt"
+  or die "unable to open filterfile for writing";
+print $inputfile "exclude table_data xxx";
+close $inputfile;
+
+command_fails_like(
+   [
+       'pg_restore', '-p', $port, '-f', $plainfile,
+       "--filter=$tempdir/inputfile.txt"
+   ],
+   qr/exclude filter for "table data" is not allowed/,
+   "invalid syntax: exclusion of unallowed object");
+
+#########################################
+# test restore of other objects
+
+command_ok(
+   [
+       'pg_dump', '-p', $port, '-f', "$tempdir/filter_test.dump",
+       "-Fc", 'sourcedb'
+   ],
+   "dump all objects from sourcedb");
+
+open $inputfile, '>', "$tempdir/inputfile.txt"
+  or die "unable to open filterfile for writing";
+print $inputfile "include function foo1(integer)";
+close $inputfile;
+
+command_ok(
+   [
+       'pg_restore', '-p', $port, '-f', $plainfile,
+       "--filter=$tempdir/inputfile.txt",
+       "-Fc", "$tempdir/filter_test.dump"
+   ],
+   "restore function with filter");
+
+$dump = slurp_file($plainfile);
+
+ok($dump =~ qr/^CREATE FUNCTION public\.foo1/m, "wanted function restored");
+ok( $dump !~ qr/^CREATE TABLE public\.foo2/m,
+   "unwanted function is not restored");
+
+# this should be white space tolerant (against the -P argument)
+
+open $inputfile, '>', "$tempdir/inputfile.txt"
+  or die "unable to open filterfile for writing";
+print $inputfile "include function  foo3 ( double  precision ,   integer)  ";
+close $inputfile;
+
+command_ok(
+   [
+       'pg_restore', '-p', $port, '-f', $plainfile,
+       "--filter=$tempdir/inputfile.txt",
+       "-Fc", "$tempdir/filter_test.dump"
+   ],
+   "restore function with filter");
+
+$dump = slurp_file($plainfile);
+
+ok($dump =~ qr/^CREATE FUNCTION public\.foo3/m, "wanted function restored");
+
+open $inputfile, '>', "$tempdir/inputfile.txt"
+  or die "unable to open filterfile for writing";
+print $inputfile "include index t1_idx1\n";
+
+# attention! this hit pg_restore bug - correct name of trigger is "trg1"
+# not "t1 trg1". Should be fixed when pg_restore will be fixed
+print $inputfile "include trigger t1 trg1\n";
+close $inputfile;
+
+command_ok(
+   [
+       'pg_restore', '-p', $port, '-f', $plainfile,
+       "--filter=$tempdir/inputfile.txt",
+       "-Fc", "$tempdir/filter_test.dump"
+   ],
+   "restore function with filter");
+
+$dump = slurp_file($plainfile);
+
+ok($dump =~ qr/^CREATE INDEX t1_idx1/m, "wanted index restored");
+ok($dump !~ qr/^CREATE INDEX t2_idx2/m, "unwanted index are not restored");
+ok($dump =~ qr/^CREATE TRIGGER trg1/m, "wanted trigger restored");
+ok($dump !~ qr/^CREATE TRIGGER trg2/m, "unwanted trigger is not restored");
+
+open $inputfile, '>', "$tempdir/inputfile.txt"
+  or die "unable to open filterfile for writing";
+print $inputfile "include schema s1\n";
+close $inputfile;
+
+command_ok(
+   [
+       'pg_restore', '-p', $port, '-f', $plainfile,
+       "--filter=$tempdir/inputfile.txt",
+       "-Fc", "$tempdir/filter_test.dump"
+   ],
+   "restore function with filter");
+
+$dump = slurp_file($plainfile);
+
+ok($dump =~ qr/^CREATE TABLE s1\.t1/m, "wanted table from schema restored");
+ok( $dump =~ qr/^CREATE SEQUENCE s1\.s1/m,
+   "wanted sequence from schema restored");
+ok($dump !~ qr/^CREATE TABLE s2\t2/m, "unwanted table is not restored");
+
+open $inputfile, '>', "$tempdir/inputfile.txt"
+  or die "unable to open filterfile for writing";
+print $inputfile "exclude schema s1\n";
+close $inputfile;
+
+command_ok(
+   [
+       'pg_restore', '-p', $port, '-f', $plainfile,
+       "--filter=$tempdir/inputfile.txt",
+       "-Fc", "$tempdir/filter_test.dump"
+   ],
+   "restore function with filter");
+
+$dump = slurp_file($plainfile);
+
+ok($dump !~ qr/^CREATE TABLE s1\.t1/m,
+   "unwanted table from schema is not restored");
+ok($dump !~ qr/^CREATE SEQUENCE s1\.s1/m,
+   "unwanted sequence from schema is not restored");
+ok($dump =~ qr/^CREATE TABLE s2\.t2/m, "wanted table restored");
+ok($dump =~ qr/^CREATE TABLE public\.t1/m, "wanted table restored");
+
+#########################################
+# test of supported syntax
+
+open $inputfile, '>', "$tempdir/inputfile.txt"
+  or die "unable to open filterfile for writing";
+
+print $inputfile "include table_and_children footab\n";
+close $inputfile;
+
+command_ok(
+   [
+       'pg_dump', '-p', $port, '-f', $plainfile,
+       "--filter=$tempdir/inputfile.txt", 'postgres'
+   ],
+   "filter file without patterns");
+
+$dump = slurp_file($plainfile);
+
+ok($dump =~ qr/^CREATE TABLE public\.bootab/m, "dumped children table");
+
+open $inputfile, '>', "$tempdir/inputfile.txt"
+  or die "unable to open filterfile for writing";
+
+print $inputfile "exclude table_and_children footab\n";
+close $inputfile;
+
+command_ok(
+   [
+       'pg_dump', '-p', $port, '-f', $plainfile,
+       "--filter=$tempdir/inputfile.txt", 'postgres'
+   ],
+   "filter file without patterns");
+
+$dump = slurp_file($plainfile);
+
+ok($dump !~ qr/^CREATE TABLE public\.bootab/m,
+   "exclude dumped children table");
+
+open $inputfile, '>', "$tempdir/inputfile.txt"
+  or die "unable to open filterfile for writing";
+
+print $inputfile "exclude table_data_and_children footab\n";
+close $inputfile;
+
+command_ok(
+   [
+       'pg_dump', '-p', $port, '-f', $plainfile,
+       "--filter=$tempdir/inputfile.txt", 'postgres'
+   ],
+   "filter file without patterns");
+
+$dump = slurp_file($plainfile);
+
+ok($dump =~ qr/^CREATE TABLE public\.bootab/m, "dumped children table");
+ok($dump !~ qr/^COPY public\.bootab/m, "exclude dumped children table");
+
+#########################################
+# Test extension
+
+open $inputfile, '>', "$tempdir/inputfile.txt"
+  or die "unable to open filterfile for writing";
+print $inputfile "include extension doesnt_exists\n";
+close $inputfile;
+
+command_fails_like(
+   [
+       'pg_dump', '-p', $port, '-f', $plainfile,
+       "--filter=$tempdir/inputfile.txt", 'postgres'
+   ],
+   qr/pg_dump: error: no matching extensions were found/,
+   "dump nonexisting extension");
+
+
+done_testing();
index d26d1a84e81aadc4b6176fe224207fc7a3059563..46df01cc8d237679921f46111fc9cfd558c86c02 100644 (file)
@@ -455,6 +455,7 @@ sub mkvcbuild
    $pgdumpall->AddIncludeDir('src/backend');
    $pgdumpall->AddFile('src/bin/pg_dump/pg_dumpall.c');
    $pgdumpall->AddFile('src/bin/pg_dump/dumputils.c');
+   $pgdumpall->AddFile('src/bin/pg_dump/filter.c');
    $pgdumpall->AddLibrary('ws2_32.lib');
 
    my $pgrestore = AddSimpleFrontend('pg_dump', 1);
index 86a9886d4f77ef6085b949cb0bf80c11314d5324..d659adbfd6c92204fef2499da16b1cb99d4b10c7 100644 (file)
@@ -745,6 +745,9 @@ FileFdwPlanState
 FileNameMap
 FileSet
 FileTag
+FilterCommandType
+FilterObjectType
+FilterStateData
 FinalPathExtraData
 FindColsContext
 FindSplitData