From 7d8ac622144e20c9e3aaf958bc0644a3c74fffea Mon Sep 17 00:00:00 2001
From: Masahiko Sawada <sawada.mshk@gmail.com>
Date: Thu, 6 Nov 2025 00:02:03 -0800
Subject: [PATCH 1/6] Separate format-specific fields from CopyToStateData
 struct.

Previously, CopyToStateData struct contained all state variables used
throughout COPY TO operations. To support the upcoming pluggable COPY
format feature, this commit moves fields specific to built-in formats
into their own dedicated struct.

This commit also introduces a new callback CopyToEstimateStateSpace,
allowing format routines to estimate and return the memory size needed
for their state data. The format's state data must embed
CopyToStateData as its first field, and the returned size must be
equal to or larger than CopyToStateData. While separate structs for
core and format-specific usage might seem feasible, using
contiguousmemory avoids overhead from extra pointer traversal.
---
 src/backend/commands/copyto.c    | 249 ++++++++++++++++---------------
 src/include/commands/copy.h      |   6 +-
 src/include/commands/copyapi.h   |   5 +
 src/include/commands/copystate.h |  68 +++++++++
 4 files changed, 208 insertions(+), 120 deletions(-)
 create mode 100644 src/include/commands/copystate.h

diff --git a/src/backend/commands/copyto.c b/src/backend/commands/copyto.c
index cef452584e5..6a0a66507ba 100644
--- a/src/backend/commands/copyto.c
+++ b/src/backend/commands/copyto.c
@@ -22,6 +22,8 @@
 #include "access/tableam.h"
 #include "catalog/pg_inherits.h"
 #include "commands/copyapi.h"
+#include "commands/copystate.h"
+#include "commands/defrem.h"
 #include "commands/progress.h"
 #include "executor/execdesc.h"
 #include "executor/executor.h"
@@ -39,19 +41,7 @@
 #include "utils/snapmgr.h"
 
 /*
- * Represents the different dest cases we need to worry about at
- * the bottom level
- */
-typedef enum CopyDest
-{
-	COPY_FILE,					/* to file (or a piped program) */
-	COPY_FRONTEND,				/* to frontend */
-	COPY_CALLBACK,				/* to callback function */
-} CopyDest;
-
-/*
- * This struct contains all the state variables used throughout a COPY TO
- * operation.
+ * Struct used for text and CSV format.
  *
  * Multi-byte encodings: all supported client-side encodings encode multi-byte
  * characters by having the first byte's high bit set. Subsequent bytes of the
@@ -64,41 +54,14 @@ typedef enum CopyDest
  * invoke pg_encoding_mblen() to skip over them. encoding_embeds_ascii is true
  * when we have to do it the hard way.
  */
-typedef struct CopyToStateData
+typedef struct CopyToStateTextLike
 {
-	/* format-specific routines */
-	const CopyToRoutine *routine;
-
-	/* low-level state data */
-	CopyDest	copy_dest;		/* type of copy source/destination */
-	FILE	   *copy_file;		/* used if copy_dest == COPY_FILE */
-	StringInfo	fe_msgbuf;		/* used for all dests during COPY TO */
+	CopyToStateData base;
 
 	int			file_encoding;	/* file or remote side's character encoding */
 	bool		need_transcoding;	/* file encoding diff from server? */
 	bool		encoding_embeds_ascii;	/* ASCII can be non-first byte? */
-
-	/* parameters from the COPY command */
-	Relation	rel;			/* relation to copy to */
-	QueryDesc  *queryDesc;		/* executable query to copy from */
-	List	   *attnumlist;		/* integer list of attnums to copy */
-	char	   *filename;		/* filename, or NULL for STDOUT */
-	bool		is_program;		/* is 'filename' a program to popen? */
-	copy_data_dest_cb data_dest_cb; /* function for writing data */
-
-	CopyFormatOptions opts;
-	Node	   *whereClause;	/* WHERE condition (or NULL) */
-	List	   *partitions;		/* OID list of partitions to copy data from */
-
-	/*
-	 * Working state
-	 */
-	MemoryContext copycontext;	/* per-copy execution context */
-
-	FmgrInfo   *out_functions;	/* lookup info for output functions */
-	MemoryContext rowcontext;	/* per-row evaluation context */
-	uint64		bytes_processed;	/* number of bytes processed so far */
-} CopyToStateData;
+}			CopyToStateTextLike;
 
 /* DestReceiver for COPY (query) TO */
 typedef struct
