Skip to content

Commit 70e94d2

Browse files
committed
Fix COPY FROM for null marker strings that correspond to invalid encoding.
The COPY documentation says "COPY FROM matches the input against the null string before removing backslashes". It is therefore reasonable to presume that null markers like E'\\0' will work ... and they did, until someone put the tests in the wrong order during microoptimization-driven rewrites. Since then, we've been failing if the null marker is something that would de-escape to an invalidly-encoded string. Since null markers generally need to be something that can't appear in the data, this represents a nontrivial loss of functionality; surprising nobody noticed it earlier. Per report from Jeff Davis. Backpatch to 8.4 where this got broken.
1 parent 29e0b4c commit 70e94d2

File tree

3 files changed

+60
-16
lines changed

3 files changed

+60
-16
lines changed

src/backend/commands/copy.c

Lines changed: 29 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2767,7 +2767,17 @@ CopyReadAttributesText(CopyState cstate, int maxfields, char **fieldvals)
27672767
start_ptr = cur_ptr;
27682768
fieldvals[fieldno] = output_ptr;
27692769

2770-
/* Scan data for field */
2770+
/*
2771+
* Scan data for field.
2772+
*
2773+
* Note that in this loop, we are scanning to locate the end of field
2774+
* and also speculatively performing de-escaping. Once we find the
2775+
* end-of-field, we can match the raw field contents against the null
2776+
* marker string. Only after that comparison fails do we know that
2777+
* de-escaping is actually the right thing to do; therefore we *must
2778+
* not* throw any syntax errors before we've done the null-marker
2779+
* check.
2780+
*/
27712781
for (;;)
27722782
{
27732783
char c;
@@ -2880,26 +2890,29 @@ CopyReadAttributesText(CopyState cstate, int maxfields, char **fieldvals)
28802890
*output_ptr++ = c;
28812891
}
28822892

2883-
/* Terminate attribute value in output area */
2884-
*output_ptr++ = '\0';
2885-
2886-
/*
2887-
* If we de-escaped a non-7-bit-ASCII char, make sure we still have
2888-
* valid data for the db encoding. Avoid calling strlen here for the
2889-
* sake of efficiency.
2890-
*/
2891-
if (saw_non_ascii)
2892-
{
2893-
char *fld = fieldvals[fieldno];
2894-
2895-
pg_verifymbstr(fld, output_ptr - (fld + 1), false);
2896-
}
2897-
28982893
/* Check whether raw input matched null marker */
28992894
input_len = end_ptr - start_ptr;
29002895
if (input_len == cstate->null_print_len &&
29012896
strncmp(start_ptr, cstate->null_print, input_len) == 0)
29022897
fieldvals[fieldno] = NULL;
2898+
else
2899+
{
2900+
/*
2901+
* At this point we know the field is supposed to contain data.
2902+
*
2903+
* If we de-escaped any non-7-bit-ASCII chars, make sure the
2904+
* resulting string is valid data for the db encoding.
2905+
*/
2906+
if (saw_non_ascii)
2907+
{
2908+
char *fld = fieldvals[fieldno];
2909+
2910+
pg_verifymbstr(fld, output_ptr - fld, false);
2911+
}
2912+
}
2913+
2914+
/* Terminate attribute value in output area */
2915+
*output_ptr++ = '\0';
29032916

29042917
fieldno++;
29052918
/* Done if we hit EOL instead of a delim */

src/test/regress/expected/copy2.out

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,22 @@ a\.
239239
\.b
240240
c\.d
241241
"\."
242+
-- test handling of nonstandard null marker that violates escaping rules
243+
CREATE TEMP TABLE testnull(a int, b text);
244+
INSERT INTO testnull VALUES (1, E'\\0'), (NULL, NULL);
245+
COPY testnull TO stdout WITH NULL AS E'\\0';
246+
1 \\0
247+
\0 \0
248+
COPY testnull FROM stdin WITH NULL AS E'\\0';
249+
SELECT * FROM testnull;
250+
a | b
251+
----+----
252+
1 | \0
253+
|
254+
42 | \0
255+
|
256+
(4 rows)
257+
242258
DROP TABLE x, y;
243259
DROP FUNCTION fn_x_before();
244260
DROP FUNCTION fn_x_after();

src/test/regress/sql/copy2.sql

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,21 @@ c\.d
164164

165165
COPY testeoc TO stdout CSV;
166166

167+
-- test handling of nonstandard null marker that violates escaping rules
168+
169+
CREATE TEMP TABLE testnull(a int, b text);
170+
INSERT INTO testnull VALUES (1, E'\\0'), (NULL, NULL);
171+
172+
COPY testnull TO stdout WITH NULL AS E'\\0';
173+
174+
COPY testnull FROM stdin WITH NULL AS E'\\0';
175+
42 \\0
176+
\0 \0
177+
\.
178+
179+
SELECT * FROM testnull;
180+
181+
167182
DROP TABLE x, y;
168183
DROP FUNCTION fn_x_before();
169184
DROP FUNCTION fn_x_after();

0 commit comments

Comments
 (0)