Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit 43af714

Browse files
committed
Fix order of operations in ExecEvalFieldStoreDeForm().
If the given composite datum is toasted out-of-line, DatumGetHeapTupleHeader will perform database accesses to detoast it. That can invalidate the result of get_cached_rowtype, as documented (perhaps not plainly enough) in that function's API spec; which leads to strange errors or crashes when we try to use the TupleDesc to read the tuple. In short then, trying to update a field of a composite column could fail intermittently if the overall column value is wide enough to require toasting. We can fix the bug at no cost by just changing the order of operations, since we don't need the TupleDesc until after detoasting. (Other callers of get_cached_rowtype appear to get this right already, so there's only one bug.) Note that the added regression test case reveals this bug reliably only with debug_discard_caches/CLOBBER_CACHE_ALWAYS. Per bug #17994 from Alexander Lakhin. Sadly, this patch does not fix the missing-values issue revealed in the bug discussion; we'll need some more work to cover that. Discussion: https://postgr.es/m/17994-5c7100b51b4790e9@postgresql.org
1 parent b750e74 commit 43af714

File tree

3 files changed

+31
-12
lines changed

3 files changed

+31
-12
lines changed

src/backend/executor/execExprInterp.c

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2015,7 +2015,8 @@ CheckOpSlotCompatibility(ExprEvalStep *op, TupleTableSlot *slot)
20152015
* changed: if not NULL, *changed is set to true on any update
20162016
*
20172017
* The returned TupleDesc is not guaranteed pinned; caller must pin it
2018-
* to use it across any operation that might incur cache invalidation.
2018+
* to use it across any operation that might incur cache invalidation,
2019+
* including for example detoasting of input tuples.
20192020
* (The TupleDesc is always refcounted, so just use IncrTupleDescRefCount.)
20202021
*
20212022
* NOTE: because composite types can change contents, we must be prepared
@@ -3174,17 +3175,6 @@ ExecEvalFieldSelect(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
31743175
void
31753176
ExecEvalFieldStoreDeForm(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
31763177
{
3177-
TupleDesc tupDesc;
3178-
3179-
/* Lookup tupdesc if first time through or if type changes */
3180-
tupDesc = get_cached_rowtype(op->d.fieldstore.fstore->resulttype, -1,
3181-
op->d.fieldstore.rowcache, NULL);
3182-
3183-
/* Check that current tupdesc doesn't have more fields than we allocated */
3184-
if (unlikely(tupDesc->natts > op->d.fieldstore.ncolumns))
3185-
elog(ERROR, "too many columns in composite type %u",
3186-
op->d.fieldstore.fstore->resulttype);
3187-
31883178
if (*op->resnull)
31893179
{
31903180
/* Convert null input tuple into an all-nulls row */
@@ -3200,13 +3190,28 @@ ExecEvalFieldStoreDeForm(ExprState *state, ExprEvalStep *op, ExprContext *econte
32003190
Datum tupDatum = *op->resvalue;
32013191
HeapTupleHeader tuphdr;
32023192
HeapTupleData tmptup;
3193+
TupleDesc tupDesc;
32033194

32043195
tuphdr = DatumGetHeapTupleHeader(tupDatum);
32053196
tmptup.t_len = HeapTupleHeaderGetDatumLength(tuphdr);
32063197
ItemPointerSetInvalid(&(tmptup.t_self));
32073198
tmptup.t_tableOid = InvalidOid;
32083199
tmptup.t_data = tuphdr;
32093200

3201+
/*
3202+
* Lookup tupdesc if first time through or if type changes. Because
3203+
* we don't pin the tupdesc, we must not do this lookup until after
3204+
* doing DatumGetHeapTupleHeader: that could do database access while
3205+
* detoasting the datum.
3206+
*/
3207+
tupDesc = get_cached_rowtype(op->d.fieldstore.fstore->resulttype, -1,
3208+
op->d.fieldstore.rowcache, NULL);
3209+
3210+
/* Check that current tupdesc doesn't have more fields than allocated */
3211+
if (unlikely(tupDesc->natts > op->d.fieldstore.ncolumns))
3212+
elog(ERROR, "too many columns in composite type %u",
3213+
op->d.fieldstore.fstore->resulttype);
3214+
32103215
heap_deform_tuple(&tmptup, tupDesc,
32113216
op->d.fieldstore.values,
32123217
op->d.fieldstore.nulls);

src/test/regress/expected/rowtypes.out

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,15 @@ select (fn).first, substr((fn).last, 1, 20), length((fn).last) from people;
176176
Jim | abcdefghijklabcdefgh | 1200000
177177
(2 rows)
178178

179+
-- try an update on a toasted composite value, too
180+
update people set fn.first = 'Jack';
181+
select (fn).first, substr((fn).last, 1, 20), length((fn).last) from people;
182+
first | substr | length
183+
-------+----------------------+---------
184+
Jack | Blow | 4
185+
Jack | abcdefghijklabcdefgh | 1200000
186+
(2 rows)
187+
179188
-- Test row comparison semantics. Prior to PG 8.2 we did this in a totally
180189
-- non-spec-compliant way.
181190
select ROW(1,2) < ROW(1,3) as true;

src/test/regress/sql/rowtypes.sql

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,11 @@ insert into people select ('Jim', f1, null)::fullname, current_date from pp;
8787

8888
select (fn).first, substr((fn).last, 1, 20), length((fn).last) from people;
8989

90+
-- try an update on a toasted composite value, too
91+
update people set fn.first = 'Jack';
92+
93+
select (fn).first, substr((fn).last, 1, 20), length((fn).last) from people;
94+
9095
-- Test row comparison semantics. Prior to PG 8.2 we did this in a totally
9196
-- non-spec-compliant way.
9297

0 commit comments

Comments
 (0)