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); +}