From d2d530680a895235c6edaadf48ae67a08c64dfe5 Mon Sep 17 00:00:00 2001
From: TatsuyaKawata <kawatatatsuya0913@gmail.com>
Date: Tue, 25 Nov 2025 01:00:42 +0900
Subject: [PATCH v1] Adding TRIM_SPACE option to COPY

---
 src/backend/commands/copy.c          | 13 +++++++
 src/backend/commands/copyfromparse.c | 56 ++++++++++++++++++++++++++--
 src/include/commands/copy.h          |  1 +
 3 files changed, 67 insertions(+), 3 deletions(-)

diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 28e878c3688..be1b3981ca0 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -557,6 +557,7 @@ ProcessCopyOptions(ParseState *pstate,
 	bool		on_error_specified = false;
 	bool		log_verbosity_specified = false;
 	bool		reject_limit_specified = false;
+	bool		trim_space_specified = false;
 	ListCell   *option;
 
 	/* Support external use for option sanity checking */
@@ -730,6 +731,13 @@ ProcessCopyOptions(ParseState *pstate,
 			reject_limit_specified = true;
 			opts_out->reject_limit = defGetCopyRejectLimitOption(defel);
 		}
+		else if (strcmp(defel->defname, "trim_space") == 0)
+		{
+			if (trim_space_specified)
+				errorConflictingDefElem(defel, pstate);
+			trim_space_specified = true;
+			opts_out->trim_space = defGetBoolean(defel);
+		}
 		else
 			ereport(ERROR,
 					(errcode(ERRCODE_SYNTAX_ERROR),
@@ -758,6 +766,11 @@ ProcessCopyOptions(ParseState *pstate,
 				(errcode(ERRCODE_SYNTAX_ERROR),
 				 errmsg("cannot specify %s in BINARY mode", "DEFAULT")));
 
+	if (opts_out->binary && opts_out->trim_space)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("cannot specify %s in BINARY mode", "TRIM_SPACE")));
+
 	/* Set defaults for omitted options */
 	if (!opts_out->delim)
 		opts_out->delim = opts_out->csv_mode ? "," : "\t";
diff --git a/src/backend/commands/copyfromparse.c b/src/backend/commands/copyfromparse.c
index a09e7fbace3..9417c1f9e21 100644
--- a/src/backend/commands/copyfromparse.c
+++ b/src/backend/commands/copyfromparse.c
@@ -1798,6 +1798,30 @@ CopyReadAttributesText(CopyFromState cstate)
 
 				pg_verifymbstr(fld, output_ptr - fld, false);
 			}
+
+			/* Apply TRIM_SPACE if requested */
+			if (cstate->opts.trim_space)
+			{
+				char	   *fld = cstate->raw_fields[fieldno];
+				char	   *start = fld;
+				char	   *end = output_ptr;
+
+				/* Trim leading spaces */
+				while (start < end && *start == ' ')
+					start++;
+
+				/* Trim trailing spaces */
+				while (end > start && *(end - 1) == ' ')
+					end--;
+
+				/* Move trimmed string to the beginning if needed */
+				if (start > fld)
+				{
+					memmove(fld, start, end - start);
+				}
+
+				output_ptr = fld + (end - start);
+			}
 		}
 
 		/* Terminate attribute value in output area */
@@ -1965,9 +1989,6 @@ CopyReadAttributesCSV(CopyFromState cstate)
 		}
 endfield:
 
-		/* Terminate attribute value in output area */
-		*output_ptr++ = '\0';
-
 		/* Check whether raw input matched null marker */
 		input_len = end_ptr - start_ptr;
 		if (!saw_quote && input_len == cstate->opts.null_print_len &&
@@ -1999,6 +2020,35 @@ endfield:
 								   NameStr(att->attname))));
 			}
 		}
+		else
+		{
+			/* Apply TRIM_SPACE if requested */
+			if (cstate->opts.trim_space)
+			{
+				char	   *fld = cstate->raw_fields[fieldno];
+				char	   *start = fld;
+				char	   *end = output_ptr;
+
+				/* Trim leading spaces */
+				while (start < end && *start == ' ')
+					start++;
+
+				/* Trim trailing spaces */
+				while (end > start && *(end - 1) == ' ')
+					end--;
+
+				/* Move trimmed string to the beginning if needed */
+				if (start > fld)
+				{
+					memmove(fld, start, end - start);
+				}
+
+				output_ptr = fld + (end - start);
+			}
+		}
+
+		/* Terminate attribute value in output area */
+		*output_ptr++ = '\0';
 
 		fieldno++;
 		/* Done if we hit EOL instead of a delim */
diff --git a/src/include/commands/copy.h b/src/include/commands/copy.h
index 541176e1980..1769a193789 100644
--- a/src/include/commands/copy.h
+++ b/src/include/commands/copy.h
@@ -85,6 +85,7 @@ typedef struct CopyFormatOptions
 	CopyLogVerbosityChoice log_verbosity;	/* verbosity of logged messages */
 	int64		reject_limit;	/* maximum tolerable number of errors */
 	List	   *convert_select; /* list of column names (can be NIL) */
+	bool		trim_space;		/* trim leading/trailing spaces? */
 } CopyFormatOptions;
 
 /* These are private in commands/copy[from|to].c */
-- 
2.34.1

