From e02ecd11c93306a0160b7ffb87a00e1422b2bd8a Mon Sep 17 00:00:00 2001 From: Matheus Alcantara Date: Mon, 18 May 2026 19:23:43 -0300 Subject: [PATCH v15 1/2] Extract CopyEscapeText() for reuse outside COPY TO Refactor CopyAttributeOutText() to extract its core text escaping logic into a new public function CopyEscapeText() that operates on a StringInfo buffer with explicit parameters, removing the dependency on CopyToState. This enables other code paths, such as postgres_fdw, to reuse the COPY text format escaping logic without needing to construct a full CopyToState. CopyAttributeOutText() now becomes a thin wrapper that calls CopyEscapeText() with the appropriate values from CopyToState. Also introduce CopySendDataBuf() and CopySendCharBuf() macros as low-level helpers that operate directly on StringInfo buffers. Author: Matheus Alcantara Discussion: https://www.postgresql.org/message-id/flat/DDIZJ217OUDK.2R5WE4OGL5PTY%40gmail.com --- src/backend/commands/copyto.c | 85 ++++++++++++++++++++++++++--------- src/include/commands/copy.h | 6 +++ 2 files changed, 71 insertions(+), 20 deletions(-) diff --git a/src/backend/commands/copyto.c b/src/backend/commands/copyto.c index ffed63a2986..ceee0014cfc 100644 --- a/src/backend/commands/copyto.c +++ b/src/backend/commands/copyto.c @@ -553,6 +553,12 @@ SendCopyEnd(CopyToState cstate) pq_putemptymessage(PqMsg_CopyDone); } +#define CopySendCharBuf(buf, c) \ + appendStringInfoCharMacro(buf, c) + +#define CopySendDataBuf(buf, databuf, datasize) \ + appendBinaryStringInfo(buf, databuf, datasize) + /*---------- * CopySendData sends output data to the destination (file or frontend) * CopySendString does the same for null-terminated strings @@ -566,7 +572,7 @@ SendCopyEnd(CopyToState cstate) static void CopySendData(CopyToState cstate, const void *databuf, int datasize) { - appendBinaryStringInfo(cstate->fe_msgbuf, databuf, datasize); + CopySendDataBuf(cstate->fe_msgbuf, databuf, datasize); } static void @@ -578,7 +584,7 @@ CopySendString(CopyToState cstate, const char *str) static void CopySendChar(CopyToState cstate, char c) { - appendStringInfoCharMacro(cstate->fe_msgbuf, c); + CopySendCharBuf(cstate->fe_msgbuf, c); } static void @@ -1417,16 +1423,44 @@ CopyOneRowTo(CopyToState cstate, TupleTableSlot *slot) CopySendData(cstate, start, ptr - start); \ } while (0) -static void -CopyAttributeOutText(CopyToState cstate, const char *string) +/* Like above, but it works with a string buffer */ +#define DUMPSOFAR_TO_BUF() \ + do { \ + if (ptr > start) \ + CopySendDataBuf(buf, start, ptr - start); \ + } while (0) + + +/* + * Escape a string for COPY TEXT format output + * + * Escapes control characters, backslashes, and the delimiter character + * according to COPY TEXT format rules. The escaped string is appended + * to 'buf'. + * + * Parameters: + * buf - StringInfo buffer to append the escaped text to + * string - the input string to escape + * delimc - the delimiter character that must be escaped + * file_encoding - target encoding for the output + * need_transcoding - if true, convert from server encoding to file_encoding + * encoding_embeds_ascii - if true, the encoding may have ASCII bytes as + * non-first bytes of multi-byte characters + */ +void +CopyEscapeText(StringInfo buf, + const char *string, + char delimc, + int file_encoding, + bool need_transcoding, + bool encoding_embeds_ascii) { const char *ptr; const char *start; char c; - char delimc = cstate->opts.delim[0]; - if (cstate->need_transcoding) - ptr = pg_server_to_any(string, strlen(string), cstate->file_encoding); + if (need_transcoding) + ptr = pg_server_to_any(string, strlen(string), file_encoding); else ptr = string; @@ -1444,7 +1478,7 @@ CopyAttributeOutText(CopyToState cstate, const char *string) * it's worth making two copies of it to get the IS_HIGHBIT_SET() test out * of the normal safe-encoding path. */ - if (cstate->encoding_embeds_ascii) + if (encoding_embeds_ascii) { start = ptr; while ((c = *ptr) != '\0') @@ -1487,19 +1521,19 @@ CopyAttributeOutText(CopyToState cstate, const char *string) continue; /* fall to end of loop */ } /* if we get here, we need to convert the control char */ - DUMPSOFAR(); - CopySendChar(cstate, '\\'); - CopySendChar(cstate, c); + DUMPSOFAR_TO_BUF(); + CopySendCharBuf(buf, '\\'); + CopySendCharBuf(buf, c); start = ++ptr; /* do not include char in next run */ } else if (c == '\\' || c == delimc) { - DUMPSOFAR(); - CopySendChar(cstate, '\\'); + DUMPSOFAR_TO_BUF(); + CopySendCharBuf(buf, '\\'); start = ptr++; /* we include char in next run */ } else if (IS_HIGHBIT_SET(c)) - ptr += pg_encoding_mblen(cstate->file_encoding, ptr); + ptr += pg_encoding_mblen(file_encoding, ptr); else ptr++; } @@ -1547,15 +1581,15 @@ CopyAttributeOutText(CopyToState cstate, const char *string) continue; /* fall to end of loop */ } /* if we get here, we need to convert the control char */ - DUMPSOFAR(); - CopySendChar(cstate, '\\'); - CopySendChar(cstate, c); + DUMPSOFAR_TO_BUF(); + CopySendCharBuf(buf, '\\'); + CopySendCharBuf(buf, c); start = ++ptr; /* do not include char in next run */ } else if (c == '\\' || c == delimc) { - DUMPSOFAR(); - CopySendChar(cstate, '\\'); + DUMPSOFAR_TO_BUF(); + CopySendCharBuf(buf, '\\'); start = ptr++; /* we include char in next run */ } else @@ -1563,7 +1597,18 @@ CopyAttributeOutText(CopyToState cstate, const char *string) } } - DUMPSOFAR(); + DUMPSOFAR_TO_BUF(); +} + +static void +CopyAttributeOutText(CopyToState cstate, const char *string) +{ + CopyEscapeText(cstate->fe_msgbuf, + string, + cstate->opts.delim[0], + cstate->file_encoding, + cstate->need_transcoding, + cstate->encoding_embeds_ascii); } /* diff --git a/src/include/commands/copy.h b/src/include/commands/copy.h index abecfe51098..27ee58c7fdb 100644 --- a/src/include/commands/copy.h +++ b/src/include/commands/copy.h @@ -136,5 +136,11 @@ extern void EndCopyTo(CopyToState cstate); extern uint64 DoCopyTo(CopyToState cstate); extern List *CopyGetAttnums(TupleDesc tupDesc, Relation rel, List *attnamelist); +extern void CopyEscapeText(StringInfo buf, + const char *string, + char delimc, + int file_encoding, + bool need_transcoding, + bool encoding_embeds_ascii); #endif /* COPY_H */ -- 2.50.1 (Apple Git-155)