Fix EXPLAIN (SETTINGS) to follow policy about when to print empty fields.
authorTom Lane <tgl@sss.pgh.pa.us>
Sun, 26 Jan 2020 21:31:48 +0000 (16:31 -0500)
committerTom Lane <tgl@sss.pgh.pa.us>
Sun, 26 Jan 2020 21:31:48 +0000 (16:31 -0500)
In non-TEXT output formats, the "Settings" field should appear when
requested, even if it would be empty.

Also, get rid of the premature optimization of counting all the
GUC_EXPLAIN variables at startup.  Since there was no provision for
adjusting that count later, all it'd take would be some extension marking
a parameter as GUC_EXPLAIN to risk an assertion failure or memory stomp.
We could make get_explain_guc_options() count those variables on-the-fly,
or dynamically resize its array ... but TBH I do not think that making a
transient array of pointers a bit smaller is worth any extra complication,
especially when you consider all the other transient space EXPLAIN eats.
So just allocate that array at the max possible size.

In HEAD, also add some regression test coverage for this feature.

Because of the memory-stomp hazard, back-patch to v12 where this
feature was added.

Discussion: https://postgr.es/m/19416.1580069629@sss.pgh.pa.us

src/backend/commands/explain.c
src/backend/utils/misc/guc.c

index 92969636b756b38aeb2318f9c4cb6ff950472292..73178dafaf5491e41db307d830115ffc07bf7e99 100644 (file)
@@ -616,17 +616,11 @@ ExplainPrintSettings(ExplainState *es)
    /* request an array of relevant settings */
    gucs = get_explain_guc_options(&num);
 
-   /* also bail out of there are no options */
-   if (!num)
-       return;
-
    if (es->format != EXPLAIN_FORMAT_TEXT)
    {
-       int         i;
-
        ExplainOpenGroup("Settings", "Settings", true, es);
 
-       for (i = 0; i < num; i++)
+       for (int i = 0; i < num; i++)
        {
            char       *setting;
            struct config_generic *conf = gucs[i];
@@ -640,12 +634,15 @@ ExplainPrintSettings(ExplainState *es)
    }
    else
    {
-       int         i;
        StringInfoData str;
 
+       /* In TEXT mode, print nothing if there are no options */
+       if (num <= 0)
+           return;
+
        initStringInfo(&str);
 
-       for (i = 0; i < num; i++)
+       for (int i = 0; i < num; i++)
        {
            char       *setting;
            struct config_generic *conf = gucs[i];
@@ -661,8 +658,7 @@ ExplainPrintSettings(ExplainState *es)
                appendStringInfo(&str, "%s = NULL", conf->name);
        }
 
-       if (num > 0)
-           ExplainPropertyText("Settings", str.data, es);
+       ExplainPropertyText("Settings", str.data, es);
    }
 }
 
index c94fe584177bec0a87363dc966b938a73195f4fd..00520fe43fe7064db133ef92d3aa1226d33b75cb 100644 (file)
@@ -4571,9 +4571,6 @@ static struct config_generic **guc_variables;
 /* Current number of variables contained in the vector */
 static int num_guc_variables;
 
-/* Current number of variables marked with GUC_EXPLAIN */
-static int num_guc_explain_variables;
-
 /* Vector capacity */
 static int size_guc_variables;
 
@@ -4837,7 +4834,6 @@ build_guc_variables(void)
 {
    int         size_vars;
    int         num_vars = 0;
-   int         num_explain_vars = 0;
    struct config_generic **guc_vars;
    int         i;
 
@@ -4848,9 +4844,6 @@ build_guc_variables(void)
        /* Rather than requiring vartype to be filled in by hand, do this: */
        conf->gen.vartype = PGC_BOOL;
        num_vars++;
-
-       if (conf->gen.flags & GUC_EXPLAIN)
-           num_explain_vars++;
    }
 
    for (i = 0; ConfigureNamesInt[i].gen.name; i++)
@@ -4859,9 +4852,6 @@ build_guc_variables(void)
 
        conf->gen.vartype = PGC_INT;
        num_vars++;
