mirror of
https://github.com/postgres/postgres.git
synced 2025-07-28 23:42:10 +03:00
Provide a FORCE NULL option to COPY in CSV mode.
This forces an input field containing the quoted null string to be returned as a NULL. Without this option, only unquoted null strings behave this way. This helps where some CSV producers insist on quoting every field, whether or not it is needed. The option takes a list of fields, and only applies to those columns. There is an equivalent column-level option added to file_fdw. Ian Barwick, with some tweaking by Andrew Dunstan, reviewed by Payal Singh.
This commit is contained in:
@ -1,4 +1,5 @@
|
||||
AAA,aaa
|
||||
XYZ,xyz
|
||||
NULL,NULL
|
||||
ABC,abc
|
||||
AAA,aaa,123,""
|
||||
XYZ,xyz,"",321
|
||||
NULL,NULL,NULL,NULL
|
||||
NULL,NULL,"NULL",NULL
|
||||
ABC,abc,"",""
|
||||
|
|
@ -48,9 +48,9 @@ struct FileFdwOption
|
||||
|
||||
/*
|
||||
* Valid options for file_fdw.
|
||||
* These options are based on the options for COPY FROM command.
|
||||
* But note that force_not_null is handled as a boolean option attached to
|
||||
* each column, not as a table option.
|
||||
* These options are based on the options for the COPY FROM command.
|
||||
* But note that force_not_null and force_null are handled as boolean options
|
||||
* attached to a column, not as table options.
|
||||
*
|
||||
* Note: If you are adding new option for user mapping, you need to modify
|
||||
* fileGetOptions(), which currently doesn't bother to look at user mappings.
|
||||
@ -69,7 +69,7 @@ static const struct FileFdwOption valid_options[] = {
|
||||
{"null", ForeignTableRelationId},
|
||||
{"encoding", ForeignTableRelationId},
|
||||
{"force_not_null", AttributeRelationId},
|
||||
|
||||
{"force_null", AttributeRelationId},
|
||||
/*
|
||||
* force_quote is not supported by file_fdw because it's for COPY TO.
|
||||
*/
|
||||
@ -187,6 +187,7 @@ file_fdw_validator(PG_FUNCTION_ARGS)
|
||||
Oid catalog = PG_GETARG_OID(1);
|
||||
char *filename = NULL;
|
||||
DefElem *force_not_null = NULL;
|
||||
DefElem *force_null = NULL;
|
||||
List *other_options = NIL;
|
||||
ListCell *cell;
|
||||
|
||||
@ -243,10 +244,10 @@ file_fdw_validator(PG_FUNCTION_ARGS)
|
||||
}
|
||||
|
||||
/*
|
||||
* Separate out filename and force_not_null, since ProcessCopyOptions
|
||||
* won't accept them. (force_not_null only comes in a boolean
|
||||
* per-column flavor here.)
|
||||
* Separate out filename and column-specific options, since
|
||||
* ProcessCopyOptions won't accept them.
|
||||
*/
|
||||
|
||||
if (strcmp(def->defname, "filename") == 0)
|
||||
{
|
||||
if (filename)
|
||||
@ -255,16 +256,42 @@ file_fdw_validator(PG_FUNCTION_ARGS)
|
||||
errmsg("conflicting or redundant options")));
|
||||
filename = defGetString(def);
|
||||
}
|
||||
/*
|
||||
* force_not_null is a boolean option; after validation we can discard
|
||||
* it - it will be retrieved later in get_file_fdw_attribute_options()
|
||||
*/
|
||||
else if (strcmp(def->defname, "force_not_null") == 0)
|
||||
{
|
||||
if (force_not_null)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_SYNTAX_ERROR),
|
||||
errmsg("conflicting or redundant options")));
|
||||
errmsg("conflicting or redundant options"),
|
||||
errhint("option \"force_not_null\" supplied more than once for a column")));
|
||||
if(force_null)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_SYNTAX_ERROR),
|
||||
errmsg("conflicting or redundant options"),
|
||||
errhint("option \"force_not_null\" cannot be used together with \"force_null\"")));
|
||||
force_not_null = def;
|
||||
/* Don't care what the value is, as long as it's a legal boolean */
|
||||
(void) defGetBoolean(def);
|
||||
}
|
||||
/* See comments for force_not_null above */
|
||||
else if (strcmp(def->defname, "force_null") == 0)
|
||||
{
|
||||
if (force_null)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_SYNTAX_ERROR),
|
||||
errmsg("conflicting or redundant options"),
|
||||
errhint("option \"force_null\" supplied more than once for a column")));
|
||||
if(force_not_null)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_SYNTAX_ERROR),
|
||||
errmsg("conflicting or redundant options"),
|
||||
errhint("option \"force_null\" cannot be used together with \"force_not_null\"")));
|
||||
force_null = def;
|
||||
(void) defGetBoolean(def);
|
||||
}
|
||||
else
|
||||
other_options = lappend(other_options, def);
|
||||
}
|
||||
@ -369,8 +396,9 @@ fileGetOptions(Oid foreigntableid,
|
||||
* Retrieve per-column generic options from pg_attribute and construct a list
|
||||
* of DefElems representing them.
|
||||
*
|
||||
* At the moment we only have "force_not_null", which should be combined into
|
||||
* a single DefElem listing all such columns, since that's what COPY expects.
|
||||
* At the moment we only have "force_not_null", and "force_null",
|
||||
* which should each be combined into a single DefElem listing all such
|
||||
* columns, since that's what COPY expects.
|
||||
*/
|
||||
static List *
|
||||
get_file_fdw_attribute_options(Oid relid)
|
||||
@ -380,6 +408,9 @@ get_file_fdw_attribute_options(Oid relid)
|
||||
AttrNumber natts;
|
||||
AttrNumber attnum;
|
||||
List *fnncolumns = NIL;
|
||||
List *fncolumns = NIL;
|
||||
|
||||
List *options = NIL;
|
||||
|
||||
rel = heap_open(relid, AccessShareLock);
|
||||
tupleDesc = RelationGetDescr(rel);
|
||||
@ -410,17 +441,29 @@ get_file_fdw_attribute_options(Oid relid)
|
||||
fnncolumns = lappend(fnncolumns, makeString(attname));
|
||||
}
|
||||
}
|
||||
else if (strcmp(def->defname, "force_null") == 0)
|
||||
{
|
||||
if (defGetBoolean(def))
|
||||
{
|
||||
char *attname = pstrdup(NameStr(attr->attname));
|
||||
|
||||
fncolumns = lappend(fncolumns, makeString(attname));
|
||||
}
|
||||
}
|
||||
/* maybe in future handle other options here */
|
||||
}
|
||||
}
|
||||
|
||||
heap_close(rel, AccessShareLock);
|
||||
|
||||
/* Return DefElem only when some column(s) have force_not_null */
|
||||
/* Return DefElem only when some column(s) have force_not_null / force_null options set */
|
||||
if (fnncolumns != NIL)
|
||||
return list_make1(makeDefElem("force_not_null", (Node *) fnncolumns));
|
||||
else
|
||||
return NIL;
|
||||
options = lappend(options, makeDefElem("force_not_null", (Node *) fnncolumns));
|
||||
|
||||
if (fncolumns != NIL)
|
||||
options = lappend(options,makeDefElem("force_null", (Node *) fncolumns));
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -81,11 +81,14 @@ OPTIONS (format 'csv', filename '@abs_srcdir@/data/agg.bad', header 'true', deli
|
||||
-- per-column options tests
|
||||
CREATE FOREIGN TABLE text_csv (
|
||||
word1 text OPTIONS (force_not_null 'true'),
|
||||
word2 text OPTIONS (force_not_null 'off')
|
||||
word2 text OPTIONS (force_not_null 'off'),
|
||||
word3 text OPTIONS (force_null 'true'),
|
||||
word4 text OPTIONS (force_null 'off')
|
||||
) SERVER file_server
|
||||
OPTIONS (format 'text', filename '@abs_srcdir@/data/text.csv', null 'NULL');
|
||||
SELECT * FROM text_csv; -- ERROR
|
||||
ALTER FOREIGN TABLE text_csv OPTIONS (SET format 'csv');
|
||||
\pset null _null_
|
||||
SELECT * FROM text_csv;
|
||||
|
||||
-- force_not_null is not allowed to be specified at any foreign object level:
|
||||
@ -94,6 +97,18 @@ ALTER SERVER file_server OPTIONS (ADD force_not_null '*'); -- ERROR
|
||||
CREATE USER MAPPING FOR public SERVER file_server OPTIONS (force_not_null '*'); -- ERROR
|
||||
CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS (force_not_null '*'); -- ERROR
|
||||
|
||||
-- force_not_null cannot be specified together with force_null
|
||||
ALTER FOREIGN TABLE text_csv ALTER COLUMN word1 OPTIONS (force_null 'true'); --ERROR
|
||||
|
||||
-- force_null is not allowed to be specified at any foreign object level:
|
||||
ALTER FOREIGN DATA WRAPPER file_fdw OPTIONS (ADD force_null '*'); -- ERROR
|
||||
ALTER SERVER file_server OPTIONS (ADD force_null '*'); -- ERROR
|
||||
CREATE USER MAPPING FOR public SERVER file_server OPTIONS (force_null '*'); -- ERROR
|
||||
CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS (force_null '*'); -- ERROR
|
||||
|
||||
-- force_null cannot be specified together with force_not_null
|
||||
ALTER FOREIGN TABLE text_csv ALTER COLUMN word3 OPTIONS (force_not_null 'true'); --ERROR
|
||||
|
||||
-- basic query tests
|
||||
SELECT * FROM agg_text WHERE b > 10.0 ORDER BY a;
|
||||
SELECT * FROM agg_csv ORDER BY a;
|
||||
|
@ -96,20 +96,24 @@ OPTIONS (format 'csv', filename '@abs_srcdir@/data/agg.bad', header 'true', deli
|
||||
-- per-column options tests
|
||||
CREATE FOREIGN TABLE text_csv (
|
||||
word1 text OPTIONS (force_not_null 'true'),
|
||||
word2 text OPTIONS (force_not_null 'off')
|
||||
word2 text OPTIONS (force_not_null 'off'),
|
||||
word3 text OPTIONS (force_null 'true'),
|
||||
word4 text OPTIONS (force_null 'off')
|
||||
) SERVER file_server
|
||||
OPTIONS (format 'text', filename '@abs_srcdir@/data/text.csv', null 'NULL');
|
||||
SELECT * FROM text_csv; -- ERROR
|
||||
ERROR: COPY force not null available only in CSV mode
|
||||
ALTER FOREIGN TABLE text_csv OPTIONS (SET format 'csv');
|
||||
\pset null _null_
|
||||
SELECT * FROM text_csv;
|
||||
word1 | word2
|
||||
-------+-------
|
||||
AAA | aaa
|
||||
XYZ | xyz
|
||||
NULL |
|
||||
ABC | abc
|
||||
(4 rows)
|
||||
word1 | word2 | word3 | word4
|
||||
-------+--------+--------+--------
|
||||
AAA | aaa | 123 |
|
||||
XYZ | xyz | | 321
|
||||
NULL | _null_ | _null_ | _null_
|
||||
NULL | _null_ | _null_ | _null_
|
||||
ABC | abc | |
|
||||
(5 rows)
|
||||
|
||||
-- force_not_null is not allowed to be specified at any foreign object level:
|
||||
ALTER FOREIGN DATA WRAPPER file_fdw OPTIONS (ADD force_not_null '*'); -- ERROR
|
||||
@ -124,6 +128,27 @@ HINT: There are no valid options in this context.
|
||||
CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS (force_not_null '*'); -- ERROR
|
||||
ERROR: invalid option "force_not_null"
|
||||
HINT: Valid options in this context are: filename, format, header, delimiter, quote, escape, null, encoding
|
||||
-- force_not_null cannot be specified together with force_null
|
||||
ALTER FOREIGN TABLE text_csv ALTER COLUMN word1 OPTIONS (force_null 'true'); --ERROR
|
||||
ERROR: conflicting or redundant options
|
||||
HINT: option "force_null" cannot be used together with "force_not_null"
|
||||
-- force_null is not allowed to be specified at any foreign object level:
|
||||
ALTER FOREIGN DATA WRAPPER file_fdw OPTIONS (ADD force_null '*'); -- ERROR
|
||||
ERROR: invalid option "force_null"
|
||||
HINT: There are no valid options in this context.
|
||||
ALTER SERVER file_server OPTIONS (ADD force_null '*'); -- ERROR
|
||||
ERROR: invalid option "force_null"
|
||||
HINT: There are no valid options in this context.
|
||||
CREATE USER MAPPING FOR public SERVER file_server OPTIONS (force_null '*'); -- ERROR
|
||||
ERROR: invalid option "force_null"
|
||||
HINT: There are no valid options in this context.
|
||||
CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS (force_null '*'); -- ERROR
|
||||
ERROR: invalid option "force_null"
|
||||
HINT: Valid options in this context are: filename, format, header, delimiter, quote, escape, null, encoding
|
||||
-- force_null cannot be specified together with force_not_null
|
||||
ALTER FOREIGN TABLE text_csv ALTER COLUMN word3 OPTIONS (force_not_null 'true'); --ERROR
|
||||
ERROR: conflicting or redundant options
|
||||
HINT: option "force_not_null" cannot be used together with "force_null"
|
||||
-- basic query tests
|
||||
SELECT * FROM agg_text WHERE b > 10.0 ORDER BY a;
|
||||
a | b
|
||||
|
Reference in New Issue
Block a user