Re: Make COPY format extendable: Extract COPY TO format implementations

Поиск
Список
Период
Сортировка
От Sutou Kouhei
Тема Re: Make COPY format extendable: Extract COPY TO format implementations
Дата
Msg-id 20251003.160650.355943655010493993.kou@clear-code.com
обсуждение исходный текст
Ответ на Re: Make COPY format extendable: Extract COPY TO format implementations  (Masahiko Sawada <sawada.mshk@gmail.com>)
Список pgsql-hackers
Hi,

In <CAD21AoADXWgdizS0mV5w8wdfftDRsm8sUtNW=CzYYS1OhjFD2A@mail.gmail.com>
  "Re: Make COPY format extendable: Extract COPY TO format implementations" on Mon, 15 Sep 2025 10:00:18 -0700,
  Masahiko Sawada <sawada.mshk@gmail.com> wrote:

>> > I think we can use a local variable of CopyFormatOptions and memcpy it
>> > to the opts of the returned cstate.
>>
>> It'll work too. Can we proceed this proposal with this
>> approach? Should we collect more opinions before we proceed?
>> If so, Could you add key people in this area to Cc to hear
>> their opinions?
> 
> Since we don't have a single decision-maker, we should proceed through
> consensus-building and careful evaluation of each approach. I see that
> several senior hackers are already included in this thread, which is
> excellent. Since you and I, who have been involved in these
> discussions, agreed with this approach, I believe we can proceed with
> this direction. If anyone proposes alternative solutions that we find
> more compelling, we might have to change the approach.

OK. There is no objection for now.

How about the attached patch? The patch uses the approach
only for CopyToStateData. If this looks good, I can do it
for CopyFromStateData too.

This patch splits CopyToStateData to

* CopyToStateData
* CopyToStateInternalData
* CopyToStateBuiltinData

structs.

This is based on the category described in

https://www.postgresql.org/message-id/flat/CAD21AoBa0Wm3C2H12jaqkvLidP2zEhsC%2Bgf%3D3w7XiA4LQnvx0g%40mail.gmail.com#85cb988b0bec243d1e8dce699e02e009
:

> 1. fields used only the core (not by format callback)
> 2. fields used by both the core and format callbacks
> 3. built-in format specific fields (mostly for text and csv)

CopyToStateInternalData is for 1.
CopyToStateData is for 2.
CopyToStateBuiltinData is for 3.


This patch adds CopyToRoutine::CopyToGetStateSize() that
returns size of state struct for the routine. For example,
Built-in formats use sizeof(CopyToStateBuiltinData) for it.

BeginCopyTo() allocates sizeof(CopyToStateInternalData) +
CopyToGetStateSize() size continuous memory and uses the
front part as CopyToStateInternalData and the back part as
CopyToStateData/CopyToStateBuilinData.


Thanks,
-- 
kou
From 20539ad10512ef45785c4bd70b93f94eec1125ba Mon Sep 17 00:00:00 2001
From: Sutou Kouhei <kou@clear-code.com>
Date: Fri, 3 Oct 2025 15:23:01 +0900
Subject: [PATCH] Split CopyToStateData to CopyToState{,Internal,Builtin}Data

---
 src/backend/commands/copyto.c  | 282 ++++++++++++++++++---------------
 src/include/commands/copyapi.h |  53 +++++++
 2 files changed, 211 insertions(+), 124 deletions(-)

diff --git a/src/backend/commands/copyto.c b/src/backend/commands/copyto.c
index 67b94b91cae..30298c0df0c 100644
--- a/src/backend/commands/copyto.c
+++ b/src/backend/commands/copyto.c
@@ -37,19 +37,36 @@
 #include "utils/snapmgr.h"
 
 /*
- * Represents the different dest cases we need to worry about at
- * the bottom level
+ * This struct contains the state variables used internally. All COPY TO
+ * routines including built-in format routines should not use this.
  */