@@ -116,13 +79,14 @@ static const char BinarySignature[11] = "PGCOPY\n\377\r\n\0";
 static void EndCopy(CopyToState cstate);
 static void ClosePipeToProgram(CopyToState cstate);
 static void CopyOneRowTo(CopyToState cstate, TupleTableSlot *slot);
-static void CopyAttributeOutText(CopyToState cstate, const char *string);
-static void CopyAttributeOutCSV(CopyToState cstate, const char *string,
+static void CopyAttributeOutText(CopyToStateTextLike * cstate, const char *string);
+static void CopyAttributeOutCSV(CopyToStateTextLike * cstate, const char *string,
 								bool use_quote);
 static void CopyRelationTo(CopyToState cstate, Relation rel, Relation root_rel,
 						   uint64 *processed);
 
 /* built-in format-specific routines */
+static Size CopyToEstimateStateTextLike(void);
 static void CopyToTextLikeStart(CopyToState cstate, TupleDesc tupDesc);
 static void CopyToTextLikeOutFunc(CopyToState cstate, Oid atttypid, FmgrInfo *finfo);
 static void CopyToTextOneRow(CopyToState cstate, TupleTableSlot *slot);
@@ -130,6 +94,7 @@ static void CopyToCSVOneRow(CopyToState cstate, TupleTableSlot *slot);
 static void CopyToTextLikeOneRow(CopyToState cstate, TupleTableSlot *slot,
 								 bool is_csv);
 static void CopyToTextLikeEnd(CopyToState cstate);
+static Size CopyToEstimateStateBinary(void);
 static void CopyToBinaryStart(CopyToState cstate, TupleDesc tupDesc);
 static void CopyToBinaryOutFunc(CopyToState cstate, Oid atttypid, FmgrInfo *finfo);
 static void CopyToBinaryOneRow(CopyToState cstate, TupleTableSlot *slot);
@@ -155,6 +120,7 @@ static void CopySendInt16(CopyToState cstate, int16 val);
 
 /* text format */
 static const CopyToRoutine CopyToRoutineText = {
+	.CopyToEstimateStateSpace = CopyToEstimateStateTextLike,
 	.CopyToStart = CopyToTextLikeStart,
 	.CopyToOutFunc = CopyToTextLikeOutFunc,
 	.CopyToOneRow = CopyToTextOneRow,
@@ -163,6 +129,7 @@ static const CopyToRoutine CopyToRoutineText = {
 
 /* CSV format */
 static const CopyToRoutine CopyToRoutineCSV = {
+	.CopyToEstimateStateSpace = CopyToEstimateStateTextLike,
 	.CopyToStart = CopyToTextLikeStart,
 	.CopyToOutFunc = CopyToTextLikeOutFunc,
 	.CopyToOneRow = CopyToCSVOneRow,
@@ -171,62 +138,77 @@ static const CopyToRoutine CopyToRoutineCSV = {
 
 /* binary format */
 static const CopyToRoutine CopyToRoutineBinary = {
+	.CopyToEstimateStateSpace = CopyToEstimateStateBinary,
 	.CopyToStart = CopyToBinaryStart,
 	.CopyToOutFunc = CopyToBinaryOutFunc,
 	.CopyToOneRow = CopyToBinaryOneRow,
 	.CopyToEnd = CopyToBinaryEnd,
 };
 
-/* Return a COPY TO routine for the given options */
-static const CopyToRoutine *
-CopyToGetRoutine(const CopyFormatOptions *opts)
+static Size
+CopyToEstimateStateTextLike(void)
 {
-	if (opts->csv_mode)
-		return &CopyToRoutineCSV;
-	else if (opts->binary)
-		return &CopyToRoutineBinary;
-
-	/* default is text */
-	return &CopyToRoutineText;
+	return sizeof(CopyToStateTextLike);
 }
 
 /* Implementation of the start callback for text and CSV formats */
 static void
-CopyToTextLikeStart(CopyToState cstate, TupleDesc tupDesc)
+CopyToTextLikeStart(CopyToState ccstate, TupleDesc tupDesc)
 {
+	CopyToStateTextLike *cstate = (CopyToStateTextLike *) ccstate;
+
+	/* Use client encoding when ENCODING option is not specified. */
+	if (cstate->base.opts.file_encoding < 0)
+		cstate->file_encoding = pg_get_client_encoding();
+	else
+		cstate->file_encoding = cstate->base.opts.file_encoding;
+
+	/*
+	 * Set up encoding conversion info if the file and server encodings differ
+	 * (see also pg_server_to_any).
+	 */
+	if (cstate->file_encoding == GetDatabaseEncoding() ||
+		cstate->file_encoding == PG_SQL_ASCII)
+		cstate->need_transcoding = false;
+	else
+		cstate->need_transcoding = true;
+
+	/* See Multibyte encoding comment above */
+	cstate->encoding_embeds_ascii = PG_ENCODING_IS_CLIENT_ONLY(cstate->file_encoding);
+
 	/*
 	 * For non-binary copy, we need to convert null_print to file encoding,
 	 * because it will be sent directly with CopySendString.
 	 */
 	if (cstate->need_transcoding)
-		cstate->opts.null_print_client = pg_server_to_any(cstate->opts.null_print,
-														  cstate->opts.null_print_len,
-														  cstate->file_encoding);
+		cstate->base.opts.null_print_client = pg_server_to_any(cstate->base.opts.null_print,
+															   cstate->base.opts.null_print_len,
+															   cstate->file_encoding);
 
 	/* if a header has been requested send the line */
-	if (cstate->opts.header_line == COPY_HEADER_TRUE)
+	if (cstate->base.opts.header_line == COPY_HEADER_TRUE)
 	{
 		ListCell   *cur;
 		bool		hdr_delim = false;
 
-		foreach(cur, cstate->attnumlist)
+		foreach(cur, cstate->base.attnumlist)
 		{
 			int			attnum = lfirst_int(cur);
 			char	   *colname;
 
 			if (hdr_delim)
-				CopySendChar(cstate, cstate->opts.delim[0]);
+				CopySendChar(ccstate, cstate->base.opts.delim[0]);
 			hdr_delim = true;
 
 			colname = NameStr(TupleDescAttr(tupDesc, attnum - 1)->attname);
 
-			if (cstate->opts.csv_mode)
+			if (cstate->base.opts.csv_mode)
 				CopyAttributeOutCSV(cstate, colname, false);
 			else
 				CopyAttributeOutText(cstate, colname);
 		}
 
-		CopySendTextLikeEndOfRow(cstate);
+		CopySendTextLikeEndOfRow(ccstate);
 	}
 }
 
@@ -294,10 +276,10 @@ CopyToTextLikeOneRow(CopyToState cstate,
 										value);
 
 			if (is_csv)
-				CopyAttributeOutCSV(cstate, string,
+				CopyAttributeOutCSV((CopyToStateTextLike *) cstate, string,
 									cstate->opts.force_quote_flags[attnum - 1]);
 			else
-				CopyAttributeOutText(cstate, string);
+				CopyAttributeOutText((CopyToStateTextLike *) cstate, string);
 		}
 	}
 
@@ -311,6 +293,13 @@ CopyToTextLikeEnd(CopyToState cstate)
 	/* Nothing to do here */
 }
 
+static Size
+CopyToEstimateStateBinary(void)
+{
+	/* Binary format doesn't require additional fields */
+	return sizeof(CopyToStateData);
+}
+
 /*
  * Implementation of the start callback for binary format. Send a header
  * for a binary copy.
@@ -406,7 +395,7 @@ SendCopyBegin(CopyToState cstate)
 	for (i = 0; i < natts; i++)
 		pq_sendint16(&buf, format); /* per-column formats */
 	pq_endmessage(&buf);
-	cstate->copy_dest = COPY_FRONTEND;
+	cstate->copy_dest = COPY_DEST_FRONTEND;
 }
 
 static void
@@ -453,7 +442,7 @@ CopySendEndOfRow(CopyToState cstate)
 
 	switch (cstate->copy_dest)
 	{
-		case COPY_FILE:
+		case COPY_DEST_FILE:
 			if (fwrite(fe_msgbuf->data, fe_msgbuf->len, 1,
 					   cstate->copy_file) != 1 ||
 				ferror(cstate->copy_file))
@@ -487,11 +476,11 @@ CopySendEndOfRow(CopyToState cstate)
 							 errmsg("could not write to COPY file: %m")));
 			}
 			break;
