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
Message: Re: postgres_fdw: Use COPY to speed up batch inserts
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)