1 /*-------------------------------------------------------------------------
5 * Shared code for compression methods and specifications.
7 * A compression specification specifies the parameters that should be used
8 * when performing compression with a specific algorithm. The simplest
9 * possible compression specification is an integer, which sets the
12 * Otherwise, a compression specification is a comma-separated list of items,
13 * each having the form keyword or keyword=value.
15 * Currently, the supported keywords are "level", "long", and "workers".
17 * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group
20 * src/common/compression.c
21 *-------------------------------------------------------------------------
27 #include "postgres_fe.h"
37 #include "common/compression.h"
39 static int expect_integer_value(char *keyword
, char *value
,
40 pg_compress_specification
*result
);
41 static bool expect_boolean_value(char *keyword
, char *value
,
42 pg_compress_specification
*result
);
45 * Look up a compression algorithm by name. Returns true and sets *algorithm
46 * if the name is recognized. Otherwise returns false.
49 parse_compress_algorithm(char *name
, pg_compress_algorithm
*algorithm
)
51 if (strcmp(name
, "none") == 0)
52 *algorithm
= PG_COMPRESSION_NONE
;
53 else if (strcmp(name
, "gzip") == 0)
54 *algorithm
= PG_COMPRESSION_GZIP
;
55 else if (strcmp(name
, "lz4") == 0)
56 *algorithm
= PG_COMPRESSION_LZ4
;
57 else if (strcmp(name
, "zstd") == 0)
58 *algorithm
= PG_COMPRESSION_ZSTD
;
65 * Get the human-readable name corresponding to a particular compression
69 get_compress_algorithm_name(pg_compress_algorithm algorithm
)
73 case PG_COMPRESSION_NONE
:
75 case PG_COMPRESSION_GZIP
:
77 case PG_COMPRESSION_LZ4
:
79 case PG_COMPRESSION_ZSTD
:
81 /* no default, to provoke compiler warnings if values are added */
84 return "???"; /* placate compiler */
88 * Parse a compression specification for a specified algorithm.
90 * See the file header comments for a brief description of what a compression
91 * specification is expected to look like.
93 * On return, all fields of the result object will be initialized.
94 * In particular, result->parse_error will be NULL if no errors occurred
95 * during parsing, and will otherwise contain an appropriate error message.
96 * The caller may free this error message string using pfree, if desired.
97 * Note, however, even if there's no parse error, the string might not make
98 * sense: e.g. for gzip, level=12 is not sensible, but it does parse OK.
100 * The compression level is assigned by default if not directly specified
101 * by the specification.
103 * Use validate_compress_specification() to find out whether a compression
104 * specification is semantically sensible.
107 parse_compress_specification(pg_compress_algorithm algorithm
, char *specification
,
108 pg_compress_specification
*result
)
111 char *bare_level_endp
;
113 /* Initial setup of result object. */
114 result
->algorithm
= algorithm
;
116 result
->parse_error
= NULL
;
119 * Assign a default level depending on the compression method. This may
122 switch (result
->algorithm
)
124 case PG_COMPRESSION_NONE
:
127 case PG_COMPRESSION_LZ4
:
129 result
->level
= 0; /* fast compression mode */
131 result
->parse_error
=
132 psprintf(_("this build does not support compression with %s"),
136 case PG_COMPRESSION_ZSTD
:
138 result
->level
= ZSTD_CLEVEL_DEFAULT
;
140 result
->parse_error
=
141 psprintf(_("this build does not support compression with %s"),
145 case PG_COMPRESSION_GZIP
:
147 result
->level
= Z_DEFAULT_COMPRESSION
;
149 result
->parse_error
=
150 psprintf(_("this build does not support compression with %s"),
156 /* If there is no specification, we're done already. */
157 if (specification
== NULL
)
160 /* As a special case, the specification can be a bare integer. */
161 bare_level
= strtol(specification
, &bare_level_endp
, 10);
162 if (specification
!= bare_level_endp
&& *bare_level_endp
== '\0')
164 result
->level
= bare_level
;
168 /* Look for comma-separated keyword or keyword=value entries. */
181 /* Figure start, end, and length of next keyword and any value. */
182 kwstart
= kwend
= specification
;
183 while (*kwend
!= '\0' && *kwend
!= ',' && *kwend
!= '=')
185 kwlen
= kwend
- kwstart
;
188 vstart
= vend
= NULL
;
194 vstart
= vend
= kwend
+ 1;
195 while (*vend
!= '\0' && *vend
!= ',')
197 vlen
= vend
- vstart
;
201 /* Reject empty keyword. */
204 result
->parse_error
=
205 pstrdup(_("found empty string where a compression option was expected"));
209 /* Extract keyword and value as separate C strings. */
210 keyword
= palloc(kwlen
+ 1);
211 memcpy(keyword
, kwstart
, kwlen
);
212 keyword
[kwlen
] = '\0';
217 value
= palloc(vlen
+ 1);
218 memcpy(value
, vstart
, vlen
);
222 /* Handle whatever keyword we found. */
223 if (strcmp(keyword
, "level") == 0)
225 result
->level
= expect_integer_value(keyword
, value
, result
);
228 * No need to set a flag in "options", there is a default level
229 * set at least thanks to the logic above.
232 else if (strcmp(keyword
, "workers") == 0)
234 result
->workers
= expect_integer_value(keyword
, value
, result
);
235 result
->options
|= PG_COMPRESSION_OPTION_WORKERS
;
237 else if (strcmp(keyword
, "long") == 0)
239 result
->long_distance
= expect_boolean_value(keyword
, value
, result
);
240 result
->options
|= PG_COMPRESSION_OPTION_LONG_DISTANCE
;
243 result
->parse_error
=
244 psprintf(_("unrecognized compression option: \"%s\""), keyword
);
246 /* Release memory, just to be tidy. */
252 * If we got an error or have reached the end of the string, stop.
254 * If there is no value, then the end of the keyword might have been
255 * the end of the string. If there is a value, then the end of the
256 * keyword cannot have been the end of the string, but the end of the
257 * value might have been.
259 if (result
->parse_error
!= NULL
||
260 (vend
== NULL
? *kwend
== '\0' : *vend
== '\0'))
263 /* Advance to next entry and loop around. */
264 specification
= vend
== NULL
? kwend
+ 1 : vend
+ 1;
269 * Parse 'value' as an integer and return the result.
271 * If parsing fails, set result->parse_error to an appropriate message
275 expect_integer_value(char *keyword
, char *value
, pg_compress_specification
*result
)
282 result
->parse_error
=
283 psprintf(_("compression option \"%s\" requires a value"),
288 ivalue
= strtol(value
, &ivalue_endp
, 10);
289 if (ivalue_endp
== value
|| *ivalue_endp
!= '\0')
291 result
->parse_error
=
292 psprintf(_("value for compression option \"%s\" must be an integer"),
300 * Parse 'value' as a boolean and return the result.
302 * If parsing fails, set result->parse_error to an appropriate message
303 * and return -1. The caller must check result->parse_error to determine if
304 * the call was successful.
306 * Valid values are: yes, no, on, off, 1, 0.
308 * Inspired by ParseVariableBool().
311 expect_boolean_value(char *keyword
, char *value
, pg_compress_specification
*result
)
316 if (pg_strcasecmp(value
, "yes") == 0)
318 if (pg_strcasecmp(value
, "on") == 0)
320 if (pg_strcasecmp(value
, "1") == 0)
323 if (pg_strcasecmp(value
, "no") == 0)
325 if (pg_strcasecmp(value
, "off") == 0)
327 if (pg_strcasecmp(value
, "0") == 0)
330 result
->parse_error
=
331 psprintf(_("value for compression option \"%s\" must be a Boolean value"),
337 * Returns NULL if the compression specification string was syntactically
338 * valid and semantically sensible. Otherwise, returns an error message.
340 * Does not test whether this build of PostgreSQL supports the requested
341 * compression method.
344 validate_compress_specification(pg_compress_specification
*spec
)
348 int default_level
= 0;
350 /* If it didn't even parse OK, it's definitely no good. */
351 if (spec
->parse_error
!= NULL
)
352 return spec
->parse_error
;
355 * Check that the algorithm expects a compression level and it is within
356 * the legal range for the algorithm.
358 switch (spec
->algorithm
)
360 case PG_COMPRESSION_GZIP
:
363 default_level
= Z_DEFAULT_COMPRESSION
;
366 case PG_COMPRESSION_LZ4
:
368 default_level
= 0; /* fast mode */
370 case PG_COMPRESSION_ZSTD
:
372 max_level
= ZSTD_maxCLevel();
373 min_level
= ZSTD_minCLevel();
374 default_level
= ZSTD_CLEVEL_DEFAULT
;
377 case PG_COMPRESSION_NONE
:
378 if (spec
->level
!= 0)
379 return psprintf(_("compression algorithm \"%s\" does not accept a compression level"),
380 get_compress_algorithm_name(spec
->algorithm
));
384 if ((spec
->level
< min_level
|| spec
->level
> max_level
) &&
385 spec
->level
!= default_level
)
386 return psprintf(_("compression algorithm \"%s\" expects a compression level between %d and %d (default at %d)"),
387 get_compress_algorithm_name(spec
->algorithm
),
388 min_level
, max_level
, default_level
);
391 * Of the compression algorithms that we currently support, only zstd
392 * allows parallel workers.
394 if ((spec
->options
& PG_COMPRESSION_OPTION_WORKERS
) != 0 &&
395 (spec
->algorithm
!= PG_COMPRESSION_ZSTD
))
397 return psprintf(_("compression algorithm \"%s\" does not accept a worker count"),
398 get_compress_algorithm_name(spec
->algorithm
));
402 * Of the compression algorithms that we currently support, only zstd
403 * supports long-distance mode.
405 if ((spec
->options
& PG_COMPRESSION_OPTION_LONG_DISTANCE
) != 0 &&
406 (spec
->algorithm
!= PG_COMPRESSION_ZSTD
))
408 return psprintf(_("compression algorithm \"%s\" does not support long-distance mode"),
409 get_compress_algorithm_name(spec
->algorithm
));
418 * Basic parsing of a value specified through a command-line option, commonly
421 * The parsing consists of a METHOD:DETAIL string fed later to
422 * parse_compress_specification(). This only extracts METHOD and DETAIL.
423 * If only an integer is found, the method is implied by the value specified.
426 parse_compress_options(const char *option
, char **algorithm
, char **detail
)
433 * Check whether the compression specification consists of a bare integer.
435 * For backward-compatibility, assume "none" if the integer found is zero
436 * and "gzip" otherwise.
438 result
= strtol(option
, &endp
, 10);
443 *algorithm
= pstrdup("none");
448 *algorithm
= pstrdup("gzip");
449 *detail
= pstrdup(option
);
455 * Check whether there is a compression detail following the algorithm
458 sep
= strchr(option
, ':');
461 *algorithm
= pstrdup(option
);
468 alg
= palloc((sep
- option
) + 1);
469 memcpy(alg
, option
, sep
- option
);
470 alg
[sep
- option
] = '\0';
473 *detail
= pstrdup(sep
+ 1);
476 #endif /* FRONTEND */