mirror of
https://github.com/postgres/postgres.git
synced 2026-01-26 09:41:40 +03:00
file_fdw: Support multi-line HEADER option.
Commit bc2f348 introduced multi-line HEADER support for COPY. This commit
extends this capability to file_fdw, allowing multiple header lines to be
skipped.
Because CREATE/ALTER FOREIGN TABLE requires option values to be single-quoted,
this commit also updates defGetCopyHeaderOption() to accept integer values
specified as strings for HEADER option.
Author: Shinya Kato <shinya11.kato@gmail.com>
Reviewed-by: Fujii Masao <masao.fujii@gmail.com>
Reviewed-by: songjinzhou <tsinghualucky912@foxmail.com>
Reviewed-by: Japin Li <japinli@hotmail.com>
Reviewed-by: Chao Li <li.evan.chao@gmail.com>
Discussion: https://postgr.es/m/CAOzEurT+iwC47VHPMS+uJ4WSzvOLPsZ2F2_wopm8M7O+CZa3Xw@mail.gmail.com
This commit is contained in:
4
contrib/file_fdw/data/multiline_header.csv
Normal file
4
contrib/file_fdw/data/multiline_header.csv
Normal file
@@ -0,0 +1,4 @@
|
||||
first header line
|
||||
second header line
|
||||
1,alpha
|
||||
2,beta
|
||||
|
@@ -104,6 +104,12 @@ CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS (reject_limit '1');
|
||||
ERROR: COPY REJECT_LIMIT requires ON_ERROR to be set to IGNORE
|
||||
CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS (on_error 'ignore', reject_limit '0'); -- ERROR
|
||||
ERROR: REJECT_LIMIT (0) must be greater than zero
|
||||
CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS (header '-1'); -- ERROR
|
||||
ERROR: a negative integer value cannot be specified for header
|
||||
CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS (header '2.5'); -- ERROR
|
||||
ERROR: header requires a Boolean value, an integer value greater than or equal to zero, or the string "match"
|
||||
CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS (header 'unsupported'); -- ERROR
|
||||
ERROR: header requires a Boolean value, an integer value greater than or equal to zero, or the string "match"
|
||||
CREATE FOREIGN TABLE tbl () SERVER file_server; -- ERROR
|
||||
ERROR: either filename or program is required for file_fdw foreign tables
|
||||
\set filename :abs_srcdir '/data/agg.data'
|
||||
@@ -142,6 +148,25 @@ OPTIONS (format 'csv', filename :'filename', delimiter ',', header 'match');
|
||||
SELECT * FROM header_doesnt_match; -- ERROR
|
||||
ERROR: column name mismatch in header line field 1: got "1", expected "a"
|
||||
CONTEXT: COPY header_doesnt_match, line 1: "1,foo"
|
||||
-- test multi-line header
|
||||
\set filename :abs_srcdir '/data/multiline_header.csv'
|
||||
CREATE FOREIGN TABLE multi_header (a int, b text) SERVER file_server
|
||||
OPTIONS (format 'csv', filename :'filename', header '2');
|
||||
SELECT * FROM multi_header ORDER BY a;
|
||||
a | b
|
||||
---+-------
|
||||
1 | alpha
|
||||
2 | beta
|
||||
(2 rows)
|
||||
|
||||
CREATE FOREIGN TABLE multi_header_skip (a int, b text) SERVER file_server
|
||||
OPTIONS (format 'csv', filename :'filename', header '5');
|
||||
SELECT count(*) FROM multi_header_skip;
|
||||
count
|
||||
-------
|
||||
0
|
||||
(1 row)
|
||||
|
||||
-- per-column options tests
|
||||
\set filename :abs_srcdir '/data/text.csv'
|
||||
CREATE FOREIGN TABLE text_csv (
|
||||
@@ -543,7 +568,7 @@ SET ROLE regress_file_fdw_superuser;
|
||||
-- cleanup
|
||||
RESET ROLE;
|
||||
DROP EXTENSION file_fdw CASCADE;
|
||||
NOTICE: drop cascades to 9 other objects
|
||||
NOTICE: drop cascades to 11 other objects
|
||||
DETAIL: drop cascades to server file_server
|
||||
drop cascades to user mapping for regress_file_fdw_superuser on server file_server
|
||||
drop cascades to user mapping for regress_no_priv_user on server file_server
|
||||
@@ -552,5 +577,7 @@ drop cascades to foreign table agg_csv
|
||||
drop cascades to foreign table agg_bad
|
||||
drop cascades to foreign table header_match
|
||||
drop cascades to foreign table header_doesnt_match
|
||||
drop cascades to foreign table multi_header
|
||||
drop cascades to foreign table multi_header_skip
|
||||
drop cascades to foreign table text_csv
|
||||
DROP ROLE regress_file_fdw_superuser, regress_file_fdw_user, regress_no_priv_user;
|
||||
|
||||
@@ -84,6 +84,9 @@ CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS (format 'binary', on_erro
|
||||
CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS (log_verbosity 'unsupported'); -- ERROR
|
||||
CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS (reject_limit '1'); -- ERROR
|
||||
CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS (on_error 'ignore', reject_limit '0'); -- ERROR
|
||||
CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS (header '-1'); -- ERROR
|
||||
CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS (header '2.5'); -- ERROR
|
||||
CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS (header 'unsupported'); -- ERROR
|
||||
CREATE FOREIGN TABLE tbl () SERVER file_server; -- ERROR
|
||||
|
||||
\set filename :abs_srcdir '/data/agg.data'
|
||||
@@ -119,6 +122,16 @@ CREATE FOREIGN TABLE header_doesnt_match (a int, foo text) SERVER file_server
|
||||
OPTIONS (format 'csv', filename :'filename', delimiter ',', header 'match');
|
||||
SELECT * FROM header_doesnt_match; -- ERROR
|
||||
|
||||
-- test multi-line header
|
||||
\set filename :abs_srcdir '/data/multiline_header.csv'
|
||||
CREATE FOREIGN TABLE multi_header (a int, b text) SERVER file_server
|
||||
OPTIONS (format 'csv', filename :'filename', header '2');
|
||||
SELECT * FROM multi_header ORDER BY a;
|
||||
|
||||
CREATE FOREIGN TABLE multi_header_skip (a int, b text) SERVER file_server
|
||||
OPTIONS (format 'csv', filename :'filename', header '5');
|
||||
SELECT count(*) FROM multi_header_skip;
|
||||
|
||||
-- per-column options tests
|
||||
\set filename :abs_srcdir '/data/text.csv'
|
||||
CREATE FOREIGN TABLE text_csv (
|
||||
|
||||
@@ -65,7 +65,7 @@
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
Specifies whether the data has a header line,
|
||||
Specifies whether to skip a header line, or how many header lines to skip,
|
||||
the same as <command>COPY</command>'s <literal>HEADER</literal> option.
|
||||
</para>
|
||||
</listitem>
|
||||
@@ -179,7 +179,7 @@
|
||||
to be specified without a corresponding value, the foreign table option
|
||||
syntax requires a value to be present in all cases. To activate
|
||||
<command>COPY</command> options typically written without a value, you can pass
|
||||
the value TRUE, since all such options are Booleans.
|
||||
the value TRUE, since all such options accept Booleans.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
#include "mb/pg_wchar.h"
|
||||
#include "miscadmin.h"
|
||||
#include "nodes/makefuncs.h"
|
||||
#include "nodes/miscnodes.h"
|
||||
#include "optimizer/optimizer.h"
|
||||
#include "parser/parse_coerce.h"
|
||||
#include "parser/parse_collate.h"
|
||||
@@ -374,6 +375,8 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
|
||||
static int
|
||||
defGetCopyHeaderOption(DefElem *def, bool is_from)
|
||||
{
|
||||
int ival = COPY_HEADER_FALSE;
|
||||
|
||||
/*
|
||||
* If no parameter value given, assume "true" is meant.
|
||||
*/
|
||||
@@ -381,28 +384,14 @@ defGetCopyHeaderOption(DefElem *def, bool is_from)
|
||||
return COPY_HEADER_TRUE;
|
||||
|
||||
/*
|
||||
* Allow an integer value greater than or equal to zero, "true", "false",
|
||||
* "on", "off", or "match".
|
||||
* Allow an integer value greater than or equal to zero (integers
|
||||
* specified as strings are also accepted, mainly for file_fdw foreign
|
||||
* table options), "true", "false", "on", "off", or "match".
|
||||
*/
|
||||
switch (nodeTag(def->arg))
|
||||
{
|
||||
case T_Integer:
|
||||
{
|
||||
int ival = intVal(def->arg);
|
||||
|
||||
if (ival < 0)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
||||
errmsg("a negative integer value cannot be "
|
||||
"specified for %s", def->defname)));
|
||||
|
||||
if (!is_from && ival > 1)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
||||
errmsg("cannot use multi-line header in COPY TO")));
|
||||
|
||||
return ival;
|
||||
}
|
||||
ival = intVal(def->arg);
|
||||
break;
|
||||
default:
|
||||
{
|
||||
@@ -429,17 +418,38 @@ defGetCopyHeaderOption(DefElem *def, bool is_from)
|
||||
sval)));
|
||||
return COPY_HEADER_MATCH;
|
||||
}
|
||||
else
|
||||
{
|
||||
ErrorSaveContext escontext = {T_ErrorSaveContext};
|
||||
|
||||
/* Check if the header is a valid integer */
|
||||
ival = pg_strtoint32_safe(sval, (Node *) &escontext);
|
||||
if (escontext.error_occurred)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_SYNTAX_ERROR),
|
||||
/*- translator: first %s is the name of a COPY option, e.g. ON_ERROR,
|
||||
second %s is the special value "match" for that option */
|
||||
errmsg("%s requires a Boolean value, an integer "
|
||||
"value greater than or equal to zero, "
|
||||
"or the string \"%s\"",
|
||||
def->defname, "match")));
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_SYNTAX_ERROR),
|
||||
/*- translator: first %s is the name of a COPY option, e.g. ON_ERROR,
|
||||
second %s is the special value "match" for that option */
|
||||
errmsg("%s requires a Boolean value, an integer value greater "
|
||||
"than or equal to zero, or the string \"%s\"",
|
||||
def->defname, "match")));
|
||||
return COPY_HEADER_FALSE; /* keep compiler quiet */
|
||||
|
||||
if (ival < 0)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
||||
errmsg("a negative integer value cannot be "
|
||||
"specified for %s", def->defname)));
|
||||
|
||||
if (!is_from && ival > 1)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
||||
errmsg("cannot use multi-line header in COPY TO")));
|
||||
|
||||
return ival;
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -104,6 +104,24 @@ select count(*) from copytest5;
|
||||
0
|
||||
(1 row)
|
||||
|
||||
-- test header line feature (given as strings)
|
||||
truncate copytest5;
|
||||
copy copytest5 from stdin (format csv, header '0');
|
||||
select * from copytest5 order by c1;
|
||||
c1
|
||||
----
|
||||
1
|
||||
2
|
||||
(2 rows)
|
||||
|
||||
truncate copytest5;
|
||||
copy copytest5 from stdin (format csv, header '1');
|
||||
select * from copytest5 order by c1;
|
||||
c1
|
||||
----
|
||||
2
|
||||
(1 row)
|
||||
|
||||
-- test copy from with a partitioned table
|
||||
create table parted_copytest (
|
||||
a int,
|
||||
|
||||
@@ -138,6 +138,12 @@ COPY x from stdin with (header 2.5);
|
||||
ERROR: header requires a Boolean value, an integer value greater than or equal to zero, or the string "match"
|
||||
COPY x to stdout with (header 2);
|
||||
ERROR: cannot use multi-line header in COPY TO
|
||||
COPY x to stdout with (header '-1');
|
||||
ERROR: a negative integer value cannot be specified for header
|
||||
COPY x from stdin with (header '2.5');
|
||||
ERROR: header requires a Boolean value, an integer value greater than or equal to zero, or the string "match"
|
||||
COPY x to stdout with (header '2');
|
||||
ERROR: cannot use multi-line header in COPY TO
|
||||
-- too many columns in column list: should fail
|
||||
COPY x (a, b, c, d, e, d, c) from stdin;
|
||||
ERROR: column "d" specified more than once
|
||||
|
||||
@@ -124,6 +124,21 @@ this is a second header line.
|
||||
\.
|
||||
select count(*) from copytest5;
|
||||
|
||||
-- test header line feature (given as strings)
|
||||
truncate copytest5;
|
||||
copy copytest5 from stdin (format csv, header '0');
|
||||
1
|
||||
2
|
||||
\.
|
||||
select * from copytest5 order by c1;
|
||||
|
||||
truncate copytest5;
|
||||
copy copytest5 from stdin (format csv, header '1');
|
||||
1
|
||||
2
|
||||
\.
|
||||
select * from copytest5 order by c1;
|
||||
|
||||
-- test copy from with a partitioned table
|
||||
create table parted_copytest (
|
||||
a int,
|
||||
|
||||
@@ -93,6 +93,9 @@ COPY x from stdin with (on_error ignore, reject_limit 0);
|
||||
COPY x from stdin with (header -1);
|
||||
COPY x from stdin with (header 2.5);
|
||||
COPY x to stdout with (header 2);
|
||||
COPY x to stdout with (header '-1');
|
||||
COPY x from stdin with (header '2.5');
|
||||
COPY x to stdout with (header '2');
|
||||
|
||||
-- too many columns in column list: should fail
|
||||
COPY x (a, b, c, d, e, d, c) from stdin;
|
||||
|
||||
Reference in New Issue
Block a user