Age Owner Branch data TLA Line data Source code
1 : : /*-------------------------------------------------------------------------
2 : : *
3 : : * toast_compression.c
4 : : * Functions for toast compression.
5 : : *
6 : : * Copyright (c) 2021-2024, PostgreSQL Global Development Group
7 : : *
8 : : *
9 : : * IDENTIFICATION
10 : : * src/backend/access/common/toast_compression.c
11 : : *
12 : : *-------------------------------------------------------------------------
13 : : */
14 : : #include "postgres.h"
15 : :
16 : : #ifdef USE_LZ4
17 : : #include <lz4.h>
18 : : #endif
19 : :
20 : : #include "access/detoast.h"
21 : : #include "access/toast_compression.h"
22 : : #include "common/pg_lzcompress.h"
23 : : #include "varatt.h"
24 : :
25 : : /* GUC */
26 : : int default_toast_compression = TOAST_PGLZ_COMPRESSION;
27 : :
28 : : #define NO_LZ4_SUPPORT() \
29 : : ereport(ERROR, \
30 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), \
31 : : errmsg("compression method lz4 not supported"), \
32 : : errdetail("This functionality requires the server to be built with lz4 support.")))
33 : :
34 : : /*
35 : : * Compress a varlena using PGLZ.
36 : : *
37 : : * Returns the compressed varlena, or NULL if compression fails.
38 : : */
39 : : struct varlena *
1122 rhaas@postgresql.org 40 :CBC 22381 : pglz_compress_datum(const struct varlena *value)
41 : : {
42 : : int32 valsize,
43 : : len;
44 : 22381 : struct varlena *tmp = NULL;
45 : :
595 peter@eisentraut.org 46 [ - + - - : 22381 : valsize = VARSIZE_ANY_EXHDR(value);
- - - - +
+ ]
47 : :
48 : : /*
49 : : * No point in wasting a palloc cycle if value size is outside the allowed
50 : : * range for compression.
51 : : */
1122 rhaas@postgresql.org 52 [ + + ]: 22381 : if (valsize < PGLZ_strategy_default->min_input_size ||
53 [ - + ]: 22033 : valsize > PGLZ_strategy_default->max_input_size)
54 : 348 : return NULL;
55 : :
56 : : /*
57 : : * Figure out the maximum possible size of the pglz output, add the bytes
58 : : * that will be needed for varlena overhead, and allocate that amount.
59 : : */
60 : 22033 : tmp = (struct varlena *) palloc(PGLZ_MAX_OUTPUT(valsize) +
61 : : VARHDRSZ_COMPRESSED);
62 : :
63 [ + + ]: 22033 : len = pglz_compress(VARDATA_ANY(value),
64 : : valsize,
65 : : (char *) tmp + VARHDRSZ_COMPRESSED,
66 : : NULL);
67 [ + + ]: 22033 : if (len < 0)
68 : : {
69 : 5465 : pfree(tmp);
70 : 5465 : return NULL;
71 : : }
72 : :
1119 tgl@sss.pgh.pa.us 73 : 16568 : SET_VARSIZE_COMPRESSED(tmp, len + VARHDRSZ_COMPRESSED);
74 : :
1122 rhaas@postgresql.org 75 : 16568 : return tmp;
76 : : }
77 : :
78 : : /*
79 : : * Decompress a varlena that was compressed using PGLZ.
80 : : */
81 : : struct varlena *
82 : 65457 : pglz_decompress_datum(const struct varlena *value)
83 : : {
84 : : struct varlena *result;
85 : : int32 rawsize;
86 : :
87 : : /* allocate memory for the uncompressed data */
1119 tgl@sss.pgh.pa.us 88 : 65457 : result = (struct varlena *) palloc(VARDATA_COMPRESSED_GET_EXTSIZE(value) + VARHDRSZ);
89 : :
90 : : /* decompress the data */
91 : 65457 : rawsize = pglz_decompress((char *) value + VARHDRSZ_COMPRESSED,
92 : 65457 : VARSIZE(value) - VARHDRSZ_COMPRESSED,
1122 rhaas@postgresql.org 93 : 65457 : VARDATA(result),
1119 tgl@sss.pgh.pa.us 94 : 65457 : VARDATA_COMPRESSED_GET_EXTSIZE(value), true);
1122 rhaas@postgresql.org 95 [ - + ]: 65457 : if (rawsize < 0)
1122 rhaas@postgresql.org 96 [ # # ]:UBC 0 : ereport(ERROR,
97 : : (errcode(ERRCODE_DATA_CORRUPTED),
98 : : errmsg_internal("compressed pglz data is corrupt")));
99 : :
1122 rhaas@postgresql.org 100 :CBC 65457 : SET_VARSIZE(result, rawsize + VARHDRSZ);
101 : :
102 : 65457 : return result;
103 : : }
104 : :
105 : : /*
106 : : * Decompress part of a varlena that was compressed using PGLZ.
107 : : */
108 : : struct varlena *
109 : 33 : pglz_decompress_datum_slice(const struct varlena *value,
110 : : int32 slicelength)
111 : : {
112 : : struct varlena *result;
113 : : int32 rawsize;
114 : :
115 : : /* allocate memory for the uncompressed data */
116 : 33 : result = (struct varlena *) palloc(slicelength + VARHDRSZ);
117 : :
118 : : /* decompress the data */
1119 tgl@sss.pgh.pa.us 119 : 33 : rawsize = pglz_decompress((char *) value + VARHDRSZ_COMPRESSED,
120 : 33 : VARSIZE(value) - VARHDRSZ_COMPRESSED,
1122 rhaas@postgresql.org 121 : 33 : VARDATA(result),
122 : : slicelength, false);
123 [ - + ]: 33 : if (rawsize < 0)
1122 rhaas@postgresql.org 124 [ # # ]:UBC 0 : ereport(ERROR,
125 : : (errcode(ERRCODE_DATA_CORRUPTED),
126 : : errmsg_internal("compressed pglz data is corrupt")));
127 : :
1122 rhaas@postgresql.org 128 :CBC 33 : SET_VARSIZE(result, rawsize + VARHDRSZ);
129 : :
130 : 33 : return result;
131 : : }
132 : :
133 : : /*
134 : : * Compress a varlena using LZ4.
135 : : *
136 : : * Returns the compressed varlena, or NULL if compression fails.
137 : : */
138 : : struct varlena *
139 : 18 : lz4_compress_datum(const struct varlena *value)
140 : : {
141 : : #ifndef USE_LZ4
142 : : NO_LZ4_SUPPORT();
143 : : return NULL; /* keep compiler quiet */
144 : : #else
145 : : int32 valsize;
146 : : int32 len;
147 : : int32 max_size;
148 : 18 : struct varlena *tmp = NULL;
149 : :
150 [ - + - - : 18 : valsize = VARSIZE_ANY_EXHDR(value);
- - - - -
+ ]
151 : :
152 : : /*
153 : : * Figure out the maximum possible size of the LZ4 output, add the bytes
154 : : * that will be needed for varlena overhead, and allocate that amount.
155 : : */
156 : 18 : max_size = LZ4_compressBound(valsize);
1119 tgl@sss.pgh.pa.us 157 : 18 : tmp = (struct varlena *) palloc(max_size + VARHDRSZ_COMPRESSED);
158 : :
1122 rhaas@postgresql.org 159 [ - + ]: 18 : len = LZ4_compress_default(VARDATA_ANY(value),
160 : : (char *) tmp + VARHDRSZ_COMPRESSED,
161 : : valsize, max_size);
162 [ - + ]: 18 : if (len <= 0)
1122 rhaas@postgresql.org 163 [ # # ]:UBC 0 : elog(ERROR, "lz4 compression failed");
164 : :
165 : : /* data is incompressible so just free the memory and return NULL */
1122 rhaas@postgresql.org 166 [ - + ]:CBC 18 : if (len > valsize)
167 : : {
1122 rhaas@postgresql.org 168 :UBC 0 : pfree(tmp);
169 : 0 : return NULL;
170 : : }
171 : :
1119 tgl@sss.pgh.pa.us 172 :CBC 18 : SET_VARSIZE_COMPRESSED(tmp, len + VARHDRSZ_COMPRESSED);
173 : :
1122 rhaas@postgresql.org 174 : 18 : return tmp;
175 : : #endif
176 : : }
177 : :
178 : : /*
179 : : * Decompress a varlena that was compressed using LZ4.
180 : : */
181 : : struct varlena *
182 : 50 : lz4_decompress_datum(const struct varlena *value)
183 : : {
184 : : #ifndef USE_LZ4
185 : : NO_LZ4_SUPPORT();
186 : : return NULL; /* keep compiler quiet */
187 : : #else
188 : : int32 rawsize;
189 : : struct varlena *result;
190 : :
191 : : /* allocate memory for the uncompressed data */
1119 tgl@sss.pgh.pa.us 192 : 50 : result = (struct varlena *) palloc(VARDATA_COMPRESSED_GET_EXTSIZE(value) + VARHDRSZ);
193 : :
194 : : /* decompress the data */
195 : 50 : rawsize = LZ4_decompress_safe((char *) value + VARHDRSZ_COMPRESSED,
1122 rhaas@postgresql.org 196 : 50 : VARDATA(result),
1119 tgl@sss.pgh.pa.us 197 : 50 : VARSIZE(value) - VARHDRSZ_COMPRESSED,
198 : 50 : VARDATA_COMPRESSED_GET_EXTSIZE(value));
1122 rhaas@postgresql.org 199 [ - + ]: 50 : if (rawsize < 0)
1122 rhaas@postgresql.org 200 [ # # ]:UBC 0 : ereport(ERROR,
201 : : (errcode(ERRCODE_DATA_CORRUPTED),
202 : : errmsg_internal("compressed lz4 data is corrupt")));
203 : :
204 : :
1122 rhaas@postgresql.org 205 :CBC 50 : SET_VARSIZE(result, rawsize + VARHDRSZ);
206 : :
207 : 50 : return result;
208 : : #endif
209 : : }
210 : :
211 : : /*
212 : : * Decompress part of a varlena that was compressed using LZ4.
213 : : */
214 : : struct varlena *
215 : 9 : lz4_decompress_datum_slice(const struct varlena *value, int32 slicelength)
216 : : {
217 : : #ifndef USE_LZ4
218 : : NO_LZ4_SUPPORT();
219 : : return NULL; /* keep compiler quiet */
220 : : #else
221 : : int32 rawsize;
222 : : struct varlena *result;
223 : :
224 : : /* slice decompression not supported prior to 1.8.3 */
225 [ - + ]: 9 : if (LZ4_versionNumber() < 10803)
1122 rhaas@postgresql.org 226 :UBC 0 : return lz4_decompress_datum(value);
227 : :
228 : : /* allocate memory for the uncompressed data */
1122 rhaas@postgresql.org 229 :CBC 9 : result = (struct varlena *) palloc(slicelength + VARHDRSZ);
230 : :
231 : : /* decompress the data */
1119 tgl@sss.pgh.pa.us 232 : 9 : rawsize = LZ4_decompress_safe_partial((char *) value + VARHDRSZ_COMPRESSED,
1122 rhaas@postgresql.org 233 : 9 : VARDATA(result),
1119 tgl@sss.pgh.pa.us 234 : 9 : VARSIZE(value) - VARHDRSZ_COMPRESSED,
235 : : slicelength,
236 : : slicelength);
1122 rhaas@postgresql.org 237 [ - + ]: 9 : if (rawsize < 0)
1122 rhaas@postgresql.org 238 [ # # ]:UBC 0 : ereport(ERROR,
239 : : (errcode(ERRCODE_DATA_CORRUPTED),
240 : : errmsg_internal("compressed lz4 data is corrupt")));
241 : :
1122 rhaas@postgresql.org 242 :CBC 9 : SET_VARSIZE(result, rawsize + VARHDRSZ);
243 : :
244 : 9 : return result;
245 : : #endif
246 : : }
247 : :
248 : : /*
249 : : * Extract compression ID from a varlena.
250 : : *
251 : : * Returns TOAST_INVALID_COMPRESSION_ID if the varlena is not compressed.
252 : : */
253 : : ToastCompressionId
254 : 81 : toast_get_compression_id(struct varlena *attr)
255 : : {
1068 tgl@sss.pgh.pa.us 256 : 81 : ToastCompressionId cmid = TOAST_INVALID_COMPRESSION_ID;
257 : :
258 : : /*
259 : : * If it is stored externally then fetch the compression method id from
260 : : * the external toast pointer. If compressed inline, fetch it from the
261 : : * toast compression header.
262 : : */
1122 rhaas@postgresql.org 263 [ + + + - ]: 81 : if (VARATT_IS_EXTERNAL_ONDISK(attr))
264 : 12 : {
265 : : struct varatt_external toast_pointer;
266 : :
267 [ - + + - : 12 : VARATT_EXTERNAL_GET_POINTER(toast_pointer, attr);
+ - - + -
+ ]
268 : :
269 [ + - ]: 12 : if (VARATT_EXTERNAL_IS_COMPRESSED(toast_pointer))
1119 tgl@sss.pgh.pa.us 270 : 12 : cmid = VARATT_EXTERNAL_GET_COMPRESS_METHOD(toast_pointer);
271 : : }
1122 rhaas@postgresql.org 272 [ + + ]: 69 : else if (VARATT_IS_COMPRESSED(attr))
1119 tgl@sss.pgh.pa.us 273 : 66 : cmid = VARDATA_COMPRESSED_GET_COMPRESS_METHOD(attr);
274 : :
1122 rhaas@postgresql.org 275 : 81 : return cmid;
276 : : }
277 : :
278 : : /*
279 : : * CompressionNameToMethod - Get compression method from compression name
280 : : *
281 : : * Search in the available built-in methods. If the compression not found
282 : : * in the built-in methods then return InvalidCompressionMethod.
283 : : */
284 : : char
1117 285 : 68 : CompressionNameToMethod(const char *compression)
286 : : {
287 [ + + ]: 68 : if (strcmp(compression, "pglz") == 0)
288 : 30 : return TOAST_PGLZ_COMPRESSION;
289 [ + + ]: 38 : else if (strcmp(compression, "lz4") == 0)
290 : : {
291 : : #ifndef USE_LZ4
292 : : NO_LZ4_SUPPORT();
293 : : #endif
294 : 32 : return TOAST_LZ4_COMPRESSION;
295 : : }
296 : :
297 : 6 : return InvalidCompressionMethod;
298 : : }
299 : :
300 : : /*
301 : : * GetCompressionMethodName - Get compression method name
302 : : */
303 : : const char *
304 : 15 : GetCompressionMethodName(char method)
305 : : {
306 [ + + - ]: 15 : switch (method)
307 : : {
308 : 6 : case TOAST_PGLZ_COMPRESSION:
309 : 6 : return "pglz";
310 : 9 : case TOAST_LZ4_COMPRESSION:
311 : 9 : return "lz4";
1117 rhaas@postgresql.org 312 :UBC 0 : default:
313 [ # # ]: 0 : elog(ERROR, "invalid compression method %c", method);
314 : : return NULL; /* keep compiler quiet */
315 : : }
316 : : }
|