aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/backend/access/heap/tuptoaster.c122
-rw-r--r--src/backend/commands/cluster.c4
2 files changed, 117 insertions, 9 deletions
diff --git a/src/backend/access/heap/tuptoaster.c b/src/backend/access/heap/tuptoaster.c
index 4f4dd69291f..0ce04a57275 100644
--- a/src/backend/access/heap/tuptoaster.c
+++ b/src/backend/access/heap/tuptoaster.c
@@ -74,7 +74,9 @@ do { \
static void toast_delete_datum(Relation rel, Datum value);
-static Datum toast_save_datum(Relation rel, Datum value, int options);
+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);
@@ -431,6 +433,7 @@ toast_insert_or_update(Relation rel, HeapTuple newtup, HeapTuple oldtup,
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];
@@ -466,6 +469,7 @@ toast_insert_or_update(Relation rel, HeapTuple newtup, HeapTuple oldtup,
* ----------
*/
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));
@@ -550,6 +554,7 @@ toast_insert_or_update(Relation rel, HeapTuple newtup, HeapTuple oldtup,
*/
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
@@ -676,7 +681,8 @@ toast_insert_or_update(Relation rel, HeapTuple newtup, HeapTuple oldtup,
{
old_value = toast_values[i];
toast_action[i] = 'p';
- toast_values[i] = toast_save_datum(rel, toast_values[i], options);
+ 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;
@@ -726,7 +732,8 @@ toast_insert_or_update(Relation rel, HeapTuple newtup, HeapTuple oldtup,
i = biggest_attno;
old_value = toast_values[i];
toast_action[i] = 'p';
- toast_values[i] = toast_save_datum(rel, toast_values[i], options);
+ 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;
@@ -839,7 +846,8 @@ toast_insert_or_update(Relation rel, HeapTuple newtup, HeapTuple oldtup,
i = biggest_attno;
old_value = toast_values[i];
toast_action[i] = 'p';
- toast_values[i] = toast_save_datum(rel, toast_values[i], options);
+ 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;
@@ -1117,10 +1125,16 @@ toast_compress_datum(Datum value)
*
* 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, int options)
+toast_save_datum(Relation rel, Datum value,
+ struct varlena *oldexternal, int options)
{
Relation toastrel;
Relation toastidx;
@@ -1199,11 +1213,55 @@ toast_save_datum(Relation rel, Datum value, int options)
toast_pointer.va_toastrelid = RelationGetRelid(toastrel);
/*
- * Choose an unused OID within the toast table for this toast value.
+ * 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.
*/
- toast_pointer.va_valueid = GetNewOidWithIndex(toastrel,
- RelationGetRelid(toastidx),
- (AttrNumber) 1);
+ 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
@@ -1339,6 +1397,52 @@ toast_delete_datum(Relation rel, Datum value)
/* ----------
+ * 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 9a7649bb4f9..670d29ea831 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -797,6 +797,10 @@ copy_heap_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex,
* 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