* changed: if not NULL, *changed is set to true on any update
*
* The returned TupleDesc is not guaranteed pinned; caller must pin it
- * to use it across any operation that might incur cache invalidation.
+ * to use it across any operation that might incur cache invalidation,
+ * including for example detoasting of input tuples.
* (The TupleDesc is always refcounted, so just use IncrTupleDescRefCount.)
*
* NOTE: because composite types can change contents, we must be prepared
void
ExecEvalFieldStoreDeForm(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
{
- TupleDesc tupDesc;
-
- /* Lookup tupdesc if first time through or if type changes */
- tupDesc = get_cached_rowtype(op->d.fieldstore.fstore->resulttype, -1,
- op->d.fieldstore.rowcache, NULL);
-
- /* Check that current tupdesc doesn't have more fields than we allocated */
- if (unlikely(tupDesc->natts > op->d.fieldstore.ncolumns))
- elog(ERROR, "too many columns in composite type %u",
- op->d.fieldstore.fstore->resulttype);
-
if (*op->resnull)
{
/* Convert null input tuple into an all-nulls row */
Datum tupDatum = *op->resvalue;
HeapTupleHeader tuphdr;
HeapTupleData tmptup;
+ TupleDesc tupDesc;
tuphdr = DatumGetHeapTupleHeader(tupDatum);
tmptup.t_len = HeapTupleHeaderGetDatumLength(tuphdr);
tmptup.t_tableOid = InvalidOid;
tmptup.t_data = tuphdr;
+ /*
+ * Lookup tupdesc if first time through or if type changes. Because
+ * we don't pin the tupdesc, we must not do this lookup until after
+ * doing DatumGetHeapTupleHeader: that could do database access while
+ * detoasting the datum.
+ */
+ tupDesc = get_cached_rowtype(op->d.fieldstore.fstore->resulttype, -1,
+ op->d.fieldstore.rowcache, NULL);
+
+ /* Check that current tupdesc doesn't have more fields than allocated */
+ if (unlikely(tupDesc->natts > op->d.fieldstore.ncolumns))
+ elog(ERROR, "too many columns in composite type %u",
+ op->d.fieldstore.fstore->resulttype);
+
heap_deform_tuple(&tmptup, tupDesc,
op->d.fieldstore.values,
op->d.fieldstore.nulls);
Jim | abcdefghijklabcdefgh | 1200000
(2 rows)
+-- try an update on a toasted composite value, too
+update people set fn.first = 'Jack';
+select (fn).first, substr((fn).last, 1, 20), length((fn).last) from people;
+ first | substr | length
+-------+----------------------+---------
+ Jack | Blow | 4
+ Jack | abcdefghijklabcdefgh | 1200000
+(2 rows)
+
-- Test row comparison semantics. Prior to PG 8.2 we did this in a totally
-- non-spec-compliant way.
select ROW(1,2) < ROW(1,3) as true;
select (fn).first, substr((fn).last, 1, 20), length((fn).last) from people;
+-- try an update on a toasted composite value, too
+update people set fn.first = 'Jack';
+
+select (fn).first, substr((fn).last, 1, 20), length((fn).last) from people;
+
-- Test row comparison semantics. Prior to PG 8.2 we did this in a totally
-- non-spec-compliant way.