diff options
Diffstat (limited to 'src/common/compression.c')
-rw-r--r-- | src/common/compression.c | 358 |
1 files changed, 358 insertions, 0 deletions
diff --git a/src/common/compression.c b/src/common/compression.c new file mode 100644 index 0000000..df5b627 --- /dev/null +++ b/src/common/compression.c @@ -0,0 +1,358 @@ +/*------------------------------------------------------------------------- + * + * compression.c + * + * Shared code for compression methods and specifications. + * + * A compression specification specifies the parameters that should be used + * when performing compression with a specific algorithm. The simplest + * possible compression specification is an integer, which sets the + * compression level. + * + * Otherwise, a compression specification is a comma-separated list of items, + * each having the form keyword or keyword=value. + * + * Currently, the only supported keywords are "level" and "workers". + * + * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group + * + * IDENTIFICATION + * src/common/compression.c + *------------------------------------------------------------------------- + */ + +#ifndef FRONTEND +#include "postgres.h" +#else +#include "postgres_fe.h" +#endif + +#ifdef USE_ZSTD +#include <zstd.h> +#endif +#ifdef HAVE_LIBZ +#include <zlib.h> +#endif + +#include "common/compression.h" + +static int expect_integer_value(char *keyword, char *value, + pg_compress_specification *result); + +/* + * Look up a compression algorithm by name. Returns true and sets *algorithm + * if the name is recognized. Otherwise returns false. + */ +bool +parse_compress_algorithm(char *name, pg_compress_algorithm *algorithm) +{ + if (strcmp(name, "none") == 0) + *algorithm = PG_COMPRESSION_NONE; + else if (strcmp(name, "gzip") == 0) + *algorithm = PG_COMPRESSION_GZIP; + else if (strcmp(name, "lz4") == 0) + *algorithm = PG_COMPRESSION_LZ4; + else if (strcmp(name, "zstd") == 0) + *algorithm = PG_COMPRESSION_ZSTD; + else + return false; + return true; +} + +/* + * Get the human-readable name corresponding to a particular compression + * algorithm. + */ +const char * +get_compress_algorithm_name(pg_compress_algorithm algorithm) +{ + switch (algorithm) + { + case PG_COMPRESSION_NONE: + return "none"; + case PG_COMPRESSION_GZIP: + return "gzip"; + case PG_COMPRESSION_LZ4: + return "lz4"; + case PG_COMPRESSION_ZSTD: + return "zstd"; + /* no default, to provoke compiler warnings if values are added */ + } + Assert(false); + return "???"; /* placate compiler */ +} + +/* + * Parse a compression specification for a specified algorithm. + * + * See the file header comments for a brief description of what a compression + * specification is expected to look like. + * + * On return, all fields of the result object will be initialized. + * In particular, result->parse_error will be NULL if no errors occurred + * during parsing, and will otherwise contain an appropriate error message. + * The caller may free this error message string using pfree, if desired. + * Note, however, even if there's no parse error, the string might not make + * sense: e.g. for gzip, level=12 is not sensible, but it does parse OK. + * + * The compression level is assigned by default if not directly specified + * by the specification. + * + * Use validate_compress_specification() to find out whether a compression + * specification is semantically sensible. + */ +void +parse_compress_specification(pg_compress_algorithm algorithm, char *specification, + pg_compress_specification *result) +{ + int bare_level; + char *bare_level_endp; + + /* Initial setup of result object. */ + result->algorithm = algorithm; + result->options = 0; + result->parse_error = NULL; + + /* + * Assign a default level depending on the compression method. This may + * be enforced later. + */ + switch (result->algorithm) + { + case PG_COMPRESSION_NONE: + result->level = 0; + break; + case PG_COMPRESSION_LZ4: +#ifdef USE_LZ4 + result->level = 0; /* fast compression mode */ +#else + result->parse_error = + psprintf(_("this build does not support compression with %s"), + "LZ4"); +#endif + break; + case PG_COMPRESSION_ZSTD: +#ifdef USE_ZSTD + result->level = ZSTD_CLEVEL_DEFAULT; +#else + result->parse_error = + psprintf(_("this build does not support compression with %s"), + "ZSTD"); +#endif + break; + case PG_COMPRESSION_GZIP: +#ifdef HAVE_LIBZ + result->level = Z_DEFAULT_COMPRESSION; +#else + result->parse_error = + psprintf(_("this build does not support compression with %s"), + "gzip"); +#endif + break; + } + + /* If there is no specification, we're done already. */ + if (specification == NULL) + return; + + /* As a special case, the specification can be a bare integer. */ + bare_level = strtol(specification, &bare_level_endp, 10); + if (specification != bare_level_endp && *bare_level_endp == '\0') + { + result->level = bare_level; + return; + } + + /* Look for comma-separated keyword or keyword=value entries. */ + while (1) + { + char *kwstart; + char *kwend; + char *vstart; + char *vend; + int kwlen; + int vlen; + bool has_value; + char *keyword; + char *value; + + /* Figure start, end, and length of next keyword and any value. */ + kwstart = kwend = specification; + while (*kwend != '\0' && *kwend != ',' && *kwend != '=') + ++kwend; + kwlen = kwend - kwstart; + if (*kwend != '=') + { + vstart = vend = NULL; + vlen = 0; + has_value = false; + } + else + { + vstart = vend = kwend + 1; + while (*vend != '\0' && *vend != ',') + ++vend; + vlen = vend - vstart; + has_value = true; + } + + /* Reject empty keyword. */ + if (kwlen == 0) + { + result->parse_error = + pstrdup(_("found empty string where a compression option was expected")); + break; + } + + /* Extract keyword and value as separate C strings. */ + keyword = palloc(kwlen + 1); + memcpy(keyword, kwstart, kwlen); + keyword[kwlen] = '\0'; + if (!has_value) + value = NULL; + else + { + value = palloc(vlen + 1); + memcpy(value, vstart, vlen); + value[vlen] = '\0'; + } + + /* Handle whatever keyword we found. */ + if (strcmp(keyword, "level") == 0) + { + result->level = expect_integer_value(keyword, value, result); + + /* + * No need to set a flag in "options", there is a default level + * set at least thanks to the logic above. + */ + } + else if (strcmp(keyword, "workers") == 0) + { + result->workers = expect_integer_value(keyword, value, result); + result->options |= PG_COMPRESSION_OPTION_WORKERS; + } + else + result->parse_error = + psprintf(_("unrecognized compression option: \"%s\""), keyword); + + /* Release memory, just to be tidy. */ + pfree(keyword); + if (value != NULL) + pfree(value); + + /* + * If we got an error or have reached the end of the string, stop. + * + * If there is no value, then the end of the keyword might have been + * the end of the string. If there is a value, then the end of the + * keyword cannot have been the end of the string, but the end of the + * value might have been. + */ + if (result->parse_error != NULL || + (vend == NULL ? *kwend == '\0' : *vend == '\0')) + break; + + /* Advance to next entry and loop around. */ + specification = vend == NULL ? kwend + 1 : vend + 1; + } +} + +/* + * Parse 'value' as an integer and return the result. + * + * If parsing fails, set result->parse_error to an appropriate message + * and return -1. + */ +static int +expect_integer_value(char *keyword, char *value, pg_compress_specification *result) +{ + int ivalue; + char *ivalue_endp; + + if (value == NULL) + { + result->parse_error = + psprintf(_("compression option \"%s\" requires a value"), + keyword); + return -1; + } + + ivalue = strtol(value, &ivalue_endp, 10); + if (ivalue_endp == value || *ivalue_endp != '\0') + { + result->parse_error = + psprintf(_("value for compression option \"%s\" must be an integer"), + keyword); + return -1; + } + return ivalue; +} + +/* + * Returns NULL if the compression specification string was syntactically + * valid and semantically sensible. Otherwise, returns an error message. + * + * Does not test whether this build of PostgreSQL supports the requested + * compression method. + */ +char * +validate_compress_specification(pg_compress_specification *spec) +{ + int min_level = 1; + int max_level = 1; + int default_level = 0; + + /* If it didn't even parse OK, it's definitely no good. */ + if (spec->parse_error != NULL) + return spec->parse_error; + + /* + * Check that the algorithm expects a compression level and it is within + * the legal range for the algorithm. + */ + switch (spec->algorithm) + { + case PG_COMPRESSION_GZIP: + max_level = 9; +#ifdef HAVE_LIBZ + default_level = Z_DEFAULT_COMPRESSION; +#endif + break; + case PG_COMPRESSION_LZ4: + max_level = 12; + default_level = 0; /* fast mode */ + break; + case PG_COMPRESSION_ZSTD: +#ifdef USE_ZSTD + max_level = ZSTD_maxCLevel(); + min_level = ZSTD_minCLevel(); + default_level = ZSTD_CLEVEL_DEFAULT; +#endif + break; + case PG_COMPRESSION_NONE: + if (spec->level != 0) + return psprintf(_("compression algorithm \"%s\" does not accept a compression level"), + get_compress_algorithm_name(spec->algorithm)); + break; + } + + if ((spec->level < min_level || spec->level > max_level) && + spec->level != default_level) + return psprintf(_("compression algorithm \"%s\" expects a compression level between %d and %d (default at %d)"), + get_compress_algorithm_name(spec->algorithm), + min_level, max_level, default_level); + + /* + * Of the compression algorithms that we currently support, only zstd + * allows parallel workers. + */ + if ((spec->options & PG_COMPRESSION_OPTION_WORKERS) != 0 && + (spec->algorithm != PG_COMPRESSION_ZSTD)) + { + return psprintf(_("compression algorithm \"%s\" does not accept a worker count"), + get_compress_algorithm_name(spec->algorithm)); + } + + return NULL; +} |