diff --git a/src/backend/commands/copyto.c b/src/backend/commands/copyto.c index 5850608a3fb..a187d8ec284 100644 --- a/src/backend/commands/copyto.c +++ b/src/backend/commands/copyto.c @@ -395,22 +395,38 @@ CopyToJsonOneRow(CopyToState cstate, TupleTableSlot *slot) else { /* - * 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. + * Full table or query without column list. For a query, the slot's + * descriptor is either an unregistered RECORD type, which + * composite_to_json's lookup_rowtype_tupdesc() cannot look up, or, + * when the top plan node does not project, the row type of a scanned + * table, whose column names need not match the query's. Either way, + * the datum must be stamped with the query's blessed descriptor. + * + * A virtual slot has no physical tuple, so form one directly. + * Otherwise copy the slot's tuple and stamp the copy with the query's + * descriptor. That is safe because the tuple's physical layout + * matches the query's result descriptor: a scan returns its scan + * tuple unprojected only if tlist_matches_tupdesc() holds, which + * rules out dropped columns and columns with missing values. */ - if (!cstate->rel && slot->tts_tupleDescriptor->tdtypeid == RECORDOID) - { - HeapTuple tup = heap_form_tuple(cstate->tupDesc, - slot->tts_values, - slot->tts_isnull); - - rowdata = HeapTupleGetDatum(tup); - } - else + if (cstate->rel) rowdata = ExecFetchSlotHeapTupleDatum(slot); + else if (TTS_IS_VIRTUAL(slot)) + rowdata = HeapTupleGetDatum(heap_form_tuple(cstate->tupDesc, + slot->tts_values, + slot->tts_isnull)); + else + { + HeapTuple tup; + bool shouldFree; + + Assert(slot->tts_tupleDescriptor->natts == cstate->tupDesc->natts); + + tup = ExecFetchSlotHeapTuple(slot, false, &shouldFree); + rowdata = heap_copy_tuple_as_datum(tup, cstate->tupDesc); + if (shouldFree) + heap_freetuple(tup); + } } composite_to_json(rowdata, cstate->json_buf, false); diff --git a/src/test/regress/expected/copy.out b/src/test/regress/expected/copy.out index 0af0b646921..979bef5f3f2 100644 --- a/src/test/regress/expected/copy.out +++ b/src/test/regress/expected/copy.out @@ -117,6 +117,19 @@ copy (select * from copytest) to stdout (format json); {"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} +-- keys are the query's column names, also when a scan does not project +copy (select a as x, b as y, c as z from copytest_order) to stdout (format json); +{"x":1,"y":2,"z":3} +create temp table copytest_order2 (c int, b int, a int); +insert into copytest_order2 values (4, 5, 6); +copy (select * from copytest_order union all select * from copytest_order2) + to stdout (format json); +{"a":1,"b":2,"c":3} +{"a":4,"b":5,"c":6} +-- columns with missing values force a projection +alter table copytest_order2 add column d int default 7; +copy (select * from copytest_order2) to stdout (format json); +{"c":4,"b":5,"a":6,"d":7} -- all of the following should yield error copy copytest to stdout (format json, delimiter '|'); ERROR: cannot specify DELIMITER in JSON mode diff --git a/src/test/regress/sql/copy.sql b/src/test/regress/sql/copy.sql index 14da3c5ec37..6d3923fb3f0 100644 --- a/src/test/regress/sql/copy.sql +++ b/src/test/regress/sql/copy.sql @@ -101,6 +101,16 @@ copy copytest to stdout json; copy copytest to stdout (format json); copy (select * from copytest) to stdout (format json); +-- keys are the query's column names, also when a scan does not project +copy (select a as x, b as y, c as z from copytest_order) to stdout (format json); +create temp table copytest_order2 (c int, b int, a int); +insert into copytest_order2 values (4, 5, 6); +copy (select * from copytest_order union all select * from copytest_order2) + to stdout (format json); +-- columns with missing values force a projection +alter table copytest_order2 add column d int default 7; +copy (select * from copytest_order2) to stdout (format json); + -- all of the following should yield error copy copytest to stdout (format json, delimiter '|'); copy copytest to stdout (format json, null '\N');