-		case COPY_FRONTEND:
+		case COPY_DEST_FRONTEND:
 			/* Dump the accumulated row as one CopyData message */
 			(void) pq_putmessage(PqMsg_CopyData, fe_msgbuf->data, fe_msgbuf->len);
 			break;
-		case COPY_CALLBACK:
+		case COPY_DEST_CALLBACK:
 			cstate->data_dest_cb(fe_msgbuf->data, fe_msgbuf->len);
 			break;
 	}
@@ -512,7 +501,7 @@ CopySendTextLikeEndOfRow(CopyToState cstate)
 {
 	switch (cstate->copy_dest)
 	{
-		case COPY_FILE:
+		case COPY_DEST_FILE:
 			/* Default line termination depends on platform */
 #ifndef WIN32
 			CopySendChar(cstate, '\n');
@@ -520,7 +509,7 @@ CopySendTextLikeEndOfRow(CopyToState cstate)
 			CopySendString(cstate, "\r\n");
 #endif
 			break;
-		case COPY_FRONTEND:
+		case COPY_DEST_FRONTEND:
 			/* The FE/BE protocol uses \n as newline for all platforms */
 			CopySendChar(cstate, '\n');
 			break;
@@ -614,6 +603,54 @@ EndCopy(CopyToState cstate)
 	pfree(cstate);
 }
 
+/*
+ * Allocate COPY TO state data based on the format's EsimateStateSpace
+ * callback.
+ */
+static CopyToState
+create_copyto_state(ParseState *pstate, List *options)
+{
+	const CopyToRoutine *routine;
+	CopyToState cstate;
+	Size		req_size;
+	bool		format_specified = false;
+
+	routine = &CopyToRoutineText;	/* default */
+	foreach_node(DefElem, defel, options)
+	{
+		if (strcmp(defel->defname, "format") == 0)
+		{
+			char	   *fmt = defGetString(defel);
+
+			if (format_specified)
+				errorConflictingDefElem(defel, pstate);
+			format_specified = true;
+			if (strcmp(fmt, "text") == 0)
+				routine = &CopyToRoutineText;
+			else if (strcmp(fmt, "csv") == 0)
+				routine = &CopyToRoutineCSV;
+			else if (strcmp(fmt, "binary") == 0)
+				routine = &CopyToRoutineBinary;
+			else
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						 errmsg("COPY format \"%s\" not recognized", fmt),
+						 parser_errposition(pstate, defel->location)));
+
+			break;
+		}
+	}
+
+	req_size = routine->CopyToEstimateStateSpace();
+	Assert(req_size >= sizeof(CopyToStateData));
+
+	/* Allocate workspace and zero all fields */
+	cstate = (CopyToState) palloc0(req_size);
+	cstate->routine = routine;
+
+	return cstate;
+}
+
 /*
  * Setup CopyToState to read tuples from a table or a query for COPY TO.
  *
@@ -718,9 +755,7 @@ BeginCopyTo(ParseState *pstate,
 							RelationGetRelationName(rel))));
 	}
 
-
-	/* Allocate workspace and zero all fields */
-	cstate = (CopyToStateData *) palloc0(sizeof(CopyToStateData));
+	cstate = create_copyto_state(pstate, options);
 
 	/*
 	 * We allocate everything used by a cstate in a new memory context. This
@@ -735,9 +770,6 @@ BeginCopyTo(ParseState *pstate,
 	/* Extract options from the statement node tree */
 	ProcessCopyOptions(pstate, &cstate->opts, false /* is_from */ , options);
 
