#include "access/htup_details.h"
#include "access/transam.h"
#include "access/tupconvert.h"
+#include "access/tuptoaster.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_type.h"
#include "commands/defrem.h"
}
else if (TRIGGER_FIRED_BY_INSERT(trigdata->tg_event))
{
- expanded_record_set_tuple(rec_new->erh, trigdata->tg_trigtuple, false);
+ expanded_record_set_tuple(rec_new->erh, trigdata->tg_trigtuple,
+ false, false);
}
else if (TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event))
{
- expanded_record_set_tuple(rec_new->erh, trigdata->tg_newtuple, false);
- expanded_record_set_tuple(rec_old->erh, trigdata->tg_trigtuple, false);
+ expanded_record_set_tuple(rec_new->erh, trigdata->tg_newtuple,
+ false, false);
+ expanded_record_set_tuple(rec_old->erh, trigdata->tg_trigtuple,
+ false, false);
}
else if (TRIGGER_FIRED_BY_DELETE(trigdata->tg_event))
{
- expanded_record_set_tuple(rec_old->erh, trigdata->tg_trigtuple, false);
+ expanded_record_set_tuple(rec_old->erh, trigdata->tg_trigtuple,
+ false, false);
}
else
elog(ERROR, "unrecognized trigger action: not INSERT, DELETE, or UPDATE");
/* And assign it. */
expanded_record_set_field(erh, recfield->finfo.fnumber,
- value, isNull);
+ value, isNull, !estate->atomic);
break;
}
tupdescs_match)
{
/* Only need to assign a new tuple value */
- expanded_record_set_tuple(rec->erh, tuptab->vals[i], true);
+ expanded_record_set_tuple(rec->erh, tuptab->vals[i],
+ true, !estate->atomic);
}
else
{
*/
newerh = make_expanded_record_for_rec(estate, rec,
NULL, rec->erh);
- expanded_record_set_tuple(newerh, NULL, false);
+ expanded_record_set_tuple(newerh, NULL, false, false);
assign_record_var(estate, rec, newerh);
}
else
else
{
/* No coercion is needed, so just assign the row value */
- expanded_record_set_tuple(newerh, tup, true);
+ expanded_record_set_tuple(newerh, tup, true, !estate->atomic);
}
/* Complete the assignment */
}
/* Insert the coerced field values into the new expanded record */
- expanded_record_set_fields(newerh, values, nulls);
+ expanded_record_set_fields(newerh, values, nulls, !estate->atomic);
/* Complete the assignment */
assign_record_var(estate, rec, newerh);
(erh->er_typmod == rec->erh->er_typmod &&
erh->er_typmod >= 0)))
{
- expanded_record_set_tuple(rec->erh, erh->fvalue, true);
+ expanded_record_set_tuple(rec->erh, erh->fvalue,
+ true, !estate->atomic);
return;
}
(rec->rectypeid == RECORDOID ||
rec->rectypeid == erh->er_typeid))
{
- expanded_record_set_tuple(newerh, erh->fvalue, true);
+ expanded_record_set_tuple(newerh, erh->fvalue,
+ true, !estate->atomic);
assign_record_var(estate, rec, newerh);
return;
}
(tupTypmod == rec->erh->er_typmod &&
tupTypmod >= 0)))
{
- expanded_record_set_tuple(rec->erh, &tmptup, true);
+ expanded_record_set_tuple(rec->erh, &tmptup,
+ true, !estate->atomic);
return;
}
newerh = make_expanded_record_from_typeid(tupType, tupTypmod,
mcontext);
- expanded_record_set_tuple(newerh, &tmptup, true);
+ expanded_record_set_tuple(newerh, &tmptup,
+ true, !estate->atomic);
assign_record_var(estate, rec, newerh);
return;
}
* assign_simple_var --- assign a new value to any VAR datum.
*
* This should be the only mechanism for assignment to simple variables,
- * lest we do the release of the old value incorrectly.
+ * lest we do the release of the old value incorrectly (not to mention
+ * the detoasting business).
*/
static void
assign_simple_var(PLpgSQL_execstate *estate, PLpgSQL_var *var,
{
Assert(var->dtype == PLPGSQL_DTYPE_VAR ||
var->dtype == PLPGSQL_DTYPE_PROMISE);
+
+ /*
+ * In non-atomic contexts, we do not want to store TOAST pointers in
+ * variables, because such pointers might become stale after a commit.
+ * Forcibly detoast in such cases. We don't want to detoast (flatten)
+ * expanded objects, however; those should be OK across a transaction
+ * boundary since they're just memory-resident objects. (Elsewhere in
+ * this module, operations on expanded records likewise need to request
+ * detoasting of record fields when !estate->atomic. Expanded arrays are
+ * not a problem since all array entries are always detoasted.)
+ */
+ if (!estate->atomic && !isnull && var->datatype->typlen == -1 &&
+ VARATT_IS_EXTERNAL_NON_EXPANDED(DatumGetPointer(newvalue)))
+ {
+ MemoryContext oldcxt;
+ Datum detoasted;
+
+ /*
+ * Do the detoasting in the eval_mcontext to avoid long-term leakage
+ * of whatever memory toast fetching might leak. Then we have to copy
+ * the detoasted datum to the function's main context, which is a
+ * pain, but there's little choice.
+ */
+ oldcxt = MemoryContextSwitchTo(get_eval_mcontext(estate));
+ detoasted = PointerGetDatum(heap_tuple_fetch_attr((struct varlena *) DatumGetPointer(newvalue)));
+ MemoryContextSwitchTo(oldcxt);
+ /* Now's a good time to not leak the input value if it's freeable */
+ if (freeable)
+ pfree(DatumGetPointer(newvalue));
+ /* Once we copy the value, it's definitely freeable */
+ newvalue = datumCopy(detoasted, false, -1);
+ freeable = true;
+ /* Can't clean up eval_mcontext here, but it'll happen before long */
+ }
+
/* Free the old value if needed */
if (var->freeval)
{