-typedef enum CopyDest
+typedef struct CopyToStateInternalData
 {
-    COPY_FILE,                    /* to file (or a piped program) */
-    COPY_FRONTEND,                /* to frontend */
-    COPY_CALLBACK,                /* to callback function */
-} CopyDest;
+    /* format-specific routines */
+    const CopyToRoutine *routine;
+
+    /* parameters from the COPY command */
+    QueryDesc  *queryDesc;        /* executable query to copy from */
+    char       *filename;        /* filename, or NULL for STDOUT */
+    bool        is_program;        /* is 'filename' a program to popen? */
+
+    Node       *whereClause;    /* WHERE condition (or NULL) */
+
+    /*
+     * Working state
+     */
+    MemoryContext copycontext;    /* per-copy execution context */
+    MemoryContext rowcontext;    /* per-row evaluation context */
+}            CopyToStateInternalData;
+typedef struct CopyToStateInternalData *CopyToStateInternal;
+
+#define CopyToStateInternalGetState(cstate_internal) \
+    ((CopyToState) (((char *) cstate_internal) + sizeof(CopyToStateInternalData)))
+#define CopyToStateGetInternal(cstate) \
+    ((CopyToStateInternal) (((char *) cstate) - sizeof(CopyToStateInternalData)))
 
 /*
- * This struct contains all the state variables used throughout a COPY TO
- * operation.
+ * This struct contains the state variables used by built-in format routines.
  *
  * 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
@@ -62,40 +79,16 @@ 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 CopyToStateBuiltinData
 {
-    /* format-specific routines */
-    const CopyToRoutine *routine;
+    CopyToStateData parent;
 
     /* 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 */
-
     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) */
-
-    /*
-     * 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;
+}            CopyToStateBuiltinData;
+typedef struct CopyToStateBuiltinData *CopyToStateBuiltin;
 
 /* DestReceiver for COPY (query) TO */
 typedef struct
@@ -118,6 +111,7 @@ static void CopyAttributeOutCSV(CopyToState cstate, const char *string,
                                 bool use_quote);
 
 /* built-in format-specific routines */
+static size_t CopyToBuiltinGetStateSize(void);
 static void CopyToTextLikeStart(CopyToState cstate, TupleDesc tupDesc);
 static void CopyToTextLikeOutFunc(CopyToState cstate, Oid atttypid, FmgrInfo *finfo);
 static void CopyToTextOneRow(CopyToState cstate, TupleTableSlot *slot);
