v15-0001-Extract-CopyEscapeText-for-reuse-outside-COPY-TO.patch
text/plain
Filename: v15-0001-Extract-CopyEscapeText-for-reuse-outside-COPY-TO.patch
Type: text/plain
Part: 0
From e02ecd11c93306a0160b7ffb87a00e1422b2bd8a Mon Sep 17 00:00:00 2001
From: Matheus Alcantara <mths.dev@pm.me>
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 <mths.dev@pm.me>
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)