OSDN Git Service

Remove a couple hundred lines of ugly and tedious-to-maintain code by not
authorTom Lane <tgl@sss.pgh.pa.us>
Sat, 19 Sep 2009 21:51:21 +0000 (21:51 +0000)
committerTom Lane <tgl@sss.pgh.pa.us>
Sat, 19 Sep 2009 21:51:21 +0000 (21:51 +0000)
trying to parse COPY options exactly in psql's \copy support.  Instead,
just send the options as-is and let the backend sort it out.

Emmanuel Cecchet

src/bin/psql/copy.c

index 5b91df2..ae389cf 100644 (file)
@@ -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"
  * -- 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,30 +229,12 @@ 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
  * file or route its response into the file.
@@ -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);