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,