From 43c06a26a14c914f6bc3d72d01accd95e3b0f8e8 Mon Sep 17 00:00:00 2001
From: Masahiko Sawada <sawada.mshk@gmail.com>
Date: Mon, 22 Jun 2026 09:23:09 -0700
Subject: [PATCH v3 3/4] Add an hook for custom COPY format option validation.

Author:
Reviewed-by:
Discussion: https://postgr.es/m/
---
 src/backend/commands/copy.c    |  9 +++++++++
 src/backend/commands/copyapi.c |  4 +++-
 src/include/commands/copyapi.h | 14 +++++++++++++-
 3 files changed, 25 insertions(+), 2 deletions(-)

diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 0fda54905cd..dab523c68db 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -1119,6 +1119,15 @@ ProcessCopyOptions(ParseState *pstate,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 					 errmsg("COPY format \"%s\" cannot be used with COPY TO",
 							opts_out->custom_format_ent->name)));
+
+		/*
+		 * Let the format validate its fully-parsed options as a whole.  This
+		 * runs even when no format-specific options were given, so a format
+		 * can reject incompatible core options or enforce cross-option
+		 * constraints.
+		 */
+		if (opts_out->custom_format_ent->validate_fn != NULL)
+			opts_out->custom_format_ent->validate_fn(opts_out, is_from);
 	}
 }
 
diff --git a/src/backend/commands/copyapi.c b/src/backend/commands/copyapi.c
index f007d73e867..29fb98ff4c2 100644
--- a/src/backend/commands/copyapi.c
+++ b/src/backend/commands/copyapi.c
@@ -48,7 +48,8 @@ is_builtin_copy_format(const char *name)
  */
 void
 RegisterCopyCustomFormat(const char *name, const CopyToRoutine *to,
-						 const CopyFromRoutine *from, ProcessOneCopyOptionFn option_fn)
+						 const CopyFromRoutine *from, ProcessOneCopyOptionFn option_fn,
+						 ValidateCopyOptionsFn validate_fn)
 {
 	Assert(name != NULL && name[0] != '\0');
 
@@ -94,6 +95,7 @@ RegisterCopyCustomFormat(const char *name, const CopyToRoutine *to,
 	CopyCustomFormatArray[CopyCustomFormatsAssigned].to_routine = to;
 	CopyCustomFormatArray[CopyCustomFormatsAssigned].from_routine = from;
 	CopyCustomFormatArray[CopyCustomFormatsAssigned].option_fn = option_fn;
+	CopyCustomFormatArray[CopyCustomFormatsAssigned].validate_fn = validate_fn;
 	CopyCustomFormatsAssigned++;
 }
 
diff --git a/src/include/commands/copyapi.h b/src/include/commands/copyapi.h
index 1b323b23bba..f1924424df8 100644
--- a/src/include/commands/copyapi.h
+++ b/src/include/commands/copyapi.h
@@ -117,6 +117,15 @@ typedef struct CopyFromRoutine
 typedef bool (*ProcessOneCopyOptionFn) (CopyFormatOptions *opts, bool is_from,
 										DefElem *option);
 
+/*
+ * Optional callback to validate a custom format's fully-parsed options as a
+ * whole. Invoked once from ProcessCopyOptions() after all options have been
+ * processed, so it can enforce cross-option constraints and reject
+ * incompatible core options. It runs even when no format-specific options were
+ * supplied. Reports problems with ereport().
+ */
+typedef void (*ValidateCopyOptionsFn) (CopyFormatOptions *opts, bool is_from);
+
 /*
  * Sturct to store the registered custom format information.
  */
@@ -126,11 +135,14 @@ typedef struct CopyCustomFormatEntry
 	const CopyToRoutine *to_routine;
 	const CopyFromRoutine *from_routine;
 	ProcessOneCopyOptionFn option_fn;
+	ValidateCopyOptionsFn validate_fn;
 } CopyCustomFormatEntry;
 
 extern void RegisterCopyCustomFormat(const char *name, const CopyToRoutine *to,
 									 const CopyFromRoutine *from,
-									 ProcessOneCopyOptionFn option_fn);
+									 ProcessOneCopyOptionFn option_fn,
+									 ValidateCopyOptionsFn validate_fn);
+
 extern const CopyCustomFormatEntry *GetCopyCustomFormatRoutines(const char *name);
 
 #endif							/* COPYAPI_H */
-- 
2.54.0

