diff --git a/src/backend/access/common/tupconvert.c b/src/backend/access/common/tupconvert.c
index a4012525d8099e482004d76f3b490a9570286424..392a49b522de3d986cfbd93f364570ef6f90d7f9 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 22eb81edadfdf0f33eb72978647a65bf9e781474..fed0052fc6db59d3e7d7c235f38ce8c063a144ee 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 4acbc9aac8081b6c7e833c320a6d400ba84e4d6f..43b36f6566d3cd4634d8b1685b32c53879ccc58e 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 0d9c62b486d58867a780a73bca7c12a2cb6aaf64..8d63060500a0d80ac9c4fd8c35abcb5cacf5b9ea 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)
 --