diff --git a/src/backend/access/common/tupconvert.c b/src/backend/access/common/tupconvert.c index a4012525d8..392a49b522 100644 --- a/src/backend/access/common/tupconvert.c +++ b/src/backend/access/common/tupconvert.c @@ -138,13 +138,14 @@ convert_tuples_by_position(TupleDesc indesc, nincols, noutcols))); /* - * Check to see if the map is one-to-one, in which case we need not do - * the tuple conversion. That's not enough though if either source or - * destination (tuples) contains OIDs; we'd need conversion in that case - * to inject the right OID into the tuple datum. + * Check to see if the map is one-to-one, in which case we need not do a + * tuple conversion. We must also insist that both tupdescs either + * specify or don't specify an OID column, else we need a conversion to + * add/remove space for that. (For some callers, presence or absence of + * an OID column perhaps would not really matter, but let's be safe.) */ if (indesc->natts == outdesc->natts && - !indesc->tdhasoid && !outdesc->tdhasoid) + indesc->tdhasoid == outdesc->tdhasoid) { for (i = 0; i < n; i++) { @@ -215,13 +216,14 @@ convert_tuples_by_name(TupleDesc indesc, attrMap = convert_tuples_by_name_map(indesc, outdesc, msg); /* - * Check to see if the map is one-to-one, in which case we need not do - * the tuple conversion. That's not enough though if either source or - * destination (tuples) contains OIDs; we'd need conversion in that case - * to inject the right OID into the tuple datum. + * Check to see if the map is one-to-one, in which case we need not do a + * tuple conversion. We must also insist that both tupdescs either + * specify or don't specify an OID column, else we need a conversion to + * add/remove space for that. (For some callers, presence or absence of + * an OID column perhaps would not really matter, but let's be safe.) */ if (indesc->natts == outdesc->natts && - !indesc->tdhasoid && !outdesc->tdhasoid) + indesc->tdhasoid == outdesc->tdhasoid) { same = true; for (i = 0; i < n; i++) diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c index 22eb81edad..fed0052fc6 100644 --- a/src/backend/executor/execExprInterp.c +++ b/src/backend/executor/execExprInterp.c @@ -2840,21 +2840,31 @@ ExecEvalConvertRowtype(ExprState *state, ExprEvalStep *op, ExprContext *econtext MemoryContextSwitchTo(old_cxt); } - /* - * No-op if no conversion needed (not clear this can happen here). - */ - if (op->d.convert_rowtype.map == NULL) - return; - - /* - * do_convert_tuple needs a HeapTuple not a bare HeapTupleHeader. - */ + /* Following steps need a HeapTuple not a bare HeapTupleHeader */ tmptup.t_len = HeapTupleHeaderGetDatumLength(tuple); tmptup.t_data = tuple; - result = do_convert_tuple(&tmptup, op->d.convert_rowtype.map); - - *op->resvalue = HeapTupleGetDatum(result); + if (op->d.convert_rowtype.map != NULL) + { + /* Full conversion with attribute rearrangement needed */ + result = do_convert_tuple(&tmptup, op->d.convert_rowtype.map); + /* Result already has appropriate composite-datum header fields */ + *op->resvalue = HeapTupleGetDatum(result); + } + else + { + /* + * The tuple is physically compatible as-is, but we need to insert the + * destination rowtype OID in its composite-datum header field, so we + * have to copy it anyway. heap_copy_tuple_as_datum() is convenient + * for this since it will both make the physical copy and insert the + * correct composite header fields. Note that we aren't expecting to + * have to flatten any toasted fields: the input was a composite + * datum, so it shouldn't contain any. So heap_copy_tuple_as_datum() + * is overkill here, but its check for external fields is cheap. + */ + *op->resvalue = heap_copy_tuple_as_datum(&tmptup, outdesc); + } } /* diff --git a/src/test/regress/expected/rowtypes.out b/src/test/regress/expected/rowtypes.out index 4acbc9aac8..43b36f6566 100644 --- a/src/test/regress/expected/rowtypes.out +++ b/src/test/regress/expected/rowtypes.out @@ -657,6 +657,15 @@ select row_to_json(r) from (select q2,q1 from tt1 offset 0) r; {"q2":0,"q1":0} (3 rows) +-- check no-op rowtype conversions +create temp table tt3 () inherits(tt2); +insert into tt3 values(33,44); +select row_to_json(tt3::tt2::tt1) from tt3; + row_to_json +------------------- + {"q1":33,"q2":44} +(1 row) + -- -- IS [NOT] NULL should not recurse into nested composites (bug #14235) -- diff --git a/src/test/regress/sql/rowtypes.sql b/src/test/regress/sql/rowtypes.sql index 0d9c62b486..8d63060500 100644 --- a/src/test/regress/sql/rowtypes.sql +++ b/src/test/regress/sql/rowtypes.sql @@ -287,6 +287,11 @@ create temp table tt2 () inherits(tt1); insert into tt2 values(0,0); select row_to_json(r) from (select q2,q1 from tt1 offset 0) r; +-- check no-op rowtype conversions +create temp table tt3 () inherits(tt2); +insert into tt3 values(33,44); +select row_to_json(tt3::tt2::tt1) from tt3; + -- -- IS [NOT] NULL should not recurse into nested composites (bug #14235) --