toast-preserve-value-OIDs.patch
text/x-patch
Filename: toast-preserve-value-OIDs.patch
Type: text/x-patch
Part: 0
Message:
Re: VACUUM FULL versus TOAST
diff --git a/src/backend/access/heap/tuptoaster.c b/src/backend/access/heap/tuptoaster.c
index 4f4dd69291fd50008f8e313176a02cd5bc955e08..785c679879012508b4925b4f8ce93e1c712f7ec4 100644
*** a/src/backend/access/heap/tuptoaster.c
--- b/src/backend/access/heap/tuptoaster.c
*************** do { \
*** 74,80 ****
static void toast_delete_datum(Relation rel, Datum value);
! static Datum toast_save_datum(Relation rel, Datum value, int options);
static struct varlena *toast_fetch_datum(struct varlena * attr);
static struct varlena *toast_fetch_datum_slice(struct varlena * attr,
int32 sliceoffset, int32 length);
--- 74,82 ----
static void toast_delete_datum(Relation rel, Datum value);
! static Datum toast_save_datum(Relation rel, Datum value,
! struct varlena *oldexternal, int options);
! static bool toast_valueid_exists(Oid toastrelid, Oid valueid);
static struct varlena *toast_fetch_datum(struct varlena * attr);
static struct varlena *toast_fetch_datum_slice(struct varlena * attr,
int32 sliceoffset, int32 length);
*************** toast_insert_or_update(Relation rel, Hea
*** 431,436 ****
--- 433,439 ----
bool toast_oldisnull[MaxHeapAttributeNumber];
Datum toast_values[MaxHeapAttributeNumber];
Datum toast_oldvalues[MaxHeapAttributeNumber];
+ struct varlena *toast_oldexternal[MaxHeapAttributeNumber];
int32 toast_sizes[MaxHeapAttributeNumber];
bool toast_free[MaxHeapAttributeNumber];
bool toast_delold[MaxHeapAttributeNumber];
*************** toast_insert_or_update(Relation rel, Hea
*** 466,471 ****
--- 469,475 ----
* ----------
*/
memset(toast_action, ' ', numAttrs * sizeof(char));
+ memset(toast_oldexternal, 0, numAttrs * sizeof(struct varlena *));
memset(toast_free, 0, numAttrs * sizeof(bool));
memset(toast_delold, 0, numAttrs * sizeof(bool));
*************** toast_insert_or_update(Relation rel, Hea
*** 550,555 ****
--- 554,560 ----
*/
if (VARATT_IS_EXTERNAL(new_value))
{
+ toast_oldexternal[i] = new_value;
if (att[i]->attstorage == 'p')
new_value = heap_tuple_untoast_attr(new_value);
else
*************** toast_insert_or_update(Relation rel, Hea
*** 676,682 ****
{
old_value = toast_values[i];
toast_action[i] = 'p';
! toast_values[i] = toast_save_datum(rel, toast_values[i], options);
if (toast_free[i])
pfree(DatumGetPointer(old_value));
toast_free[i] = true;
--- 681,688 ----
{
old_value = toast_values[i];
toast_action[i] = 'p';
! toast_values[i] = toast_save_datum(rel, toast_values[i],
! toast_oldexternal[i], options);
if (toast_free[i])
pfree(DatumGetPointer(old_value));
toast_free[i] = true;
*************** toast_insert_or_update(Relation rel, Hea
*** 726,732 ****
i = biggest_attno;
old_value = toast_values[i];
toast_action[i] = 'p';
! toast_values[i] = toast_save_datum(rel, toast_values[i], options);
if (toast_free[i])
pfree(DatumGetPointer(old_value));
toast_free[i] = true;
--- 732,739 ----
i = biggest_attno;
old_value = toast_values[i];
toast_action[i] = 'p';
! toast_values[i] = toast_save_datum(rel, toast_values[i],
! toast_oldexternal[i], options);
if (toast_free[i])
pfree(DatumGetPointer(old_value));
toast_free[i] = true;
*************** toast_insert_or_update(Relation rel, Hea
*** 839,845 ****
i = biggest_attno;
old_value = toast_values[i];
toast_action[i] = 'p';
! toast_values[i] = toast_save_datum(rel, toast_values[i], options);
if (toast_free[i])
pfree(DatumGetPointer(old_value));
toast_free[i] = true;
--- 846,853 ----
i = biggest_attno;
old_value = toast_values[i];
toast_action[i] = 'p';
! toast_values[i] = toast_save_datum(rel, toast_values[i],
! toast_oldexternal[i], options);
if (toast_free[i])
pfree(DatumGetPointer(old_value));
toast_free[i] = true;
*************** toast_compress_datum(Datum value)
*** 1117,1126 ****
*
* Save one single datum into the secondary relation and return
* a Datum reference for it.
* ----------
*/
static Datum
! toast_save_datum(Relation rel, Datum value, int options)
{
Relation toastrel;
Relation toastidx;
--- 1125,1140 ----
*
* Save one single datum into the secondary relation and return
* a Datum reference for it.
+ *
+ * rel: the main relation we're working with (not the toast rel!)
+ * value: datum to be pushed to toast storage
+ * oldexternal: if not NULL, toast pointer previously representing the datum
+ * options: options to be passed to heap_insert() for toast rows
* ----------
*/
static Datum
! toast_save_datum(Relation rel, Datum value,
! struct varlena *oldexternal, int options)
{
Relation toastrel;
Relation toastidx;
*************** toast_save_datum(Relation rel, Datum val
*** 1199,1209 ****
toast_pointer.va_toastrelid = RelationGetRelid(toastrel);
/*
! * Choose an unused OID within the toast table for this toast value.
*/
! toast_pointer.va_valueid = GetNewOidWithIndex(toastrel,
! RelationGetRelid(toastidx),
! (AttrNumber) 1);
/*
* Initialize constant parts of the tuple data
--- 1213,1267 ----
toast_pointer.va_toastrelid = RelationGetRelid(toastrel);
/*
! * Choose an OID to use as the value ID for this toast value.
! *
! * Normally we just choose an unused OID within the toast table. But
! * during table-rewriting operations where we are preserving an existing
! * toast table OID, we want to preserve toast value OIDs too. So, if
! * rd_toastoid is set and we had a prior external value from that same
! * toast table, re-use its value ID. If we didn't have a prior external
! * value (which is a corner case, but possible if the table's attstorage
! * options have been changed), we have to pick a value ID that doesn't
! * conflict with either new or existing toast value OIDs.
*/
! if (!OidIsValid(rel->rd_toastoid))
! {
! /* normal case: just choose an unused OID */
! toast_pointer.va_valueid =
! GetNewOidWithIndex(toastrel,
! RelationGetRelid(toastidx),
! (AttrNumber) 1);
! }
! else
! {
! /* rewrite case: check to see if value was in old toast table */
! toast_pointer.va_valueid = InvalidOid;
! if (oldexternal != NULL)
! {
! struct varatt_external old_toast_pointer;
!
! Assert(VARATT_IS_EXTERNAL(oldexternal));
! /* Must copy to access aligned fields */
! VARATT_EXTERNAL_GET_POINTER(old_toast_pointer, oldexternal);
! if (old_toast_pointer.va_toastrelid == rel->rd_toastoid)
! toast_pointer.va_valueid = old_toast_pointer.va_valueid;
! }
! if (toast_pointer.va_valueid == InvalidOid)
! {
! /*
! * new value; must choose an OID that doesn't conflict in either
! * old or new toast table
! */
! do
! {
! toast_pointer.va_valueid =
! GetNewOidWithIndex(toastrel,
! RelationGetRelid(toastidx),
! (AttrNumber) 1);
! } while (toast_valueid_exists(rel->rd_toastoid,
! toast_pointer.va_valueid));
! }
! }
/*
* Initialize constant parts of the tuple data
*************** toast_delete_datum(Relation rel, Datum v
*** 1339,1344 ****
--- 1397,1448 ----
/* ----------
+ * toast_valueid_exists -
+ *
+ * Test whether a toast value with the given ID exists in the toast relation
+ * ----------
+ */
+ static bool
+ toast_valueid_exists(Oid toastrelid, Oid valueid)
+ {
+ bool result = false;
+ Relation toastrel;
+ ScanKeyData toastkey;
+ SysScanDesc toastscan;
+
+ /*
+ * Open the toast relation
+ */
+ toastrel = heap_open(toastrelid, AccessShareLock);
+
+ /*
+ * Setup a scan key to find chunks with matching va_valueid
+ */
+ ScanKeyInit(&toastkey,
+ (AttrNumber) 1,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(valueid));
+
+ /*
+ * Is there any such chunk?
+ */
+ toastscan = systable_beginscan(toastrel, toastrel->rd_rel->reltoastidxid,
+ true, SnapshotToast, 1, &toastkey);
+
+ if (systable_getnext(toastscan) != NULL)
+ result = true;
+
+ /*
+ * End scan and close relations
+ */
+ systable_endscan(toastscan);
+ heap_close(toastrel, AccessShareLock);
+
+ return result;
+ }
+
+
+ /* ----------
* toast_fetch_datum -
*
* Reconstruct an in memory Datum from the chunks saved
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 9a7649bb4f95581e0169f0a341a9d8d0b1287db3..670d29ea83192a82297100a16b59e7a48303f1fd 100644
*** a/src/backend/commands/cluster.c
--- b/src/backend/commands/cluster.c
*************** copy_heap_data(Oid OIDNewHeap, Oid OIDOl
*** 797,802 ****
--- 797,806 ----
* When doing swap by content, any toast pointers written into NewHeap
* must use the old toast table's OID, because that's where the toast
* data will eventually be found. Set this up by setting rd_toastoid.
+ * This also tells tuptoaster.c to preserve the toast value OIDs,
+ * which we want so as not to invalidate toast pointers in system
+ * catalog caches.
+ *
* Note that we must hold NewHeap open until we are done writing data,
* since the relcache will not guarantee to remember this setting once
* the relation is closed. Also, this technique depends on the fact