@@ -150,6 +144,7 @@ static void CopySendInt16(CopyToState cstate, int16 val);
 
 /* text format */
 static const CopyToRoutine CopyToRoutineText = {
+    .CopyToGetStateSize = CopyToBuiltinGetStateSize,
     .CopyToStart = CopyToTextLikeStart,
     .CopyToOutFunc = CopyToTextLikeOutFunc,
     .CopyToOneRow = CopyToTextOneRow,
@@ -158,6 +153,7 @@ static const CopyToRoutine CopyToRoutineText = {
 
 /* CSV format */
 static const CopyToRoutine CopyToRoutineCSV = {
+    .CopyToGetStateSize = CopyToBuiltinGetStateSize,
     .CopyToStart = CopyToTextLikeStart,
     .CopyToOutFunc = CopyToTextLikeOutFunc,
     .CopyToOneRow = CopyToCSVOneRow,
@@ -166,6 +162,7 @@ static const CopyToRoutine CopyToRoutineCSV = {
 
 /* binary format */
 static const CopyToRoutine CopyToRoutineBinary = {
+    .CopyToGetStateSize = CopyToBuiltinGetStateSize,
     .CopyToStart = CopyToBinaryStart,
     .CopyToOutFunc = CopyToBinaryOutFunc,
     .CopyToOneRow = CopyToBinaryOneRow,
@@ -185,18 +182,27 @@ CopyToGetRoutine(const CopyFormatOptions *opts)
     return &CopyToRoutineText;
 }
 
+/* Implementation of the allocate callback for all built-in formats */
+static size_t
+CopyToBuiltinGetStateSize(void)
+{
+    return sizeof(CopyToStateBuiltinData);
+}
+
 /* Implementation of the start callback for text and CSV formats */
 static void
 CopyToTextLikeStart(CopyToState cstate, TupleDesc tupDesc)
 {
+    CopyToStateBuiltin cstate_builtin = (CopyToStateBuiltin) cstate;
+
     /*
      * 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)
+    if (cstate_builtin->need_transcoding)
         cstate->opts.null_print_client = pg_server_to_any(cstate->opts.null_print,
                                                           cstate->opts.null_print_len,
-                                                          cstate->file_encoding);
+                                                          cstate_builtin->file_encoding);
 
     /* if a header has been requested send the line */
     if (cstate->opts.header_line == COPY_HEADER_TRUE)
@@ -401,7 +407,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
@@ -444,16 +450,17 @@ CopySendChar(CopyToState cstate, char c)
 static void
 CopySendEndOfRow(CopyToState cstate)
 {
+    CopyToStateInternal cstate_internal = CopyToStateGetInternal(cstate);
     StringInfo    fe_msgbuf = cstate->fe_msgbuf;
 
     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))
             {
-                if (cstate->is_program)
+                if (cstate_internal->is_program)
                 {
                     if (errno == EPIPE)
                     {
@@ -482,11 +489,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;
     }
@@ -507,7 +514,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');
@@ -515,7 +522,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;
@@ -561,9 +568,10 @@ CopySendInt16(CopyToState cstate, int16 val)
 static void
 ClosePipeToProgram(CopyToState cstate)
 {
+    CopyToStateInternal cstate_internal = CopyToStateGetInternal(cstate);
     int            pclose_rc;
 
-    Assert(cstate->is_program);
+    Assert(cstate_internal->is_program);
 
     pclose_rc = ClosePipeStream(cstate->copy_file);
     if (pclose_rc == -1)
@@ -575,7 +583,7 @@ ClosePipeToProgram(CopyToState cstate)
         ereport(ERROR,
                 (errcode(ERRCODE_EXTERNAL_ROUTINE_EXCEPTION),
                  errmsg("program \"%s\" failed",
-                        cstate->filename),
+                        cstate_internal->filename),
                  errdetail_internal("%s", wait_result_to_str(pclose_rc))));
     }
 }
@@ -586,23 +594,25 @@ ClosePipeToProgram(CopyToState cstate)
 static void
 EndCopy(CopyToState cstate)
 {
-    if (cstate->is_program)
+    CopyToStateInternal cstate_internal = CopyToStateGetInternal(cstate);
+
+    if (cstate_internal->is_program)
     {
         ClosePipeToProgram(cstate);
     }
     else
     {
-        if (cstate->filename != NULL && FreeFile(cstate->copy_file))
+        if (cstate_internal->filename != NULL && FreeFile(cstate->copy_file))
             ereport(ERROR,
                     (errcode_for_file_access(),
                      errmsg("could not close file \"%s\": %m",
-                            cstate->filename)));
+                            cstate_internal->filename)));
     }
 
     pgstat_progress_end_command();
 
-    MemoryContextDelete(cstate->copycontext);
-    pfree(cstate);
+    MemoryContextDelete(cstate_internal->copycontext);
+    pfree(CopyToStateGetInternal(cstate));
 }
 
 /*
@@ -630,11 +640,16 @@ BeginCopyTo(ParseState *pstate,
             List *attnamelist,
             List *options)
 {
+    CopyToStateInternal cstate_internal;
     CopyToState cstate;
+    CopyToStateBuiltin cstate_builtin;
     bool        pipe = (filename == NULL && data_dest_cb == NULL);
     TupleDesc    tupDesc;
     int            num_phys_attrs;
+    MemoryContext copycontext;
     MemoryContext oldcontext;
+    CopyFormatOptions opts = {0};
+    const CopyToRoutine *routine;
     const int    progress_cols[] = {
         PROGRESS_COPY_COMMAND,
         PROGRESS_COPY_TYPE
@@ -686,24 +701,34 @@ BeginCopyTo(ParseState *pstate,
     }
 
 
-    /* Allocate workspace and zero all fields */
-    cstate = (CopyToStateData *) palloc0(sizeof(CopyToStateData));
-
     /*
      * We allocate everything used by a cstate in a new memory context. This
      * avoids memory leaks during repeated use of COPY in a query.
      */
-    cstate->copycontext = AllocSetContextCreate(CurrentMemoryContext,
-                                                "COPY",
-                                                ALLOCSET_DEFAULT_SIZES);
+    copycontext = AllocSetContextCreate(CurrentMemoryContext,
+                                        "COPY",
+                                        ALLOCSET_DEFAULT_SIZES);
 
-    oldcontext = MemoryContextSwitchTo(cstate->copycontext);
+    oldcontext = MemoryContextSwitchTo(copycontext);
 
     /* Extract options from the statement node tree */
-    ProcessCopyOptions(pstate, &cstate->opts, false /* is_from */ , options);
+    ProcessCopyOptions(pstate, &opts, false /* is_from */ , options);
 
-    /* Set format routine */
-    cstate->routine = CopyToGetRoutine(&cstate->opts);
+    /* Get format routine */
+    routine = CopyToGetRoutine(&opts);
+
+    /* Allocate workspace and set known values */
+    MemoryContextSwitchTo(oldcontext);
+    cstate_internal = (CopyToStateInternal) palloc0(sizeof(CopyToStateInternal) + routine->CopyToGetStateSize());
+    MemoryContextSwitchTo(copycontext);
+    cstate = CopyToStateInternalGetState(cstate_internal);
+    if (routine == &CopyToRoutineText || routine == &CopyToRoutineCSV || routine == &CopyToRoutineBinary)
+        cstate_builtin = (CopyToStateBuiltin) cstate;
+    else
+        cstate_builtin = NULL;
+    cstate_internal->copycontext = copycontext;
+    cstate->opts = opts;
+    cstate_internal->routine = routine;
 
     /* Process the source/target relation or query */
     if (rel)
@@ -835,19 +860,19 @@ BeginCopyTo(ParseState *pstate,
         ((DR_copy *) dest)->cstate = cstate;
 
         /* Create a QueryDesc requesting no output */
-        cstate->queryDesc = CreateQueryDesc(plan, pstate->p_sourcetext,
-                                            GetActiveSnapshot(),
-                                            InvalidSnapshot,
-                                            dest, NULL, NULL, 0);
+        cstate_internal->queryDesc = CreateQueryDesc(plan, pstate->p_sourcetext,
+                                                     GetActiveSnapshot(),
+                                                     InvalidSnapshot,
+                                                     dest, NULL, NULL, 0);
 
         /*
          * Call ExecutorStart to prepare the plan for execution.
          *
          * ExecutorStart computes a result tupdesc for us
          */
-        ExecutorStart(cstate->queryDesc, 0);
+        ExecutorStart(cstate_internal->queryDesc, 0);
 
-        tupDesc = cstate->queryDesc->tupDesc;
+        tupDesc = cstate_internal->queryDesc->tupDesc;
     }
 
     /* Generate or convert list of attributes to process */
@@ -883,31 +908,34 @@ 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;
+    if (cstate_builtin)
+    {
+        /* Use client encoding when ENCODING option is not specified. */
+        if (cstate->opts.file_encoding < 0)
+            cstate_builtin->file_encoding = pg_get_client_encoding();
+        else
+            cstate_builtin->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;
+        /*
+         * Set up encoding conversion info if the file and server encodings
+         * differ (see also pg_server_to_any).
+         */
+        if (cstate_builtin->file_encoding == GetDatabaseEncoding() ||
+            cstate_builtin->file_encoding == PG_SQL_ASCII)
+            cstate_builtin->need_transcoding = false;
+        else
+            cstate_builtin->need_transcoding = true;
 
-    /* See Multibyte encoding comment above */
-    cstate->encoding_embeds_ascii = PG_ENCODING_IS_CLIENT_ONLY(cstate->file_encoding);
+        /* See Multibyte encoding comment above */
+        cstate_builtin->encoding_embeds_ascii = PG_ENCODING_IS_CLIENT_ONLY(cstate_builtin->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)
@@ -920,18 +948,18 @@ BeginCopyTo(ParseState *pstate,
     }
     else
     {
-        cstate->filename = pstrdup(filename);
-        cstate->is_program = is_program;
+        cstate_internal->filename = pstrdup(filename);
+        cstate_internal->is_program = is_program;
 
         if (is_program)
         {
             progress_vals[1] = PROGRESS_COPY_TYPE_PROGRAM;
-            cstate->copy_file = OpenPipeStream(cstate->filename, PG_BINARY_W);
+            cstate->copy_file = OpenPipeStream(cstate_internal->filename, PG_BINARY_W);
             if (cstate->copy_file == NULL)
                 ereport(ERROR,
                         (errcode_for_file_access(),
                          errmsg("could not execute command \"%s\": %m",
-                                cstate->filename)));
+                                cstate_internal->filename)));
         }
         else
         {
@@ -952,7 +980,7 @@ BeginCopyTo(ParseState *pstate,
             oumask = umask(S_IWGRP | S_IWOTH);
             PG_TRY();
             {
-                cstate->copy_file = AllocateFile(cstate->filename, PG_BINARY_W);
+                cstate->copy_file = AllocateFile(cstate_internal->filename, PG_BINARY_W);
             }
             PG_FINALLY();
             {
@@ -967,7 +995,7 @@ BeginCopyTo(ParseState *pstate,
                 ereport(ERROR,
                         (errcode_for_file_access(),
                          errmsg("could not open file \"%s\" for writing: %m",
-                                cstate->filename),
+                                cstate_internal->filename),
                          (save_errno == ENOENT || save_errno == EACCES) ?
                          errhint("COPY TO instructs the PostgreSQL server process to write a file. "
                                  "You may want a client-side facility such as psql's \\copy.") : 0));
@@ -977,12 +1005,12 @@ BeginCopyTo(ParseState *pstate,
                 ereport(ERROR,
                         (errcode_for_file_access(),
                          errmsg("could not stat file \"%s\": %m",
-                                cstate->filename)));
+                                cstate_internal->filename)));
 
             if (S_ISDIR(st.st_mode))
                 ereport(ERROR,
                         (errcode(ERRCODE_WRONG_OBJECT_TYPE),
-                         errmsg("\"%s\" is a directory", cstate->filename)));
+                         errmsg("\"%s\" is a directory", cstate_internal->filename)));
         }
     }
 
