Skip to content

Commit 1a83a80

Browse files
committed
Allow fractional input values for integer GUCs, and improve rounding logic.
Historically guc.c has just refused examples like set work_mem = '30.1GB', but it seems more useful for it to take that and round off the value to some reasonable approximation of what the user said. Just rounding to the parameter's native unit would work, but it would lead to rather silly-looking settings, such as 31562138kB for this example. Instead let's round to the nearest multiple of the next smaller unit (if any), producing 30822MB. Also, do the units conversion math in floating point and round to integer (if needed) only at the end. This produces saner results for inputs that aren't exact multiples of the parameter's native unit, and removes another difference in the behavior for integer vs. float parameters. In passing, document the ability to use hex or octal input where it ought to be documented. Discussion: https://postgr.es/m/1798.1552165479@sss.pgh.pa.us
1 parent fe0b2c1 commit 1a83a80

File tree

4 files changed

+74
-34
lines changed

4 files changed

+74
-34
lines changed

doc/src/sgml/config.sgml

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,14 +51,21 @@
5151
In general, enclose the value in single quotes, doubling any single
5252
quotes within the value. Quotes can usually be omitted if the value
5353
is a simple number or identifier, however.
54+
(Values that match a SQL keyword require quoting in some contexts.)
5455
</para>
5556
</listitem>
5657

5758
<listitem>
5859
<para>
5960
<emphasis>Numeric (integer and floating point):</emphasis>
60-
A decimal point is permitted only for floating-point parameters.
61-
Do not use thousands separators. Quotes are not required.
61+
Numeric parameters can be specified in the customary integer and
62+
floating-point formats; fractional values are rounded to the nearest
63+
integer if the parameter is of integer type. Integer parameters
64+
additionally accept hexadecimal input (beginning
65+
with <literal>0x</literal>) and octal input (beginning
66+
with <literal>0</literal>), but these formats cannot have a fraction.
67+
Do not use thousands separators.
68+
Quotes are not required, except for hexadecimal input.
6269
</para>
6370
</listitem>
6471

@@ -99,6 +106,13 @@
99106
</para>
100107
</listitem>
101108
</itemizedlist>
109+
110+
If a fractional value is specified with a unit, it will be rounded
111+
to a multiple of the next smaller unit if there is one.
112+
For example, <literal>30.1 GB</literal> will be converted
113+
to <literal>30822 MB</literal> not <literal>32319628902 B</literal>.
114+
If the parameter is of integer type, a final rounding to integer
115+
occurs after any units conversion.
102116
</para>
103117
</listitem>
104118

src/backend/utils/misc/guc.c

Lines changed: 54 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -5995,7 +5995,19 @@ convert_to_base_unit(double value, const char *unit,
59955995
if (base_unit == table[i].base_unit &&
59965996
strcmp(unitstr, table[i].unit) == 0)
59975997
{
5998-
*base_value = value * table[i].multiplier;
5998+
double cvalue = value * table[i].multiplier;
5999+
6000+
/*
6001+
* If the user gave a fractional value such as "30.1GB", round it
6002+
* off to the nearest multiple of the next smaller unit, if there
6003+
* is one.
6004+
*/
6005+
if (*table[i + 1].unit &&
6006+
base_unit == table[i + 1].base_unit)
6007+
cvalue = rint(cvalue / table[i + 1].multiplier) *
6008+
table[i + 1].multiplier;
6009+
6010+
*base_value = cvalue;
59996011
return true;
60006012
}
60016013
}
@@ -6141,8 +6153,10 @@ get_config_unit_name(int flags)
61416153

61426154
/*
61436155
* Try to parse value as an integer. The accepted formats are the
6144-
* usual decimal, octal, or hexadecimal formats, optionally followed by
6145-
* a unit name if "flags" indicates a unit is allowed.
6156+
* usual decimal, octal, or hexadecimal formats, as well as floating-point
6157+
* formats (which will be rounded to integer after any units conversion).
6158+
* Optionally, the value can be followed by a unit name if "flags" indicates
6159+
* a unit is allowed.
61466160
*
61476161
* If the string parses okay, return true, else false.
61486162
* If okay and result is not NULL, return the value in *result.
@@ -6152,7 +6166,11 @@ get_config_unit_name(int flags)
61526166
bool
61536167
parse_int(const char *value, int *result, int flags, const char **hintmsg)
61546168
{
6155-
int64 val;
6169+
/*
6170+
* We assume here that double is wide enough to represent any integer
6171+
* value with adequate precision.
6172+
*/
6173+
double val;
61566174
char *endptr;
61576175

61586176
/* To suppress compiler warnings, always set output params */
@@ -6161,35 +6179,42 @@ parse_int(const char *value, int *result, int flags, const char **hintmsg)
61616179
if (hintmsg)
61626180
*hintmsg = NULL;
61636181

