Add error pattern checks for some TAP tests for non-existing objects
[pgsql.git] / src / bin / pg_dump / filter.c
blob7214d514137716a5c0146e5e9bffbb5ba38ebebe
1 /*-------------------------------------------------------------------------
3 * filter.c
4 * Implementation of simple filter file parser
6 * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
7 * Portions Copyright (c) 1994, Regents of the University of California
9 * IDENTIFICATION
10 * src/bin/pg_dump/filter.c
12 *-------------------------------------------------------------------------
14 #include "postgres_fe.h"
16 #include "common/logging.h"
17 #include "common/string.h"
18 #include "filter.h"
19 #include "lib/stringinfo.h"
20 #include "pqexpbuffer.h"
22 #define is_keyword_str(cstr, str, bytes) \
23 ((strlen(cstr) == (bytes)) && (pg_strncasecmp((cstr), (str), (bytes)) == 0))
26 * Following routines are called from pg_dump, pg_dumpall and pg_restore.
27 * Since the implementation of exit_nicely is application specific, each
28 * application need to pass a function pointer to the exit_nicely function to
29 * use for exiting on errors.
33 * Opens filter's file and initialize fstate structure.
35 void
36 filter_init(FilterStateData *fstate, const char *filename, exit_function f_exit)
38 fstate->filename = filename;
39 fstate->lineno = 0;
40 fstate->exit_nicely = f_exit;
41 initStringInfo(&fstate->linebuff);
43 if (strcmp(filename, "-") != 0)
45 fstate->fp = fopen(filename, "r");
46 if (!fstate->fp)
48 pg_log_error("could not open filter file \"%s\": %m", filename);
49 fstate->exit_nicely(1);
52 else
53 fstate->fp = stdin;
57 * Release allocated resources for the given filter.
59 void
60 filter_free(FilterStateData *fstate)
62 if (!fstate)
63 return;
65 free(fstate->linebuff.data);
66 fstate->linebuff.data = NULL;
68 if (fstate->fp && fstate->fp != stdin)
70 if (fclose(fstate->fp) != 0)
71 pg_log_error("could not close filter file \"%s\": %m", fstate->filename);
73 fstate->fp = NULL;
78 * Translate FilterObjectType enum to string. The main purpose is for error
79 * message formatting.
81 const char *
82 filter_object_type_name(FilterObjectType fot)
84 switch (fot)
86 case FILTER_OBJECT_TYPE_NONE:
87 return "comment or empty line";
88 case FILTER_OBJECT_TYPE_TABLE_DATA:
89 return "table data";
90 case FILTER_OBJECT_TYPE_TABLE_DATA_AND_CHILDREN:
91 return "table data and children";
92 case FILTER_OBJECT_TYPE_DATABASE:
93 return "database";
94 case FILTER_OBJECT_TYPE_EXTENSION:
95 return "extension";
96 case FILTER_OBJECT_TYPE_FOREIGN_DATA:
97 return "foreign data";
98 case FILTER_OBJECT_TYPE_FUNCTION:
99 return "function";
100 case FILTER_OBJECT_TYPE_INDEX:
101 return "index";
102 case FILTER_OBJECT_TYPE_SCHEMA:
103 return "schema";
104 case FILTER_OBJECT_TYPE_TABLE:
105 return "table";
106 case FILTER_OBJECT_TYPE_TABLE_AND_CHILDREN:
107 return "table and children";
108 case FILTER_OBJECT_TYPE_TRIGGER:
109 return "trigger";
112 /* should never get here */
113 pg_unreachable();
117 * Returns true when keyword is one of supported object types, and
118 * set related objtype. Returns false, when keyword is not assigned
119 * with known object type.
121 static bool
122 get_object_type(const char *keyword, int size, FilterObjectType *objtype)
124 if (is_keyword_str("table_data", keyword, size))
125 *objtype = FILTER_OBJECT_TYPE_TABLE_DATA;
126 else if (is_keyword_str("table_data_and_children", keyword, size))
127 *objtype = FILTER_OBJECT_TYPE_TABLE_DATA_AND_CHILDREN;
128 else if (is_keyword_str("database", keyword, size))
129 *objtype = FILTER_OBJECT_TYPE_DATABASE;
130 else if (is_keyword_str("extension", keyword, size))
131 *objtype = FILTER_OBJECT_TYPE_EXTENSION;
132 else if (is_keyword_str("foreign_data", keyword, size))
133 *objtype = FILTER_OBJECT_TYPE_FOREIGN_DATA;
134 else if (is_keyword_str("function", keyword, size))
135 *objtype = FILTER_OBJECT_TYPE_FUNCTION;
136 else if (is_keyword_str("index", keyword, size))
137 *objtype = FILTER_OBJECT_TYPE_INDEX;
138 else if (is_keyword_str("schema", keyword, size))
139 *objtype = FILTER_OBJECT_TYPE_SCHEMA;
140 else if (is_keyword_str("table", keyword, size))
141 *objtype = FILTER_OBJECT_TYPE_TABLE;
142 else if (is_keyword_str("table_and_children", keyword, size))
143 *objtype = FILTER_OBJECT_TYPE_TABLE_AND_CHILDREN;
144 else if (is_keyword_str("trigger", keyword, size))
145 *objtype = FILTER_OBJECT_TYPE_TRIGGER;
146 else
147 return false;
149 return true;
153 void
154 pg_log_filter_error(FilterStateData *fstate, const char *fmt,...)
156 va_list argp;
157 char buf[256];
159 va_start(argp, fmt);
160 vsnprintf(buf, sizeof(buf), fmt, argp);
161 va_end(argp);
163 if (fstate->fp == stdin)
164 pg_log_error("invalid format in filter read from standard input on line %d: %s",
165 fstate->lineno, buf);
166 else
167 pg_log_error("invalid format in filter read from file \"%s\" on line %d: %s",
168 fstate->filename, fstate->lineno, buf);
172 * filter_get_keyword - read the next filter keyword from buffer
174 * Search for keywords (limited to ascii alphabetic characters) in
175 * the passed in line buffer. Returns NULL when the buffer is empty or the first
176 * char is not alpha. The char '_' is allowed, except as the first character.
177 * The length of the found keyword is returned in the size parameter.
179 static const char *
180 filter_get_keyword(const char **line, int *size)
182 const char *ptr = *line;
183 const char *result = NULL;
185 /* Set returned length preemptively in case no keyword is found */
186 *size = 0;
188 /* Skip initial whitespace */
189 while (isspace((unsigned char) *ptr))
190 ptr++;
192 if (isalpha((unsigned char) *ptr))
194 result = ptr++;
196 while (isalpha((unsigned char) *ptr) || *ptr == '_')
197 ptr++;
199 *size = ptr - result;
202 *line = ptr;
204 return result;
208 * read_quoted_string - read quoted possibly multi line string
210 * Reads a quoted string which can span over multiple lines and returns a
211 * pointer to next char after ending double quotes; it will exit on errors.
213 static const char *
214 read_quoted_string(FilterStateData *fstate,
215 const char *str,
216 PQExpBuffer pattern)
218 appendPQExpBufferChar(pattern, '"');
219 str++;
221 while (1)
224 * We can ignore \r or \n chars because the string is read by
225 * pg_get_line_buf, so these chars should be just trailing chars.
227 if (*str == '\r' || *str == '\n')
229 str++;
230 continue;
233 if (*str == '\0')
235 Assert(fstate->linebuff.data);
237 if (!pg_get_line_buf(fstate->fp, &fstate->linebuff))
239 if (ferror(fstate->fp))
240 pg_log_error("could not read from filter file \"%s\": %m",
241 fstate->filename);
242 else
243 pg_log_filter_error(fstate, _("unexpected end of file"));
245 fstate->exit_nicely(1);
248 str = fstate->linebuff.data;
250 appendPQExpBufferChar(pattern, '\n');
251 fstate->lineno++;
254 if (*str == '"')
256 appendPQExpBufferChar(pattern, '"');
257 str++;
259 if (*str == '"')
261 appendPQExpBufferChar(pattern, '"');
262 str++;
264 else
265 break;
267 else if (*str == '\\')
269 str++;
270 if (*str == 'n')
271 appendPQExpBufferChar(pattern, '\n');
272 else if (*str == '\\')
273 appendPQExpBufferChar(pattern, '\\');
275 str++;
277 else
278 appendPQExpBufferChar(pattern, *str++);
281 return str;
285 * read_pattern - reads on object pattern from input
287 * This function will parse any valid identifier (quoted or not, qualified or
288 * not), which can also includes the full signature for routines.
289 * Note that this function takes special care to sanitize the detected
290 * identifier (removing extraneous whitespaces or other unnecessary
291 * characters). This is necessary as most backup/restore filtering functions
292 * only recognize identifiers if they are written exactly the same way as
293 * they are output by the server.
295 * Returns a pointer to next character after the found identifier and exits
296 * on error.
298 static const char *
299 read_pattern(FilterStateData *fstate, const char *str, PQExpBuffer pattern)
301 bool skip_space = true;
302 bool found_space = false;
304 /* Skip initial whitespace */
305 while (isspace((unsigned char) *str))
306 str++;
308 if (*str == '\0')
310 pg_log_filter_error(fstate, _("missing object name pattern"));
311 fstate->exit_nicely(1);
314 while (*str && *str != '#')
316 while (*str && !isspace((unsigned char) *str) && !strchr("#,.()\"", *str))
319 * Append space only when it is allowed, and when it was found in
320 * original string.
322 if (!skip_space && found_space)
324 appendPQExpBufferChar(pattern, ' ');
325 skip_space = true;
328 appendPQExpBufferChar(pattern, *str++);
331 skip_space = false;
333 if (*str == '"')
335 if (found_space)
336 appendPQExpBufferChar(pattern, ' ');
338 str = read_quoted_string(fstate, str, pattern);
340 else if (*str == ',')
342 appendPQExpBufferStr(pattern, ", ");
343 skip_space = true;
344 str++;
346 else if (*str && strchr(".()", *str))
348 appendPQExpBufferChar(pattern, *str++);
349 skip_space = true;
352 found_space = false;
354 /* skip ending whitespaces */
355 while (isspace((unsigned char) *str))
357 found_space = true;
358 str++;
362 return str;
366 * filter_read_item - Read command/type/pattern triplet from a filter file
368 * This will parse one filter item from the filter file, and while it is a
369 * row based format a pattern may span more than one line due to how object
370 * names can be constructed. The expected format of the filter file is:
372 * <command> <object_type> <pattern>
374 * command can be "include" or "exclude".
376 * Supported object types are described by enum FilterObjectType
377 * (see function get_object_type).
379 * pattern can be any possibly-quoted and possibly-qualified identifier. It
380 * follows the same rules as other object include and exclude functions so it
381 * can also use wildcards.
383 * Returns true when one filter item was successfully read and parsed. When
384 * object name contains \n chars, then more than one line from input file can
385 * be processed. Returns false when the filter file reaches EOF. In case of
386 * error, the function will emit an appropriate error message and exit.
388 bool
389 filter_read_item(FilterStateData *fstate,
390 char **objname,
391 FilterCommandType *comtype,
392 FilterObjectType *objtype)
394 if (pg_get_line_buf(fstate->fp, &fstate->linebuff))
396 const char *str = fstate->linebuff.data;
397 const char *keyword;
398 int size;
399 PQExpBufferData pattern;
401 fstate->lineno++;
403 /* Skip initial white spaces */
404 while (isspace((unsigned char) *str))
405 str++;
408 * Skip empty lines or lines where the first non-whitespace character
409 * is a hash indicating a comment.
411 if (*str != '\0' && *str != '#')
414 * First we expect sequence of two keywords, {include|exclude}
415 * followed by the object type to operate on.
417 keyword = filter_get_keyword(&str, &size);
418 if (!keyword)
420 pg_log_filter_error(fstate,
421 _("no filter command found (expected \"include\" or \"exclude\")"));
422 fstate->exit_nicely(1);
425 if (is_keyword_str("include", keyword, size))
426 *comtype = FILTER_COMMAND_TYPE_INCLUDE;
427 else if (is_keyword_str("exclude", keyword, size))
428 *comtype = FILTER_COMMAND_TYPE_EXCLUDE;
429 else
431 pg_log_filter_error(fstate,
432 _("invalid filter command (expected \"include\" or \"exclude\")"));
433 fstate->exit_nicely(1);
436 keyword = filter_get_keyword(&str, &size);
437 if (!keyword)
439 pg_log_filter_error(fstate, _("missing filter object type"));
440 fstate->exit_nicely(1);
443 if (!get_object_type(keyword, size, objtype))
445 pg_log_filter_error(fstate,
446 _("unsupported filter object type: \"%.*s\""), size, keyword);
447 fstate->exit_nicely(1);
450 initPQExpBuffer(&pattern);
452 str = read_pattern(fstate, str, &pattern);
453 *objname = pattern.data;
455 else
457 *objname = NULL;
458 *comtype = FILTER_COMMAND_TYPE_NONE;
459 *objtype = FILTER_OBJECT_TYPE_NONE;
462 return true;
465 if (ferror(fstate->fp))
467 pg_log_error("could not read from filter file \"%s\": %m", fstate->filename);
468 fstate->exit_nicely(1);
471 return false;