|
74 | 74 |
|
75 | 75 |
|
76 | 76 | static void toast_delete_datum(Relation rel, Datum value); |
77 | | -static Datum toast_save_datum(Relation rel, Datum value, int options); |
| 77 | +static Datum toast_save_datum(Relation rel, Datum value, |
| 78 | + struct varlena *oldexternal, int options); |
| 79 | +static bool toast_valueid_exists(Oid toastrelid, Oid valueid); |
78 | 80 | static struct varlena *toast_fetch_datum(struct varlena * attr); |
79 | 81 | static struct varlena *toast_fetch_datum_slice(struct varlena * attr, |
80 | 82 | int32 sliceoffset, int32 length); |
@@ -431,6 +433,7 @@ toast_insert_or_update(Relation rel, HeapTuple newtup, HeapTuple oldtup, |
431 | 433 | bool toast_oldisnull[MaxHeapAttributeNumber]; |
432 | 434 | Datum toast_values[MaxHeapAttributeNumber]; |
433 | 435 | Datum toast_oldvalues[MaxHeapAttributeNumber]; |
| 436 | + struct varlena *toast_oldexternal[MaxHeapAttributeNumber]; |
434 | 437 | int32 toast_sizes[MaxHeapAttributeNumber]; |
435 | 438 | bool toast_free[MaxHeapAttributeNumber]; |
436 | 439 | bool toast_delold[MaxHeapAttributeNumber]; |
@@ -466,6 +469,7 @@ toast_insert_or_update(Relation rel, HeapTuple newtup, HeapTuple oldtup, |
466 | 469 | * ---------- |
467 | 470 | */ |
468 | 471 | memset(toast_action, ' ', numAttrs * sizeof(char)); |
| 472 | + memset(toast_oldexternal, 0, numAttrs * sizeof(struct varlena *)); |
469 | 473 | memset(toast_free, 0, numAttrs * sizeof(bool)); |
470 | 474 | memset(toast_delold, 0, numAttrs * sizeof(bool)); |
471 | 475 |
|
@@ -550,6 +554,7 @@ toast_insert_or_update(Relation rel, HeapTuple newtup, HeapTuple oldtup, |
550 | 554 | */ |
551 | 555 | if (VARATT_IS_EXTERNAL(new_value)) |
552 | 556 | { |
| 557 | + toast_oldexternal[i] = new_value; |
553 | 558 | if (att[i]->attstorage == 'p') |
554 | 559 | new_value = heap_tuple_untoast_attr(new_value); |
555 | 560 | else |
@@ -676,7 +681,8 @@ toast_insert_or_update(Relation rel, HeapTuple newtup, HeapTuple oldtup, |
676 | 681 | { |
677 | 682 | old_value = toast_values[i]; |
678 | 683 | toast_action[i] = 'p'; |
679 | | - toast_values[i] = toast_save_datum(rel, toast_values[i], options); |
| 684 | + toast_values[i] = toast_save_datum(rel, toast_values[i], |
| 685 | + toast_oldexternal[i], options); |
680 | 686 | if (toast_free[i]) |
681 | 687 | pfree(DatumGetPointer(old_value)); |
682 | 688 | toast_free[i] = true; |
@@ -726,7 +732,8 @@ toast_insert_or_update(Relation rel, HeapTuple newtup, HeapTuple oldtup, |
726 | 732 | i = biggest_attno; |
727 | 733 | old_value = toast_values[i]; |
728 | 734 | toast_action[i] = 'p'; |
729 | | - toast_values[i] = toast_save_datum(rel, toast_values[i], options); |
| 735 | + toast_values[i] = toast_save_datum(rel, toast_values[i], |
| 736 | + toast_oldexternal[i], options); |
730 | 737 | if (toast_free[i]) |
731 | 738 | pfree(DatumGetPointer(old_value)); |
732 | 739 | toast_free[i] = true; |
@@ -839,7 +846,8 @@ toast_insert_or_update(Relation rel, HeapTuple newtup, HeapTuple oldtup, |
839 | 846 | i = biggest_attno; |
840 | 847 | old_value = toast_values[i]; |
841 | 848 | toast_action[i] = 'p'; |
842 | | - toast_values[i] = toast_save_datum(rel, toast_values[i], options); |
| 849 | + toast_values[i] = toast_save_datum(rel, toast_values[i], |
| 850 | + toast_oldexternal[i], options); |
843 | 851 | if (toast_free[i]) |
844 | 852 | pfree(DatumGetPointer(old_value)); |
845 | 853 | toast_free[i] = true; |
@@ -1117,10 +1125,16 @@ toast_compress_datum(Datum value) |
1117 | 1125 | * |
1118 | 1126 | * Save one single datum into the secondary relation and return |
1119 | 1127 | * a Datum reference for it. |
| 1128 | + * |
| 1129 | + * rel: the main relation we're working with (not the toast rel!) |
| 1130 | + * value: datum to be pushed to toast storage |
| 1131 | + * oldexternal: if not NULL, toast pointer previously representing the datum |
| 1132 | + * options: options to be passed to heap_insert() for toast rows |
1120 | 1133 | * ---------- |
1121 | 1134 | */ |
1122 | 1135 | static Datum |
1123 | | -toast_save_datum(Relation rel, Datum value, int options) |
| 1136 | +toast_save_datum(Relation rel, Datum value, |
| 1137 | + struct varlena *oldexternal, int options) |
1124 | 1138 | { |
1125 | 1139 | Relation toastrel; |
1126 | 1140 | Relation toastidx; |
@@ -1199,11 +1213,55 @@ toast_save_datum(Relation rel, Datum value, int options) |
1199 | 1213 | toast_pointer.va_toastrelid = RelationGetRelid(toastrel); |
1200 | 1214 |
|
1201 | 1215 | /* |
1202 | | - * Choose an unused OID within the toast table for this toast value. |
| 1216 | + * Choose an OID to use as the value ID for this toast value. |
| 1217 | + * |
| 1218 | + * Normally we just choose an unused OID within the toast table. But |
| 1219 | + * during table-rewriting operations where we are preserving an existing |
| 1220 | + * toast table OID, we want to preserve toast value OIDs too. So, if |
| 1221 | + * rd_toastoid is set and we had a prior external value from that same |
| 1222 | + * toast table, re-use its value ID. If we didn't have a prior external |
| 1223 | + * value (which is a corner case, but possible if the table's attstorage |
| 1224 | + * options have been changed), we have to pick a value ID that doesn't |
| 1225 | + * conflict with either new or existing toast value OIDs. |
1203 | 1226 | */ |
1204 | | - toast_pointer.va_valueid = GetNewOidWithIndex(toastrel, |
1205 | | - RelationGetRelid(toastidx), |
1206 | | - (AttrNumber) 1); |
| 1227 | + if (!OidIsValid(rel->rd_toastoid)) |
| 1228 | + { |
| 1229 | + /* normal case: just choose an unused OID */ |
| 1230 | + toast_pointer.va_valueid = |
| 1231 | + GetNewOidWithIndex(toastrel, |
| 1232 | + RelationGetRelid(toastidx), |
| 1233 | + (AttrNumber) 1); |
| 1234 | + } |
| 1235 | + else |
| 1236 | + { |
| 1237 | + /* rewrite case: check to see if value was in old toast table */ |
| 1238 | + toast_pointer.va_valueid = InvalidOid; |
| 1239 | + if (oldexternal != NULL) |
| 1240 | + { |
| 1241 | + struct varatt_external old_toast_pointer; |
| 1242 | + |
| 1243 | + Assert(VARATT_IS_EXTERNAL(oldexternal)); |
| 1244 | + /* Must copy to access aligned fields */ |
| 1245 | + VARATT_EXTERNAL_GET_POINTER(old_toast_pointer, oldexternal); |
| 1246 | + if (old_toast_pointer.va_toastrelid == rel->rd_toastoid) |
| 1247 | + toast_pointer.va_valueid = old_toast_pointer.va_valueid; |
| 1248 | + } |
| 1249 | + if (toast_pointer.va_valueid == InvalidOid) |
| 1250 | + { |
| 1251 | + /* |
| 1252 | + * new value; must choose an OID that doesn't conflict in either |
| 1253 | + * old or new toast table |
| 1254 | + */ |
| 1255 | + do |
| 1256 | + { |
| 1257 | + toast_pointer.va_valueid = |
| 1258 | + GetNewOidWithIndex(toastrel, |
| 1259 | + RelationGetRelid(toastidx), |
| 1260 | + (AttrNumber) 1); |
| 1261 | + } while (toast_valueid_exists(rel->rd_toastoid, |
| 1262 | + toast_pointer.va_valueid)); |
| 1263 | + } |
| 1264 | + } |
1207 | 1265 |
|
1208 | 1266 | /* |
1209 | 1267 | * Initialize constant parts of the tuple data |
@@ -1338,6 +1396,52 @@ toast_delete_datum(Relation rel, Datum value) |
1338 | 1396 | } |
1339 | 1397 |
|
1340 | 1398 |
|
| 1399 | +/* ---------- |
| 1400 | + * toast_valueid_exists - |
| 1401 | + * |
| 1402 | + * Test whether a toast value with the given ID exists in the toast relation |
| 1403 | + * ---------- |
| 1404 | + */ |
| 1405 | +static bool |
| 1406 | +toast_valueid_exists(Oid toastrelid, Oid valueid) |
| 1407 | +{ |
| 1408 | + bool result = false; |
| 1409 | + Relation toastrel; |
| 1410 | + ScanKeyData toastkey; |
| 1411 | + SysScanDesc toastscan; |
| 1412 | + |
| 1413 | + /* |
| 1414 | + * Open the toast relation |
| 1415 | + */ |
| 1416 | + toastrel = heap_open(toastrelid, AccessShareLock); |
| 1417 | + |
| 1418 | + /* |
| 1419 | + * Setup a scan key to find chunks with matching va_valueid |
| 1420 | + */ |
| 1421 | + ScanKeyInit(&toastkey, |
| 1422 | + (AttrNumber) 1, |
| 1423 | + BTEqualStrategyNumber, F_OIDEQ, |
| 1424 | + ObjectIdGetDatum(valueid)); |
| 1425 | + |
| 1426 | + /* |
| 1427 | + * Is there any such chunk? |
| 1428 | + */ |
| 1429 | + toastscan = systable_beginscan(toastrel, toastrel->rd_rel->reltoastidxid, |
| 1430 | + true, SnapshotToast, 1, &toastkey); |
| 1431 | + |
| 1432 | + if (systable_getnext(toastscan) != NULL) |
| 1433 | + result = true; |
| 1434 | + |
| 1435 | + /* |
| 1436 | + * End scan and close relations |
| 1437 | + */ |
| 1438 | + systable_endscan(toastscan); |
| 1439 | + heap_close(toastrel, AccessShareLock); |
| 1440 | + |
| 1441 | + return result; |
| 1442 | +} |
| 1443 | + |
| 1444 | + |
1341 | 1445 | /* ---------- |
1342 | 1446 | * toast_fetch_datum - |
1343 | 1447 | * |
|
0 commit comments