Skip to content

Commit 3819e05

Browse files
committed
Read toast chunks in ascending order, looping if required
Toast chunks might be stored out-of-order in the table. We previously ignored that and hoped for the best, but our toast.sql test actually exhibits the problem on pre-14 servers where the 5th chunk of the last test is small enough to fit into the first disk block. Fix by looping over the table until all chunks have been read. A smarter solution would require either toast index lookups or caching the chunks. Close #20. Author: Christoph Berg <[email protected]> Debugging-By: Svetlana Derevyanko <[email protected]>
1 parent 549383a commit 3819e05

File tree

5 files changed

+86
-40
lines changed

5 files changed

+86
-40
lines changed

decode.c

Lines changed: 58 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1364,16 +1364,21 @@ ReadStringFromToast(const char *buffer,
13641364
varatt_external toast_ptr;
13651365
char *toast_data = NULL;
13661366
/* Number of chunks the TOAST data is divided into */
1367-
int32 num_chunks;
1367+
uint32 num_chunks;
1368+
/* Next chunk to read */
1369+
uint32 want_chunk_id = 0;
13681370
/* Actual size of external TOASTed value */
13691371
int32 toast_ext_size;
13701372
/* Path to directory with TOAST relation file */
13711373
char *toast_relation_path;
13721374
/* Filename of TOAST relation file */
13731375
char toast_relation_filename[MAXPGPATH];
13741376
FILE *toast_rel_fp;
1377+
unsigned int toast_relation_block_size;
1378+
unsigned int toastDataRead = 0;
13751379
unsigned int block_options = 0;
13761380
unsigned int control_options = 0;
1381+
int loops = 0;
13771382

13781383
VARATT_EXTERNAL_GET_POINTER(toast_ptr, buffer);
13791384

@@ -1383,7 +1388,7 @@ ReadStringFromToast(const char *buffer,
13831388
#else
13841389
toast_ext_size = toast_ptr.va_extsize;
13851390
#endif
1386-
num_chunks = (toast_ext_size - 1) / TOAST_MAX_CHUNK_SIZE + 1;
1391+
num_chunks = ((toast_ext_size - 1) / TOAST_MAX_CHUNK_SIZE) + 1;
13871392

13881393
printf(" TOAST value. Raw size: %8d, external size: %8d, "
13891394
"value id: %6d, toast relation id: %6d, chunks: %6d\n",
@@ -1399,46 +1404,63 @@ ReadStringFromToast(const char *buffer,
13991404
sprintf(toast_relation_filename, "%s/%d",
14001405
*toast_relation_path ? toast_relation_path : ".",
14011406
toast_ptr.va_toastrelid);
1407+
free(toast_relation_path);
14021408
toast_rel_fp = fopen(toast_relation_filename, "rb");
14031409
if (!toast_rel_fp) {
14041410
printf("Cannot open TOAST relation %s\n",
14051411
toast_relation_filename);
1406-
result = -1;
1412+
return -1;
14071413
}
1408-
else
1414+
1415+
toast_relation_block_size = GetBlockSize(toast_rel_fp);
1416+
toast_data = malloc(toast_ptr.va_rawsize);
1417+
1418+
/* Loop until all chunks have been read */
1419+
while (want_chunk_id < num_chunks)
14091420
{
1410-
unsigned int toast_relation_block_size = GetBlockSize(toast_rel_fp);
1421+
/* Restart at beginning of file */
14111422
fseek(toast_rel_fp, 0, SEEK_SET);
1412-
toast_data = malloc(toast_ptr.va_rawsize);
14131423

14141424
result = DumpFileContents(block_options,
1415-
control_options,
1416-
toast_rel_fp,
1417-
toast_relation_block_size,
1418-
-1, /* no start block */
1419-
-1, /* no end block */
1420-
true, /* is toast relation */
1421-
toast_ptr.va_valueid,
1422-
toast_ext_size,
1423-
toast_data);
1424-
1425-
if (result == 0)
1426-
{
1427-
if (VARATT_EXTERNAL_IS_COMPRESSED(toast_ptr))
1428-
result = DumpCompressedString(toast_data, toast_ext_size, parse_value);
1429-
else
1430-
result = parse_value(toast_data, toast_ext_size);
1431-
}
1432-
else
1425+
control_options,
1426+
toast_rel_fp,
1427+
toast_relation_block_size,
1428+
-1, /* no start block */
1429+
-1, /* no end block */
1430+
true, /* is toast relation */
1431+
toast_ptr.va_valueid,
1432+
&want_chunk_id,
1433+
toast_ext_size,
1434+
toast_data,
1435+
&toastDataRead);
1436+
1437+
if (loops++ > num_chunks)
14331438
{
1434-
printf("Error in TOAST file.\n");
1439+
printf("Not all TOAST chunks found after scanning TOAST table %" PRIu32 " times, giving up\n",
1440+
num_chunks);
1441+
result = -1;
14351442
}
14361443

1437-
free(toast_data);
1438-
fclose(toast_rel_fp);
1444+
if (result != 0)
1445+
break;
14391446
}
14401447

1441-
free(toast_relation_path);
1448+
fclose(toast_rel_fp);
1449+
1450+
if (result == 0)
1451+
{
1452+
if (VARATT_EXTERNAL_IS_COMPRESSED(toast_ptr))
1453+
result = DumpCompressedString(toast_data, toast_ext_size, parse_value);
1454+
else
1455+
result = parse_value(toast_data, toast_ext_size);
1456+
}
1457+
else
1458+
{
1459+
printf("Error in TOAST file.\n");
1460+
}
1461+
1462+
free(toast_data);
1463+
14421464
}
14431465
/* If tag is indirect or expanded, it was stored in memory. */
14441466
else
@@ -1516,6 +1538,7 @@ ToastChunkDecode(const char *tuple_data,
15161538
Oid toast_oid,
15171539
Oid *read_toast_oid,
15181540
uint32 *chunk_id,
1541+
uint32 *want_chunk_id,
15191542
char *chunk_data,
15201543
unsigned int *chunk_data_size)
15211544
{
@@ -1561,6 +1584,10 @@ ToastChunkDecode(const char *tuple_data,
15611584
return;
15621585
}
15631586

1587+
/* Not the chunk we want to read next */
1588+
if (*chunk_id != *want_chunk_id)
1589+
return;
1590+
15641591
size -= processed_size;
15651592
data += processed_size;
15661593
if (size <= 0)
@@ -1587,4 +1614,7 @@ ToastChunkDecode(const char *tuple_data,
15871614
"Partial data: %s\n", size, copyString.data);
15881615
return;
15891616
}
1617+
1618+
/* advance to next chunk */
1619+
*want_chunk_id += 1;
15901620
}

decode.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ ToastChunkDecode(const char* tuple_data,
2121
Oid toast_oid,
2222
Oid *read_toast_oid,
2323
uint32 *chunk_id,
24+
uint32 *want_chunk_id,
2425
char *chunk_data,
2526
unsigned int *chunk_data_size);
2627

expected/toast_4.out

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ Block 0 ********************************************************
7878
<Header> -----
7979
Block Offset: 0x00000000 Offsets: Lower 52 (0x0034)
8080
Block: Size 8192 Version 4 Upper 7476 (0x1d34)
81-
LSN: logid . recoff 0x........ Special 8192 (0x2000)
81+
LSN: logid ...... recoff 0x........ Special 8192 (0x2000)
8282
Items: 7 Free Space: 7424
8383
Checksum: 0x.... Prune XID: 0x00000000 Flags: 0x0004 (ALL_VISIBLE)
8484
Length (including item array): 52

pg_filedump.c

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,7 @@ static void FormatBlock(unsigned int blockOptions,
123123
unsigned int blockSize,
124124
bool isToast,
125125
Oid toastOid,
126+
uint32 *want_chunk_id,
126127
unsigned int toastExternalSize,
127128
char *toastValue,
128129
unsigned int *toastRead);
@@ -137,6 +138,7 @@ static void FormatItemBlock(char *buffer,
137138
Page page,
138139
bool isToast,
139140
Oid toastOid,
141+
uint32 *want_chunk_id,
140142
unsigned int toastExternalSize,
141143
char *toastValue,
142144
unsigned int *toastRead);
@@ -1386,6 +1388,7 @@ FormatItemBlock(char *buffer,
13861388
Page page,
13871389
bool isToast,
13881390
Oid toastOid,
1391+
uint32 *want_chunk_id,
13891392
unsigned int toastExternalSize,
13901393
char *toastValue,
13911394
unsigned int *toastRead)
@@ -1557,13 +1560,19 @@ FormatItemBlock(char *buffer,
15571560
Oid read_toast_oid;
15581561

15591562
ToastChunkDecode(&buffer[itemOffset], itemSize, toastOid,
1560-
&read_toast_oid, &chunkId, toastValue + *toastRead,
1563+
&read_toast_oid, &chunkId, want_chunk_id, toastValue + *toastRead,
15611564
&chunkSize);
15621565

15631566
if (verbose && read_toast_oid == toastOid)
1564-
printf("%s Read TOAST chunk. TOAST Oid: %d, chunk id: %d, "
1565-
"chunk data size: %d\n",
1566-
indent, toastOid, chunkId, chunkSize);
1567+
{
1568+
if (chunkId == *want_chunk_id - 1) /* value already incremented */
1569+
printf("%s Read TOAST chunk. TOAST Oid: %d, chunk id: %d, "
1570+
"chunk data size: %d\n",
1571+
indent, toastOid, chunkId, chunkSize);
1572+
else
1573+
printf("%s Skipped out-of-order TOAST chunk. TOAST Oid: %d, chunk id: %d\n",
1574+
indent, toastOid, chunkId);
1575+
}
15671576

15681577
*toastRead += chunkSize;
15691578

@@ -2096,6 +2105,7 @@ FormatBlock(unsigned int blockOptions,
20962105
unsigned int blockSize,
20972106
bool isToast,
20982107
Oid toastOid,
2108+
uint32 *want_chunk_id,
20992109
unsigned int toastExternalSize,
21002110
char *toastValue,
21012111
unsigned int *toastRead)
@@ -2132,6 +2142,7 @@ FormatBlock(unsigned int blockOptions,
21322142
page,
21332143
isToast,
21342144
toastOid,
2145+
want_chunk_id,
21352146
toastExternalSize,
21362147
toastValue,
21372148
toastRead);
@@ -2372,12 +2383,13 @@ DumpFileContents(unsigned int blockOptions,
23722383
int blockEnd,
23732384
bool isToast,
23742385
Oid toastOid,
2386+
uint32 *want_chunk_id,
23752387
unsigned int toastExternalSize,
2376-
char *toastValue)
2388+
char *toastValue,
2389+
unsigned int *toastDataRead)
23772390
{
23782391
unsigned int initialRead = 1;
23792392
unsigned int contentsToDump = 1;
2380-
unsigned int toastDataRead = 0;
23812393
BlockNumber currentBlock = 0;
23822394
int result = 0;
23832395
/* On a positive block size, allocate a local buffer to store
@@ -2419,7 +2431,7 @@ DumpFileContents(unsigned int blockOptions,
24192431
* subsequent read gets the error. */
24202432
if (initialRead)
24212433
printf("Error: Premature end of file encountered.\n");
2422-
else if (!(blockOptions & BLOCK_BINARY))
2434+
else if (!(blockOptions & BLOCK_BINARY) && (!isToast || verbose))
24232435
printf("\n*** End of File Encountered. Last Block "
24242436
"Read: %d ***\n", currentBlock - 1);
24252437

@@ -2445,9 +2457,10 @@ DumpFileContents(unsigned int blockOptions,
24452457
blockSize,
24462458
isToast,
24472459
toastOid,
2460+
want_chunk_id,
24482461
toastExternalSize,
24492462
toastValue,
2450-
&toastDataRead);
2463+
toastDataRead);
24512464
}
24522465
}
24532466
}
@@ -2468,7 +2481,7 @@ DumpFileContents(unsigned int blockOptions,
24682481
initialRead = 0;
24692482

24702483
/* If TOAST data is read */
2471-
if (isToast && toastDataRead >= toastExternalSize)
2484+
if (isToast && *toastDataRead >= toastExternalSize)
24722485
break;
24732486
}
24742487

@@ -2583,8 +2596,10 @@ main(int argv, char **argc)
25832596
blockEnd,
25842597
false /* is toast realtion */,
25852598
0, /* no toast Oid */
2599+
NULL, /* no current TOAST chunk */
25862600
0, /* no toast external size */
2587-
NULL /* no out toast value */
2601+
NULL, /* no out toast value */
2602+
NULL /* no toast value length */
25882603
);
25892604
}
25902605

pg_filedump.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,4 +154,4 @@ unsigned int GetBlockSize(FILE *fp);
154154
int DumpFileContents(unsigned int blockOptions, unsigned int controlOptions,
155155
FILE *fp, unsigned int blockSize, int blockStart,
156156
int blockEnd, bool isToast, Oid toastOid,
157-
unsigned int toastExternalSize, char *toastValue);
157+
uint32 *want_chunk_id, unsigned int toastExternalSize, char *toastValue, unsigned int *toastDataRead);

0 commit comments

Comments
 (0)