-	/* Set format routine */
-	cstate->routine = CopyToGetRoutine(&cstate->opts);
-
 	/* Process the source/target relation or query */
 	if (rel)
 	{
@@ -918,31 +950,12 @@ BeginCopyTo(ParseState *pstate,
 		}
 	}
 
-	/* Use client encoding when ENCODING option is not specified. */
-	if (cstate->opts.file_encoding < 0)
-		cstate->file_encoding = pg_get_client_encoding();
-	else
-		cstate->file_encoding = cstate->opts.file_encoding;
-
-	/*
-	 * Set up encoding conversion info if the file and server encodings differ
-	 * (see also pg_server_to_any).
-	 */
-	if (cstate->file_encoding == GetDatabaseEncoding() ||
-		cstate->file_encoding == PG_SQL_ASCII)
-		cstate->need_transcoding = false;
-	else
-		cstate->need_transcoding = true;
-
-	/* See Multibyte encoding comment above */
-	cstate->encoding_embeds_ascii = PG_ENCODING_IS_CLIENT_ONLY(cstate->file_encoding);
-
-	cstate->copy_dest = COPY_FILE;	/* default */
+	cstate->copy_dest = COPY_DEST_FILE; /* default */
 
 	if (data_dest_cb)
 	{
 		progress_vals[1] = PROGRESS_COPY_TYPE_CALLBACK;
-		cstate->copy_dest = COPY_CALLBACK;
+		cstate->copy_dest = COPY_DEST_CALLBACK;
 		cstate->data_dest_cb = data_dest_cb;
 	}
 	else if (pipe)
