mirror of
https://github.com/postgres/postgres.git
synced 2025-06-25 01:02:05 +03:00
Fix bogus tuple-slot management in logical replication UPDATE handling.
slot_modify_cstrings seriously abused the TupleTableSlot API by relying on a slot's underlying data to stay valid across ExecClearTuple. Since this abuse was also quite undocumented, it's little surprise that the case got broken during the v12 slot rewrites. As reported in bug #16129 from Ondřej Jirman, this could lead to crashes or data corruption when a logical replication subscriber processes a row update. Problems would only arise if the subscriber's table contained columns of pass-by-ref types that were not being copied from the publisher. Fix by explicitly copying the datum/isnull arrays from the source slot that the old row was in already. This ends up being about the same thing that happened pre-v12, but hopefully in a less opaque and fragile way. We might've caught the problem sooner if there were any test cases dealing with updates involving non-replicated or dropped columns. Now there are. Back-patch to v10 where this code came in. Even though the failure does not manifest before v12, IMO this code is too fragile to leave as-is. In any case we certainly want the additional test coverage. Patch by me; thanks to Tomas Vondra for initial investigation. Discussion: https://postgr.es/m/16129-a0c0f48e71741e5f@postgresql.org
This commit is contained in:
@ -380,13 +380,19 @@ slot_store_cstrings(TupleTableSlot *slot, LogicalRepRelMapEntry *rel,
|
||||
}
|
||||
|
||||
/*
|
||||
* Modify slot with user data provided as C strings.
|
||||
* Replace selected columns with user data provided as C strings.
|
||||
* This is somewhat similar to heap_modify_tuple but also calls the type
|
||||
* input function on the user data as the input is the text representation
|
||||
* of the types.
|
||||
* input functions on the user data.
|
||||
* "slot" is filled with a copy of the tuple in "srcslot", with
|
||||
* columns selected by the "replaces" array replaced with data values
|
||||
* from "values".
|
||||
* Caution: unreplaced pass-by-ref columns in "slot" will point into the
|
||||
* storage for "srcslot". This is OK for current usage, but someday we may
|
||||
* need to materialize "slot" at the end to make it independent of "srcslot".
|
||||
*/
|
||||
static void
|
||||
slot_modify_cstrings(TupleTableSlot *slot, LogicalRepRelMapEntry *rel,
|
||||
slot_modify_cstrings(TupleTableSlot *slot, TupleTableSlot *srcslot,
|
||||
LogicalRepRelMapEntry *rel,
|
||||
char **values, bool *replaces)
|
||||
{
|
||||
int natts = slot->tts_tupleDescriptor->natts;
|
||||
@ -394,10 +400,19 @@ slot_modify_cstrings(TupleTableSlot *slot, LogicalRepRelMapEntry *rel,
|
||||
SlotErrCallbackArg errarg;
|
||||
ErrorContextCallback errcallback;
|
||||
|
||||
slot_getallattrs(slot);
|
||||
/* We'll fill "slot" with a virtual tuple, so we must start with ... */
|
||||
ExecClearTuple(slot);
|
||||
|
||||
/* Push callback + info on the error context stack */
|
||||
/*
|
||||
* Copy all the column data from srcslot, so that we'll have valid values
|
||||
* for unreplaced columns.
|
||||
*/
|
||||
Assert(natts == srcslot->tts_tupleDescriptor->natts);
|
||||
slot_getallattrs(srcslot);
|
||||
memcpy(slot->tts_values, srcslot->tts_values, natts * sizeof(Datum));
|
||||
memcpy(slot->tts_isnull, srcslot->tts_isnull, natts * sizeof(bool));
|
||||
|
||||
/* For error reporting, push callback + info on the error context stack */
|
||||
errarg.rel = rel;
|
||||
errarg.local_attnum = -1;
|
||||
errarg.remote_attnum = -1;
|
||||
@ -445,6 +460,7 @@ slot_modify_cstrings(TupleTableSlot *slot, LogicalRepRelMapEntry *rel,
|
||||
/* Pop the error context stack */
|
||||
error_context_stack = errcallback.previous;
|
||||
|
||||
/* And finally, declare that "slot" contains a valid virtual tuple */
|
||||
ExecStoreVirtualTuple(slot);
|
||||
}
|
||||
|
||||
@ -755,8 +771,8 @@ apply_handle_update(StringInfo s)
|
||||
{
|
||||
/* Process and store remote tuple in the slot */
|
||||
oldctx = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
|
||||
ExecStoreTuple(localslot->tts_tuple, remoteslot, InvalidBuffer, false);
|
||||
slot_modify_cstrings(remoteslot, rel, newtup.values, newtup.changed);
|
||||
slot_modify_cstrings(remoteslot, localslot, rel,
|
||||
newtup.values, newtup.changed);
|
||||
MemoryContextSwitchTo(oldctx);
|
||||
|
||||
EvalPlanQualSetSlot(&epqstate, remoteslot);
|
||||
|
Reference in New Issue
Block a user