aboutsummaryrefslogtreecommitdiff
path: root/src/bin/psql/copy.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/bin/psql/copy.c')
-rw-r--r--src/bin/psql/copy.c319
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", "\"", '\\', &quote, 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", "\"", '\\', &quote, 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", "'", '\\', &quote, 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)
{