From 819a96d8cac5d41eae1dd2b32cdfd817199dd5ad Mon Sep 17 00:00:00 2001
From: Masahiko Sawada <sawada.mshk@gmail.com>
Date: Mon, 10 Nov 2025 15:05:05 -0800
Subject: [PATCH 5/6] Add Copy{To,From}ProcessOneOption callback.

---
 src/backend/commands/copyfrom.c | 15 +++++++++++----
 src/backend/commands/copyto.c   | 17 ++++++++++++-----
 src/include/commands/copyapi.h  | 18 +++++++++++++++++-
 3 files changed, 40 insertions(+), 10 deletions(-)

diff --git a/src/backend/commands/copyfrom.c b/src/backend/commands/copyfrom.c
index 6ca5466f244..c23c4b1188a 100644
--- a/src/backend/commands/copyfrom.c
+++ b/src/backend/commands/copyfrom.c
@@ -132,6 +132,7 @@ static void CopyFromBinaryEnd(CopyFromState cstate);
 /* text format */
 static const CopyFromRoutine CopyFromRoutineText = {
 	.CopyFromEstimateStateSpace = CopyFromBuiltinsEstimateSpace,
+	.CopyFromProcessOneOption = NULL,
 	.CopyFromInFunc = CopyFromTextLikeInFunc,
 	.CopyFromStart = CopyFromTextLikeStart,
 	.CopyFromOneRow = CopyFromTextOneRow,
@@ -141,6 +142,7 @@ static const CopyFromRoutine CopyFromRoutineText = {
 /* CSV format */
 static const CopyFromRoutine CopyFromRoutineCSV = {
 	.CopyFromEstimateStateSpace = CopyFromBuiltinsEstimateSpace,
+	.CopyFromProcessOneOption = NULL,
 	.CopyFromInFunc = CopyFromTextLikeInFunc,
 	.CopyFromStart = CopyFromTextLikeStart,
 	.CopyFromOneRow = CopyFromCSVOneRow,
@@ -150,6 +152,7 @@ static const CopyFromRoutine CopyFromRoutineCSV = {
 /* binary format */
 static const CopyFromRoutine CopyFromRoutineBinary = {
 	.CopyFromEstimateStateSpace = CopyFromBuiltinsEstimateSpace,
+	.CopyFromProcessOneOption = NULL,
 	.CopyFromInFunc = CopyFromBinaryInFunc,
 	.CopyFromStart = CopyFromBinaryStart,
 	.CopyFromOneRow = CopyFromBinaryOneRow,
@@ -2048,10 +2051,14 @@ ProcessCopyFromOptions(CopyFromState cstate, List *options, ParseState *pstate)
 
 	foreach_node(DefElem, option, other_options)
 	{
-		ereport(ERROR,
-				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
-				 errmsg("COPY format \"%s\" not recognized", option->defname),
-				 parser_errposition(pstate, option->location)));
+		if (cstate->routine->CopyFromProcessOneOption &&
+			cstate->routine->CopyFromProcessOneOption(cstate, option))
+			 /* custom option is processed */ ;
+		else
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+					 errmsg("COPY format \"%s\" not recognized", option->defname),
+					 parser_errposition(pstate, option->location)));
 	}
 
 	if (temp_state)
diff --git a/src/backend/commands/copyto.c b/src/backend/commands/copyto.c
index ea31fa911f9..9136098a0ed 100644
--- a/src/backend/commands/copyto.c
+++ b/src/backend/commands/copyto.c
@@ -122,6 +122,7 @@ static void CopySendInt16(CopyToState cstate, int16 val);
 /* text format */
 static const CopyToRoutine CopyToRoutineText = {
 	.CopyToEstimateStateSpace = CopyToEstimateStateTextLike,
+	.CopyToProcessOneOption = NULL,
 	.CopyToStart = CopyToTextLikeStart,
 	.CopyToOutFunc = CopyToTextLikeOutFunc,
 	.CopyToOneRow = CopyToTextOneRow,
@@ -131,6 +132,7 @@ static const CopyToRoutine CopyToRoutineText = {
 /* CSV format */
 static const CopyToRoutine CopyToRoutineCSV = {
 	.CopyToEstimateStateSpace = CopyToEstimateStateTextLike,
+	.CopyToProcessOneOption = NULL,
 	.CopyToStart = CopyToTextLikeStart,
 	.CopyToOutFunc = CopyToTextLikeOutFunc,
 	.CopyToOneRow = CopyToCSVOneRow,
@@ -140,6 +142,7 @@ static const CopyToRoutine CopyToRoutineCSV = {
 /* binary format */
 static const CopyToRoutine CopyToRoutineBinary = {
 	.CopyToEstimateStateSpace = CopyToEstimateStateBinary,
+	.CopyToProcessOneOption = NULL,
 	.CopyToStart = CopyToBinaryStart,
 	.CopyToOutFunc = CopyToBinaryOutFunc,
 	.CopyToOneRow = CopyToBinaryOneRow,
@@ -1589,12 +1592,16 @@ ProcessCopyToOptions(CopyToState cstate, List *options, ParseState *pstate)
 
 	ProcessCopyBuiltinOptions(options, opts, false, &other_options, pstate);
 
-	foreach_node(DefElem, option, options)
+	foreach_node(DefElem, option, other_options)
 	{
-		ereport(ERROR,
-				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
-				 errmsg("COPY format \"%s\" not recognized", option->defname),
-				 parser_errposition(pstate, option->location)));
+		if (cstate->routine->CopyToProcessOneOption &&
+			cstate->routine->CopyToProcessOneOption(cstate, option))
+			 /* custom option is processed */ ;
+		else
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+					 errmsg("COPY format \"%s\" not recognized", option->defname),
+					 parser_errposition(pstate, option->location)));
 	}
 
 	if (temp_state)
diff --git a/src/include/commands/copyapi.h b/src/include/commands/copyapi.h
index 2090a4e1c61..e19279d0c3d 100644
--- a/src/include/commands/copyapi.h
+++ b/src/include/commands/copyapi.h
@@ -27,6 +27,14 @@ typedef struct CopyToRoutine
 	 */
 	Size		(*CopyToEstimateStateSpace) (void);
 
+	/*
+	 * Process one COPY TO option. Return true if the option is processed,
+	 * otherwise return false.
+	 *
+	 * This is an optional callback.
+	 */
+	bool		(*CopyToProcessOneOption) (CopyToState cstate, DefElem *option);
+
 	/*
 	 * Set output function information. This callback is called once at the
 	 * beginning of COPY TO.
@@ -70,6 +78,14 @@ typedef struct CopyFromRoutine
 	 */
 	Size		(*CopyFromEstimateStateSpace) (void);
 
+	/*
+	 * Process one COPY FROM option. Return true if the option is processed,
+	 * otherwise return false.
+	 *
+	 * This is an optional callback.
+	 */
+	bool		(*CopyFromProcessOneOption) (CopyFromState cstate, DefElem *option);
+
 	/*
 	 * Set input function information. This callback is called once at the
 	 * beginning of COPY FROM.
@@ -112,7 +128,7 @@ typedef struct CopyFromRoutine
 	void		(*CopyFromEnd) (CopyFromState cstate);
 } CopyFromRoutine;
 
-extern int CopyFromGetData(CopyFromState cstate, void *databuf, int minread, int maxread);
+extern int	CopyFromGetData(CopyFromState cstate, void *databuf, int minread, int maxread);
 extern void CopyToFlushData(CopyToState cstate);
 
 extern void RegisterCopyCustomFormat(const char *fmt_name, const CopyFromRoutine *from_routine,
-- 
2.47.3