@@ -1233,16 +1246,16 @@ CopyOneRowTo(CopyToState cstate, TupleTableSlot *slot)
 #define DUMPSOFAR() \
 	do { \
 		if (ptr > start) \
-			CopySendData(cstate, start, ptr - start); \
+			CopySendData((CopyToState) cstate, start, ptr - start); \
 	} while (0)
 
 static void
-CopyAttributeOutText(CopyToState cstate, const char *string)
+CopyAttributeOutText(CopyToStateTextLike * cstate, const char *string)
 {
 	const char *ptr;
 	const char *start;
 	char		c;
-	char		delimc = cstate->opts.delim[0];
+	char		delimc = cstate->base.opts.delim[0];
 
 	if (cstate->need_transcoding)
 		ptr = pg_server_to_any(string, strlen(string), cstate->file_encoding);
@@ -1307,14 +1320,14 @@ CopyAttributeOutText(CopyToState cstate, const char *string)
 				}
 				/* if we get here, we need to convert the control char */
 				DUMPSOFAR();
-				CopySendChar(cstate, '\\');
-				CopySendChar(cstate, c);
+				CopySendChar((CopyToState) cstate, '\\');
+				CopySendChar((CopyToState) cstate, c);
 				start = ++ptr;	/* do not include char in next run */
 			}
 			else if (c == '\\' || c == delimc)
 			{
 				DUMPSOFAR();
-				CopySendChar(cstate, '\\');
+				CopySendChar((CopyToState) cstate, '\\');
 				start = ptr++;	/* we include char in next run */
 			}
 			else if (IS_HIGHBIT_SET(c))
@@ -1367,14 +1380,14 @@ CopyAttributeOutText(CopyToState cstate, const char *string)
 				}
 				/* if we get here, we need to convert the control char */
 				DUMPSOFAR();
-				CopySendChar(cstate, '\\');
-				CopySendChar(cstate, c);
+				CopySendChar((CopyToState) cstate, '\\');
+				CopySendChar((CopyToState) cstate, c);
 				start = ++ptr;	/* do not include char in next run */
 			}
 			else if (c == '\\' || c == delimc)
 			{
 				DUMPSOFAR();
-				CopySendChar(cstate, '\\');
+				CopySendChar((CopyToState) cstate, '\\');
 				start = ptr++;	/* we include char in next run */
 			}
 			else
@@ -1390,19 +1403,19 @@ CopyAttributeOutText(CopyToState cstate, const char *string)
  * CSV-style escaping
  */
 static void
-CopyAttributeOutCSV(CopyToState cstate, const char *string,
+CopyAttributeOutCSV(CopyToStateTextLike * cstate, const char *string,
 					bool use_quote)
 {
 	const char *ptr;
 	const char *start;
 	char		c;
-	char		delimc = cstate->opts.delim[0];
-	char		quotec = cstate->opts.quote[0];
-	char		escapec = cstate->opts.escape[0];
-	bool		single_attr = (list_length(cstate->attnumlist) == 1);
+	char		delimc = cstate->base.opts.delim[0];
+	char		quotec = cstate->base.opts.quote[0];
+	char		escapec = cstate->base.opts.escape[0];
+	bool		single_attr = (list_length(cstate->base.attnumlist) == 1);
 
 	/* force quoting if it matches null_print (before conversion!) */
-	if (!use_quote && strcmp(string, cstate->opts.null_print) == 0)
+	if (!use_quote && strcmp(string, cstate->base.opts.null_print) == 0)
 		use_quote = true;
 
 	if (cstate->need_transcoding)
@@ -1445,7 +1458,7 @@ CopyAttributeOutCSV(CopyToState cstate, const char *string,
 
 	if (use_quote)
 	{
-		CopySendChar(cstate, quotec);
+		CopySendChar((CopyToState) cstate, quotec);
 
 		/*
 		 * We adopt the same optimization strategy as in CopyAttributeOutText
@@ -1456,7 +1469,7 @@ CopyAttributeOutCSV(CopyToState cstate, const char *string,
 			if (c == quotec || c == escapec)
 			{
 				DUMPSOFAR();
-				CopySendChar(cstate, escapec);
+				CopySendChar((CopyToState) cstate, escapec);
 				start = ptr;	/* we include char in next run */
 			}
 			if (IS_HIGHBIT_SET(c) && cstate->encoding_embeds_ascii)
@@ -1466,12 +1479,12 @@ CopyAttributeOutCSV(CopyToState cstate, const char *string,
 		}
 		DUMPSOFAR();
 
-		CopySendChar(cstate, quotec);
+		CopySendChar((CopyToState) cstate, quotec);
 	}
 	else
 	{
 		/* If it doesn't need quoting, we can just dump it as-is */
-		CopySendString(cstate, ptr);
+		CopySendString((CopyToState) cstate, ptr);
 	}
 }
 
diff --git a/src/include/commands/copy.h b/src/include/commands/copy.h
index 541176e1980..d75a70715a4 100644
--- a/src/include/commands/copy.h
+++ b/src/include/commands/copy.h
@@ -87,10 +87,12 @@ typedef struct CopyFormatOptions
 	List	   *convert_select; /* list of column names (can be NIL) */
 } CopyFormatOptions;
 
