From 1b6302647302f459fdb63008c3842a1b0d43d1b7 Mon Sep 17 00:00:00 2001 From: Heikki Linnakangas Date: Mon, 23 Feb 2015 18:06:16 +0200 Subject: [PATCH] Refactor unit conversions code in guc.c. Replace the if-switch-case constructs with two conversion tables, containing all the supported conversions between human-readable unit strings and the base units used in GUC variables. This makes the code easier to read, and makes adding new units simpler. --- src/backend/utils/misc/guc.c | 433 ++++++++++++++++------------------- src/include/utils/guc.h | 2 + 2 files changed, 194 insertions(+), 241 deletions(-) diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c index cf401d3cf0..2499bee739 100644 --- a/src/backend/utils/misc/guc.c +++ b/src/backend/utils/misc/guc.c @@ -97,20 +97,6 @@ #define CONFIG_EXEC_PARAMS_NEW "global/config_exec_params.new" #endif -#define KB_PER_MB (1024) -#define KB_PER_GB (1024*1024) -#define KB_PER_TB (1024*1024*1024) - -#define MS_PER_S 1000 -#define S_PER_MIN 60 -#define MS_PER_MIN (1000 * 60) -#define MIN_PER_H 60 -#define S_PER_H (60 * 60) -#define MS_PER_H (1000 * 60 * 60) -#define MIN_PER_D (60 * 24) -#define S_PER_D (60 * 60 * 24) -#define MS_PER_D (1000 * 60 * 60 * 24) - /* * Precision with which REAL type guc values are to be printed for GUC * serialization. @@ -666,6 +652,88 @@ const char *const config_type_names[] = /* PGC_ENUM */ "enum" }; +/* + * Unit conversion tables. + * + * There are two tables, one for memory units, and another for time units. + * For each supported conversion from one unit to another, we have an entry + * in the table. + * + * To keep things simple, and to avoid intermediate-value overflows, + * conversions are never chained. There needs to be a direct conversion + * between all units (of the same type). + * + * The conversions from each base unit must be kept in order from greatest + * to smallest unit; convert_from_base_unit() relies on that. (The order of + * the base units does not matter.) + */ +#define MAX_UNIT_LEN 3 /* length of longest recognized unit string */ + +typedef struct +{ + char unit[MAX_UNIT_LEN + 1]; /* unit, as a string, like "kB" or "min" */ + int base_unit; /* GUC_UNIT_XXX */ + int multiplier; /* If positive, multiply the value with this for + * unit -> base_unit conversion. If negative, + * divide (with the absolute value) */ +} unit_conversion; + +/* Ensure that the constants in the tables don't overflow or underflow */ +#if BLCKSZ < 1024 || BLCKSZ > (1024*1024) +#error BLCKSZ must be between 1KB and 1MB +#endif +#if XLOG_BLCKSZ < 1024 || XLOG_BLCKSZ > (1024*1024) +#error XLOG_BLCKSZ must be between 1KB and 1MB +#endif + +static const char *memory_units_hint = + gettext_noop("Valid units for this parameter are \"kB\", \"MB\", \"GB\", and \"TB\"."); + +static const unit_conversion memory_unit_conversion_table[] = +{ + { "TB", GUC_UNIT_KB, 1024*1024*1024 }, + { "GB", GUC_UNIT_KB, 1024*1024 }, + { "MB", GUC_UNIT_KB, 1024 }, + { "kB", GUC_UNIT_KB, 1 }, + + { "TB", GUC_UNIT_BLOCKS, (1024*1024*1024) / (BLCKSZ / 1024) }, + { "GB", GUC_UNIT_BLOCKS, (1024*1024) / (BLCKSZ / 1024) }, + { "MB", GUC_UNIT_BLOCKS, 1024 / (BLCKSZ / 1024) }, + { "kB", GUC_UNIT_BLOCKS, -(BLCKSZ / 1024) }, + + { "TB", GUC_UNIT_XBLOCKS, (1024*1024*1024) / (XLOG_BLCKSZ / 1024) }, + { "GB", GUC_UNIT_XBLOCKS, (1024*1024) / (XLOG_BLCKSZ / 1024) }, + { "MB", GUC_UNIT_XBLOCKS, 1024 / (XLOG_BLCKSZ / 1024) }, + { "kB", GUC_UNIT_XBLOCKS, -(XLOG_BLCKSZ / 1024) }, + + { "" } /* end of table marker */ +}; + +static const char *time_units_hint = + gettext_noop("Valid units for this parameter are \"ms\", \"s\", \"min\", \"h\", and \"d\"."); + +static const unit_conversion time_unit_conversion_table[] = +{ + { "d", GUC_UNIT_MS, 1000 * 60 * 60 * 24 }, + { "h", GUC_UNIT_MS, 1000 * 60 * 60 }, + { "min", GUC_UNIT_MS, 1000 * 60}, + { "s", GUC_UNIT_MS, 1000 }, + { "ms", GUC_UNIT_MS, 1 }, + + { "d", GUC_UNIT_S, 60 * 60 * 24 }, + { "h", GUC_UNIT_S, 60 * 60 }, + { "min", GUC_UNIT_S, 60 }, + { "s", GUC_UNIT_S, 1 }, + { "ms", GUC_UNIT_S, -1000 }, + + { "d", GUC_UNIT_MIN, 60 * 24 }, + { "h", GUC_UNIT_MIN, 60 }, + { "min", GUC_UNIT_MIN, 1 }, + { "s", GUC_UNIT_MIN, -60 }, + { "ms", GUC_UNIT_MIN, -1000 * 60 }, + + { "" } /* end of table marker */ +}; /* * Contents of GUC tables @@ -5029,6 +5097,88 @@ ReportGUCOption(struct config_generic * record) } } +/* + * Convert a value from one of the human-friendly units ("kB", "min" etc.) + * to the given base unit. 'value' and 'unit' are the input value and unit + * to convert from. The converted value is stored in *base_value. + * + * Returns true on success, false if the input unit is not recognized. + */ +static bool +convert_to_base_unit(int64 value, const char *unit, + int base_unit, int64 *base_value) +{ + const unit_conversion *table; + int i; + + if (base_unit & GUC_UNIT_MEMORY) + table = memory_unit_conversion_table; + else + table = time_unit_conversion_table; + + for (i = 0; *table[i].unit; i++) + { + if (base_unit == table[i].base_unit && + strcmp(unit, table[i].unit) == 0) + { + if (table[i].multiplier < 0) + *base_value = value / (-table[i].multiplier); + else + *base_value = value * table[i].multiplier; + return true; + } + } + return false; +} + +/* + * Convert a value in some base unit to a human-friendly unit. The output + * unit is chosen so that it's the greatest unit that can represent the value + * without loss. For example, if the base unit is GUC_UNIT_KB, 1024 is + * converted to 1 MB, but 1025 is represented as 1025 kB. + */ +static void +convert_from_base_unit(int64 base_value, int base_unit, + int64 *value, const char **unit) +{ + const unit_conversion *table; + int i; + + *unit = NULL; + + if (base_unit & GUC_UNIT_MEMORY) + table = memory_unit_conversion_table; + else + table = time_unit_conversion_table; + + for (i = 0; *table[i].unit; i++) + { + if (base_unit == table[i].base_unit) + { + /* + * Accept the first conversion that divides the value evenly. + * We assume that the conversions for each base unit are ordered + * from greatest unit to the smallest! + */ + if (table[i].multiplier < 0) + { + *value = base_value * (-table[i].multiplier); + *unit = table[i].unit; + break; + } + else if (base_value % table[i].multiplier == 0) + { + *value = base_value / table[i].multiplier; + *unit = table[i].unit; + break; + } + } + } + + Assert(*unit != NULL); +} + + /* * Try to parse value as an integer. The accepted formats are the * usual decimal, octal, or hexadecimal formats, optionally followed by @@ -5072,170 +5222,37 @@ parse_int(const char *value, int *result, int flags, const char **hintmsg) /* Handle possible unit */ if (*endptr != '\0') { - /* - * Note: the multiple-switch coding technique here is a bit tedious, - * but seems necessary to avoid intermediate-value overflows. - */ - if (flags & GUC_UNIT_MEMORY) - { - /* Set hint for use if no match or trailing garbage */ - if (hintmsg) - *hintmsg = gettext_noop("Valid units for this parameter are \"kB\", \"MB\", \"GB\", and \"TB\"."); + char unit[MAX_UNIT_LEN + 1]; + int unitlen; + bool converted = false; -#if BLCKSZ < 1024 || BLCKSZ > (1024*1024) -#error BLCKSZ must be between 1KB and 1MB -#endif -#if XLOG_BLCKSZ < 1024 || XLOG_BLCKSZ > (1024*1024) -#error XLOG_BLCKSZ must be between 1KB and 1MB -#endif - - if (strncmp(endptr, "kB", 2) == 0) - { - endptr += 2; - switch (flags & GUC_UNIT_MEMORY) - { - case GUC_UNIT_BLOCKS: - val /= (BLCKSZ / 1024); - break; - case GUC_UNIT_XBLOCKS: - val /= (XLOG_BLCKSZ / 1024); - break; - } - } - else if (strncmp(endptr, "MB", 2) == 0) - { - endptr += 2; - switch (flags & GUC_UNIT_MEMORY) - { - case GUC_UNIT_KB: - val *= KB_PER_MB; - break; - case GUC_UNIT_BLOCKS: - val *= KB_PER_MB / (BLCKSZ / 1024); - break; - case GUC_UNIT_XBLOCKS: - val *= KB_PER_MB / (XLOG_BLCKSZ / 1024); - break; - } - } - else if (strncmp(endptr, "GB", 2) == 0) - { - endptr += 2; - switch (flags & GUC_UNIT_MEMORY) - { - case GUC_UNIT_KB: - val *= KB_PER_GB; - break; - case GUC_UNIT_BLOCKS: - val *= KB_PER_GB / (BLCKSZ / 1024); - break; - case GUC_UNIT_XBLOCKS: - val *= KB_PER_GB / (XLOG_BLCKSZ / 1024); - break; - } - } - else if (strncmp(endptr, "TB", 2) == 0) - { - endptr += 2; - switch (flags & GUC_UNIT_MEMORY) - { - case GUC_UNIT_KB: - val *= KB_PER_TB; - break; - case GUC_UNIT_BLOCKS: - val *= KB_PER_TB / (BLCKSZ / 1024); - break; - case GUC_UNIT_XBLOCKS: - val *= KB_PER_TB / (XLOG_BLCKSZ / 1024); - break; - } - } - } - else if (flags & GUC_UNIT_TIME) - { - /* Set hint for use if no match or trailing garbage */ - if (hintmsg) - *hintmsg = gettext_noop("Valid units for this parameter are \"ms\", \"s\", \"min\", \"h\", and \"d\"."); - - if (strncmp(endptr, "ms", 2) == 0) - { - endptr += 2; - switch (flags & GUC_UNIT_TIME) - { - case GUC_UNIT_S: - val /= MS_PER_S; - break; - case GUC_UNIT_MIN: - val /= MS_PER_MIN; - break; - } - } - else if (strncmp(endptr, "s", 1) == 0) - { - endptr += 1; - switch (flags & GUC_UNIT_TIME) - { - case GUC_UNIT_MS: - val *= MS_PER_S; - break; - case GUC_UNIT_MIN: - val /= S_PER_MIN; - break; - } - } - else if (strncmp(endptr, "min", 3) == 0) - { - endptr += 3; - switch (flags & GUC_UNIT_TIME) - { - case GUC_UNIT_MS: - val *= MS_PER_MIN; - break; - case GUC_UNIT_S: - val *= S_PER_MIN; - break; - } - } - else if (strncmp(endptr, "h", 1) == 0) - { - endptr += 1; - switch (flags & GUC_UNIT_TIME) - { - case GUC_UNIT_MS: - val *= MS_PER_H; - break; - case GUC_UNIT_S: - val *= S_PER_H; - break; - case GUC_UNIT_MIN: - val *= MIN_PER_H; - break; - } - } - else if (strncmp(endptr, "d", 1) == 0) - { - endptr += 1; - switch (flags & GUC_UNIT_TIME) - { - case GUC_UNIT_MS: - val *= MS_PER_D; - break; - case GUC_UNIT_S: - val *= S_PER_D; - break; - case GUC_UNIT_MIN: - val *= MIN_PER_D; - break; - } - } - } + if ((flags & GUC_UNIT) == 0) + return false; /* this setting does not accept a unit */ + unitlen = 0; + while (*endptr != '\0' && !isspace((unsigned char) *endptr) && + unitlen < MAX_UNIT_LEN) + unit[unitlen++] = *(endptr++); + unit[unitlen] = '\0'; /* allow whitespace after unit */ while (isspace((unsigned char) *endptr)) endptr++; - if (*endptr != '\0') - return false; /* appropriate hint, if any, already set */ + if (*endptr == '\0') + converted = convert_to_base_unit(val, unit, (flags & GUC_UNIT), + &val); + if (!converted) + { + /* invalid unit, or garbage after the unit; set hint and fail. */ + if (hintmsg) + { + if (flags & GUC_UNIT_MEMORY) + *hintmsg = memory_units_hint; + else + *hintmsg = time_units_hint; + } + return false; + } /* Check for overflow due to units conversion */ if (val != (int64) ((int32) val)) @@ -8108,76 +8125,10 @@ _ShowOption(struct config_generic * record, bool use_units) int64 result = *conf->variable; const char *unit; - if (use_units && result > 0 && - (record->flags & GUC_UNIT_MEMORY)) + if (use_units && result > 0 && (record->flags & GUC_UNIT)) { - switch (record->flags & GUC_UNIT_MEMORY) - { - case GUC_UNIT_BLOCKS: - result *= BLCKSZ / 1024; - break; - case GUC_UNIT_XBLOCKS: - result *= XLOG_BLCKSZ / 1024; - break; - } - - if (result % KB_PER_TB == 0) - { - result /= KB_PER_TB; - unit = "TB"; - } - else if (result % KB_PER_GB == 0) - { - result /= KB_PER_GB; - unit = "GB"; - } - else if (result % KB_PER_MB == 0) - { - result /= KB_PER_MB; - unit = "MB"; - } - else - { - unit = "kB"; - } - } - else if (use_units && result > 0 && - (record->flags & GUC_UNIT_TIME)) - { - switch (record->flags & GUC_UNIT_TIME) - { - case GUC_UNIT_S: - result *= MS_PER_S; - break; - case GUC_UNIT_MIN: - result *= MS_PER_MIN; - break; - } - - if (result % MS_PER_D == 0) - { - result /= MS_PER_D; - unit = "d"; - } - else if (result % MS_PER_H == 0) - { - result /= MS_PER_H; - unit = "h"; - } - else if (result % MS_PER_MIN == 0) - { - result /= MS_PER_MIN; - unit = "min"; - } - else if (result % MS_PER_S == 0) - { - result /= MS_PER_S; - unit = "s"; - } - else - { - unit = "ms"; - } + convert_from_base_unit(result, record->flags & GUC_UNIT, + &result, &unit); } else unit = ""; diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h index 717f46b2a9..9a9a7a021a 100644 --- a/src/include/utils/guc.h +++ b/src/include/utils/guc.h @@ -212,6 +212,8 @@ typedef enum #define GUC_UNIT_MIN 0x4000 /* value is in minutes */ #define GUC_UNIT_TIME 0x7000 /* mask for MS, S, MIN */ +#define GUC_UNIT (GUC_UNIT_MEMORY | GUC_UNIT_TIME) + #define GUC_NOT_WHILE_SEC_REST 0x8000 /* can't set if security restricted */ #define GUC_DISALLOW_IN_AUTO_FILE 0x00010000 /* can't set in * PG_AUTOCONF_FILENAME */