@@ -1004,12 +1032,14 @@ BeginCopyTo(ParseState *pstate,
 void
 EndCopyTo(CopyToState cstate)
 {
-    if (cstate->queryDesc != NULL)
+    CopyToStateInternal cstate_internal = CopyToStateGetInternal(cstate);
+
+    if (cstate_internal->queryDesc != NULL)
     {
         /* Close down the query and free resources. */
-        ExecutorFinish(cstate->queryDesc);
-        ExecutorEnd(cstate->queryDesc);
-        FreeQueryDesc(cstate->queryDesc);
+        ExecutorFinish(cstate_internal->queryDesc);
+        ExecutorEnd(cstate_internal->queryDesc);
+        FreeQueryDesc(cstate_internal->queryDesc);
         PopActiveSnapshot();
     }
 
@@ -1025,7 +1055,8 @@ EndCopyTo(CopyToState cstate)
 uint64
 DoCopyTo(CopyToState cstate)
 {
-    bool        pipe = (cstate->filename == NULL && cstate->data_dest_cb == NULL);
+    CopyToStateInternal cstate_internal = CopyToStateGetInternal(cstate);
+    bool        pipe = (cstate_internal->filename == NULL && cstate->data_dest_cb == NULL);
     bool        fe_copy = (pipe && whereToSendOutput == DestRemote);
     TupleDesc    tupDesc;
     int            num_phys_attrs;
@@ -1038,7 +1069,7 @@ DoCopyTo(CopyToState cstate)
     if (cstate->rel)
         tupDesc = RelationGetDescr(cstate->rel);
     else
-        tupDesc = cstate->queryDesc->tupDesc;
+        tupDesc = cstate_internal->queryDesc->tupDesc;
     num_phys_attrs = tupDesc->natts;
     cstate->opts.null_print_client = cstate->opts.null_print;    /* default */
 
@@ -1052,8 +1083,8 @@ DoCopyTo(CopyToState cstate)
         int            attnum = lfirst_int(cur);
         Form_pg_attribute attr = TupleDescAttr(tupDesc, attnum - 1);
 
-        cstate->routine->CopyToOutFunc(cstate, attr->atttypid,
-                                       &cstate->out_functions[attnum - 1]);
+        cstate_internal->routine->CopyToOutFunc(cstate, attr->atttypid,
+                                                &cstate->out_functions[attnum - 1]);
     }
 
     /*
@@ -1062,11 +1093,11 @@ DoCopyTo(CopyToState cstate)
      * datatype output routines, and should be faster than retail pfree's
      * anyway.  (We don't need a whole econtext as CopyFrom does.)
      */
-    cstate->rowcontext = AllocSetContextCreate(CurrentMemoryContext,
-                                               "COPY TO",
-                                               ALLOCSET_DEFAULT_SIZES);
+    cstate_internal->rowcontext = AllocSetContextCreate(CurrentMemoryContext,
+                                                        "COPY TO",
+                                                        ALLOCSET_DEFAULT_SIZES);
 
-    cstate->routine->CopyToStart(cstate, tupDesc);
+    cstate_internal->routine->CopyToStart(cstate, tupDesc);
 
     if (cstate->rel)
     {
@@ -1101,13 +1132,13 @@ DoCopyTo(CopyToState cstate)
     else
     {
         /* run the plan --- the dest receiver will send tuples */
-        ExecutorRun(cstate->queryDesc, ForwardScanDirection, 0);
-        processed = ((DR_copy *) cstate->queryDesc->dest)->processed;
+        ExecutorRun(cstate_internal->queryDesc, ForwardScanDirection, 0);
+        processed = ((DR_copy *) cstate_internal->queryDesc->dest)->processed;
     }
 
-    cstate->routine->CopyToEnd(cstate);
+    cstate_internal->routine->CopyToEnd(cstate);
 
-    MemoryContextDelete(cstate->rowcontext);
+    MemoryContextDelete(cstate_internal->rowcontext);
 
     if (fe_copy)
         SendCopyEnd(cstate);
@@ -1121,15 +1152,16 @@ DoCopyTo(CopyToState cstate)
 static inline void
 CopyOneRowTo(CopyToState cstate, TupleTableSlot *slot)
 {
+    CopyToStateInternal cstate_internal = CopyToStateGetInternal(cstate);
     MemoryContext oldcontext;
 
-    MemoryContextReset(cstate->rowcontext);
-    oldcontext = MemoryContextSwitchTo(cstate->rowcontext);
+    MemoryContextReset(cstate_internal->rowcontext);
+    oldcontext = MemoryContextSwitchTo(cstate_internal->rowcontext);
 
     /* Make sure the tuple is fully deconstructed */
     slot_getallattrs(slot);
 
-    cstate->routine->CopyToOneRow(cstate, slot);
+    cstate_internal->routine->CopyToOneRow(cstate, slot);
 
     MemoryContextSwitchTo(oldcontext);
 }
@@ -1146,13 +1178,14 @@ CopyOneRowTo(CopyToState cstate, TupleTableSlot *slot)
 static void
 CopyAttributeOutText(CopyToState cstate, const char *string)
 {
+    CopyToStateBuiltin cstate_builtin = (CopyToStateBuiltin) cstate;
     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 (cstate_builtin->need_transcoding)
+        ptr = pg_server_to_any(string, strlen(string), cstate_builtin->file_encoding);
     else
         ptr = string;
 
@@ -1170,7 +1203,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 (cstate_builtin->encoding_embeds_ascii)
     {
         start = ptr;
         while ((c = *ptr) != '\0')
@@ -1225,7 +1258,7 @@ CopyAttributeOutText(CopyToState cstate, const char *string)
                 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(cstate_builtin->file_encoding, ptr);
             else
                 ptr++;
         }
@@ -1300,6 +1333,7 @@ static void
 CopyAttributeOutCSV(CopyToState cstate, const char *string,
                     bool use_quote)
 {
+    CopyToStateBuiltin cstate_builtin = (CopyToStateBuiltin) cstate;
     const char *ptr;
     const char *start;
     char        c;
@@ -1312,8 +1346,8 @@ CopyAttributeOutCSV(CopyToState cstate, const char *string,
     if (!use_quote && strcmp(string, cstate->opts.null_print) == 0)
         use_quote = true;
 
-    if (cstate->need_transcoding)
-        ptr = pg_server_to_any(string, strlen(string), cstate->file_encoding);
+    if (cstate_builtin->need_transcoding)
+        ptr = pg_server_to_any(string, strlen(string), cstate_builtin->file_encoding);
     else
         ptr = string;
 
@@ -1342,8 +1376,8 @@ CopyAttributeOutCSV(CopyToState cstate, const char *string,
                     use_quote = true;
                     break;
                 }
-                if (IS_HIGHBIT_SET(c) && cstate->encoding_embeds_ascii)
-                    tptr += pg_encoding_mblen(cstate->file_encoding, tptr);
+                if (IS_HIGHBIT_SET(c) && cstate_builtin->encoding_embeds_ascii)
+                    tptr += pg_encoding_mblen(cstate_builtin->file_encoding, tptr);
                 else
                     tptr++;
             }
@@ -1366,8 +1400,8 @@ CopyAttributeOutCSV(CopyToState cstate, const char *string,
                 CopySendChar(cstate, escapec);
                 start = ptr;    /* we include char in next run */
             }
-            if (IS_HIGHBIT_SET(c) && cstate->encoding_embeds_ascii)
-                ptr += pg_encoding_mblen(cstate->file_encoding, ptr);
+            if (IS_HIGHBIT_SET(c) && cstate_builtin->encoding_embeds_ascii)
+                ptr += pg_encoding_mblen(cstate_builtin->file_encoding, ptr);
             else
                 ptr++;
         }
diff --git a/src/include/commands/copyapi.h b/src/include/commands/copyapi.h
index 2a2d2f9876b..7c536c74a18 100644
--- a/src/include/commands/copyapi.h
+++ b/src/include/commands/copyapi.h
@@ -16,12 +16,65 @@
 
 #include "commands/copy.h"
 
+/*
+ * Represents the different dest cases we need to worry about at
+ * the bottom level
+ */
+typedef enum CopyDest
+{
+    COPY_DEST_FILE,                /* to file (or a piped program) */
+    COPY_DEST_FRONTEND,            /* to frontend */
+    COPY_DEST_CALLBACK,            /* to callback function */
+} CopyDest;
+
+/*
+ * This struct contains the state variables used by PostgreSQL, built-in
+ * format routines and custom format routines.
+ */
+typedef struct CopyToStateData
+{
+    /* parameters from the COPY command */
+    Relation    rel;            /* relation to copy to */
+    List       *attnumlist;        /* integer list of attnums to copy */
+    copy_data_dest_cb data_dest_cb; /* function for writing data */
+    CopyFormatOptions opts;
+    FmgrInfo   *out_functions;    /* lookup info for output functions */
+
+    /* 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 */
+
+    /*
+     * Working state
+     */
+    uint64        bytes_processed;    /* number of bytes processed so far */
+} CopyToStateData;
+
 /*
  * API structure for a COPY TO format implementation. Note this must be
  * allocated in a server-lifetime manner, typically as a static const struct.
  */
 typedef struct CopyToRoutine
 {
+    /*
+     * Return state size for this routine.
+     *
+     * If this routine uses CopyToStateData as-is, `return
+     * sizeof(CopyToStateData)` can be used.
+     *
+     * If this routine needs additional data than CopyToStateData, a new
+     * struct based on CopyToStateData can be used something like:
+     *
+     * typedef struct MyCopyToStateDate {
+     *     struct CopyToStateData parent;
+     *     int define_additional_members_here;
+     * } MyCopyToStateData;
+     *
+     * In the case, this callback returns `sizeof(MyCopyToStateData)`.
+     */
+    size_t        (*CopyToGetStateSize) (void);
+
     /*
      * Set output function information. This callback is called once at the
      * beginning of COPY TO.
-- 
2.51.0


В списке pgsql-hackers по дате отправления: