src/backend/replication/logical/reorderbuffer.c
static void
ReorderBufferToastReplace(ReorderBuffer *rb, ReorderBufferTXN *txn,
						  Relation relation, ReorderBufferChange *change)
{
	TupleDesc	desc;
	int			natt;
	Datum	   *attrs;
	bool	   *isnull;
	bool	   *free;
	HeapTuple	tmphtup;
	Relation	toast_rel;
	TupleDesc	toast_desc;
	MemoryContext oldcontext;
	ReorderBufferTupleBuf *newtup;

	/* no toast tuples changed */
	if (txn->toast_hash == NULL)
		return;

	oldcontext = MemoryContextSwitchTo(rb->context);

	/* we should only have toast tuples in an INSERT or UPDATE */
	Assert(change->data.tp.newtuple);

	desc = RelationGetDescr(relation);

	toast_rel = RelationIdGetRelation(relation->rd_rel->reltoastrelid);
	toast_desc = RelationGetDescr(toast_rel);

	/* should we allocate from stack instead? */
	attrs = palloc0(sizeof(Datum) * desc->natts);
	isnull = palloc0(sizeof(bool) * desc->natts);
	free = palloc0(sizeof(bool) * desc->natts);

	newtup = change->data.tp.newtuple;

	heap_deform_tuple(&newtup->tuple, desc, attrs, isnull);

	for (natt = 0; natt < desc->natts; natt++)
	{
		Form_pg_attribute attr = desc->attrs[natt];
		ReorderBufferToastEnt *ent;
		struct varlena *varlena;

		/* va_rawsize is the size of the original datum -- including header */
		struct varatt_external toast_pointer;
		struct varatt_indirect redirect_pointer;
		struct varlena *new_datum = NULL;
		struct varlena *reconstructed;
		dlist_iter	it;
		Size		data_done = 0;

		/* system columns aren't toasted */
		if (attr->attnum < 0)
			continue;

		if (attr->attisdropped)
			continue;

		/* not a varlena datatype */
		if (attr->attlen != -1)
			continue;

		/* no data */
		if (isnull[natt])
			continue;

		/* ok, we know we have a toast datum */
		varlena = (struct varlena *) DatumGetPointer(attrs[natt]);

		/* no need to do anything if the tuple isn't external */
		if (!VARATT_IS_EXTERNAL(varlena))
			continue;

		VARATT_EXTERNAL_GET_POINTER(toast_pointer, varlena);

		/*
		 * Check whether the toast tuple changed, replace if so.
		 */
		ent = (ReorderBufferToastEnt *)
			hash_search(txn->toast_hash,
						(void *) &toast_pointer.va_valueid,
						HASH_FIND,
						NULL);
		if (ent == NULL)
			continue;

		new_datum =
			(struct varlena *) palloc0(INDIRECT_POINTER_SIZE);

		free[natt] = true;

		reconstructed = palloc0(toast_pointer.va_rawsize);

		ent->reconstructed = reconstructed;

		/* stitch toast tuple back together from its parts */
		dlist_foreach(it, &ent->chunks)
		{
			bool		isnull;
			ReorderBufferChange *cchange;
			ReorderBufferTupleBuf *ctup;
			Pointer		chunk;

			cchange = dlist_container(ReorderBufferChange, node, it.cur);
			ctup = cchange->data.tp.newtuple;
			chunk = DatumGetPointer(
						  fastgetattr(&ctup->tuple, 3, toast_desc, &isnull));

			Assert(!isnull);
			Assert(!VARATT_IS_EXTERNAL(chunk));
			Assert(!VARATT_IS_SHORT(chunk));

			memcpy(VARDATA(reconstructed) + data_done,
				   VARDATA(chunk),
				   VARSIZE(chunk) - VARHDRSZ);
			data_done += VARSIZE(chunk) - VARHDRSZ;
		}
		Assert(data_done == toast_pointer.va_extsize);

		/* make sure its marked as compressed or not */
		if (VARATT_EXTERNAL_IS_COMPRESSED(toast_pointer))
			SET_VARSIZE_COMPRESSED(reconstructed, data_done + VARHDRSZ);
		else
			SET_VARSIZE(reconstructed, data_done + VARHDRSZ);

		memset(&redirect_pointer, 0, sizeof(redirect_pointer));
		redirect_pointer.pointer = reconstructed;

		SET_VARTAG_EXTERNAL(new_datum, VARTAG_INDIRECT);
		memcpy(VARDATA_EXTERNAL(new_datum), &redirect_pointer,
			   sizeof(redirect_pointer));

		attrs[natt] = PointerGetDatum(new_datum);
	}

	/*
	 * Build tuple in separate memory & copy tuple back into the tuplebuf
	 * passed to the output plugin. We can't directly heap_fill_tuple() into
	 * the tuplebuf because attrs[] will point back into the current content.
	 */
	tmphtup = heap_form_tuple(desc, attrs, isnull);
	Assert(newtup->tuple.t_len <= MaxHeapTupleSize);
	Assert(ReorderBufferTupleBufData(newtup) == newtup->tuple.t_data);

	memcpy(newtup->tuple.t_data, tmphtup->t_data, tmphtup->t_len);
	newtup->tuple.t_len = tmphtup->t_len;

	/*
	 * free resources we won't further need, more persistent stuff will be
	 * free'd in ReorderBufferToastReset().
	 */
	RelationClose(toast_rel);
	pfree(tmphtup);
	for (natt = 0; natt < desc->natts; natt++)
	{
		if (free[natt])
			pfree(DatumGetPointer(attrs[natt]));
	}
	pfree(attrs);
	pfree(free);
	pfree(isnull);

	MemoryContextSwitchTo(oldcontext);
}
/*
 * Rejigger change->newtuple to point to in-memory toast tuples instead to
 * on-disk toast tuples that may not longer exist (think DROP TABLE or VACUUM).
 *
 * We cannot replace unchanged toast tuples though, so those will still point
 * to on-disk toast data.
 */
