Files
postgresql/src/common/compression.c
Tom Lane 23e7b38bfe Pre-beta mechanical code beautification.
Run pgindent, pgperltidy, and reformat-dat-files.
I manually fixed a couple of comments that pgindent uglified.
2022-05-12 15:17:30 -04:00

295 lines
8.0 KiB
C

/*-------------------------------------------------------------------------
*
* 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
#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.
*
* 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->level = -1;
result->parse_error = NULL;
/* 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;
result->options |= PG_COMPRESSION_OPTION_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);
result->options |= PG_COMPRESSION_OPTION_LEVEL;
}
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(_("unknown 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)
{
/* If it didn't even parse OK, it's definitely no good. */
if (spec->parse_error != NULL)
return spec->parse_error;
/*
* If a compression level was specified, check that the algorithm expects
* a compression level and that the level is within the legal range for
* the algorithm.
*/
if ((spec->options & PG_COMPRESSION_OPTION_LEVEL) != 0)
{
int min_level = 1;
int max_level;
if (spec->algorithm == PG_COMPRESSION_GZIP)
max_level = 9;
else if (spec->algorithm == PG_COMPRESSION_LZ4)
max_level = 12;
else if (spec->algorithm == PG_COMPRESSION_ZSTD)
max_level = 22;
else
return psprintf(_("compression algorithm \"%s\" does not accept a compression level"),
get_compress_algorithm_name(spec->algorithm));
if (spec->level < min_level || spec->level > max_level)
return psprintf(_("compression algorithm \"%s\" expects a compression level between %d and %d"),
get_compress_algorithm_name(spec->algorithm),
min_level, max_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;
}