-/* These are private in commands/copy[from|to].c */
-typedef struct CopyFromStateData *CopyFromState;
+/* defined in copystate.h */
 typedef struct CopyToStateData *CopyToState;
 
+/* Private in commands/copyfrom.c */
+typedef struct CopyFromStateData *CopyFromState;
+
 typedef int (*copy_data_source_cb) (void *outbuf, int minread, int maxread);
 typedef void (*copy_data_dest_cb) (void *data, int len);
 
diff --git a/src/include/commands/copyapi.h b/src/include/commands/copyapi.h
index 2a2d2f9876b..3b9982d54b8 100644
--- a/src/include/commands/copyapi.h
+++ b/src/include/commands/copyapi.h
@@ -22,6 +22,11 @@
  */
 typedef struct CopyToRoutine
 {
+	/*
+	 * Estimate and return the memory size required to store the state data.
+	 */
+	Size		(*CopyToEstimateStateSpace) (void);
+
 	/*
 	 * Set output function information. This callback is called once at the
 	 * beginning of COPY TO.
diff --git a/src/include/commands/copystate.h b/src/include/commands/copystate.h
new file mode 100644
index 00000000000..7561083a323
--- /dev/null
+++ b/src/include/commands/copystate.h
@@ -0,0 +1,68 @@
+/*-------------------------------------------------------------------------
+ *
+ * copystate.h
+ *	  Definitions for state data used by COPY TO and COPY FROM.
+ *
+ *
+ * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/commands/copystate.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef COPYSTATE_H
+#define COPYSTATE_H
+
+#include "postgres.h"
+#include "commands/copy.h"
+#include "executor/execdesc.h"
+
+/*
+ * Represents the different dest cases we need to worry about at
+ * the bottom level
+ */
+typedef enum CopyDest
+{
+	COPY_DEST_FILE,				/* to/from file (or a piped program) */
+	COPY_DEST_FRONTEND,			/* to/from frontend */
+	COPY_DEST_CALLBACK,			/* to/from callback function */
+} CopyDest;
+
+/*
+ * This struct contains the state variables commonly used throughout a
+ * COPY TO operation.
+ */
+typedef struct CopyToStateData
+{
+	/* format-specific routines */
+	const struct CopyToRoutine *routine;
+
+	/* low-level state data */
+	CopyDest	copy_dest;		/* type of copy source/destination */
+	FILE	   *copy_file;		/* used if copy_dest == COPY_DEST_FILE */
+	StringInfo	fe_msgbuf;		/* used for all dests during COPY TO */
+
+	/* parameters from the COPY command */
+	Relation	rel;			/* relation to copy to */
+	QueryDesc  *queryDesc;		/* executable query to copy from */
+	List	   *attnumlist;		/* integer list of attnums to copy */
+	char	   *filename;		/* filename, or NULL for STDOUT */
+	bool		is_program;		/* is 'filename' a program to popen? */
+	copy_data_dest_cb data_dest_cb; /* function for writing data */
+
+	CopyFormatOptions opts;
+	Node	   *whereClause;	/* WHERE condition (or NULL) */
+	List	   *partitions;		/* OID list of partitions to copy data from */
+
+	/*
+	 * Working state
+	 */
+	MemoryContext copycontext;	/* per-copy execution context */
+
+	FmgrInfo   *out_functions;	/* lookup info for output functions */
+	MemoryContext rowcontext;	/* per-row evaluation context */
+	uint64		bytes_processed;	/* number of bytes processed so far */
+} CopyToStateData;
+
+#endif
-- 
2.47.3

