diff --git a/src/bin/psql/copy.c b/src/bin/psql/copy.c index 5b91df26b407d9a0cca27cf00fe40ca2085d16c1..ae389cfdae08927d48d4b72c75628cb71307e15e 100644 --- a/src/bin/psql/copy.c +++ b/src/bin/psql/copy.c @@ -3,7 +3,7 @@ * * Copyright (c) 2000-2009, PostgreSQL Global Development Group * - * $PostgreSQL: pgsql/src/bin/psql/copy.c,v 1.82 2009/08/07 20:16:11 tgl Exp $ + * $PostgreSQL: pgsql/src/bin/psql/copy.c,v 1.83 2009/09/19 21:51:21 tgl Exp $ */ #include "postgres_fe.h" #include "copy.h" @@ -32,43 +32,28 @@ * -- parses \copy command line * * The documented syntax is: - * \copy tablename [(columnlist)] from|to filename - * [ with ] [ binary ] [ oids ] [ delimiter [as] char ] [ null [as] string ] - * [ csv [ header ] [ quote [ AS ] string ] escape [as] string - * [ force not null column [, ...] | force quote column [, ...] | * ] ] + * \copy tablename [(columnlist)] from|to filename [options] + * \copy ( select stmt ) to filename [options] * - * \copy ( select stmt ) to filename - * [ with ] [ binary ] [ delimiter [as] char ] [ null [as] string ] - * [ csv [ header ] [ quote [ AS ] string ] escape [as] string - * [ force quote column [, ...] | * ] ] - * - * Force quote only applies for copy to; force not null only applies for - * copy from. + * An undocumented fact is that you can still write BINARY before the + * tablename; this is a hangover from the pre-7.3 syntax. The options + * syntax varies across backend versions, but we avoid all that mess + * by just transmitting the stuff after the filename literally. * * 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. + * filename can be single-quoted like SQL literals. * * returns a malloc'ed structure with the options, or NULL on parsing error */ struct copy_options { - char *table; - char *column_list; + char *before_tofrom; /* COPY string before TO/FROM */ + char *after_tofrom; /* COPY string after TO/FROM filename */ char *file; /* NULL = stdin/stdout */ bool psql_inout; /* true = use psql stdin/stdout */ - bool from; - bool binary; - bool oids; - bool csv_mode; - bool header; - char *delim; - char *null; - char *quote; - char *escape; - char *force_quote_list; - char *force_notnull_list; + bool from; /* true = FROM, false = TO */ }; @@ -77,15 +62,9 @@ free_copy_options(struct copy_options * ptr) { if (!ptr) return; - free(ptr->table); - free(ptr->column_list); + free(ptr->before_tofrom); + free(ptr->after_tofrom); free(ptr->file); - free(ptr->delim); - free(ptr->null); - free(ptr->quote); - free(ptr->escape); - free(ptr->force_quote_list); - free(ptr->force_notnull_list); free(ptr); } @@ -108,14 +87,11 @@ static struct copy_options * parse_slash_copy(const char *args) { struct copy_options *result; - char *line; char *token; const char *whitespace = " \t\n\r"; char nonstd_backslash = standard_strings() ? 0 : '\\'; - if (args) - line = pg_strdup(args); - else + if (!args) { psql_error("\\copy: arguments required\n"); return NULL; @@ -123,22 +99,23 @@ parse_slash_copy(const char *args) result = pg_calloc(1, sizeof(struct copy_options)); - token = strtokx(line, whitespace, ".,()", "\"", + result->before_tofrom = pg_strdup(""); /* initialize for appending */ + + token = strtokx(args, whitespace, ".,()", "\"", 0, false, false, pset.encoding); if (!token) goto error; + /* The following can be removed when we drop 7.3 syntax support */ if (pg_strcasecmp(token, "binary") == 0) { - result->binary = true; + xstrcat(&result->before_tofrom, token); token = strtokx(NULL, whitespace, ".,()", "\"", 0, false, false, pset.encoding); if (!token) goto error; } - result->table = pg_strdup(token); - /* Handle COPY (SELECT) case */ if (token[0] == '(') { @@ -146,7 +123,9 @@ parse_slash_copy(const char *args) while (parens > 0) { - token = strtokx(NULL, whitespace, ".,()", "\"'", + xstrcat(&result->before_tofrom, " "); + xstrcat(&result->before_tofrom, token); + token = strtokx(NULL, whitespace, "()", "\"'", nonstd_backslash, true, false, pset.encoding); if (!token) goto error; @@ -154,11 +133,11 @@ parse_slash_copy(const char *args) parens++; else if (token[0] == ')') parens--; - xstrcat(&result->table, " "); - xstrcat(&result->table, token); } } + xstrcat(&result->before_tofrom, " "); + xstrcat(&result->before_tofrom, token); token = strtokx(NULL, whitespace, ".,()", "\"", 0, false, false, pset.encoding); if (!token) @@ -171,12 +150,12 @@ parse_slash_copy(const char *args) if (token[0] == '.') { /* handle schema . table */ - xstrcat(&result->table, token); + xstrcat(&result->before_tofrom, token); token = strtokx(NULL, whitespace, ".,()", "\"", 0, false, false, pset.encoding); if (!token) goto error; - xstrcat(&result->table, token); + xstrcat(&result->before_tofrom, token); token = strtokx(NULL, whitespace, ".,()", "\"", 0, false, false, pset.encoding); if (!token) @@ -186,24 +165,19 @@ parse_slash_copy(const char *args) if (token[0] == '(') { /* handle parenthesized column list */ - result->column_list = pg_strdup(token); for (;;) { - token = strtokx(NULL, whitespace, ".,()", "\"", - 0, false, false, pset.encoding); - if (!token || strchr(".,()", token[0])) - goto error; - xstrcat(&result->column_list, token); - token = strtokx(NULL, whitespace, ".,()", "\"", + xstrcat(&result->before_tofrom, " "); + xstrcat(&result->before_tofrom, token); + token = strtokx(NULL, whitespace, "()", "\"", 0, false, false, pset.encoding); if (!token) goto error; - xstrcat(&result->column_list, token); if (token[0] == ')') break; - if (token[0] != ',') - goto error; } + xstrcat(&result->before_tofrom, " "); + xstrcat(&result->before_tofrom, token); token = strtokx(NULL, whitespace, ".,()", "\"", 0, false, false, pset.encoding); if (!token) @@ -241,156 +215,11 @@ parse_slash_copy(const char *args) expand_tilde(&result->file); } - token = strtokx(NULL, whitespace, NULL, NULL, + /* Collect the rest of the line (COPY options) */ + token = strtokx(NULL, "", NULL, NULL, 0, false, false, pset.encoding); - if (token) - { - /* - * WITH is optional. Also, the backend will allow WITH followed by - * nothing, so we do too. - */ - if (pg_strcasecmp(token, "with") == 0) - token = strtokx(NULL, whitespace, NULL, NULL, - 0, false, false, pset.encoding); - - while (token) - { - bool fetch_next; - - fetch_next = true; - - if (pg_strcasecmp(token, "oids") == 0) - result->oids = true; - else if (pg_strcasecmp(token, "binary") == 0) - result->binary = true; - else if (pg_strcasecmp(token, "csv") == 0) - result->csv_mode = true; - else if (pg_strcasecmp(token, "header") == 0) - result->header = true; - else if (pg_strcasecmp(token, "delimiter") == 0) - { - if (result->delim) - goto error; - token = strtokx(NULL, whitespace, NULL, "'", - nonstd_backslash, true, false, pset.encoding); - if (token && pg_strcasecmp(token, "as") == 0) - token = strtokx(NULL, whitespace, NULL, "'", - nonstd_backslash, true, false, pset.encoding); - if (token) - result->delim = pg_strdup(token); - else - goto error; - } - else if (pg_strcasecmp(token, "null") == 0) - { - if (result->null) - goto error; - token = strtokx(NULL, whitespace, NULL, "'", - nonstd_backslash, true, false, pset.encoding); - if (token && pg_strcasecmp(token, "as") == 0) - token = strtokx(NULL, whitespace, NULL, "'", - nonstd_backslash, true, false, pset.encoding); - if (token) - result->null = pg_strdup(token); - else - goto error; - } - else if (pg_strcasecmp(token, "quote") == 0) - { - if (result->quote) - goto error; - token = strtokx(NULL, whitespace, NULL, "'", - nonstd_backslash, true, false, pset.encoding); - if (token && pg_strcasecmp(token, "as") == 0) - token = strtokx(NULL, whitespace, NULL, "'", - nonstd_backslash, true, false, pset.encoding); - if (token) - result->quote = pg_strdup(token); - else - goto error; - } - else if (pg_strcasecmp(token, "escape") == 0) - { - if (result->escape) - goto error; - token = strtokx(NULL, whitespace, NULL, "'", - nonstd_backslash, true, false, pset.encoding); - if (token && pg_strcasecmp(token, "as") == 0) - token = strtokx(NULL, whitespace, NULL, "'", - nonstd_backslash, true, false, pset.encoding); - if (token) - result->escape = pg_strdup(token); - else - goto error; - } - else if (pg_strcasecmp(token, "force") == 0) - { - token = strtokx(NULL, whitespace, ",", "\"", - 0, false, false, pset.encoding); - if (pg_strcasecmp(token, "quote") == 0) - { - if (result->force_quote_list) - goto error; - /* handle column list */ - fetch_next = false; - for (;;) - { - token = strtokx(NULL, whitespace, ",", "\"", - 0, false, false, pset.encoding); - if (!token || strchr(",", token[0])) - goto error; - if (!result->force_quote_list) - result->force_quote_list = pg_strdup(token); - else - xstrcat(&result->force_quote_list, token); - token = strtokx(NULL, whitespace, ",", "\"", - 0, false, false, pset.encoding); - if (!token || token[0] != ',') - break; - xstrcat(&result->force_quote_list, token); - } - } - else if (pg_strcasecmp(token, "not") == 0) - { - if (result->force_notnull_list) - goto error; - token = strtokx(NULL, whitespace, ",", "\"", - 0, false, false, pset.encoding); - if (pg_strcasecmp(token, "null") != 0) - goto error; - /* handle column list */ - fetch_next = false; - for (;;) - { - token = strtokx(NULL, whitespace, ",", "\"", - 0, false, false, pset.encoding); - if (!token || strchr(",", token[0])) - goto error; - if (!result->force_notnull_list) - result->force_notnull_list = pg_strdup(token); - else - xstrcat(&result->force_notnull_list, token); - token = strtokx(NULL, whitespace, ",", "\"", - 0, false, false, pset.encoding); - if (!token || token[0] != ',') - break; - xstrcat(&result->force_notnull_list, token); - } - } - else - goto error; - } - else - goto error; - - if (fetch_next) - token = strtokx(NULL, whitespace, NULL, NULL, - 0, false, false, pset.encoding); - } - } - - free(line); + result->after_tofrom = pg_strdup(token); return result; @@ -400,29 +229,11 @@ error: else psql_error("\\copy: parse error at end of line\n"); free_copy_options(result); - free(line); return NULL; } -/* - * Handle one of the "string" options of COPY. If the user gave a quoted - * string, pass it to the backend as-is; if it wasn't quoted then quote - * and escape it. - */ -static void -emit_copy_option(PQExpBuffer query, const char *keyword, const char *option) -{ - appendPQExpBufferStr(query, keyword); - if (option[0] == '\'' || - ((option[0] == 'E' || option[0] == 'e') && option[1] == '\'')) - appendPQExpBufferStr(query, option); - else - appendStringLiteralConn(query, option, pset.db); -} - - /* * Execute a \copy command (frontend copy). We have to open a file, then * submit a COPY query to the backend and either feed it data from the @@ -444,51 +255,7 @@ do_copy(const char *args) if (!options) return false; - initPQExpBuffer(&query); - - printfPQExpBuffer(&query, "COPY "); - - appendPQExpBuffer(&query, "%s ", options->table); - - if (options->column_list) - appendPQExpBuffer(&query, "%s ", options->column_list); - - if (options->from) - appendPQExpBuffer(&query, "FROM STDIN"); - else - appendPQExpBuffer(&query, "TO STDOUT"); - - - if (options->binary) - appendPQExpBuffer(&query, " BINARY "); - - if (options->oids) - appendPQExpBuffer(&query, " OIDS "); - - if (options->delim) - emit_copy_option(&query, " DELIMITER ", options->delim); - - if (options->null) - emit_copy_option(&query, " NULL AS ", options->null); - - if (options->csv_mode) - appendPQExpBuffer(&query, " CSV"); - - if (options->header) - appendPQExpBuffer(&query, " HEADER"); - - if (options->quote) - emit_copy_option(&query, " QUOTE AS ", options->quote); - - if (options->escape) - emit_copy_option(&query, " ESCAPE AS ", options->escape); - - if (options->force_quote_list) - appendPQExpBuffer(&query, " FORCE QUOTE %s", options->force_quote_list); - - if (options->force_notnull_list) - appendPQExpBuffer(&query, " FORCE NOT NULL %s", options->force_notnull_list); - + /* prepare to read or write the target file */ if (options->file) canonicalize_path(options->file); @@ -504,8 +271,7 @@ do_copy(const char *args) else { if (options->file) - copystream = fopen(options->file, - options->binary ? PG_BINARY_W : "w"); + copystream = fopen(options->file, PG_BINARY_W); else if (!options->psql_inout) copystream = pset.queryFout; else @@ -531,6 +297,17 @@ do_copy(const char *args) return false; } + /* build the command we will send to the backend */ + initPQExpBuffer(&query); + printfPQExpBuffer(&query, "COPY "); + appendPQExpBufferStr(&query, options->before_tofrom); + if (options->from) + appendPQExpBuffer(&query, " FROM STDIN "); + else + appendPQExpBuffer(&query, " TO STDOUT "); + if (options->after_tofrom) + appendPQExpBufferStr(&query, options->after_tofrom); + result = PSQLexec(query.data, true); termPQExpBuffer(&query);