From 1d4c1e5bb80b72ab7b1d6802ae7794f5dbc92d44 Mon Sep 17 00:00:00 2001 From: AyoubKAZ Date: Wed, 4 Nov 2026 00:57:14 +0100 Subject: [PATCH v3] Fix DEFAULT marker handling in COPY FROM ... HEADER MATCH When using COPY FROM with both HEADER MATCH and DEFAULT options, the header line is parsed using CopyReadAttributesText() or CopyReadAttributesCSV(). Because default marker matching was active during header line parsing, any header column name identical to the DEFAULT marker string was mistakenly processed as a data default marker. This caused two bugs depending on whether the column had a default: 1. If the column had no default expression, parsing the header line raised an error ("unexpected default marker in COPY data") 2. If the column had a default expression, CopyReadAttributes*() set cstate->defaults[m] = true while reading the header. Because header parsing and the first data row are processed in the same NextCopyFrom() call, and cstate->defaults is cleared only at the start of NextCopyFrom(), the flag set during header parsing just followed up into the first actual row . As a result, the input value in the first row was discarded and replaced with the column's default value, while subsequent data rows were unaffected. Fix by temporarily disabling default marker recognition while parsing the header line under HEADER MATCH. Reported-by: Ayoub Kazar Author: Ayoub Kazar Reviewed-by: Chao Li Discussion: https://postgr.es/m/2da457c8-a9a1-49b1-a89f-719190dff7ae@data-bene.io --- src/backend/commands/copyfromparse.c | 10 ++++++++++ src/test/regress/expected/copy2.out | 18 ++++++++++++++++++ src/test/regress/sql/copy2.sql | 20 ++++++++++++++++++++ 3 files changed, 48 insertions(+) diff --git a/src/backend/commands/copyfromparse.c b/src/backend/commands/copyfromparse.c index 06450535779..4cc656117f3 100644 --- a/src/backend/commands/copyfromparse.c +++ b/src/backend/commands/copyfromparse.c @@ -831,12 +831,22 @@ NextCopyFromRawFieldsInternal(CopyFromState cstate, char ***fields, int *nfields if (cstate->opts.header_line == COPY_HEADER_MATCH) { int fldnum; + char *save_default_print = cstate->opts.default_print; + + /* + * Disable DEFAULT marker recognition while parsing the header + * line, so that column names matching the default string are not + * treated as default markers. + */ + cstate->opts.default_print = NULL; if (is_csv) fldct = CopyReadAttributesCSV(cstate); else fldct = CopyReadAttributesText(cstate); + cstate->opts.default_print = save_default_print; + if (fldct != list_length(cstate->attnumlist)) ereport(ERROR, (errcode(ERRCODE_BAD_COPY_FILE_FORMAT), diff --git a/src/test/regress/expected/copy2.out b/src/test/regress/expected/copy2.out index df6fcd91799..57523d8de9c 100644 --- a/src/test/regress/expected/copy2.out +++ b/src/test/regress/expected/copy2.out @@ -1008,6 +1008,24 @@ select id, text_value, ts_value from copy_default; 3 | test | Tue Jul 05 00:00:00 2022 (3 rows) +truncate copy_default; +-- DEFAULT with HEADER MATCH where header field matches DEFAULT marker +copy copy_default from stdin with (format csv, header match, default 'id'); +select id, text_value, ts_value from copy_default; + id | text_value | ts_value +----+------------+-------------------------- + 1 | hello | Mon Jul 04 00:00:00 2022 +(1 row) + +truncate copy_default; +copy copy_default from stdin with (format csv, header match, default 'text_value'); +select id, text_value, ts_value from copy_default; + id | text_value | ts_value +----+------------+-------------------------- + 1 | hello | Mon Jul 04 00:00:00 2022 + 2 | test | Sun Jul 03 00:00:00 2022 +(2 rows) + truncate copy_default; -- DEFAULT cannot be used in COPY TO copy (select 1 as test) TO stdout with (default '\D'); diff --git a/src/test/regress/sql/copy2.sql b/src/test/regress/sql/copy2.sql index ec6f5e8cfdc..dc31137e51b 100644 --- a/src/test/regress/sql/copy2.sql +++ b/src/test/regress/sql/copy2.sql @@ -819,5 +819,25 @@ select id, text_value, ts_value from copy_default; truncate copy_default; +-- DEFAULT with HEADER MATCH where header field matches DEFAULT marker +copy copy_default from stdin with (format csv, header match, default 'id'); +id,text_value,ts_value +1,hello,2022-07-04 +\. + +select id, text_value, ts_value from copy_default; + +truncate copy_default; + +copy copy_default from stdin with (format csv, header match, default 'text_value'); +id,text_value,ts_value +1,hello,2022-07-04 +2,text_value,2022-07-03 +\. + +select id, text_value, ts_value from copy_default; + +truncate copy_default; + -- DEFAULT cannot be used in COPY TO copy (select 1 as test) TO stdout with (default '\D'); -- 2.50.1 (Apple Git-155)