* 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,
+ * To keep things simple, and to avoid possible roundoff error,
* 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.)
+ * The conversions for each base unit must be kept in order from greatest to
+ * smallest human-friendly unit; convert_xxx_from_base_unit() rely on that.
+ * (The order of the base-unit groups does not matter.)
*/
#define MAX_UNIT_LEN 3 /* length of longest recognized unit string */
char unit[MAX_UNIT_LEN + 1]; /* unit, as a string, like "kB" or
* "min" */
int base_unit; /* GUC_UNIT_XXX */
- int64 multiplier; /* If positive, multiply the value with this
- * for unit -> base_unit conversion. If
- * negative, divide (with the absolute value) */
+ double multiplier; /* Factor for converting unit -> base_unit */
} unit_conversion;
/* Ensure that the constants in the tables don't overflow or underflow */
static const unit_conversion memory_unit_conversion_table[] =
{
- /*
- * TB -> bytes conversion always overflows 32-bit integer, so this always
- * produces an error. Include it nevertheless for completeness, and so
- * that you get an "out of range" error, rather than "invalid unit".
- */
- {"TB", GUC_UNIT_BYTE, INT64CONST(1024) * 1024 * 1024 * 1024},
- {"GB", GUC_UNIT_BYTE, 1024 * 1024 * 1024},
- {"MB", GUC_UNIT_BYTE, 1024 * 1024},
- {"kB", GUC_UNIT_BYTE, 1024},
- {"B", GUC_UNIT_BYTE, 1},
-
- {"TB", GUC_UNIT_KB, 1024 * 1024 * 1024},
- {"GB", GUC_UNIT_KB, 1024 * 1024},
- {"MB", GUC_UNIT_KB, 1024},
- {"kB", GUC_UNIT_KB, 1},
- {"B", GUC_UNIT_KB, -1024},
-
- {"TB", GUC_UNIT_MB, 1024 * 1024},
- {"GB", GUC_UNIT_MB, 1024},
- {"MB", GUC_UNIT_MB, 1},
- {"kB", GUC_UNIT_MB, -1024},
- {"B", GUC_UNIT_MB, -(1024 * 1024)},
-
- {"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)},
- {"B", GUC_UNIT_BLOCKS, -BLCKSZ},
-
- {"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)},
- {"B", GUC_UNIT_XBLOCKS, -XLOG_BLCKSZ},
+ {"TB", GUC_UNIT_BYTE, 1024.0 * 1024.0 * 1024.0 * 1024.0},
+ {"GB", GUC_UNIT_BYTE, 1024.0 * 1024.0 * 1024.0},
+ {"MB", GUC_UNIT_BYTE, 1024.0 * 1024.0},
+ {"kB", GUC_UNIT_BYTE, 1024.0},
+ {"B", GUC_UNIT_BYTE, 1.0},
+
+ {"TB", GUC_UNIT_KB, 1024.0 * 1024.0 * 1024.0},
+ {"GB", GUC_UNIT_KB, 1024.0 * 1024.0},
+ {"MB", GUC_UNIT_KB, 1024.0},
+ {"kB", GUC_UNIT_KB, 1.0},
+ {"B", GUC_UNIT_KB, 1.0 / 1024.0},
+
+ {"TB", GUC_UNIT_MB, 1024.0 * 1024.0},
+ {"GB", GUC_UNIT_MB, 1024.0},
+ {"MB", GUC_UNIT_MB, 1.0},
+ {"kB", GUC_UNIT_MB, 1.0 / 1024.0},
+ {"B", GUC_UNIT_MB, 1.0 / (1024.0 * 1024.0)},
+
+ {"TB", GUC_UNIT_BLOCKS, (1024.0 * 1024.0 * 1024.0) / (BLCKSZ / 1024)},
+ {"GB", GUC_UNIT_BLOCKS, (1024.0 * 1024.0) / (BLCKSZ / 1024)},
+ {"MB", GUC_UNIT_BLOCKS, 1024.0 / (BLCKSZ / 1024)},
+ {"kB", GUC_UNIT_BLOCKS, 1.0 / (BLCKSZ / 1024)},
+ {"B", GUC_UNIT_BLOCKS, 1.0 / BLCKSZ},
+
+ {"TB", GUC_UNIT_XBLOCKS, (1024.0 * 1024.0 * 1024.0) / (XLOG_BLCKSZ / 1024)},
+ {"GB", GUC_UNIT_XBLOCKS, (1024.0 * 1024.0) / (XLOG_BLCKSZ / 1024)},
+ {"MB", GUC_UNIT_XBLOCKS, 1024.0 / (XLOG_BLCKSZ / 1024)},
+ {"kB", GUC_UNIT_XBLOCKS, 1.0 / (XLOG_BLCKSZ / 1024)},
+ {"B", GUC_UNIT_XBLOCKS, 1.0 / XLOG_BLCKSZ},
{""} /* 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 char *time_units_hint = gettext_noop("Valid units for this parameter are \"us\", \"ms\", \"s\", \"min\", \"h\", and \"d\".");
static const unit_conversion time_unit_conversion_table[] =
{
{"min", GUC_UNIT_MS, 1000 * 60},
{"s", GUC_UNIT_MS, 1000},
{"ms", GUC_UNIT_MS, 1},
+ {"us", GUC_UNIT_MS, 1.0 / 1000},
{"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},
+ {"ms", GUC_UNIT_S, 1.0 / 1000},
+ {"us", GUC_UNIT_S, 1.0 / (1000 * 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},
+ {"s", GUC_UNIT_MIN, 1.0 / 60},
+ {"ms", GUC_UNIT_MIN, 1.0 / (1000 * 60)},
+ {"us", GUC_UNIT_MIN, 1.0 / (1000 * 1000 * 60)},
{""} /* end of table marker */
};
NULL, NULL, NULL
},
- {
- {"vacuum_cost_delay", PGC_USERSET, RESOURCES_VACUUM_DELAY,
- gettext_noop("Vacuum cost delay in milliseconds."),
- NULL,
- GUC_UNIT_MS
- },
- &VacuumCostDelay,
- 0, 0, 100,
- NULL, NULL, NULL
- },
-
- {
- {"autovacuum_vacuum_cost_delay", PGC_SIGHUP, AUTOVACUUM,
- gettext_noop("Vacuum cost delay in milliseconds, for autovacuum."),
- NULL,
- GUC_UNIT_MS
- },
- &autovacuum_vac_cost_delay,
- 20, -1, 100,
- NULL, NULL, NULL
- },
-
{
{"autovacuum_vacuum_cost_limit", PGC_SIGHUP, AUTOVACUUM,
gettext_noop("Vacuum cost amount available before napping, for autovacuum."),
check_random_seed, assign_random_seed, show_random_seed
},
+ {
+ {"vacuum_cost_delay", PGC_USERSET, RESOURCES_VACUUM_DELAY,
+ gettext_noop("Vacuum cost delay in milliseconds."),
+ NULL,
+ GUC_UNIT_MS
+ },
+ &VacuumCostDelay,
+ 0, 0, 100,
+ NULL, NULL, NULL
+ },
+
+ {
+ {"autovacuum_vacuum_cost_delay", PGC_SIGHUP, AUTOVACUUM,
+ gettext_noop("Vacuum cost delay in milliseconds, for autovacuum."),
+ NULL,
+ GUC_UNIT_MS
+ },
+ &autovacuum_vac_cost_delay,
+ 20, -1, 100,
+ NULL, NULL, NULL
+ },
+
{
{"autovacuum_vacuum_scale_factor", PGC_SIGHUP, AUTOVACUUM,
gettext_noop("Number of tuple updates or deletes prior to vacuum as a fraction of reltuples."),
/*
* 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.
+ * to convert from (there can be trailing spaces in the unit string).
+ * The converted value is stored in *base_value.
+ * It's caller's responsibility to round off the converted value as necessary
+ * and check for out-of-range.
*
* 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)
+convert_to_base_unit(double value, const char *unit,
+ int base_unit, double *base_value)
{
+ char unitstr[MAX_UNIT_LEN + 1];
+ int unitlen;
const unit_conversion *table;
int i;
+ /* extract unit string to compare to table entries */
+ unitlen = 0;
+ while (*unit != '\0' && !isspace((unsigned char) *unit) &&
+ unitlen < MAX_UNIT_LEN)
+ unitstr[unitlen++] = *(unit++);
+ unitstr[unitlen] = '\0';
+ /* allow whitespace after unit */
+ while (isspace((unsigned char) *unit))
+ unit++;
+ if (*unit != '\0')
+ return false; /* unit too long, or garbage after it */
+
+ /* now search the appropriate table */
if (base_unit & GUC_UNIT_MEMORY)
table = memory_unit_conversion_table;
else
for (i = 0; *table[i].unit; i++)
{
if (base_unit == table[i].base_unit &&
- strcmp(unit, table[i].unit) == 0)
+ strcmp(unitstr, table[i].unit) == 0)
{
- if (table[i].multiplier < 0)
- *base_value = value / (-table[i].multiplier);
- else
- *base_value = value * table[i].multiplier;
+ *base_value = value * table[i].multiplier;
return true;
}
}
}
/*
- * 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.
+ * Convert an integer 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)
+convert_int_from_base_unit(int64 base_value, int base_unit,
+ int64 *value, const char **unit)
{
const unit_conversion *table;
int i;
if (base_unit == table[i].base_unit)
{
/*
- * Accept the first conversion that divides the value evenly. We
+ * 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)
+ if (table[i].multiplier <= 1.0 ||
+ base_value % (int64) table[i].multiplier == 0)
{
- *value = base_value * (-table[i].multiplier);
+ *value = (int64) rint(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;
+ }
+ }
+
+ Assert(*unit != NULL);
+}
+
+/*
+ * Convert a floating-point value in some base unit to a human-friendly unit.
+ *
+ * Same as above, except we have to do the math a bit differently, and
+ * there's a possibility that we don't find any exact divisor.
+ */
+static void
+convert_real_from_base_unit(double base_value, int base_unit,
+ double *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; or
+ * if there is none, use the smallest (last) target unit.
+ *
+ * What we actually care about here is whether snprintf with "%g"
+ * will print the value as an integer, so the obvious test of
+ * "*value == rint(*value)" is too strict; roundoff error might
+ * make us choose an unreasonably small unit. As a compromise,
+ * accept a divisor that is within 1e-8 of producing an integer.
+ */
+ *value = base_value / table[i].multiplier;
+ *unit = table[i].unit;
+ if (*value > 0 &&
+ fabs((rint(*value) / *value) - 1.0) <= 1e-8)
break;
- }
}
}
* If the string parses okay, return true, else false.
* If okay and result is not NULL, return the value in *result.
* If not okay and hintmsg is not NULL, *hintmsg is set to a suitable
- * HINT message, or NULL if no hint provided.
+ * HINT message, or NULL if no hint provided.
*/
bool
parse_int(const char *value, int *result, int flags, const char **hintmsg)
/* Handle possible unit */
if (*endptr != '\0')
{
- char unit[MAX_UNIT_LEN + 1];
- int unitlen;
- bool converted = false;
+ double cval;
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')
- converted = convert_to_base_unit(val, unit, (flags & GUC_UNIT),
- &val);
- if (!converted)
+ if (!convert_to_base_unit((double) val,
+ endptr, (flags & GUC_UNIT),
+ &cval))
{
/* invalid unit, or garbage after the unit; set hint and fail. */
if (hintmsg)
return false;
}
- /* Check for overflow due to units conversion */
- if (val != (int64) ((int32) val))
+ /* Round to int, then check for overflow due to units conversion */
+ cval = rint(cval);
+ if (cval > INT_MAX || cval < INT_MIN)
{
if (hintmsg)
*hintmsg = gettext_noop("Value exceeds integer range.");
return false;
}
+ val = (int64) cval;
}
if (result)
/*
* Try to parse value as a floating point number in the usual format.
+ *
* If the string parses okay, return true, else false.
* If okay and result is not NULL, return the value in *result.
+ * If not okay and hintmsg is not NULL, *hintmsg is set to a suitable
+ * HINT message, or NULL if no hint provided.
*/
bool
-parse_real(const char *value, double *result)
+parse_real(const char *value, double *result, int flags, const char **hintmsg)
{
double val;
char *endptr;
+ /* To suppress compiler warnings, always set output params */
if (result)
- *result = 0; /* suppress compiler warning */
+ *result = 0;
+ if (hintmsg)
+ *hintmsg = NULL;
errno = 0;
val = strtod(value, &endptr);
+
if (endptr == value || errno == ERANGE)
- return false;
+ return false; /* no HINT for these cases */
/* reject NaN (infinities will fail range checks later) */
if (isnan(val))
- return false;
+ return false; /* treat same as syntax error; no HINT */
- /* allow whitespace after number */
+ /* allow whitespace between number and unit */
while (isspace((unsigned char) *endptr))
endptr++;
+
+ /* Handle possible unit */
if (*endptr != '\0')
- return false;
+ {
+ if ((flags & GUC_UNIT) == 0)
+ return false; /* this setting does not accept a unit */
+
+ if (!convert_to_base_unit(val,
+ endptr, (flags & GUC_UNIT),
+ &val))
+ {
+ /* 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;
+ }
+ }
if (result)
*result = val;
case PGC_REAL:
{
struct config_real *conf = (struct config_real *) record;
+ const char *hintmsg;
- if (!parse_real(value, &newval->realval))
+ if (!parse_real(value, &newval->realval,
+ conf->gen.flags, &hintmsg))
{
ereport(elevel,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("parameter \"%s\" requires a numeric value",
- name)));
+ errmsg("invalid value for parameter \"%s\": \"%s\"",
+ name, value),
+ hintmsg ? errhint("%s", _(hintmsg)) : 0));
return false;
}
const char *unit;
if (use_units && result > 0 && (record->flags & GUC_UNIT))
- {
- convert_from_base_unit(result, record->flags & GUC_UNIT,
- &result, &unit);
- }
+ convert_int_from_base_unit(result,
+ record->flags & GUC_UNIT,
+ &result, &unit);
else
unit = "";
val = conf->show_hook();
else
{
- snprintf(buffer, sizeof(buffer), "%g",
- *conf->variable);
+ double result = *conf->variable;
+ const char *unit;
+
+ if (use_units && result > 0 && (record->flags & GUC_UNIT))
+ convert_real_from_base_unit(result,
+ record->flags & GUC_UNIT,
+ &result, &unit);
+ else
+ unit = "";
+
+ snprintf(buffer, sizeof(buffer), "%g%s",
+ result, unit);
val = buffer;
}
}