mirror of
https://github.com/postgres/postgres.git
synced 2025-06-23 14:01:44 +03:00
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.
This commit is contained in:
@ -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
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (strcasecmp(token, "from") == 0)
|
||||
result->from = true;
|
||||
else if (strcasecmp(token, "to") == 0)
|
||||
result->from = false;
|
||||
else
|
||||
goto error;
|
||||
|
||||
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);
|
||||
|
||||
/*
|
||||
* Allows old COPY syntax for backward compatibility
|
||||
* 2002-06-19
|
||||
*/
|
||||
if (token && strcasecmp(token, "using") == 0)
|
||||
{
|
||||
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)
|
||||
goto error;
|
||||
result->delim = xstrdup(token);
|
||||
token = strtokx(NULL, whitespace, NULL, NULL,
|
||||
0, false, 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, "with") == 0)
|
||||
if (strcasecmp(token, "delimiter") == 0)
|
||||
{
|
||||
token = strtokx(NULL, " \t\n\r", NULL, '\\', NULL, NULL, pset.encoding);
|
||||
if (!token || strcasecmp(token, "oids") != 0)
|
||||
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
|
||||
result->oids = true;
|
||||
|
||||
if (!error)
|
||||
{
|
||||
token = strtokx(NULL, " \t\n\r", NULL, '\\', NULL, NULL, pset.encoding);
|
||||
if (!token)
|
||||
error = true;
|
||||
}
|
||||
goto error;
|
||||
}
|
||||
else if (strcasecmp(token, "null") == 0)
|
||||
{
|
||||
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
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (!error && strcasecmp(token, "from") == 0)
|
||||
result->from = true;
|
||||
else if (!error && strcasecmp(token, "to") == 0)
|
||||
result->from = false;
|
||||
else
|
||||
error = true;
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
|
||||
if (!error)
|
||||
{
|
||||
token = strtokx(NULL, " \t\n\r", "'", '\\', "e, NULL, 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);
|
||||
}
|
||||
|
||||
if (!error)
|
||||
{
|
||||
token = strtokx(NULL, " \t\n\r", NULL, '\\', NULL, NULL, pset.encoding);
|
||||
if (token)
|
||||
{
|
||||
/*
|
||||
* Allows old COPY syntax for backward compatibility
|
||||
* 2002-06-19
|
||||
*/
|
||||
if (strcasecmp(token, "using") == 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;
|
||||
}
|
||||
else
|
||||
error = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!error && token)
|
||||
{
|
||||
if (strcasecmp(token, "with") == 0)
|
||||
{
|
||||
while (!error && (token = strtokx(NULL, " \t\n\r", NULL, '\\', NULL, NULL, pset.encoding)))
|
||||
{
|
||||
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;
|
||||
}
|
||||
else
|
||||
error = true;
|
||||
}
|
||||
}
|
||||
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)
|
||||
{
|
||||
|
Reference in New Issue
Block a user