-
-       if (conf->gen.flags & GUC_EXPLAIN)
-           num_explain_vars++;
    }
 
    for (i = 0; ConfigureNamesReal[i].gen.name; i++)
@@ -4870,9 +4860,6 @@ build_guc_variables(void)
 
        conf->gen.vartype = PGC_REAL;
        num_vars++;
-
-       if (conf->gen.flags & GUC_EXPLAIN)
-           num_explain_vars++;
    }
 
    for (i = 0; ConfigureNamesString[i].gen.name; i++)
@@ -4881,9 +4868,6 @@ build_guc_variables(void)
 
        conf->gen.vartype = PGC_STRING;
        num_vars++;
-
-       if (conf->gen.flags & GUC_EXPLAIN)
-           num_explain_vars++;
    }
 
    for (i = 0; ConfigureNamesEnum[i].gen.name; i++)
@@ -4892,9 +4876,6 @@ build_guc_variables(void)
 
        conf->gen.vartype = PGC_ENUM;
        num_vars++;
-
-       if (conf->gen.flags & GUC_EXPLAIN)
-           num_explain_vars++;
    }
 
    /*
@@ -4926,7 +4907,6 @@ build_guc_variables(void)
        free(guc_variables);
    guc_variables = guc_vars;
    num_guc_variables = num_vars;
-   num_guc_explain_variables = num_explain_vars;
    size_guc_variables = size_vars;
    qsort((void *) guc_variables, num_guc_variables,
          sizeof(struct config_generic *), guc_var_compare);
@@ -8875,41 +8855,40 @@ ShowAllGUCConfig(DestReceiver *dest)
 }
 
 /*
- * Returns an array of modified GUC options to show in EXPLAIN. Only options
- * related to query planning (marked with GUC_EXPLAIN), with values different
- * from built-in defaults.
+ * Return an array of modified GUC options to show in EXPLAIN.
+ *
+ * We only report options related to query planning (marked with GUC_EXPLAIN),
+ * with values different from their built-in defaults.
  */
 struct config_generic **
 get_explain_guc_options(int *num)
 {
-   int         i;
    struct config_generic **result;
 
    *num = 0;
 
    /*
-    * Allocate enough space to fit all GUC_EXPLAIN options. We may not need
-    * all the space, but there are fairly few such options so we don't waste
-    * a lot of memory.
+    * While only a fraction of all the GUC variables are marked GUC_EXPLAIN,
+    * it doesn't seem worth dynamically resizing this array.
     */
-   result = palloc(sizeof(struct config_generic *) * num_guc_explain_variables);
+   result = palloc(sizeof(struct config_generic *) * num_guc_variables);
 
-   for (i = 0; i < num_guc_variables; i++)
+   for (int i = 0; i < num_guc_variables; i++)
    {
        bool        modified;
        struct config_generic *conf = guc_variables[i];
 
-       /* return only options visible to the user */
+       /* return only parameters marked for inclusion in explain */
+       if (!(conf->flags & GUC_EXPLAIN))
+           continue;
+
+       /* return only options visible to the current user */
        if ((conf->flags & GUC_NO_SHOW_ALL) ||
            ((conf->flags & GUC_SUPERUSER_ONLY) &&
             !is_member_of_role(GetUserId(), DEFAULT_ROLE_READ_ALL_SETTINGS)))
            continue;
 
-       /* only parameters explicitly marked for inclusion in explain */
-       if (!(conf->flags & GUC_EXPLAIN))
-           continue;
-
-       /* return only options that were modified (w.r.t. config file) */
+       /* return only options that are different from their boot values */
        modified = false;
 
        switch (conf->vartype)
@@ -8958,15 +8937,12 @@ get_explain_guc_options(int *num)
                elog(ERROR, "unexpected GUC type: %d", conf->vartype);
        }
 
-       /* skip GUC variables that match the built-in default */
        if (!modified)
            continue;
 
-       /* assign to the values array */
+       /* OK, report it */
        result[*num] = conf;
        *num = *num + 1;
-
-       Assert(*num <= num_guc_explain_variables);
    }
 
    return result;