On 2026-03-08 Su 11:48 PM, jian he wrote:
On Mon, Mar 9, 2026 at 3:44 AM Andrew Dunstan<[email protected]> wrote:
Hmm. But should we be scribbling on slot->tts_tupleDescriptor like that?
How about something like this?:

-        * Full table or query without column list.  Ensure the slot uses
-        * cstate->tupDesc so that the datum is stamped with the right type;
-        * for queries output type is RECORDOID this must be the blessed
-        * descriptor so that composite_to_json can look it up via
-        * lookup_rowtype_tupdesc.
+        * Full table or query without column list.  For queries, the slot's
+        * TupleDesc may carry RECORDOID, which is not registered in the
type
+        * cache and would cause composite_to_json's lookup_rowtype_tupdesc
+        * call to fail.  Build a HeapTuple stamped with the blessed
+        * descriptor so the type can be looked up correctly.
           */
          if (!cstate->rel && slot->tts_tupleDescriptor->tdtypeid ==
RECORDOID)
-           slot->tts_tupleDescriptor = cstate->queryDesc->tupDesc;
+       {
+           HeapTuple   tup;

-       rowdata = ExecFetchSlotHeapTupleDatum(slot);
+           tup = heap_form_tuple(cstate->tupDesc,
+                                 slot->tts_values,
+                                 slot->tts_isnull);
+           rowdata = HeapTupleGetDatum(tup);
+       }
+       else
+       {
+           rowdata = ExecFetchSlotHeapTupleDatum(slot);
+       }

This is better. I've tried to get rid of json_projvalues and json_projnulls.
Just using heap_form_tuple, but it won't work.

I incorporated the v28-0004 COPY column list into v9-0002.
With this patch set, we added four fields to the struct CopyToStateData.

+    StringInfo    json_buf;        /* reusable buffer for JSON output,
+                                 * initialized in BeginCopyTo */
+    TupleDesc    tupDesc;        /* Descriptor for JSON output; for a column
+                                 * list this is a projected descriptor */
+    Datum       *json_projvalues;    /* pre-allocated projection values, or
+                                     * NULL */
+    bool       *json_projnulls; /* pre-allocated projection nulls, or NULL */

Using the script in
https://www.postgresql.org/message-id/CACJufxFFZqxC3p4WjpTEi4riaJm%3DpADX%2Bpy0yQ0%3DRWTn5cqK3Q%40mail.gmail.com
I tested it again on macOS and Linux, and there are no regressions for
COPY TO with the TEXT and CSV formats.


OK, I think we're really close now. Here is a tiny fixup patch that fixes an error message and a comment, and adds a missing test case.


cheers


andrew

--
Andrew Dunstan
EDB:https://www.enterprisedb.com
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 84254d46a67..c11c2eb48fa 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -972,8 +972,8 @@ ProcessCopyOptions(ParseState *pstate,
        /* Check json format */
        if (opts_out->format == COPY_FORMAT_JSON && is_from)
                ereport(ERROR,
-                               errcode(ERRCODE_INVALID_PARAMETER_VALUE),
-                               errmsg("COPY %s mode cannot be used with %s", 
"JSON", "COPY FROM"));
+                               errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                               errmsg("COPY %s is not supported for %s", 
"FORMAT JSON", "COPY FROM"));
 
        if (opts_out->format != COPY_FORMAT_JSON && opts_out->force_array)
                ereport(ERROR,
diff --git a/src/backend/commands/copyto.c b/src/backend/commands/copyto.c
index 85ca7c947f3..de280ad8612 100644
--- a/src/backend/commands/copyto.c
+++ b/src/backend/commands/copyto.c
@@ -1061,8 +1061,8 @@ BeginCopyTo(ParseState *pstate,
                        cstate->tupDesc = BlessTupleDesc(resultDesc);
 
                        /*
-                        * * Pre-allocate arrays for projecting selected column 
values
-                        * into  sequential positions matching the custom 
TupleDesc.
+                        * Pre-allocate arrays for projecting selected column 
values into
+                        * sequential positions matching the custom TupleDesc.
                         */
                        cstate->json_projvalues = palloc_array(Datum, natts);
                        cstate->json_projnulls = palloc_array(bool, natts);
diff --git a/src/test/regress/expected/copy.out 
b/src/test/regress/expected/copy.out
index bcf45845b61..3e566b96183 100644
--- a/src/test/regress/expected/copy.out
+++ b/src/test/regress/expected/copy.out
@@ -136,7 +136,7 @@ LINE 1: copy copytest to stdout (format json, on_error 
ignore);
 copy copytest to stdout (format json, reject_limit 1);
 ERROR:  COPY REJECT_LIMIT requires ON_ERROR to be set to IGNORE
 copy copytest from stdin(format json);
-ERROR:  COPY JSON mode cannot be used with COPY FROM
+ERROR:  COPY FORMAT JSON is not supported for COPY FROM
 -- all of the above should yield error
 -- column list with json format
 copy copytest (style, test, filler) to stdout (format json);
@@ -167,6 +167,10 @@ copy copytest to stdout (format json, force_array false);
 {"style":"Unix","test":"abc\ndef","filler":2}
 {"style":"Mac","test":"abc\rdef","filler":3}
 {"style":"esc\\ape","test":"a\\r\\\r\\\n\\nb","filler":4}
+-- empty result set with force_array
+copy (select 1 where false) to stdout (format json, force_array true);
+[
+]
 -- column list with diverse data types
 create temp table copyjsontest_types (
     id int,
diff --git a/src/test/regress/sql/copy.sql b/src/test/regress/sql/copy.sql
index bc12ac879ef..520934271ec 100644
--- a/src/test/regress/sql/copy.sql
+++ b/src/test/regress/sql/copy.sql
@@ -119,6 +119,9 @@ copy copytest to stdout (format json, force_array);
 copy copytest(style, test) to stdout (format json, force_array true);
 copy copytest to stdout (format json, force_array false);
 
+-- empty result set with force_array
+copy (select 1 where false) to stdout (format json, force_array true);
+
 -- column list with diverse data types
 create temp table copyjsontest_types (
     id int,

Reply via email to