Refactor unit conversions code in guc.c.
authorHeikki Linnakangas <heikki.linnakangas@iki.fi>
Mon, 23 Feb 2015 16:06:16 +0000 (18:06 +0200)
committerHeikki Linnakangas <heikki.linnakangas@iki.fi>
Mon, 23 Feb 2015 16:06:16 +0000 (18:06 +0200)
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
src/include/utils/guc.h

index cf401d3cf03ecbb30987151ae0e8784e14033a12..2499bee7399c50dc7c5d129bd10258e84b7a97b7 100644 (file)
 #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,171 +5222,38 @@ 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 ((flags & GUC_UNIT) == 0)
+                       return false;   /* this setting does not accept a unit */
 
-                       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)
+               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)
                {
-                       /* Set hint for use if no match or trailing garbage */
+                       /* invalid unit, or garbage after the unit; set hint and fail. */
                        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_MEMORY)
+                                       *hintmsg = memory_units_hint;
+                               else
+                                       *hintmsg = time_units_hint;
                        }
+                       return false;
                }
 
-               /* allow whitespace after unit */
-               while (isspace((unsigned char) *endptr))
-                       endptr++;
-
-               if (*endptr != '\0')
-                       return false;           /* appropriate hint, if any, already set */
-
                /* 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))
-                                       {
-                                               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))
+                                       if (use_units && result > 0 && (record->flags & GUC_UNIT))
                                        {
-                                               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 = "";
index 717f46b2a9444ab2e4d594d99a312e60fc15bfda..9a9a7a021a89082dfa723e96afcbaa1379173dd1 100644 (file)
@@ -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 */