Rejigger change->newtuple to point to in-memory toast tuples instead to
on-disk toast tuples that may not longer exist (think DROP TABLE or VACUUM).

We cannot replace unchanged toast tuples though, so those will still point
to on-disk toast data.
Rejigger change - > newtuple to point to in - memory toast tuples instead to on - disk toast tuples that may not longer exist ( think DROP TABLE or VACUUM ) . We cannot replace unchanged toast tuples though so those will still point to on - disk toast data .
static void
ReorderBufferToastReplace(ReorderBuffer *rb, ReorderBufferTXN *txn,
						  Relation relation, ReorderBufferChange *change)
{
	TupleDesc	desc;
	int			natt;
	Datum	   *attrs;
	bool	   *isnull;
	bool	   *free;
	HeapTuple	tmphtup;
	Relation	toast_rel;
	TupleDesc	toast_desc;
	MemoryContext oldcontext;
	ReorderBufferTupleBuf *newtup;
	if (txn->toast_hash == NULL)
		return;
	oldcontext = MemoryContextSwitchTo(rb->context);
	Assert(change->data.tp.newtuple);
	desc = RelationGetDescr(relation);
	toast_rel = RelationIdGetRelation(relation->rd_rel->reltoastrelid);
	toast_desc = RelationGetDescr(toast_rel);
	attrs = palloc0(sizeof(Datum) * desc->natts);
	isnull = palloc0(sizeof(bool) * desc->natts);
	free = palloc0(sizeof(bool) * desc->natts);
	newtup = change->data.tp.newtuple;
	heap_deform_tuple(&newtup->tuple, desc, attrs, isnull);
	for (natt = 0; natt < desc->natts; natt++)
	{
		Form_pg_attribute attr = desc->attrs[natt];
		ReorderBufferToastEnt *ent;
		struct varlena *varlena;
		struct varatt_external toast_pointer;
		struct varatt_indirect redirect_pointer;
		struct varlena *new_datum = NULL;
		struct varlena *reconstructed;
		dlist_iter	it;
		Size		data_done = 0;
		if (attr->attnum < 0)
			continue;
		if (attr->attisdropped)
			continue;
		if (attr->attlen != -1)
			continue;
		if (isnull[natt])
			continue;
		varlena = (struct varlena *) DatumGetPointer(attrs[natt]);
		if (!VARATT_IS_EXTERNAL(varlena))
			continue;
		VARATT_EXTERNAL_GET_POINTER(toast_pointer, varlena);
		ent = (ReorderBufferToastEnt *)
			hash_search(txn->toast_hash,
						(void *) &toast_pointer.va_valueid,
						HASH_FIND,
						NULL);
		if (ent == NULL)
			continue;
		new_datum =
			(struct varlena *) palloc0(INDIRECT_POINTER_SIZE);
		free[natt] = true;
		reconstructed = palloc0(toast_pointer.va_rawsize);
		ent->reconstructed = reconstructed;
		dlist_foreach(it, &ent->chunks)
		{
			bool		isnull;
			ReorderBufferChange *cchange;
			ReorderBufferTupleBuf *ctup;
			Pointer		chunk;
			cchange = dlist_container(ReorderBufferChange, node, it.cur);
			ctup = cchange->data.tp.newtuple;
			chunk = DatumGetPointer(
						  fastgetattr(&ctup->tuple, 3, toast_desc, &isnull));
			Assert(!isnull);
			Assert(!VARATT_IS_EXTERNAL(chunk));
			Assert(!VARATT_IS_SHORT(chunk));
			memcpy(VARDATA(reconstructed) + data_done,
				   VARDATA(chunk),
				   VARSIZE(chunk) - VARHDRSZ);
			data_done += VARSIZE(chunk) - VARHDRSZ;
		}
		Assert(data_done == toast_pointer.va_extsize);
		if (VARATT_EXTERNAL_IS_COMPRESSED(toast_pointer))
			SET_VARSIZE_COMPRESSED(reconstructed, data_done + VARHDRSZ);
		else
			SET_VARSIZE(reconstructed, data_done + VARHDRSZ);
		memset(&redirect_pointer, 0, sizeof(redirect_pointer));
		redirect_pointer.pointer = reconstructed;
		SET_VARTAG_EXTERNAL(new_datum, VARTAG_INDIRECT);
		memcpy(VARDATA_EXTERNAL(new_datum), &redirect_pointer,
			   sizeof(redirect_pointer));
		attrs[natt] = PointerGetDatum(new_datum);
	}
	tmphtup = heap_form_tuple(desc, attrs, isnull);
	Assert(newtup->tuple.t_len <= MaxHeapTupleSize);
	Assert(ReorderBufferTupleBufData(newtup) == newtup->tuple.t_data);
	memcpy(newtup->tuple.t_data, tmphtup->t_data, tmphtup->t_len);
	newtup->tuple.t_len = tmphtup->t_len;
	RelationClose(toast_rel);
	pfree(tmphtup);
	for (natt = 0; natt < desc->natts; natt++)
	{
		if (free[natt])
			pfree(DatumGetPointer(attrs[natt]));
	}
	pfree(attrs);
	pfree(free);
	pfree(isnull);
	MemoryContextSwitchTo(oldcontext);
}
static void ReorderBufferToastReplace ( ReorderBuffer * rb , ReorderBufferTXN * txn , Relation relation , ReorderBufferChange * change ) { TupleDesc desc ; int natt ; Datum * attrs ; bool * isnull ; bool * free ; HeapTuple tmphtup ; Relation toast_rel ; TupleDesc toast_desc ; MemoryContext oldcontext ; ReorderBufferTupleBuf * newtup ; if ( txn -> toast_hash == NULL ) return ; oldcontext = MemoryContextSwitchTo ( rb -> context ) ; Assert ( change -> data . tp . newtuple ) ; desc = RelationGetDescr ( relation ) ; toast_rel = RelationIdGetRelation ( relation -> rd_rel -> reltoastrelid ) ; toast_desc = RelationGetDescr ( toast_rel ) ; attrs = palloc0 ( sizeof ( Datum ) * desc -> natts ) ; isnull = palloc0 ( sizeof ( bool ) * desc -> natts ) ; free = palloc0 ( sizeof ( bool ) * desc -> natts ) ; newtup = change -> data . tp . newtuple ; heap_deform_tuple ( & newtup -> tuple , desc , attrs , isnull ) ; for ( natt = 0 ; natt < desc -> natts ; natt ++ ) { Form_pg_attribute attr = desc -> attrs [ natt ] ; ReorderBufferToastEnt * ent ; struct varlena * varlena ; struct varatt_external toast_pointer ; struct varatt_indirect redirect_pointer ; struct varlena * new_datum = NULL ; struct varlena * reconstructed ; dlist_iter it ; Size data_done = 0 ; if ( attr -> attnum < 0 ) continue ; if ( attr -> attisdropped ) continue ; if ( attr -> attlen != -1 ) continue ; if ( isnull [ natt ] ) continue ; varlena = ( struct varlena * ) DatumGetPointer ( attrs [ natt ] ) ; if ( ! VARATT_IS_EXTERNAL ( varlena ) ) continue ; VARATT_EXTERNAL_GET_POINTER ( toast_pointer , varlena ) ; ent = ( ReorderBufferToastEnt * ) hash_search ( txn -> toast_hash , ( void * ) & toast_pointer . va_valueid , HASH_FIND , NULL ) ; if ( ent == NULL ) continue ; new_datum = ( struct varlena * ) palloc0 ( INDIRECT_POINTER_SIZE ) ; free [ natt ] = true ; reconstructed = palloc0 ( toast_pointer . va_rawsize ) ; ent -> reconstructed = reconstructed ; dlist_foreach ( it , & ent -> chunks ) { bool isnull ; ReorderBufferChange * cchange ; ReorderBufferTupleBuf * ctup ; Pointer chunk ; cchange = dlist_container ( ReorderBufferChange , node , it . cur ) ; ctup = cchange -> data . tp . newtuple ; chunk = DatumGetPointer ( fastgetattr ( & ctup -> tuple , 3 , toast_desc , & isnull ) ) ; Assert ( ! isnull ) ; Assert ( ! VARATT_IS_EXTERNAL ( chunk ) ) ; Assert ( ! VARATT_IS_SHORT ( chunk ) ) ; memcpy ( VARDATA ( reconstructed ) + data_done , VARDATA ( chunk ) , VARSIZE ( chunk ) - VARHDRSZ ) ; data_done += VARSIZE ( chunk ) - VARHDRSZ ; } Assert ( data_done == toast_pointer . va_extsize ) ; if ( VARATT_EXTERNAL_IS_COMPRESSED ( toast_pointer ) ) SET_VARSIZE_COMPRESSED ( reconstructed , data_done + VARHDRSZ ) ; else SET_VARSIZE ( reconstructed , data_done + VARHDRSZ ) ; memset ( & redirect_pointer , 0 , sizeof ( redirect_pointer ) ) ; redirect_pointer . pointer = reconstructed ; SET_VARTAG_EXTERNAL ( new_datum , VARTAG_INDIRECT ) ; memcpy ( VARDATA_EXTERNAL ( new_datum ) , & redirect_pointer , sizeof ( redirect_pointer ) ) ; attrs [ natt ] = PointerGetDatum ( new_datum ) ; } tmphtup = heap_form_tuple ( desc , attrs , isnull ) ; Assert ( newtup -> tuple . t_len <= MaxHeapTupleSize ) ; Assert ( ReorderBufferTupleBufData ( newtup ) == newtup -> tuple . t_data ) ; memcpy ( newtup -> tuple . t_data , tmphtup -> t_data , tmphtup -> t_len ) ; newtup -> tuple . t_len = tmphtup -> t_len ; RelationClose ( toast_rel ) ; pfree ( tmphtup ) ; for ( natt = 0 ; natt < desc -> natts ; natt ++ ) { if ( free [ natt ] ) pfree ( DatumGetPointer ( attrs [ natt ] ) ) ; } pfree ( attrs ) ; pfree ( free ) ; pfree ( isnull ) ; MemoryContextSwitchTo ( oldcontext ) ; }
Rejigger change->newtuple to point to in-memory toast tuples instead to
 on-disk toast tuples that may not longer exist (think DROP TABLE or VACUUM).
Rejigger change - > newtuple to point to in - memory toast tuples instead to on - disk toast tuples that may not longer exist ( think DROP TABLE or VACUUM ) .
/* no toast tuples changed */ /* we should only have toast tuples in an INSERT or UPDATE */ /* should we allocate from stack instead? */ /* va_rawsize is the size of the original datum -- including header */ /* system columns aren't toasted */ /* not a varlena datatype */ /* no data */ /* ok, we know we have a toast datum */ /* no need to do anything if the tuple isn't external */ /*
		 * Check whether the toast tuple changed, replace if so.
		 */ /* stitch toast tuple back together from its parts */ /* make sure its marked as compressed or not */ /*
	 * Build tuple in separate memory & copy tuple back into the tuplebuf
	 * passed to the output plugin. We can't directly heap_fill_tuple() into
	 * the tuplebuf because attrs[] will point back into the current content.
	 */ /*
	 * free resources we won't further need, more persistent stuff will be
	 * free'd in ReorderBufferToastReset().
	 */