Skip to content

Commit 4d0e994

Browse files
committed
Add support for partial TOAST decompression
When asked for a slice of a TOAST entry, decompress enough to return the slice instead of decompressing the entire object. For use cases where the slice is at, or near, the beginning of the entry, this avoids a lot of unnecessary decompression work. This changes the signature of pglz_decompress() by adding a boolean to indicate if it's ok for the call to finish before consuming all of the source or destination buffers. Author: Paul Ramsey Reviewed-By: Rafia Sabih, Darafei Praliaskouski, Regina Obe Discussion: https://postgr.es/m/CACowWR07EDm7Y4m2kbhN_jnys%3DBBf9A6768RyQdKm_%3DNpkcaWg%40mail.gmail.com
1 parent d50d172 commit 4d0e994

File tree

5 files changed

+70
-36
lines changed

5 files changed

+70
-36
lines changed

src/backend/access/heap/tuptoaster.c

+36-2
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ static struct varlena *toast_fetch_datum(struct varlena *attr);
7575
static struct varlena *toast_fetch_datum_slice(struct varlena *attr,
7676
int32 sliceoffset, int32 length);
7777
static struct varlena *toast_decompress_datum(struct varlena *attr);
78+
static struct varlena *toast_decompress_datum_slice(struct varlena *attr, int32 slicelength);
7879
static int toast_open_indexes(Relation toastrel,
7980
LOCKMODE lock,
8081
Relation **toastidxs,
@@ -301,7 +302,11 @@ heap_tuple_untoast_attr_slice(struct varlena *attr,
301302
{
302303
struct varlena *tmp = preslice;
303304

304-
preslice = toast_decompress_datum(tmp);
305+
/* Decompress enough to encompass the slice and the offset */
306+
if (slicelength > 0 && sliceoffset >= 0)
307+
preslice = toast_decompress_datum_slice(tmp, slicelength + sliceoffset);
308+
else
309+
preslice = toast_decompress_datum(tmp);
305310

306311
if (tmp != attr)
307312
pfree(tmp);
@@ -2272,9 +2277,38 @@ toast_decompress_datum(struct varlena *attr)
22722277
if (pglz_decompress(TOAST_COMPRESS_RAWDATA(attr),
22732278
VARSIZE(attr) - TOAST_COMPRESS_HDRSZ,
22742279
VARDATA(result),
2275-
TOAST_COMPRESS_RAWSIZE(attr)) < 0)
2280+
TOAST_COMPRESS_RAWSIZE(attr), true) < 0)
2281+
elog(ERROR, "compressed data is corrupted");
2282+
2283+
return result;
2284+
}
2285+
2286+
2287+
/* ----------
2288+
* toast_decompress_datum_slice -
2289+
*
2290+
* Decompress the front of a compressed version of a varlena datum.
2291+
* offset handling happens in heap_tuple_untoast_attr_slice.
2292+
* Here we just decompress a slice from the front.
2293+
*/
2294+
static struct varlena *
2295+
toast_decompress_datum_slice(struct varlena *attr, int32 slicelength)
2296+
{
2297+
struct varlena *result;
2298+
int32 rawsize;
2299+
2300+
Assert(VARATT_IS_COMPRESSED(attr));
2301+
2302+
result = (struct varlena *) palloc(slicelength + VARHDRSZ);
2303+
2304+
rawsize = pglz_decompress(TOAST_COMPRESS_RAWDATA(attr),
2305+
VARSIZE(attr) - TOAST_COMPRESS_HDRSZ,
2306+
VARDATA(result),
2307+
slicelength, false);
2308+
if (rawsize < 0)
22762309
elog(ERROR, "compressed data is corrupted");
22772310

2311+
SET_VARSIZE(result, rawsize + VARHDRSZ);
22782312
return result;
22792313
}
22802314

src/backend/access/transam/xlogreader.c

