Skip to content

Commit ca7a0d1

Browse files
committed
Fix two issues with HEADER MATCH in COPY
072132f used the attnum offset to access the raw_fields array when checking that the attribute names of the header and of the relation match, leading to incorrect results or even crashes if the attribute numbers of a relation are changed, like on a dropped attribute. This fixes the logic to use the correct attribute names for the header matching requirements. Also, this commit disallows HEADER MATCH in COPY TO as there is no validation that can be done in this case. The tests are expanded for HEADER MATCH with COPY FROM and dropped columns, with cases where a relation has a dropped and re-added column, as well as a reduced set of columns. Author: Julien Rouhaud Reviewed-by: Peter Eisentraut, Michael Paquier Discussion: https://postgr.es/m/20220607154744.vvmitnqhyxrne5ms@jrouhaud
1 parent eba331a commit ca7a0d1

File tree

5 files changed

+97
-8
lines changed

5 files changed

+97
-8
lines changed

doc/src/sgml/ref/copy.sgml

+2
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,8 @@ COPY { <replaceable class="parameter">table_name</replaceable> [ ( <replaceable
282282
of the columns in the header line must match the actual column names of
283283
the table, otherwise an error is raised.
284284
This option is not allowed when using <literal>binary</literal> format.
285+
The <literal>MATCH</literal> option is only valid for <command>COPY
286+
FROM</command> commands.
285287
</para>
286288
</listitem>
287289
</varlistentry>

src/backend/commands/copy.c

+9-2
Original file line numberDiff line numberDiff line change
@@ -318,7 +318,7 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
318318
* defGetBoolean() but also accepts the special value "match".
319319
*/
320320
static CopyHeaderChoice
321-
defGetCopyHeaderChoice(DefElem *def)
321+
defGetCopyHeaderChoice(DefElem *def, bool is_from)
322322
{
323323
/*
324324
* If no parameter given, assume "true" is meant.
@@ -360,7 +360,14 @@ defGetCopyHeaderChoice(DefElem *def)
360360
if (pg_strcasecmp(sval, "off") == 0)
361361
return COPY_HEADER_FALSE;
362362
if (pg_strcasecmp(sval, "match") == 0)
363+
{
364+
if (!is_from)
365+
ereport(ERROR,
366+
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
367+
errmsg("cannot use \"%s\" with HEADER in COPY TO",
368+
sval)));
363369
return COPY_HEADER_MATCH;
370+
}
364371
}
365372
break;
366373
}
@@ -452,7 +459,7 @@ ProcessCopyOptions(ParseState *pstate,
452459
if (header_specified)
453460
errorConflictingDefElem(defel, pstate);
454461
header_specified = true;
455-
opts_out->header_line = defGetCopyHeaderChoice(defel);
462+
opts_out->header_line = defGetCopyHeaderChoice(defel, is_from);
456463
}
457464
else if (strcmp(defel->defname, "quote") == 0)
458465
{

src/backend/commands/copyfromparse.c

+3-2
Original file line numberDiff line numberDiff line change
@@ -789,11 +789,12 @@ NextCopyFromRawFields(CopyFromState cstate, char ***fields, int *nfields)
789789
foreach(cur, cstate->attnumlist)
790790
{
791791
int attnum = lfirst_int(cur);
792-
char *colName = cstate->raw_fields[attnum - 1];
792+
char *colName;
793793
Form_pg_attribute attr = TupleDescAttr(tupDesc, attnum - 1);
794794

795-
fldnum++;
795+
Assert(fldnum < cstate->max_fields);
796796

797+
colName = cstate->raw_fields[fldnum++];
797798
if (colName == NULL)
798799
ereport(ERROR,
799800
(errcode(ERRCODE_BAD_COPY_FILE_FORMAT),

src/test/regress/expected/copy.out

+42-1
Original file line numberDiff line numberDiff line change
@@ -182,9 +182,21 @@ create table header_copytest (
182182
b int,
183183
c text
184184
);
185+
-- Make sure it works with with dropped columns
186+
alter table header_copytest drop column c;
187+
alter table header_copytest add column c text;
188+
copy header_copytest to stdout with (header match);
189+
ERROR: cannot use "match" with HEADER in COPY TO
185190
copy header_copytest from stdin with (header wrong_choice);
186191
ERROR: header requires a Boolean value or "match"
192+
-- works
187193
copy header_copytest from stdin with (header match);
194+
copy header_copytest (c, a, b) from stdin with (header match);
195+
copy header_copytest from stdin with (header match, format csv);
196+
-- errors
197+
copy header_copytest (c, b, a) from stdin with (header match);
198+
ERROR: column name mismatch in header line field 1: got "a", expected "c"
199+
CONTEXT: COPY header_copytest, line 1: "a b c"
188200
copy header_copytest from stdin with (header match);
189201
ERROR: column name mismatch in header line field 3: got null value ("\N"), expected "c"
190202
CONTEXT: COPY header_copytest, line 1: "a b \N"
@@ -197,5 +209,34 @@ CONTEXT: COPY header_copytest, line 1: "a b c d"
197209
copy header_copytest from stdin with (header match);
198210
ERROR: column name mismatch in header line field 3: got "d", expected "c"
199211
CONTEXT: COPY header_copytest, line 1: "a b d"
200-
copy header_copytest from stdin with (header match, format csv);
212+
SELECT * FROM header_copytest ORDER BY a;
213+
a | b | c
214+
---+---+-----
215+
1 | 2 | foo
216+
3 | 4 | bar
217+
5 | 6 | baz
218+
(3 rows)
219+
220+
-- Drop an extra column, in the middle of the existing set.
221+
alter table header_copytest drop column b;
222+
-- works
223+
copy header_copytest (c, a) from stdin with (header match);
224+
copy header_copytest (a, c) from stdin with (header match);
225+
-- errors
226+
copy header_copytest from stdin with (header match);
227+
ERROR: wrong number of fields in header line: field count is 3, expected 2
228+
CONTEXT: COPY header_copytest, line 1: "a ........pg.dropped.2........ c"
229+
copy header_copytest (a, c) from stdin with (header match);
230+
ERROR: wrong number of fields in header line: field count is 3, expected 2
231+
CONTEXT: COPY header_copytest, line 1: "a c b"
232+
SELECT * FROM header_copytest ORDER BY a;
233+
a | c
234+
---+-----
235+
1 | foo
236+
3 | bar
237+
5 | baz
238+
7 | foo
239+
8 | foo
240+
(5 rows)
241+
201242
drop table header_copytest;

src/test/regress/sql/copy.sql

+41-3
Original file line numberDiff line numberDiff line change
@@ -204,11 +204,29 @@ create table header_copytest (
204204
b int,
205205
c text
206206
);
207+
-- Make sure it works with with dropped columns
208+
alter table header_copytest drop column c;
209+
alter table header_copytest add column c text;
210+
copy header_copytest to stdout with (header match);
207211
copy header_copytest from stdin with (header wrong_choice);
212+
-- works
208213
copy header_copytest from stdin with (header match);
209214
a b c
210215
1 2 foo
211216
\.
217+
copy header_copytest (c, a, b) from stdin with (header match);
218+
c a b
219+
bar 3 4
220+
\.
221+
copy header_copytest from stdin with (header match, format csv);
222+
a,b,c
223+
5,6,baz
224+
\.
225+
-- errors
226+
copy header_copytest (c, b, a) from stdin with (header match);
227+
a b c
228+
1 2 foo
229+
\.
212230
copy header_copytest from stdin with (header match);
213231
a b \N
214232
1 2 foo
@@ -225,8 +243,28 @@ copy header_copytest from stdin with (header match);
225243
a b d
226244
1 2 foo
227245
\.
228-
copy header_copytest from stdin with (header match, format csv);
229-
a,b,c
230-
1,2,foo
246+
SELECT * FROM header_copytest ORDER BY a;
247+
248+
-- Drop an extra column, in the middle of the existing set.
249+
alter table header_copytest drop column b;
250+
-- works
251+
copy header_copytest (c, a) from stdin with (header match);
252+
c a
253+
foo 7
254+
\.
255+
copy header_copytest (a, c) from stdin with (header match);
256+
a c
257+
8 foo
231258
\.
259+
-- errors
260+
copy header_copytest from stdin with (header match);
261+
a ........pg.dropped.2........ c
262+
1 2 foo
263+
\.
264+
copy header_copytest (a, c) from stdin with (header match);
265+
a c b
266+
1 foo 2
267+
\.
268+
269+
SELECT * FROM header_copytest ORDER BY a;
232270
drop table header_copytest;

0 commit comments

Comments
 (0)