summaryrefslogtreecommitdiffstats
path: root/src/common/compression.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/common/compression.c')
-rw-r--r--src/common/compression.c358
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;
+}