+1-1
Original file line numberDiff line numberDiff line change
@@ -1425,7 +1425,7 @@ RestoreBlockImage(XLogReaderState *record, uint8 block_id, char *page)
14251425
{
14261426
/* If a backup block image is compressed, decompress it */
14271427
if (pglz_decompress(ptr, bkpb->bimg_len, tmp.data,
1428-
BLCKSZ - bkpb->hole_length) < 0)
1428+
BLCKSZ - bkpb->hole_length, true) < 0)
14291429
{
14301430
report_invalid_record(record, "invalid compressed image at %X/%X, block %d",
14311431
(uint32) (record->ReadRecPtr >> 32),

src/backend/utils/adt/varlena.c

+13-9
Original file line numberDiff line numberDiff line change
@@ -1894,7 +1894,7 @@ text_starts_with(PG_FUNCTION_ARGS)
18941894
result = false;
18951895
else
18961896
{
1897-
text *targ1 = DatumGetTextPP(arg1);
1897+
text *targ1 = text_substring(arg1, 1, len2, false);
18981898
text *targ2 = DatumGetTextPP(arg2);
18991899

19001900
result = (memcmp(VARDATA_ANY(targ1), VARDATA_ANY(targ2),
@@ -5346,17 +5346,21 @@ text_concat_ws(PG_FUNCTION_ARGS)
53465346
Datum
53475347
text_left(PG_FUNCTION_ARGS)
53485348
{
5349-
text *str = PG_GETARG_TEXT_PP(0);
5350-
const char *p = VARDATA_ANY(str);
5351-
int len = VARSIZE_ANY_EXHDR(str);
5352-
int n = PG_GETARG_INT32(1);
5353-
int rlen;
5349+
int n = PG_GETARG_INT32(1);
53545350

53555351
if (n < 0)
5356-
n = pg_mbstrlen_with_len(p, len) + n;
5357-
rlen = pg_mbcharcliplen(p, len, n);
5352+
{
5353+
text *str = PG_GETARG_TEXT_PP(0);
5354+
const char *p = VARDATA_ANY(str);
5355+
int len = VARSIZE_ANY_EXHDR(str);
5356+
int rlen;
53585357

5359-
PG_RETURN_TEXT_P(cstring_to_text_with_len(p, rlen));
5358+
n = pg_mbstrlen_with_len(p, len) + n;
5359+
rlen = pg_mbcharcliplen(p, len, n);
5360+
PG_RETURN_TEXT_P(cstring_to_text_with_len(p, rlen));
5361+
}
5362+
else
5363+
PG_RETURN_TEXT_P(text_substring(PG_GETARG_DATUM(0), 1, n, false));
53605364
}
53615365

53625366
/*

src/common/pg_lzcompress.c

+19-23
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
*
3030
* int32
3131
* pglz_decompress(const char *source, int32 slen, char *dest,
32-
* int32 rawsize)
32+
* int32 rawsize, bool check_complete)
3333
*
3434
* source is the compressed input.
3535
*
@@ -44,6 +44,12 @@
4444
*
4545
* rawsize is the length of the uncompressed data.
4646
*
47+
* check_complete is a flag to let us know if -1 should be
48+
* returned in cases where we don't reach the end of the
49+
* source or dest buffers, or not. This should be false
50+
* if the caller is asking for only a partial result and
51+
* true otherwise.
52+
*
4753
* The return value is the number of bytes written in the
4854
* buffer dest, or -1 if decompression fails.
4955
*
@@ -674,13 +680,14 @@ pglz_compress(const char *source, int32 slen, char *dest,
674680
* pglz_decompress -
675681
*
676682
* Decompresses source into dest. Returns the number of bytes
677-
* decompressed in the destination buffer, or -1 if decompression
678-
* fails.
683+
* decompressed in the destination buffer, and *optionally*
684+
* checks that both the source and dest buffers have been
685+
* fully read and written to, respectively.
679686
* ----------
680687
*/
681688
int32
682689
pglz_decompress(const char *source, int32 slen, char *dest,
683-
int32 rawsize)
690+
int32 rawsize, bool check_complete)
684691
{
685692
const unsigned char *sp;
686693
const unsigned char *srcend;
@@ -701,8 +708,9 @@ pglz_decompress(const char *source, int32 slen, char *dest,
701708
unsigned char ctrl = *sp++;
702709
int ctrlc;
703710

704-
for (ctrlc = 0; ctrlc < 8 && sp < srcend; ctrlc++)
711+
for (ctrlc = 0; ctrlc < 8 && sp < srcend && dp < destend; ctrlc++)
705712
{
713+
706714
if (ctrl & 1)
707715
{
708716
/*
@@ -721,25 +729,13 @@ pglz_decompress(const char *source, int32 slen, char *dest,
721729
if (len == 18)
722730
len += *sp++;
723731

724-
/*
725-
* Check for output buffer overrun, to ensure we don't clobber
726-
* memory in case of corrupt input. Note: we must advance dp
727-
* here to ensure the error is detected below the loop. We
728-
* don't simply put the elog inside the loop since that will
729-
* probably interfere with optimization.
730-
*/
731-
if (dp + len > destend)
732-
{
733-
dp += len;
734-
break;
735-
}
736-
737732
/*
738733
* Now we copy the bytes specified by the tag from OUTPUT to
739734
* OUTPUT. It is dangerous and platform dependent to use
740735
* memcpy() here, because the copied areas could overlap
741736
* extremely!
742737
*/
738+
len = Min(len, destend - dp);
743739
while (len--)
744740
{
745741
*dp = dp[-off];
@@ -752,9 +748,6 @@ pglz_decompress(const char *source, int32 slen, char *dest,
752748
* An unset control bit means LITERAL BYTE. So we just copy
753749
* one from INPUT to OUTPUT.
754750
*/
755-
if (dp >= destend) /* check for buffer overrun */
756-
break; /* do not clobber memory */
757-
758751
*dp++ = *sp++;
759752
}
760753

@@ -767,12 +760,15 @@ pglz_decompress(const char *source, int32 slen, char *dest,
767760

768761
/*
769762
* Check we decompressed the right amount.
763+
* If we are slicing, then we won't necessarily
764+
* be at the end of the source or dest buffers
765+
* when we hit a stop, so we don't test them.
770766
*/
771-
if (dp != destend || sp != srcend)
767+
if (check_complete && (dp != destend || sp != srcend))
772768
return -1;
773769

774770
/*
775771
* That's it.
776772
*/
777-
return rawsize;
773+
return (char*)dp - dest;
778774
}

src/include/common/pg_lzcompress.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,6 @@ extern const PGLZ_Strategy *const PGLZ_strategy_always;
8686
extern int32 pglz_compress(const char *source, int32 slen, char *dest,
8787
const PGLZ_Strategy *strategy);
8888
extern int32 pglz_decompress(const char *source, int32 slen, char *dest,
89-
int32 rawsize);
89+
int32 rawsize, bool check_complete);
9090

9191
#endif /* _PG_LZCOMPRESS_H_ */

0 commit comments

Comments
 (0)