6164-
/* We assume here that int64 is at least as wide as long */
6182+
/*
6183+
* Try to parse as an integer (allowing octal or hex input). If the
6184+
* conversion stops at a decimal point or 'e', or overflows, re-parse as
6185+
* float. This should work fine as long as we have no unit names starting
6186+
* with 'e'. If we ever do, the test could be extended to check for a
6187+
* sign or digit after 'e', but for now that's unnecessary.
6188+
*/
61656189
errno = 0;
61666190
val = strtol(value, &endptr, 0);
6167-
6168-
if (endptr == value)
6169-
return false; /* no HINT for integer syntax error */
6170-
6171-
if (errno == ERANGE || val != (int64) ((int32) val))
6191+
if (*endptr == '.' || *endptr == 'e' || *endptr == 'E' ||
6192+
errno == ERANGE)
61726193
{
6173-
if (hintmsg)
6174-
*hintmsg = gettext_noop("Value exceeds integer range.");
6175-
return false;
6194+
errno = 0;
6195+
val = strtod(value, &endptr);
61766196
}
61776197

6178-
/* allow whitespace between integer and unit */
6198+
if (endptr == value || errno == ERANGE)
6199+
return false; /* no HINT for these cases */
6200+
6201+
/* reject NaN (infinities will fail range check below) */
6202+
if (isnan(val))
6203+
return false; /* treat same as syntax error; no HINT */
6204+
6205+
/* allow whitespace between number and unit */
61796206
while (isspace((unsigned char) *endptr))
61806207
endptr++;
61816208

61826209
/* Handle possible unit */
61836210
if (*endptr != '\0')
61846211
{
6185-
double cval;
6186-
61876212
if ((flags & GUC_UNIT) == 0)
61886213
return false; /* this setting does not accept a unit */
61896214

6190-
if (!convert_to_base_unit((double) val,
6215+
if (!convert_to_base_unit(val,
61916216
endptr, (flags & GUC_UNIT),
6192-
&cval))
6217+
&val))
61936218
{
61946219
/* invalid unit, or garbage after the unit; set hint and fail. */
61956220
if (hintmsg)
@@ -6201,27 +6226,27 @@ parse_int(const char *value, int *result, int flags, const char **hintmsg)
62016226
}
62026227
return false;
62036228
}
6229+
}
62046230

6205-
/* Round to int, then check for overflow due to units conversion */
6206-
cval = rint(cval);
6207-
if (cval > INT_MAX || cval < INT_MIN)
6208-
{
6209-
if (hintmsg)
6210-
*hintmsg = gettext_noop("Value exceeds integer range.");
6211-
return false;
6212-
}
6213-
val = (int64) cval;
6231+
/* Round to int, then check for overflow */
6232+
val = rint(val);
6233+
6234+
if (val > INT_MAX || val < INT_MIN)
6235+
{
6236+
if (hintmsg)
6237+
*hintmsg = gettext_noop("Value exceeds integer range.");
6238+
return false;
62146239
}
62156240

62166241
if (result)
62176242
*result = (int) val;
62186243
return true;
62196244
}
62206245

6221-
6222-
62236246
/*
62246247
* Try to parse value as a floating point number in the usual format.
6248+
* Optionally, the value can be followed by a unit name if "flags" indicates
6249+
* a unit is allowed.
62256250
*
62266251
* If the string parses okay, return true, else false.
62276252
* If okay and result is not NULL, return the value in *result.

src/test/regress/expected/reloptions.out

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,9 @@ ERROR: unrecognized parameter "not_existing_option"
2626
CREATE TABLE reloptions_test2(i INT) WITH (not_existing_namespace.fillfactor=2);
2727
ERROR: unrecognized parameter namespace "not_existing_namespace"
2828
-- Fail while setting improper values
29-
CREATE TABLE reloptions_test2(i INT) WITH (fillfactor=30.5);
30-
ERROR: invalid value for integer option "fillfactor": 30.5
29+
CREATE TABLE reloptions_test2(i INT) WITH (fillfactor=-30.1);
30+
ERROR: value -30.1 out of bounds for option "fillfactor"
31+
DETAIL: Valid values are between "10" and "100".
3132
CREATE TABLE reloptions_test2(i INT) WITH (fillfactor='string');
3233
ERROR: invalid value for integer option "fillfactor": string
3334
CREATE TABLE reloptions_test2(i INT) WITH (fillfactor=true);

src/test/regress/sql/reloptions.sql

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ CREATE TABLE reloptions_test2(i INT) WITH (not_existing_option=2);
1515
CREATE TABLE reloptions_test2(i INT) WITH (not_existing_namespace.fillfactor=2);
1616

1717
-- Fail while setting improper values
18-
CREATE TABLE reloptions_test2(i INT) WITH (fillfactor=30.5);
18+
CREATE TABLE reloptions_test2(i INT) WITH (fillfactor=-30.1);
1919
CREATE TABLE reloptions_test2(i INT) WITH (fillfactor='string');
2020
CREATE TABLE reloptions_test2(i INT) WITH (fillfactor=true);
2121
CREATE TABLE reloptions_test2(i INT) WITH (autovacuum_enabled=12);

0 commit comments

Comments
 (0)