diff options
author | Tom Lane <tgl@sss.pgh.pa.us> | 2002-10-19 00:22:14 +0000 |
---|---|---|
committer | Tom Lane <tgl@sss.pgh.pa.us> | 2002-10-19 00:22:14 +0000 |
commit | 44dc9c1faa3bc6dfea71dc9dd7dc83bb642a818c (patch) | |
tree | 1cbfd2d139a488cd88546f05d4b4bf629d37e0cd /src/bin/psql/copy.c | |
parent | 4cff161703beb10aab08b614feb9dfffc3860352 (diff) | |
download | postgresql-44dc9c1faa3bc6dfea71dc9dd7dc83bb642a818c.tar.gz postgresql-44dc9c1faa3bc6dfea71dc9dd7dc83bb642a818c.zip |
Fix psql's \copy to accept table names containing schemas, as well as
a column list. Bring its parsing of quoted names and quoted strings
somewhat up to speed --- I believe it now handles all non-error cases
the same way the backend would, but weird boundary conditions are not
necessarily done the same way.
Diffstat (limited to 'src/bin/psql/copy.c')
-rw-r--r-- | src/bin/psql/copy.c | 319 |
1 files changed, 195 insertions, 124 deletions
diff --git a/src/bin/psql/copy.c b/src/bin/psql/copy.c index 7e1b05d5fe8..b70519be159 100644 --- a/src/bin/psql/copy.c +++ b/src/bin/psql/copy.c @@ -3,7 +3,7 @@ * * Copyright 2000 by PostgreSQL Global Development Group * - * $Header: /cvsroot/pgsql/src/bin/psql/copy.c,v 1.27 2002/10/15 02:24:16 tgl Exp $ + * $Header: /cvsroot/pgsql/src/bin/psql/copy.c,v 1.28 2002/10/19 00:22:14 tgl Exp $ */ #include "postgres_fe.h" #include "copy.h" @@ -38,11 +38,15 @@ bool copy_in_state; * parse_slash_copy * -- parses \copy command line * - * Accepted syntax: \copy table|"table" [with oids] from|to filename|'filename' [with ] [ oids ] [ delimiter '<char>'] [ null as 'string' ] + * Accepted syntax: \copy table [(columnlist)] [with oids] from|to filename [with ] [ oids ] [ delimiter char] [ null as string ] * (binary is not here yet) * * Old syntax for backward compatibility: (2002-06-19): - * \copy table|"table" [with oids] from|to filename|'filename' [ using delimiters '<char>'] [ with null as 'string' ] + * \copy table [(columnlist)] [with oids] from|to filename [ using delimiters char] [ with null as string ] + * + * table name can be double-quoted and can have a schema part. + * column names can be double-quoted. + * filename, char, and string can be single-quoted like SQL literals. * * returns a malloc'ed structure with the options, or NULL on parsing error */ @@ -50,6 +54,7 @@ bool copy_in_state; struct copy_options { char *table; + char *column_list; char *file; /* NULL = stdin/stdout */ bool from; bool binary; @@ -65,6 +70,7 @@ free_copy_options(struct copy_options * ptr) if (!ptr) return; free(ptr->table); + free(ptr->column_list); free(ptr->file); free(ptr->delim); free(ptr->null); @@ -72,14 +78,32 @@ free_copy_options(struct copy_options * ptr) } +/* catenate "more" onto "var", freeing the original value of *var */ +static void +xstrcat(char **var, const char *more) +{ + char *newvar; + + newvar = (char *) malloc(strlen(*var) + strlen(more) + 1); + if (!newvar) + { + psql_error("out of memory\n"); + exit(EXIT_FAILURE); + } + strcpy(newvar, *var); + strcat(newvar, more); + free(*var); + *var = newvar; +} + + static struct copy_options * parse_slash_copy(const char *args) { struct copy_options *result; char *line; char *token; - bool error = false; - char quote; + const char *whitespace = " \t\n\r"; if (args) line = xstrdup(args); @@ -95,152 +119,183 @@ parse_slash_copy(const char *args) exit(EXIT_FAILURE); } - token = strtokx(line, " \t\n\r", "\"", '\\', "e, NULL, pset.encoding); + token = strtokx(line, whitespace, ".,()", "\"", + 0, false, pset.encoding); if (!token) - error = true; - else - { + goto error; + #ifdef NOT_USED - /* this is not implemented yet */ - if (!quote && strcasecmp(token, "binary") == 0) + /* this is not implemented yet */ + if (strcasecmp(token, "binary") == 0) + { + result->binary = true; + token = strtokx(NULL, whitespace, ".,()", "\"", + 0, false, pset.encoding); + if (!token) + goto error; + } +#endif + + result->table = xstrdup(token); + + token = strtokx(NULL, whitespace, ".,()", "\"", + 0, false, pset.encoding); + if (!token) + goto error; + + /* + * strtokx() will not have returned a multi-character token starting with + * '.', so we don't need strcmp() here. Likewise for '(', etc, below. + */ + if (token[0] == '.') + { + /* handle schema . table */ + xstrcat(&result->table, token); + token = strtokx(NULL, whitespace, ".,()", "\"", + 0, false, pset.encoding); + if (!token) + goto error; + xstrcat(&result->table, token); + token = strtokx(NULL, whitespace, ".,()", "\"", + 0, false, pset.encoding); + if (!token) + goto error; + } + + if (token[0] == '(') + { + /* handle parenthesized column list */ + result->column_list = xstrdup(token); + for (;;) { - result->binary = true; - token = strtokx(NULL, " \t\n\r", "\"", '\\', "e, NULL, pset.encoding); + token = strtokx(NULL, whitespace, ".,()", "\"", + 0, false, pset.encoding); + if (!token || strchr(".,()", token[0])) + goto error; + xstrcat(&result->column_list, token); + token = strtokx(NULL, whitespace, ".,()", "\"", + 0, false, pset.encoding); if (!token) - error = true; + goto error; + xstrcat(&result->column_list, token); + if (token[0] == ')') + break; + if (token[0] != ',') + goto error; } - if (token) -#endif - result->table = xstrdup(token); + token = strtokx(NULL, whitespace, ".,()", "\"", + 0, false, pset.encoding); + if (!token) + goto error; } -#ifdef USE_ASSERT_CHECKING - assert(error || result->table); -#endif - - if (!error) + /* + * Allows old COPY syntax for backward compatibility + * 2002-06-19 + */ + if (strcasecmp(token, "with") == 0) { - token = strtokx(NULL, " \t\n\r", NULL, '\\', NULL, NULL, pset.encoding); + token = strtokx(NULL, whitespace, NULL, NULL, + 0, false, pset.encoding); + if (!token || strcasecmp(token, "oids") != 0) + goto error; + result->oids = true; + + token = strtokx(NULL, whitespace, NULL, NULL, + 0, false, pset.encoding); if (!token) - error = true; - else - { - /* - * Allows old COPY syntax for backward compatibility - * 2002-06-19 - */ - if (strcasecmp(token, "with") == 0) - { - token = strtokx(NULL, " \t\n\r", NULL, '\\', NULL, NULL, pset.encoding); - if (!token || strcasecmp(token, "oids") != 0) - error = true; - else - result->oids = true; + goto error; + } - if (!error) - { - token = strtokx(NULL, " \t\n\r", NULL, '\\', NULL, NULL, pset.encoding); - if (!token) - error = true; - } - } + if (strcasecmp(token, "from") == 0) + result->from = true; + else if (strcasecmp(token, "to") == 0) + result->from = false; + else + goto error; - if (!error && strcasecmp(token, "from") == 0) - result->from = true; - else if (!error && strcasecmp(token, "to") == 0) - result->from = false; - else - error = true; - } - } + token = strtokx(NULL, whitespace, NULL, "'", + '\\', true, pset.encoding); + if (!token) + goto error; + + if (strcasecmp(token, "stdin") == 0 || + strcasecmp(token, "stdout") == 0) + result->file = NULL; + else + result->file = xstrdup(token); + + token = strtokx(NULL, whitespace, NULL, NULL, + 0, false, pset.encoding); - if (!error) + /* + * Allows old COPY syntax for backward compatibility + * 2002-06-19 + */ + if (token && strcasecmp(token, "using") == 0) { - token = strtokx(NULL, " \t\n\r", "'", '\\', "e, NULL, pset.encoding); + token = strtokx(NULL, whitespace, NULL, NULL, + 0, false, pset.encoding); + if (!(token && strcasecmp(token, "delimiters") == 0)) + goto error; + token = strtokx(NULL, whitespace, NULL, "'", + '\\', false, pset.encoding); if (!token) - error = true; - else if (!quote && (strcasecmp(token, "stdin") == 0 || strcasecmp(token, "stdout") == 0)) - result->file = NULL; - else - result->file = xstrdup(token); + goto error; + result->delim = xstrdup(token); + token = strtokx(NULL, whitespace, NULL, NULL, + 0, false, pset.encoding); } - if (!error) + if (token) { - token = strtokx(NULL, " \t\n\r", NULL, '\\', NULL, NULL, pset.encoding); - if (token) + if (strcasecmp(token, "with") != 0) + goto error; + while ((token = strtokx(NULL, whitespace, NULL, NULL, + 0, false, pset.encoding)) != NULL) { - /* - * Allows old COPY syntax for backward compatibility - * 2002-06-19 - */ - if (strcasecmp(token, "using") == 0) + if (strcasecmp(token, "delimiter") == 0) { - token = strtokx(NULL, " \t\n\r", NULL, '\\', NULL, NULL, pset.encoding); - if (token && strcasecmp(token, "delimiters") == 0) - { - token = strtokx(NULL, " \t\n\r", "'", '\\', NULL, NULL, pset.encoding); - if (token) - { - result->delim = xstrdup(token); - token = strtokx(NULL, " \t\n\r", NULL, '\\', NULL, NULL, pset.encoding); - } - else - error = true; - } + token = strtokx(NULL, whitespace, NULL, "'", + '\\', false, pset.encoding); + if (token && strcasecmp(token, "as") == 0) + token = strtokx(NULL, whitespace, NULL, "'", + '\\', false, pset.encoding); + if (token) + result->delim = xstrdup(token); else - error = true; + goto error; } - } - } - - if (!error && token) - { - if (strcasecmp(token, "with") == 0) - { - while (!error && (token = strtokx(NULL, " \t\n\r", NULL, '\\', NULL, NULL, pset.encoding))) + else if (strcasecmp(token, "null") == 0) { - if (strcasecmp(token, "delimiter") == 0) - { - token = strtokx(NULL, " \t\n\r", "'", '\\', NULL, NULL, pset.encoding); - if (token && strcasecmp(token, "as") == 0) - token = strtokx(NULL, " \t\n\r", "'", '\\', NULL, NULL, pset.encoding); - if (token) - result->delim = xstrdup(token); - else - error = true; - } - else if (strcasecmp(token, "null") == 0) - { - token = strtokx(NULL, " \t\n\r", "'", '\\', NULL, NULL, pset.encoding); - if (token && strcasecmp(token, "as") == 0) - token = strtokx(NULL, " \t\n\r", "'", '\\', NULL, NULL, pset.encoding); - if (token) - result->null = xstrdup(token); - else - error = true; - } + token = strtokx(NULL, whitespace, NULL, "'", + '\\', false, pset.encoding); + if (token && strcasecmp(token, "as") == 0) + token = strtokx(NULL, whitespace, NULL, "'", + '\\', false, pset.encoding); + if (token) + result->null = xstrdup(token); else - error = true; + goto error; } + else + goto error; } - else - error = true; } free(line); - if (error) - { - if (token) - psql_error("\\copy: parse error at '%s'\n", token); - else - psql_error("\\copy: parse error at end of line\n"); - free_copy_options(result); - return NULL; - } + return result; + +error: + if (token) + psql_error("\\copy: parse error at '%s'\n", token); else - return result; + psql_error("\\copy: parse error at end of line\n"); + free_copy_options(result); + free(line); + + return NULL; } @@ -272,7 +327,11 @@ do_copy(const char *args) if (options->binary) appendPQExpBuffer(&query, "BINARY "); - appendPQExpBuffer(&query, "\"%s\" ", options->table); + appendPQExpBuffer(&query, "%s ", options->table); + + if (options->column_list) + appendPQExpBuffer(&query, "%s ", options->column_list); + /* Uses old COPY syntax for backward compatibility 2002-06-19 */ if (options->oids) appendPQExpBuffer(&query, "WITH OIDS "); @@ -285,10 +344,22 @@ do_copy(const char *args) /* Uses old COPY syntax for backward compatibility 2002-06-19 */ if (options->delim) - appendPQExpBuffer(&query, " USING DELIMITERS '%s'", options->delim); + { + if (options->delim[0] == '\'') + appendPQExpBuffer(&query, " USING DELIMITERS %s", + options->delim); + else + appendPQExpBuffer(&query, " USING DELIMITERS '%s'", + options->delim); + } if (options->null) - appendPQExpBuffer(&query, " WITH NULL AS '%s'", options->null); + { + if (options->null[0] == '\'') + appendPQExpBuffer(&query, " WITH NULL AS %s", options->null); + else + appendPQExpBuffer(&query, " WITH NULL AS '%s'", options->null); + } if (options->from) { |