]> git.ipfire.org Git - thirdparty/postgresql.git/commitdiff
Add input function for data type pg_dependencies
authorMichael Paquier <michael@paquier.xyz>
Wed, 26 Nov 2025 01:53:16 +0000 (10:53 +0900)
committerMichael Paquier <michael@paquier.xyz>
Wed, 26 Nov 2025 01:53:16 +0000 (10:53 +0900)
pg_dependencies is used as data type for the contents of dependencies
extended statistics.  This new input function consumes the format that
has been established by e76defbcf09e for the output function of
pg_dependencies, enforcing some sanity checks for:
- Checks for the input object, which should be a one-dimension array
with correct attributes and values.
- The key names: "attributes", "dependency", "degree".  All are
required, other key names are blocked.
- Value types for each key: "attributes" requires an array of integers,
"dependency" an attribute number, "degree" a float.
- List of attributes.  In this case, it is possible that some
dependencies are not listed in the statistics data, as items with a
degree of 0 are discarded when building the statistics.  This commit
includes checks for simple scenarios, like duplicated attributes, or
overlapping values between the list of "attributes" and the "dependency"
value.  Even if the input function considers the input as valid, a value
still needs to be cross-checked with the attributes defined in a
statistics object at import.
- Based on the discussion, the checks on the values are loose, as there
is also an argument for potentially stats injection.  For example,
"degree" should be defined in [0.0,1.0], but a check is not enforced.

This is required for a follow-up patch that aims to implement the import
of extended statistics.  Some tests are added to check the code paths of
the JSON parser checking the shape of the pg_dependencies inputs, with
91% of code coverage reached.  The tests are located in their own new
test file, for clarity.

Author: Corey Huinker <corey.huinker@gmail.com>
Reviewed-by: Jian He <jian.universality@gmail.com>
Reviewed-by: Chao Li <li.evan.chao@gmail.com>
Reviewed-by: Michael Paquier <michael@paquier.xyz>
Reviewed-by: Yuefei Shi <shiyuefei1004@gmail.com>
Discussion: https://postgr.es/m/CADkLM=dpz3KFnqP-dgJ-zvRvtjsa8UZv8wDAQdqho=qN3kX0Zg@mail.gmail.com

src/backend/utils/adt/pg_dependencies.c
src/test/regress/expected/pg_dependencies.out [new file with mode: 0644]
src/test/regress/parallel_schedule
src/test/regress/sql/pg_dependencies.sql [new file with mode: 0644]
src/tools/pgindent/typedefs.list

index 87181aa00e9a5599e5da617aa953f9ff07248d0a..56abb74417757f9e22c502a0eb630539c82a7ee5 100644 (file)
 
 #include "postgres.h"
 
+#include "common/int.h"
+#include "common/jsonapi.h"
 #include "lib/stringinfo.h"
+#include "mb/pg_wchar.h"
+#include "nodes/miscnodes.h"
 #include "statistics/extended_stats_internal.h"
 #include "statistics/statistics_format.h"
+#include "utils/builtins.h"
+#include "utils/float.h"
 #include "utils/fmgrprotos.h"
 
+typedef enum
+{
+       DEPS_EXPECT_START = 0,
+       DEPS_EXPECT_ITEM,
+       DEPS_EXPECT_KEY,
+       DEPS_EXPECT_ATTNUM_LIST,
+       DEPS_EXPECT_ATTNUM,
+       DEPS_EXPECT_DEPENDENCY,
+       DEPS_EXPECT_DEGREE,
+       DEPS_PARSE_COMPLETE,
+} DependenciesSemanticState;
+
+typedef struct
+{
+       const char *str;
+       DependenciesSemanticState state;
+
+       List       *dependency_list;
+       Node       *escontext;
+
+       bool            found_attributes;       /* Item has an attributes key */
+       bool            found_dependency;       /* Item has an dependency key */
+       bool            found_degree;   /* Item has degree key */
+       List       *attnum_list;        /* Accumulated attribute numbers */
+       AttrNumber      dependency;
+       double          degree;
+} DependenciesParseState;
+
+/*
+ * Invoked at the start of each MVDependency object.
+ *
+ * The entire JSON document should be one array of MVDependency objects.
+ *
+ * If we are anywhere else in the document, it's an error.
+ */
+static JsonParseErrorType
+dependencies_object_start(void *state)
+{
+       DependenciesParseState *parse = state;
+
+       switch (parse->state)
+       {
+               case DEPS_EXPECT_ITEM:
+                       /* Now we expect to see attributes/dependency/degree keys */
+                       parse->state = DEPS_EXPECT_KEY;
+                       return JSON_SUCCESS;
+
+               case DEPS_EXPECT_START:
+                       /* pg_dependencies must begin with a '[' */
+                       errsave(parse->escontext,
+                                       errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+                                       errmsg("malformed pg_dependencies: \"%s\"", parse->str),
+                                       errdetail("Initial element must be an array."));
+                       break;
+
+               case DEPS_EXPECT_KEY:
+                       /* In an object, expecting key */
+                       errsave(parse->escontext,
+                                       errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+                                       errmsg("malformed pg_dependencies: \"%s\"", parse->str),
+                                       errdetail("Expected an object key."));
+                       break;
+
+               case DEPS_EXPECT_ATTNUM_LIST:
+                       /* Just followed an "attributes": key */
+                       errsave(parse->escontext,
+                                       errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+                                       errmsg("malformed pg_dependencies: \"%s\"", parse->str),
+                                       errdetail("Value of \"%s\" must be an array of attribute numbers.",
+                                                         PG_DEPENDENCIES_KEY_ATTRIBUTES));
+                       break;
+
+               case DEPS_EXPECT_ATTNUM:
+                       /* In an attribute number list, expect only scalar integers */
+                       errsave(parse->escontext,
+                                       errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+                                       errmsg("malformed pg_dependencies: \"%s\"", parse->str),
+                                       errdetail("Attribute lists can only contain attribute numbers."));
+                       break;
+
+               case DEPS_EXPECT_DEPENDENCY:
+                       /* Just followed a "dependency" key */
+                       errsave(parse->escontext,
+                                       errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+                                       errmsg("malformed pg_dependencies: \"%s\"", parse->str),
+                                       errdetail("Value of \"%s\" must be an integer.",
+                                                         PG_DEPENDENCIES_KEY_DEPENDENCY));
+                       break;
+
+               case DEPS_EXPECT_DEGREE:
+                       /* Just followed a "degree" key */
+                       errsave(parse->escontext,
+                                       errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+                                       errmsg("malformed pg_dependencies: \"%s\"", parse->str),
+                                       errdetail("Value of \"%s\" must be an integer.",
+                                                         PG_DEPENDENCIES_KEY_DEGREE));
+                       break;
+
+               default:
+                       errsave(parse->escontext,
+                                       errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+                                       errmsg("malformed pg_dependencies: \"%s\"", parse->str),
+                                       errdetail("Unexpected parse state: %d", (int) parse->state));
+                       break;
+       }
+
+       return JSON_SEM_ACTION_FAILED;
+}
+
+/*
+ * Invoked at the end of an object.
+ *
+ * Handle the end of an MVDependency object's JSON representation.
+ */
+static JsonParseErrorType
+dependencies_object_end(void *state)
+{
+       DependenciesParseState *parse = state;
+
+       MVDependency *dep;
+
+       int                     natts = 0;
+
+       if (parse->state != DEPS_EXPECT_KEY)
+       {
+               errsave(parse->escontext,
+                               errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+                               errmsg("malformed pg_dependencies: \"%s\"", parse->str),
+                               errdetail("Unexpected parse state: %d", (int) parse->state));
+               return JSON_SEM_ACTION_FAILED;
+       }
+
+       if (!parse->found_attributes)
+       {
+               errsave(parse->escontext,
+                               errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+                               errmsg("malformed pg_dependencies: \"%s\"", parse->str),
+                               errdetail("Item must contain \"%s\" key",
+                                                 PG_DEPENDENCIES_KEY_ATTRIBUTES));
+               return JSON_SEM_ACTION_FAILED;
+       }
+
+       if (!parse->found_dependency)
+       {
+               errsave(parse->escontext,
+                               errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+                               errmsg("malformed pg_dependencies: \"%s\"", parse->str),
+                               errdetail("Item must contain \"%s\" key.",
+                                                 PG_DEPENDENCIES_KEY_DEPENDENCY));
+               return JSON_SEM_ACTION_FAILED;
+       }
+
+       if (!parse->found_degree)
+       {
+               errsave(parse->escontext,
+                               errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+                               errmsg("malformed pg_dependencies: \"%s\"", parse->str),
+                               errdetail("Item must contain \"%s\" key.",
+                                                 PG_DEPENDENCIES_KEY_DEGREE));
+               return JSON_SEM_ACTION_FAILED;
+       }
+
+       /*
+        * We need at least one attribute number in a dependencies item, anything
+        * less is malformed.
+        */
+       natts = list_length(parse->attnum_list);
+       if ((natts < 1) || (natts > (STATS_MAX_DIMENSIONS - 1)))
+       {
+               errsave(parse->escontext,
+                               errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+                               errmsg("malformed pg_dependencies: \"%s\"", parse->str),
+                               errdetail("The \"%s\" key must contain an array of at least %d and no more than %d elements.",
+                                                 PG_DEPENDENCIES_KEY_ATTRIBUTES, 1,
+                                                 STATS_MAX_DIMENSIONS - 1));
+               return JSON_SEM_ACTION_FAILED;
+       }
+
+       /*
+        * Allocate enough space for the dependency, the attribute numbers in the
+        * list and the final attribute number for the dependency.
+        */
+       dep = palloc0(offsetof(MVDependency, attributes) + ((natts + 1) * sizeof(AttrNumber)));
+       dep->nattributes = natts + 1;
+
+       dep->attributes[natts] = parse->dependency;
+       dep->degree = parse->degree;
+
+       /*
+        * Assign attribute numbers to the attributes array, comparing each one
+        * against the dependency attribute to ensure that there there are no
+        * matches.
+        */
+       for (int i = 0; i < natts; i++)
+       {
+               dep->attributes[i] = (AttrNumber) list_nth_int(parse->attnum_list, i);
+               if (dep->attributes[i] == parse->dependency)
+               {
+                       errsave(parse->escontext,
+                                       errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+                                       errmsg("malformed pg_dependencies: \"%s\"", parse->str),
+                                       errdetail("Item \"%s\" value %d found in the \"%s\" list.",
+                                                         PG_DEPENDENCIES_KEY_DEPENDENCY, parse->dependency,
+                                                         PG_DEPENDENCIES_KEY_ATTRIBUTES));
+                       return JSON_SEM_ACTION_FAILED;
+               }
+       }
+
+       parse->dependency_list = lappend(parse->dependency_list, (void *) dep);
+
+       /*
+        * Reset dependency item state variables to look for the next
+        * MVDependency.
+        */
+       list_free(parse->attnum_list);
+       parse->attnum_list = NIL;
+       parse->dependency = 0;
+       parse->degree = 0.0;
+       parse->found_attributes = false;
+       parse->found_dependency = false;
+       parse->found_degree = false;
+       parse->state = DEPS_EXPECT_ITEM;
+
+       return JSON_SUCCESS;
+}
+
+/*
+ * Invoked at the start of an array.
+ *
+ * Dependency input format does not have arrays, so any array elements
+ * encountered are an error.
+ */
+static JsonParseErrorType
+dependencies_array_start(void *state)
+{
+       DependenciesParseState *parse = state;
+
+       switch (parse->state)
+       {
+               case DEPS_EXPECT_ATTNUM_LIST:
+                       parse->state = DEPS_EXPECT_ATTNUM;
+                       break;
+               case DEPS_EXPECT_START:
+                       parse->state = DEPS_EXPECT_ITEM;
+                       break;
+               default:
+                       errsave(parse->escontext,
+                                       errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+                                       errmsg("malformed pg_dependencies: \"%s\"", parse->str),
+                                       errdetail("Array found in unexpected place."));
+                       return JSON_SEM_ACTION_FAILED;
+       }
+
+       return JSON_SUCCESS;
+}
+
+/*
+ * Invoked at the end of an array.
+ *
+ * Either the end of an attribute number list or the whole object.
+ */
+static JsonParseErrorType
+dependencies_array_end(void *state)
+{
+       DependenciesParseState *parse = state;
+
+       switch (parse->state)
+       {
+               case DEPS_EXPECT_ATTNUM:
+                       if (list_length(parse->attnum_list) > 0)
+                       {
+                               parse->state = DEPS_EXPECT_KEY;
+                               return JSON_SUCCESS;
+                       }
+
+                       errsave(parse->escontext,
+                                       errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+                                       errmsg("malformed pg_dependencies: \"%s\"", parse->str),
+                                       errdetail("The \"%s\" key must be an non-empty array.",
+                                                         PG_DEPENDENCIES_KEY_ATTRIBUTES));
+                       break;
+
+               case DEPS_EXPECT_ITEM:
+                       if (list_length(parse->dependency_list) > 0)
+                       {
+                               parse->state = DEPS_PARSE_COMPLETE;
+                               return JSON_SUCCESS;
+                       }
+
+                       errsave(parse->escontext,
+                                       errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+                                       errmsg("malformed pg_dependencies: \"%s\"", parse->str),
+                                       errdetail("Item array cannot be empty."));
+                       break;
+
+               default:
+
+                       /*
+                        * This can only happen if a case was missed in
+                        * dependencies_array_start().
+                        */
+                       errsave(parse->escontext,
+                                       errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+                                       errmsg("malformed pg_dependencies: \"%s\"", parse->str),
+                                       errdetail("Array found in unexpected place."));
+                       break;
+       }
+       return JSON_SEM_ACTION_FAILED;
+}
+
+/*
+ * Invoked at the start of a key/value field.
+ *
+ * The valid keys for the MVDependency object are:
+ *   - attributes
+ *   - dependency
+ *   - degree
+ */
+static JsonParseErrorType
+dependencies_object_field_start(void *state, char *fname, bool isnull)
+{
+       DependenciesParseState *parse = state;
+
+       if (strcmp(fname, PG_DEPENDENCIES_KEY_ATTRIBUTES) == 0)
+       {
+               if (parse->found_attributes)
+               {
+                       errsave(parse->escontext,
+                                       errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+                                       errmsg("malformed pg_dependencies: \"%s\"", parse->str),
+                                       errdetail("Multiple \"%s\" keys are not allowed.",
+                                                         PG_DEPENDENCIES_KEY_ATTRIBUTES));
+                       return JSON_SEM_ACTION_FAILED;
+               }
+
+               parse->found_attributes = true;
+               parse->state = DEPS_EXPECT_ATTNUM_LIST;
+               return JSON_SUCCESS;
+       }
+
+       if (strcmp(fname, PG_DEPENDENCIES_KEY_DEPENDENCY) == 0)
+       {
+               if (parse->found_dependency)
+               {
+                       errsave(parse->escontext,
+                                       errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+                                       errmsg("malformed pg_dependencies: \"%s\"", parse->str),
+                                       errdetail("Multiple \"%s\" keys are not allowed.",
+                                                         PG_DEPENDENCIES_KEY_DEPENDENCY));
+                       return JSON_SEM_ACTION_FAILED;
+               }
+
+               parse->found_dependency = true;
+               parse->state = DEPS_EXPECT_DEPENDENCY;
+               return JSON_SUCCESS;
+       }
+
+       if (strcmp(fname, PG_DEPENDENCIES_KEY_DEGREE) == 0)
+       {
+               if (parse->found_degree)
+               {
+                       errsave(parse->escontext,
+                                       errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+                                       errmsg("malformed pg_dependencies: \"%s\"", parse->str),
+                                       errdetail("Multiple \"%s\" keys are not allowed.",
+                                                         PG_DEPENDENCIES_KEY_DEGREE));
+                       return JSON_SEM_ACTION_FAILED;
+               }
+
+               parse->found_degree = true;
+               parse->state = DEPS_EXPECT_DEGREE;
+               return JSON_SUCCESS;
+       }
+
+       errsave(parse->escontext,
+                       errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+                       errmsg("malformed pg_dependencies: \"%s\"", parse->str),
+                       errdetail("Only allowed keys are \"%s\", \"%s\" and \"%s\".",
+                                         PG_DEPENDENCIES_KEY_ATTRIBUTES,
+                                         PG_DEPENDENCIES_KEY_DEPENDENCY,
+                                         PG_DEPENDENCIES_KEY_DEGREE));
+       return JSON_SEM_ACTION_FAILED;
+}
+
+/*
+ * Invoked at the start of an array element.
+ *
+ * pg_dependencies input format does not have arrays, so any array elements
+ * encountered are an error.
+ */
+static JsonParseErrorType
+dependencies_array_element_start(void *state, bool isnull)
+{
+       DependenciesParseState *parse = state;
+
+       switch (parse->state)
+       {
+               case DEPS_EXPECT_ATTNUM:
+                       if (!isnull)
+                               return JSON_SUCCESS;
+
+                       errsave(parse->escontext,
+                                       errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+                                       errmsg("malformed pg_dependencies: \"%s\"", parse->str),
+                                       errdetail("Attribute number array cannot be null."));
+                       break;
+
+               case DEPS_EXPECT_ITEM:
+                       if (!isnull)
+                               return JSON_SUCCESS;
+
+                       errsave(parse->escontext,
+                                       errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+                                       errmsg("malformed pg_dependencies: \"%s\"", parse->str),
+                                       errdetail("Item list elements cannot be null."));
+                       break;
+
+               default:
+                       errsave(parse->escontext,
+                                       errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+                                       errmsg("malformed pg_dependencies: \"%s\"", parse->str),
+                                       errdetail("Unexpected array element."));
+                       break;
+       }
+
+       return JSON_SEM_ACTION_FAILED;
+}
+
+/*
+ * Test for valid subsequent attribute number.
+ *
+ * If the previous value is positive, then current value must either be
+ * greater than the previous value, or negative.
+ *
+ * If the previous value is negative, then the value must be less than
+ * the previous value.
+ *
+ * Duplicate values are not allowed; that is already covered by the rules
+ * described above.
+ */
+static bool
+valid_subsequent_attnum(const AttrNumber prev, const AttrNumber cur)
+{
+       Assert(prev != 0);
+
+       if (prev > 0)
+               return ((cur > prev) || (cur < 0));
+
+       return (cur < prev);
+}
+
+/*
+ * Handle scalar events from the dependencies input parser.
+ *
+ * There is only one case where we will encounter a scalar, and that is the
+ * dependency degree for the previous object key.
+ */
+static JsonParseErrorType
+dependencies_scalar(void *state, char *token, JsonTokenType tokentype)
+{
+       DependenciesParseState *parse = state;
+       AttrNumber      attnum;
+       ErrorSaveContext escontext = {T_ErrorSaveContext};
+
+       switch (parse->state)
+       {
+               case DEPS_EXPECT_ATTNUM:
+                       attnum = pg_strtoint16_safe(token, (Node *) &escontext);
+
+                       if (SOFT_ERROR_OCCURRED(&escontext))
+                       {
+                               errsave(parse->escontext,
+                                               errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+                                               errmsg("malformed pg_dependencies: \"%s\"", parse->str),
+                                               errdetail("Invalid \"%s\" value.", PG_DEPENDENCIES_KEY_ATTRIBUTES));
+                               return JSON_SEM_ACTION_FAILED;
+                       }
+
+                       /*
+                        * An attribute number cannot be zero or a negative number beyond
+                        * the number of the possible expressions.
+                        */
+                       if (attnum == 0 || attnum < (0 - STATS_MAX_DIMENSIONS))
+                       {
+                               errsave(parse->escontext,
+                                               errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+                                               errmsg("malformed pg_dependencies: \"%s\"", parse->str),
+                                               errdetail("Invalid \"%s\" element: %d.",
+                                                                 PG_DEPENDENCIES_KEY_ATTRIBUTES, attnum));
+                               return JSON_SEM_ACTION_FAILED;
+                       }
+
+                       if (parse->attnum_list != NIL)
+                       {
+                               const AttrNumber prev = llast_int(parse->attnum_list);
+
+                               if (!valid_subsequent_attnum(prev, attnum))
+                               {
+                                       errsave(parse->escontext,
+                                                       errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+                                                       errmsg("malformed pg_dependencies: \"%s\"", parse->str),
+                                                       errdetail("Invalid \"%s\" element: %d cannot follow %d.",
+                                                                         PG_DEPENDENCIES_KEY_ATTRIBUTES, attnum, prev));
+                                       return JSON_SEM_ACTION_FAILED;
+                               }
+                       }
+
+                       parse->attnum_list = lappend_int(parse->attnum_list, (int) attnum);
+                       return JSON_SUCCESS;
+
+               case DEPS_EXPECT_DEPENDENCY:
+                       parse->dependency = (AttrNumber)
+                               pg_strtoint16_safe(token, (Node *) &escontext);
+
+                       if (SOFT_ERROR_OCCURRED(&escontext))
+                       {
+                               errsave(parse->escontext,
+                                               errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+                                               errmsg("malformed pg_dependencies: \"%s\"", parse->str),
+                                               errdetail("Invalid \"%s\" value.", PG_DEPENDENCIES_KEY_DEPENDENCY));
+                               return JSON_SEM_ACTION_FAILED;
+                       }
+
+                       /*
+                        * The dependency attribute number cannot be zero or a negative
+                        * number beyond the number of the possible expressions.
+                        */
+                       if (parse->dependency == 0 || parse->dependency < (0 - STATS_MAX_DIMENSIONS))
+                       {
+                               errsave(parse->escontext,
+                                               errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+                                               errmsg("malformed pg_dependencies: \"%s\"", parse->str),
+                                               errdetail("Invalid \"%s\" value: %d.",
+                                                                 PG_DEPENDENCIES_KEY_DEPENDENCY, parse->dependency));
+                               return JSON_SEM_ACTION_FAILED;
+                       }
+
+                       parse->state = DEPS_EXPECT_KEY;
+                       return JSON_SUCCESS;
+
+               case DEPS_EXPECT_DEGREE:
+                       parse->degree = float8in_internal(token, NULL, "double",
+                                                                                         token, (Node *) &escontext);
+
+                       if (SOFT_ERROR_OCCURRED(&escontext))
+                       {
+                               errsave(parse->escontext,
+                                               errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+                                               errmsg("malformed pg_dependencies: \"%s\"", parse->str),
+                                               errdetail("Invalid \"%s\" value.", PG_DEPENDENCIES_KEY_DEGREE));
+                               return JSON_SEM_ACTION_FAILED;
+                       }
+
+                       parse->state = DEPS_EXPECT_KEY;
+                       return JSON_SUCCESS;
+
+               default:
+                       errsave(parse->escontext,
+                                       errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+                                       errmsg("malformed pg_dependencies: \"%s\"", parse->str),
+                                       errdetail("Unexpected scalar."));
+                       break;
+       }
+
+       return JSON_SEM_ACTION_FAILED;
+}
+
+/*
+ * Compare the attribute arrays of two MVDependency values,
+ * looking for duplicated sets.
+ */
+static bool
+dep_attributes_eq(const MVDependency *a, const MVDependency *b)
+{
+       int                     i;
+
+       if (a->nattributes != b->nattributes)
+               return false;
+
+       for (i = 0; i < a->nattributes; i++)
+       {
+               if (a->attributes[i] != b->attributes[i])
+                       return false;
+       }
+
+       return true;
+}
+
+/*
+ * Generate a string representing an array of attribute numbers.
+ * Internally, the dependency attribute is the last element, so we
+ * leave that off.
+ *
+ * Freeing the allocated string is the responsibility of the caller.
+ */
+static char *
+dep_attnum_list(const MVDependency *item)
+{
+       StringInfoData str;
+
+       initStringInfo(&str);
+
+       appendStringInfo(&str, "%d", item->attributes[0]);
+
+       for (int i = 1; i < item->nattributes - 1; i++)
+               appendStringInfo(&str, ", %d", item->attributes[i]);
+
+       return str.data;
+}
+
+/*
+ * Return the dependency, which is the last attribute element.
+ */
+static AttrNumber
+dep_attnum_dependency(const MVDependency *item)
+{
+       return item->attributes[item->nattributes - 1];
+}
+
+/*
+ * Attempt to build and serialize the MVDependencies object.
+ *
+ * This can only be executed after the completion of the JSON parsing.
+ *
+ * In the event of an error, set the error context and return NULL.
+ */
+static bytea *
+build_mvdependencies(DependenciesParseState *parse, char *str)
+{
+       int                     ndeps = list_length(parse->dependency_list);
+
+       MVDependencies *mvdeps;
+       bytea      *bytes;
+
+       switch (parse->state)
+       {
+               case DEPS_PARSE_COMPLETE:
+
+                       /*
+                        * Parse ended in the expected place.  We should have a list of
+                        * items, but if we do not there is an issue with one of the
+                        * earlier parse steps.
+                        */
+                       if (ndeps == 0)
+                               elog(ERROR,
+                                        "pg_dependencies parsing claims success with an empty item list.");
+                       break;
+
+               case DEPS_EXPECT_START:
+                       /* blank */
+                       errsave(parse->escontext,
+                                       errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+                                       errmsg("malformed pg_dependencies: \"%s\"", str),
+                                       errdetail("Value cannot be empty."));
+                       return NULL;
+
+               default:
+                       /* Unexpected end-state. */
+                       errsave(parse->escontext,
+                                       errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+                                       errmsg("malformed pg_dependencies: \"%s\"", str),
+                                       errdetail("Unexpected end state %d.", parse->state));
+                       return NULL;
+       }
+
+       mvdeps = palloc0(offsetof(MVDependencies, deps)
+                                        + (ndeps * sizeof(MVDependency *)));
+       mvdeps->magic = STATS_DEPS_MAGIC;
+       mvdeps->type = STATS_DEPS_TYPE_BASIC;
+       mvdeps->ndeps = ndeps;
+
+       for (int i = 0; i < ndeps; i++)
+       {
+               /*
+                * Use the MVDependency objects in the dependency_list.
+                *
+                * Because we free the dependency_list after parsing is done, we
+                * cannot free it here.
+                */
+               mvdeps->deps[i] = list_nth(parse->dependency_list, i);
+
+               /*
+                * Ensure that this item does not duplicate the attributes of any
+                * pre-existing item.
+                */
+               for (int j = 0; j < i; j++)
+               {
+                       if (dep_attributes_eq(mvdeps->deps[i], mvdeps->deps[j]))
+                       {
+                               MVDependency *dep = mvdeps->deps[i];
+                               char       *attnum_list = dep_attnum_list(dep);
+                               AttrNumber      attnum_dep = dep_attnum_dependency(dep);
+
+                               errsave(parse->escontext,
+                                               errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+                                               errmsg("malformed pg_dependencies: \"%s\"", str),
+                                               errdetail("Duplicate \"%s\" array: [%s] with \"%s\": %d.",
+                                                                 PG_DEPENDENCIES_KEY_ATTRIBUTES, attnum_list,
+                                                                 PG_DEPENDENCIES_KEY_DEPENDENCY, attnum_dep));
+                               pfree(mvdeps);
+                               return NULL;
+                       }
+               }
+       }
+
+       bytes = statext_dependencies_serialize(mvdeps);
+
+       /*
+        * No need to free the individual MVDependency objects, because they are
+        * still in the dependency_list, and will be freed with that.
+        */
+       pfree(mvdeps);
+
+       return bytes;
+}
+
+
 /*
  * pg_dependencies_in          - input routine for type pg_dependencies.
  *
- * pg_dependencies is real enough to be a table column, but it has no operations
- * of its own, and disallows input too
+ * This format is valid JSON, with the expected format:
+ *    [{"attributes": [1,2], "dependency": -1, "degree": 1.0000},
+ *     {"attributes": [1,-1], "dependency": 2, "degree": 0.0000},
+ *     {"attributes": [2,-1], "dependency": 1, "degree": 1.0000}]
+ *
  */
 Datum
 pg_dependencies_in(PG_FUNCTION_ARGS)
 {
+       char       *str = PG_GETARG_CSTRING(0);
+       bytea      *bytes = NULL;
+
+       DependenciesParseState parse_state;
+       JsonParseErrorType result;
+       JsonLexContext *lex;
+       JsonSemAction sem_action;
+
+       /* initialize the semantic state */
+       parse_state.str = str;
+       parse_state.state = DEPS_EXPECT_START;
+       parse_state.dependency_list = NIL;
+       parse_state.attnum_list = NIL;
+       parse_state.dependency = 0;
+       parse_state.degree = 0.0;
+       parse_state.found_attributes = false;
+       parse_state.found_dependency = false;
+       parse_state.found_degree = false;
+       parse_state.escontext = fcinfo->context;
+
+       /* set callbacks */
+       sem_action.semstate = (void *) &parse_state;
+       sem_action.object_start = dependencies_object_start;
+       sem_action.object_end = dependencies_object_end;
+       sem_action.array_start = dependencies_array_start;
+       sem_action.array_end = dependencies_array_end;
+       sem_action.array_element_start = dependencies_array_element_start;
+       sem_action.array_element_end = NULL;
+       sem_action.object_field_start = dependencies_object_field_start;
+       sem_action.object_field_end = NULL;
+       sem_action.scalar = dependencies_scalar;
+
+       lex = makeJsonLexContextCstringLen(NULL, str, strlen(str), PG_UTF8, true);
+
+       result = pg_parse_json(lex, &sem_action);
+       freeJsonLexContext(lex);
+
+       if (result == JSON_SUCCESS)
+               bytes = build_mvdependencies(&parse_state, str);
+
+       list_free_deep(parse_state.dependency_list);
+       list_free(parse_state.attnum_list);
+
+       if (bytes)
+               PG_RETURN_BYTEA_P(bytes);
+
        /*
-        * pg_node_list stores the data in binary form and parsing text input is
-        * not needed, so disallow this.
+        * If escontext already set, just use that. Anything else is a generic
+        * JSON parse error.
         */
-       ereport(ERROR,
-                       (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-                        errmsg("cannot accept a value of type %s", "pg_dependencies")));
+       if (!SOFT_ERROR_OCCURRED(parse_state.escontext))
+               errsave(parse_state.escontext,
+                               errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+                               errmsg("malformed pg_dependencies: \"%s\"", str),
+                               errdetail("Must be valid JSON."));
 
-       PG_RETURN_VOID();                       /* keep compiler quiet */
+       PG_RETURN_NULL();
 }
 
+
 /*
  * pg_dependencies_out         - output routine for type pg_dependencies.
  */
diff --git a/src/test/regress/expected/pg_dependencies.out b/src/test/regress/expected/pg_dependencies.out
new file mode 100644 (file)
index 0000000..5c6fe66
--- /dev/null
@@ -0,0 +1,540 @@
+-- Tests for type pg_distinct
+-- Invalid inputs
+SELECT 'null'::pg_dependencies;
+ERROR:  malformed pg_dependencies: "null"
+LINE 1: SELECT 'null'::pg_dependencies;
+               ^
+DETAIL:  Unexpected scalar.
+SELECT '{"a": 1}'::pg_dependencies;
+ERROR:  malformed pg_dependencies: "{"a": 1}"
+LINE 1: SELECT '{"a": 1}'::pg_dependencies;
+               ^
+DETAIL:  Initial element must be an array.
+SELECT '[]'::pg_dependencies;
+ERROR:  malformed pg_dependencies: "[]"
+LINE 1: SELECT '[]'::pg_dependencies;
+               ^
+DETAIL:  Item array cannot be empty.
+SELECT '{}'::pg_dependencies;
+ERROR:  malformed pg_dependencies: "{}"
+LINE 1: SELECT '{}'::pg_dependencies;
+               ^
+DETAIL:  Initial element must be an array.
+SELECT '[null]'::pg_dependencies;
+ERROR:  malformed pg_dependencies: "[null]"
+LINE 1: SELECT '[null]'::pg_dependencies;
+               ^
+DETAIL:  Item list elements cannot be null.
+SELECT * FROM pg_input_error_info('null', 'pg_dependencies');
+              message              |       detail       | hint | sql_error_code 
+-----------------------------------+--------------------+------+----------------
+ malformed pg_dependencies: "null" | Unexpected scalar. |      | 22P02
+(1 row)
+
+SELECT * FROM pg_input_error_info('{"a": 1}', 'pg_dependencies');
+                message                |              detail               | hint | sql_error_code 
+---------------------------------------+-----------------------------------+------+----------------
+ malformed pg_dependencies: "{"a": 1}" | Initial element must be an array. |      | 22P02
+(1 row)
+
+SELECT * FROM pg_input_error_info('[]', 'pg_dependencies');
+             message             |           detail            | hint | sql_error_code 
+---------------------------------+-----------------------------+------+----------------
+ malformed pg_dependencies: "[]" | Item array cannot be empty. |      | 22P02
+(1 row)
+
+SELECT * FROM pg_input_error_info('{}', 'pg_dependencies');
+             message             |              detail               | hint | sql_error_code 
+---------------------------------+-----------------------------------+------+----------------
+ malformed pg_dependencies: "{}" | Initial element must be an array. |      | 22P02
+(1 row)
+
+SELECT * FROM pg_input_error_info('[null]', 'pg_dependencies');
+               message               |               detail               | hint | sql_error_code 
+-------------------------------------+------------------------------------+------+----------------
+ malformed pg_dependencies: "[null]" | Item list elements cannot be null. |      | 22P02
+(1 row)
+
+-- Invalid keys
+SELECT '[{"attributes_invalid" : [2,3], "dependency" : 4}]'::pg_dependencies;
+ERROR:  malformed pg_dependencies: "[{"attributes_invalid" : [2,3], "dependency" : 4}]"
+LINE 1: SELECT '[{"attributes_invalid" : [2,3], "dependency" : 4}]':...
+               ^
+DETAIL:  Only allowed keys are "attributes", "dependency" and "degree".
+SELECT '[{"attributes" : [2,3], "invalid" : 3, "dependency" : 4}]'::pg_dependencies;
+ERROR:  malformed pg_dependencies: "[{"attributes" : [2,3], "invalid" : 3, "dependency" : 4}]"
+LINE 1: SELECT '[{"attributes" : [2,3], "invalid" : 3, "dependency" ...
+               ^
+DETAIL:  Only allowed keys are "attributes", "dependency" and "degree".
+SELECT * FROM pg_input_error_info('[{"attributes_invalid" : [2,3], "dependency" : 4}]', 'pg_dependencies');
+                                     message                                     |                             detail                             | hint | sql_error_code 
+---------------------------------------------------------------------------------+----------------------------------------------------------------+------+----------------
+ malformed pg_dependencies: "[{"attributes_invalid" : [2,3], "dependency" : 4}]" | Only allowed keys are "attributes", "dependency" and "degree". |      | 22P02
+(1 row)
+
+SELECT * FROM pg_input_error_info('[{"attributes" : [2,3], "invalid" : 3, "dependency" : 4}]', 'pg_dependencies');
+                                        message                                         |                             detail                             | hint | sql_error_code 
+----------------------------------------------------------------------------------------+----------------------------------------------------------------+------+----------------
+ malformed pg_dependencies: "[{"attributes" : [2,3], "invalid" : 3, "dependency" : 4}]" | Only allowed keys are "attributes", "dependency" and "degree". |      | 22P02
+(1 row)
+
+-- Missing keys
+SELECT '[{"attributes" : [2,3], "dependency" : 4}]'::pg_dependencies;
+ERROR:  malformed pg_dependencies: "[{"attributes" : [2,3], "dependency" : 4}]"
+LINE 1: SELECT '[{"attributes" : [2,3], "dependency" : 4}]'::pg_depe...
+               ^
+DETAIL:  Item must contain "degree" key.
+SELECT '[{"attributes" : [2,3], "degree" : 1.000}]'::pg_dependencies;
+ERROR:  malformed pg_dependencies: "[{"attributes" : [2,3], "degree" : 1.000}]"
+LINE 1: SELECT '[{"attributes" : [2,3], "degree" : 1.000}]'::pg_depe...
+               ^
+DETAIL:  Item must contain "dependency" key.
+SELECT '[{"attributes" : [2,3], "dependency" : 4}]'::pg_dependencies;
+ERROR:  malformed pg_dependencies: "[{"attributes" : [2,3], "dependency" : 4}]"
+LINE 1: SELECT '[{"attributes" : [2,3], "dependency" : 4}]'::pg_depe...
+               ^
+DETAIL:  Item must contain "degree" key.
+SELECT * FROM pg_input_error_info('[{"attributes" : [2,3], "dependency" : 4}]', 'pg_dependencies');
+                                 message                                 |             detail              | hint | sql_error_code 
+-------------------------------------------------------------------------+---------------------------------+------+----------------
+ malformed pg_dependencies: "[{"attributes" : [2,3], "dependency" : 4}]" | Item must contain "degree" key. |      | 22P02
+(1 row)
+
+SELECT * FROM pg_input_error_info('[{"attributes" : [2,3], "degree" : 1.000}]', 'pg_dependencies');
+                                 message                                 |               detail                | hint | sql_error_code 
+-------------------------------------------------------------------------+-------------------------------------+------+----------------
+ malformed pg_dependencies: "[{"attributes" : [2,3], "degree" : 1.000}]" | Item must contain "dependency" key. |      | 22P02
+(1 row)
+
+SELECT * FROM pg_input_error_info('[{"attributes" : [2,3], "dependency" : 4}]', 'pg_dependencies');
+                                 message                                 |             detail              | hint | sql_error_code 
+-------------------------------------------------------------------------+---------------------------------+------+----------------
+ malformed pg_dependencies: "[{"attributes" : [2,3], "dependency" : 4}]" | Item must contain "degree" key. |      | 22P02
+(1 row)
+
+-- Valid keys, too many attributes
+SELECT '[{"attributes" : [1,2,3,4,5,6,7,8], "dependency" : 4, "degree": 1.000}]'::pg_dependencies;
+ERROR:  malformed pg_dependencies: "[{"attributes" : [1,2,3,4,5,6,7,8], "dependency" : 4, "degree": 1.000}]"
+LINE 1: SELECT '[{"attributes" : [1,2,3,4,5,6,7,8], "dependency" : 4...
+               ^
+DETAIL:  The "attributes" key must contain an array of at least 1 and no more than 7 elements.
+SELECT * FROM pg_input_error_info('[{"attributes" : [1,2,3,4,5,6,7,8], "dependency" : 4, "degree": 1.000}]', 'pg_dependencies');
+                                               message                                                |                                        detail                                         | hint | sql_error_code 
+------------------------------------------------------------------------------------------------------+---------------------------------------------------------------------------------------+------+----------------
+ malformed pg_dependencies: "[{"attributes" : [1,2,3,4,5,6,7,8], "dependency" : 4, "degree": 1.000}]" | The "attributes" key must contain an array of at least 1 and no more than 7 elements. |      | 22P02
+(1 row)
+
+-- Special characters
+SELECT '[{"attributes" : ["\ud83d",3], "dependency" : 4, "degree": 0.250}]'::pg_dependencies;
+ERROR:  malformed pg_dependencies: "[{"attributes" : ["\ud83d",3], "dependency" : 4, "degree": 0.250}]"
+LINE 1: SELECT '[{"attributes" : ["\ud83d",3], "dependency" : 4, "de...
+               ^
+DETAIL:  Must be valid JSON.
+SELECT '[{"attributes" : [2,3], "dependency" : "\ud83d", "degree": 0.250}]'::pg_dependencies;
+ERROR:  malformed pg_dependencies: "[{"attributes" : [2,3], "dependency" : "\ud83d", "degree": 0.250}]"
+LINE 1: SELECT '[{"attributes" : [2,3], "dependency" : "\ud83d", "de...
+               ^
+DETAIL:  Must be valid JSON.
+SELECT '[{"attributes" : [2,3], "dependency" : 4, "degree": "\ud83d"}]'::pg_dependencies;
+ERROR:  malformed pg_dependencies: "[{"attributes" : [2,3], "dependency" : 4, "degree": "\ud83d"}]"
+LINE 1: SELECT '[{"attributes" : [2,3], "dependency" : 4, "degree": ...
+               ^
+DETAIL:  Must be valid JSON.
+SELECT '[{"\ud83d" : [2,3], "dependency" : 4, "degree": 0.250}]'::pg_dependencies;
+ERROR:  malformed pg_dependencies: "[{"\ud83d" : [2,3], "dependency" : 4, "degree": 0.250}]"
+LINE 1: SELECT '[{"\ud83d" : [2,3], "dependency" : 4, "degree": 0.25...
+               ^
+DETAIL:  Must be valid JSON.
+SELECT * FROM pg_input_error_info('[{"attributes" : ["\ud83d",3], "dependency" : 4, "degree": 0.250}]', 'pg_dependencies');
+                                             message                                             |       detail        | hint | sql_error_code 
+-------------------------------------------------------------------------------------------------+---------------------+------+----------------
+ malformed pg_dependencies: "[{"attributes" : ["\ud83d",3], "dependency" : 4, "degree": 0.250}]" | Must be valid JSON. |      | 22P02
+(1 row)
+
+SELECT * FROM pg_input_error_info('[{"attributes" : [2,3], "dependency" : "\ud83d", "degree": 0.250}]', 'pg_dependencies');
+                                             message                                             |       detail        | hint | sql_error_code 
+-------------------------------------------------------------------------------------------------+---------------------+------+----------------
+ malformed pg_dependencies: "[{"attributes" : [2,3], "dependency" : "\ud83d", "degree": 0.250}]" | Must be valid JSON. |      | 22P02
+(1 row)
+
+SELECT * FROM pg_input_error_info('[{"attributes" : [2,3], "dependency" : 4, "degree": "\ud83d"}]', 'pg_dependencies');
+                                           message                                           |       detail        | hint | sql_error_code 
+---------------------------------------------------------------------------------------------+---------------------+------+----------------
+ malformed pg_dependencies: "[{"attributes" : [2,3], "dependency" : 4, "degree": "\ud83d"}]" | Must be valid JSON. |      | 22P02
+(1 row)
+
+SELECT * FROM pg_input_error_info('[{"\ud83d" : [2,3], "dependency" : 4, "degree": 0.250}]', 'pg_dependencies');
+                                       message                                        |       detail        | hint | sql_error_code 
+--------------------------------------------------------------------------------------+---------------------+------+----------------
+ malformed pg_dependencies: "[{"\ud83d" : [2,3], "dependency" : 4, "degree": 0.250}]" | Must be valid JSON. |      | 22P02
+(1 row)
+
+-- Valid keys, invalid values
+SELECT '[{"attributes" : null, "dependency" : 4, "degree": 1.000}]'::pg_dependencies;
+ERROR:  malformed pg_dependencies: "[{"attributes" : null, "dependency" : 4, "degree": 1.000}]"
+LINE 1: SELECT '[{"attributes" : null, "dependency" : 4, "degree": 1...
+               ^
+DETAIL:  Unexpected scalar.
+SELECT '[{"attributes" : [2,null], "dependency" : 4, "degree": 1.000}]'::pg_dependencies;
+ERROR:  malformed pg_dependencies: "[{"attributes" : [2,null], "dependency" : 4, "degree": 1.000}]"
+LINE 1: SELECT '[{"attributes" : [2,null], "dependency" : 4, "degree...
+               ^
+DETAIL:  Attribute number array cannot be null.
+SELECT '[{"attributes" : [2,3], "dependency" : null, "degree": 1.000}]'::pg_dependencies;
+ERROR:  malformed pg_dependencies: "[{"attributes" : [2,3], "dependency" : null, "degree": 1.000}]"
+LINE 1: SELECT '[{"attributes" : [2,3], "dependency" : null, "degree...
+               ^
+DETAIL:  Invalid "dependency" value.
+SELECT '[{"attributes" : [2,"a"], "dependency" : 4, "degree": 1.000}]'::pg_dependencies;
+ERROR:  malformed pg_dependencies: "[{"attributes" : [2,"a"], "dependency" : 4, "degree": 1.000}]"
+LINE 1: SELECT '[{"attributes" : [2,"a"], "dependency" : 4, "degree"...
+               ^
+DETAIL:  Invalid "attributes" value.
+SELECT '[{"attributes" : [2,3], "dependency" : "a", "degree": 1.000}]'::pg_dependencies;
+ERROR:  malformed pg_dependencies: "[{"attributes" : [2,3], "dependency" : "a", "degree": 1.000}]"
+LINE 1: SELECT '[{"attributes" : [2,3], "dependency" : "a", "degree"...
+               ^
+DETAIL:  Invalid "dependency" value.
+SELECT '[{"attributes" : [2,3], "dependency" : [], "degree": 1.000}]'::pg_dependencies;
+ERROR:  malformed pg_dependencies: "[{"attributes" : [2,3], "dependency" : [], "degree": 1.000}]"
+LINE 1: SELECT '[{"attributes" : [2,3], "dependency" : [], "degree":...
+               ^
+DETAIL:  Array found in unexpected place.
+SELECT '[{"attributes" : [2,3], "dependency" : [null], "degree": 1.000}]'::pg_dependencies;
+ERROR:  malformed pg_dependencies: "[{"attributes" : [2,3], "dependency" : [null], "degree": 1.000}]"
+LINE 1: SELECT '[{"attributes" : [2,3], "dependency" : [null], "degr...
+               ^
+DETAIL:  Array found in unexpected place.
+SELECT '[{"attributes" : [2,3], "dependency" : [1,null], "degree": 1.000}]'::pg_dependencies;
+ERROR:  malformed pg_dependencies: "[{"attributes" : [2,3], "dependency" : [1,null], "degree": 1.000}]"
+LINE 1: SELECT '[{"attributes" : [2,3], "dependency" : [1,null], "de...
+               ^
+DETAIL:  Array found in unexpected place.
+SELECT '[{"attributes" : 1, "dependency" : 4, "degree": 1.000}]'::pg_dependencies;
+ERROR:  malformed pg_dependencies: "[{"attributes" : 1, "dependency" : 4, "degree": 1.000}]"
+LINE 1: SELECT '[{"attributes" : 1, "dependency" : 4, "degree": 1.00...
+               ^
+DETAIL:  Unexpected scalar.
+SELECT '[{"attributes" : "a", "dependency" : 4, "degree": 1.000}]'::pg_dependencies;
+ERROR:  malformed pg_dependencies: "[{"attributes" : "a", "dependency" : 4, "degree": 1.000}]"
+LINE 1: SELECT '[{"attributes" : "a", "dependency" : 4, "degree": 1....
+               ^
+DETAIL:  Unexpected scalar.
+SELECT '[{"attributes" : [2,3], "dependency" : 4, "degree": NaN}]'::pg_dependencies;
+ERROR:  malformed pg_dependencies: "[{"attributes" : [2,3], "dependency" : 4, "degree": NaN}]"
+LINE 1: SELECT '[{"attributes" : [2,3], "dependency" : 4, "degree": ...
+               ^
+DETAIL:  Must be valid JSON.
+SELECT * FROM pg_input_error_info('[{"attributes" : null, "dependency" : 4, "degree": 1.000}]', 'pg_dependencies');
+                                         message                                         |       detail       | hint | sql_error_code 
+-----------------------------------------------------------------------------------------+--------------------+------+----------------
+ malformed pg_dependencies: "[{"attributes" : null, "dependency" : 4, "degree": 1.000}]" | Unexpected scalar. |      | 22P02
+(1 row)
+
+SELECT * FROM pg_input_error_info('[{"attributes" : [2,null], "dependency" : 4, "degree": 1.000}]', 'pg_dependencies');
+                                           message                                           |                 detail                 | hint | sql_error_code 
+---------------------------------------------------------------------------------------------+----------------------------------------+------+----------------
+ malformed pg_dependencies: "[{"attributes" : [2,null], "dependency" : 4, "degree": 1.000}]" | Attribute number array cannot be null. |      | 22P02
+(1 row)
+
+SELECT * FROM pg_input_error_info('[{"attributes" : [2,3], "dependency" : null, "degree": 1.000}]', 'pg_dependencies');
+                                           message                                           |           detail            | hint | sql_error_code 
+---------------------------------------------------------------------------------------------+-----------------------------+------+----------------
+ malformed pg_dependencies: "[{"attributes" : [2,3], "dependency" : null, "degree": 1.000}]" | Invalid "dependency" value. |      | 22P02
+(1 row)
+
+SELECT * FROM pg_input_error_info('[{"attributes" : [2,"a"], "dependency" : 4, "degree": 1.000}]', 'pg_dependencies');
+                                          message                                           |           detail            | hint | sql_error_code 
+--------------------------------------------------------------------------------------------+-----------------------------+------+----------------
+ malformed pg_dependencies: "[{"attributes" : [2,"a"], "dependency" : 4, "degree": 1.000}]" | Invalid "attributes" value. |      | 22P02
+(1 row)
+
+SELECT * FROM pg_input_error_info('[{"attributes" : [2,3], "dependency" : "a", "degree": 1.000}]', 'pg_dependencies');
+                                          message                                           |           detail            | hint | sql_error_code 
+--------------------------------------------------------------------------------------------+-----------------------------+------+----------------
+ malformed pg_dependencies: "[{"attributes" : [2,3], "dependency" : "a", "degree": 1.000}]" | Invalid "dependency" value. |      | 22P02
+(1 row)
+
+SELECT * FROM pg_input_error_info('[{"attributes" : [2,3], "dependency" : [], "degree": 1.000}]', 'pg_dependencies');
+                                          message                                          |              detail              | hint | sql_error_code 
+-------------------------------------------------------------------------------------------+----------------------------------+------+----------------
+ malformed pg_dependencies: "[{"attributes" : [2,3], "dependency" : [], "degree": 1.000}]" | Array found in unexpected place. |      | 22P02
+(1 row)
+
+SELECT * FROM pg_input_error_info('[{"attributes" : [2,3], "dependency" : [null], "degree": 1.000}]', 'pg_dependencies');
+                                            message                                            |              detail              | hint | sql_error_code 
+-----------------------------------------------------------------------------------------------+----------------------------------+------+----------------
+ malformed pg_dependencies: "[{"attributes" : [2,3], "dependency" : [null], "degree": 1.000}]" | Array found in unexpected place. |      | 22P02
+(1 row)
+
+SELECT * FROM pg_input_error_info('[{"attributes" : [2,3], "dependency" : [1,null], "degree": 1.000}]', 'pg_dependencies');
+                                             message                                             |              detail              | hint | sql_error_code 
+-------------------------------------------------------------------------------------------------+----------------------------------+------+----------------
+ malformed pg_dependencies: "[{"attributes" : [2,3], "dependency" : [1,null], "degree": 1.000}]" | Array found in unexpected place. |      | 22P02
+(1 row)
+
+SELECT * FROM pg_input_error_info('[{"attributes" : 1, "dependency" : 4, "degree": 1.000}]', 'pg_dependencies');
+                                       message                                        |       detail       | hint | sql_error_code 
+--------------------------------------------------------------------------------------+--------------------+------+----------------
+ malformed pg_dependencies: "[{"attributes" : 1, "dependency" : 4, "degree": 1.000}]" | Unexpected scalar. |      | 22P02
+(1 row)
+
+SELECT * FROM pg_input_error_info('[{"attributes" : "a", "dependency" : 4, "degree": 1.000}]', 'pg_dependencies');
+                                        message                                         |       detail       | hint | sql_error_code 
+----------------------------------------------------------------------------------------+--------------------+------+----------------
+ malformed pg_dependencies: "[{"attributes" : "a", "dependency" : 4, "degree": 1.000}]" | Unexpected scalar. |      | 22P02
+(1 row)
+
+SELECT * FROM pg_input_error_info('[{"attributes" : [2,3], "dependency" : 4, "degree": NaN}]', 'pg_dependencies');
+                                        message                                         |       detail        | hint | sql_error_code 
+----------------------------------------------------------------------------------------+---------------------+------+----------------
+ malformed pg_dependencies: "[{"attributes" : [2,3], "dependency" : 4, "degree": NaN}]" | Must be valid JSON. |      | 22P02
+(1 row)
+
+SELECT '[{"attributes": [], "dependency": 2, "degree": 1}]' ::pg_dependencies;
+ERROR:  malformed pg_dependencies: "[{"attributes": [], "dependency": 2, "degree": 1}]"
+LINE 1: SELECT '[{"attributes": [], "dependency": 2, "degree": 1}]' ...
+               ^
+DETAIL:  The "attributes" key must be an non-empty array.
+SELECT '[{"attributes" : {"a": 1}, "dependency" : 4, "degree": "1.2"}]'::pg_dependencies;
+ERROR:  malformed pg_dependencies: "[{"attributes" : {"a": 1}, "dependency" : 4, "degree": "1.2"}]"
+LINE 1: SELECT '[{"attributes" : {"a": 1}, "dependency" : 4, "degree...
+               ^
+DETAIL:  Value of "attributes" must be an array of attribute numbers.
+SELECT * FROM pg_input_error_info('[{"attributes": [], "dependency": 2, "degree": 1}]', 'pg_dependencies');
+                                     message                                     |                      detail                      | hint | sql_error_code 
+---------------------------------------------------------------------------------+--------------------------------------------------+------+----------------
+ malformed pg_dependencies: "[{"attributes": [], "dependency": 2, "degree": 1}]" | The "attributes" key must be an non-empty array. |      | 22P02
+(1 row)
+
+SELECT * FROM pg_input_error_info('[{"attributes" : {"a": 1}, "dependency" : 4, "degree": "1.2"}]', 'pg_dependencies');
+                                           message                                           |                            detail                            | hint | sql_error_code 
+---------------------------------------------------------------------------------------------+--------------------------------------------------------------+------+----------------
+ malformed pg_dependencies: "[{"attributes" : {"a": 1}, "dependency" : 4, "degree": "1.2"}]" | Value of "attributes" must be an array of attribute numbers. |      | 22P02
+(1 row)
+
+SELECT '[{"dependency" : 4, "degree": "1.2"}]'::pg_dependencies;
+ERROR:  malformed pg_dependencies: "[{"dependency" : 4, "degree": "1.2"}]"
+LINE 1: SELECT '[{"dependency" : 4, "degree": "1.2"}]'::pg_dependenc...
+               ^
+DETAIL:  Item must contain "attributes" key
+SELECT '[{"attributes" : [1,2,3,4,5,6,7], "dependency" : 0, "degree": "1.2"}]'::pg_dependencies;
+ERROR:  malformed pg_dependencies: "[{"attributes" : [1,2,3,4,5,6,7], "dependency" : 0, "degree": "1.2"}]"
+LINE 1: SELECT '[{"attributes" : [1,2,3,4,5,6,7], "dependency" : 0, ...
+               ^
+DETAIL:  Invalid "dependency" value: 0.
+SELECT '[{"attributes" : [1,2,3,4,5,6,7], "dependency" : -9, "degree": "1.2"}]'::pg_dependencies;
+ERROR:  malformed pg_dependencies: "[{"attributes" : [1,2,3,4,5,6,7], "dependency" : -9, "degree": "1.2"}]"
+LINE 1: SELECT '[{"attributes" : [1,2,3,4,5,6,7], "dependency" : -9,...
+               ^
+DETAIL:  Invalid "dependency" value: -9.
+SELECT '[{"attributes": [1,2], "dependency": 2, "degree": 1}]' ::pg_dependencies;
+ERROR:  malformed pg_dependencies: "[{"attributes": [1,2], "dependency": 2, "degree": 1}]"
+LINE 1: SELECT '[{"attributes": [1,2], "dependency": 2, "degree": 1}...
+               ^
+DETAIL:  Item "dependency" value 2 found in the "attributes" list.
+SELECT '[{"attributes" : [1, {}], "dependency" : 1, "degree": "1.2"}]'::pg_dependencies;
+ERROR:  malformed pg_dependencies: "[{"attributes" : [1, {}], "dependency" : 1, "degree": "1.2"}]"
+LINE 1: SELECT '[{"attributes" : [1, {}], "dependency" : 1, "degree"...
+               ^
+DETAIL:  Attribute lists can only contain attribute numbers.
+SELECT '[{"attributes" : [1,2], "dependency" : {}, "degree": 1.0}]'::pg_dependencies;
+ERROR:  malformed pg_dependencies: "[{"attributes" : [1,2], "dependency" : {}, "degree": 1.0}]"
+LINE 1: SELECT '[{"attributes" : [1,2], "dependency" : {}, "degree":...
+               ^
+DETAIL:  Value of "dependency" must be an integer.
+SELECT '[{"attributes" : [1,2], "dependency" : 3, "degree": {}}]'::pg_dependencies;
+ERROR:  malformed pg_dependencies: "[{"attributes" : [1,2], "dependency" : 3, "degree": {}}]"
+LINE 1: SELECT '[{"attributes" : [1,2], "dependency" : 3, "degree": ...
+               ^
+DETAIL:  Value of "degree" must be an integer.
+SELECT '[{"attributes" : [1,2], "dependency" : 1, "degree": "a"}]'::pg_dependencies;
+ERROR:  malformed pg_dependencies: "[{"attributes" : [1,2], "dependency" : 1, "degree": "a"}]"
+LINE 1: SELECT '[{"attributes" : [1,2], "dependency" : 1, "degree": ...
+               ^
+DETAIL:  Invalid "degree" value.
+SELECT * FROM pg_input_error_info('[{"dependency" : 4, "degree": "1.2"}]', 'pg_dependencies');
+                              message                               |               detail               | hint | sql_error_code 
+--------------------------------------------------------------------+------------------------------------+------+----------------
+ malformed pg_dependencies: "[{"dependency" : 4, "degree": "1.2"}]" | Item must contain "attributes" key |      | 22P02
+(1 row)
+
+SELECT * FROM pg_input_error_info('[{"attributes" : [1,2,3,4,5,6,7], "dependency" : 0, "degree": "1.2"}]', 'pg_dependencies');
+                                              message                                               |             detail             | hint | sql_error_code 
+----------------------------------------------------------------------------------------------------+--------------------------------+------+----------------
+ malformed pg_dependencies: "[{"attributes" : [1,2,3,4,5,6,7], "dependency" : 0, "degree": "1.2"}]" | Invalid "dependency" value: 0. |      | 22P02
+(1 row)
+
+SELECT * FROM pg_input_error_info('[{"attributes" : [1,2,3,4,5,6,7], "dependency" : -9, "degree": "1.2"}]', 'pg_dependencies');
+                                               message                                               |             detail              | hint | sql_error_code 
+-----------------------------------------------------------------------------------------------------+---------------------------------+------+----------------
+ malformed pg_dependencies: "[{"attributes" : [1,2,3,4,5,6,7], "dependency" : -9, "degree": "1.2"}]" | Invalid "dependency" value: -9. |      | 22P02
+(1 row)
+
+SELECT * FROM pg_input_error_info('[{"attributes": [1,2], "dependency": 2, "degree": 1}]' , 'pg_dependencies');
+                                      message                                       |                          detail                           | hint | sql_error_code 
+------------------------------------------------------------------------------------+-----------------------------------------------------------+------+----------------
+ malformed pg_dependencies: "[{"attributes": [1,2], "dependency": 2, "degree": 1}]" | Item "dependency" value 2 found in the "attributes" list. |      | 22P02
+(1 row)
+
+SELECT * FROM pg_input_error_info('[{"attributes" : [1, {}], "dependency" : 1, "degree": "1.2"}]', 'pg_dependencies');
+                                          message                                           |                       detail                        | hint | sql_error_code 
+--------------------------------------------------------------------------------------------+-----------------------------------------------------+------+----------------
+ malformed pg_dependencies: "[{"attributes" : [1, {}], "dependency" : 1, "degree": "1.2"}]" | Attribute lists can only contain attribute numbers. |      | 22P02
+(1 row)
+
+SELECT * FROM pg_input_error_info('[{"attributes" : [1,2], "dependency" : {}, "degree": 1.0}]', 'pg_dependencies');
+                                         message                                         |                  detail                   | hint | sql_error_code 
+-----------------------------------------------------------------------------------------+-------------------------------------------+------+----------------
+ malformed pg_dependencies: "[{"attributes" : [1,2], "dependency" : {}, "degree": 1.0}]" | Value of "dependency" must be an integer. |      | 22P02
+(1 row)
+
+SELECT * FROM pg_input_error_info('[{"attributes" : [1,2], "dependency" : 3, "degree": {}}]', 'pg_dependencies');
+                                        message                                        |                detail                 | hint | sql_error_code 
+---------------------------------------------------------------------------------------+---------------------------------------+------+----------------
+ malformed pg_dependencies: "[{"attributes" : [1,2], "dependency" : 3, "degree": {}}]" | Value of "degree" must be an integer. |      | 22P02
+(1 row)
+
+SELECT * FROM pg_input_error_info('[{"attributes" : [1,2], "dependency" : 1, "degree": "a"}]', 'pg_dependencies');
+                                        message                                         |         detail          | hint | sql_error_code 
+----------------------------------------------------------------------------------------+-------------------------+------+----------------
+ malformed pg_dependencies: "[{"attributes" : [1,2], "dependency" : 1, "degree": "a"}]" | Invalid "degree" value. |      | 22P02
+(1 row)
+
+-- Funky degree values, which do not fail.
+SELECT '[{"attributes" : [2], "dependency" : 4, "degree": "NaN"}]'::pg_dependencies;
+                    pg_dependencies                    
+-------------------------------------------------------
+ [{"attributes": [2], "dependency": 4, "degree": NaN}]
+(1 row)
+
+SELECT '[{"attributes" : [2], "dependency" : 4, "degree": "-inf"}]'::pg_dependencies;
+                       pg_dependencies                       
+-------------------------------------------------------------
+ [{"attributes": [2], "dependency": 4, "degree": -Infinity}]
+(1 row)
+
+SELECT '[{"attributes" : [2], "dependency" : 4, "degree": "inf"}]'::pg_dependencies;
+                      pg_dependencies                       
+------------------------------------------------------------
+ [{"attributes": [2], "dependency": 4, "degree": Infinity}]
+(1 row)
+
+SELECT '[{"attributes" : [2], "dependency" : 4, "degree": "-inf"}]'::pg_dependencies::text::pg_dependencies;
+ERROR:  malformed pg_dependencies: "[{"attributes": [2], "dependency": 4, "degree": -Infinity}]"
+DETAIL:  Must be valid JSON.
+-- Duplicated keys
+SELECT '[{"attributes" : [2,3], "attributes": [1,2], "dependency" : 4, "degree": 1.000}]'::pg_dependencies;
+ERROR:  malformed pg_dependencies: "[{"attributes" : [2,3], "attributes": [1,2], "dependency" : 4, "degree": 1.000}]"
+LINE 1: SELECT '[{"attributes" : [2,3], "attributes": [1,2], "depend...
+               ^
+DETAIL:  Multiple "attributes" keys are not allowed.
+SELECT '[{"attributes" : [2,3], "dependency" : 4, "dependency": 4, "degree": 1.000}]'::pg_dependencies;
+ERROR:  malformed pg_dependencies: "[{"attributes" : [2,3], "dependency" : 4, "dependency": 4, "degree": 1.000}]"
+LINE 1: SELECT '[{"attributes" : [2,3], "dependency" : 4, "dependenc...
+               ^
+DETAIL:  Multiple "dependency" keys are not allowed.
+SELECT '[{"attributes" : [2,3], "dependency": 4, "degree": 1.000, "degree": 1.000}]'::pg_dependencies;
+ERROR:  malformed pg_dependencies: "[{"attributes" : [2,3], "dependency": 4, "degree": 1.000, "degree": 1.000}]"
+LINE 1: SELECT '[{"attributes" : [2,3], "dependency": 4, "degree": 1...
+               ^
+DETAIL:  Multiple "degree" keys are not allowed.
+SELECT * FROM pg_input_error_info('[{"attributes" : [2,3], "attributes": [1,2], "dependency" : 4, "degree": 1.000}]', 'pg_dependencies');
+                                                    message                                                    |                   detail                    | hint | sql_error_code 
+---------------------------------------------------------------------------------------------------------------+---------------------------------------------+------+----------------
+ malformed pg_dependencies: "[{"attributes" : [2,3], "attributes": [1,2], "dependency" : 4, "degree": 1.000}]" | Multiple "attributes" keys are not allowed. |      | 22P02
+(1 row)
+
+SELECT * FROM pg_input_error_info('[{"attributes" : [2,3], "dependency" : 4, "dependency": 4, "degree": 1.000}]', 'pg_dependencies');
+                                                  message                                                  |                   detail                    | hint | sql_error_code 
+-----------------------------------------------------------------------------------------------------------+---------------------------------------------+------+----------------
+ malformed pg_dependencies: "[{"attributes" : [2,3], "dependency" : 4, "dependency": 4, "degree": 1.000}]" | Multiple "dependency" keys are not allowed. |      | 22P02
+(1 row)
+
+SELECT * FROM pg_input_error_info('[{"attributes" : [2,3], "dependency": 4, "degree": 1.000, "degree": 1.000}]', 'pg_dependencies');
+                                                 message                                                  |                 detail                  | hint | sql_error_code 
+----------------------------------------------------------------------------------------------------------+-----------------------------------------+------+----------------
+ malformed pg_dependencies: "[{"attributes" : [2,3], "dependency": 4, "degree": 1.000, "degree": 1.000}]" | Multiple "degree" keys are not allowed. |      | 22P02
+(1 row)
+
+-- Invalid attnums
+SELECT '[{"attributes" : [0,2], "dependency" : 4, "degree": 0.500}]'::pg_dependencies;
+ERROR:  malformed pg_dependencies: "[{"attributes" : [0,2], "dependency" : 4, "degree": 0.500}]"
+LINE 1: SELECT '[{"attributes" : [0,2], "dependency" : 4, "degree": ...
+               ^
+DETAIL:  Invalid "attributes" element: 0.
+SELECT '[{"attributes" : [-7,-9], "dependency" : 4, "degree": 0.500}]'::pg_dependencies;
+ERROR:  malformed pg_dependencies: "[{"attributes" : [-7,-9], "dependency" : 4, "degree": 0.500}]"
+LINE 1: SELECT '[{"attributes" : [-7,-9], "dependency" : 4, "degree"...
+               ^
+DETAIL:  Invalid "attributes" element: -9.
+SELECT * FROM pg_input_error_info('[{"attributes" : [0,2], "dependency" : 4, "degree": 0.500}]', 'pg_dependencies');
+                                         message                                          |              detail              | hint | sql_error_code 
+------------------------------------------------------------------------------------------+----------------------------------+------+----------------
+ malformed pg_dependencies: "[{"attributes" : [0,2], "dependency" : 4, "degree": 0.500}]" | Invalid "attributes" element: 0. |      | 22P02
+(1 row)
+
+SELECT * FROM pg_input_error_info('[{"attributes" : [-7,-9], "dependency" : 4, "degree": 0.500}]', 'pg_dependencies');
+                                          message                                           |              detail               | hint | sql_error_code 
+--------------------------------------------------------------------------------------------+-----------------------------------+------+----------------
+ malformed pg_dependencies: "[{"attributes" : [-7,-9], "dependency" : 4, "degree": 0.500}]" | Invalid "attributes" element: -9. |      | 22P02
+(1 row)
+
+-- Duplicated attributes
+SELECT '[{"attributes" : [2,2], "dependency" : 4, "degree": 0.500}]'::pg_dependencies;
+ERROR:  malformed pg_dependencies: "[{"attributes" : [2,2], "dependency" : 4, "degree": 0.500}]"
+LINE 1: SELECT '[{"attributes" : [2,2], "dependency" : 4, "degree": ...
+               ^
+DETAIL:  Invalid "attributes" element: 2 cannot follow 2.
+SELECT * FROM pg_input_error_info('[{"attributes" : [2,2], "dependency" : 4, "degree": 0.500}]', 'pg_dependencies');
+                                         message                                          |                      detail                      | hint | sql_error_code 
+------------------------------------------------------------------------------------------+--------------------------------------------------+------+----------------
+ malformed pg_dependencies: "[{"attributes" : [2,2], "dependency" : 4, "degree": 0.500}]" | Invalid "attributes" element: 2 cannot follow 2. |      | 22P02
+(1 row)
+
+-- Duplicated attribute lists.
+SELECT '[{"attributes" : [2,3], "dependency" : 4, "degree": 1.000},
+         {"attributes" : [2,3], "dependency" : 4, "degree": 1.000}]'::pg_dependencies;
+ERROR:  malformed pg_dependencies: "[{"attributes" : [2,3], "dependency" : 4, "degree": 1.000},
+         {"attributes" : [2,3], "dependency" : 4, "degree": 1.000}]"
+LINE 1: SELECT '[{"attributes" : [2,3], "dependency" : 4, "degree": ...
+               ^
+DETAIL:  Duplicate "attributes" array: [2, 3] with "dependency": 4.
+SELECT * FROM pg_input_error_info('[{"attributes" : [2,3], "dependency" : 4, "degree": 1.000},
+         {"attributes" : [2,3], "dependency" : 4, "degree": 1.000}]', 'pg_dependencies');
+                                         message                                         |                           detail                           | hint | sql_error_code 
+-----------------------------------------------------------------------------------------+------------------------------------------------------------+------+----------------
+ malformed pg_dependencies: "[{"attributes" : [2,3], "dependency" : 4, "degree": 1.000},+| Duplicate "attributes" array: [2, 3] with "dependency": 4. |      | 22P02
+          {"attributes" : [2,3], "dependency" : 4, "degree": 1.000}]"                    |                                                            |      | 
+(1 row)
+
+-- Valid inputs
+SELECT '[{"attributes" : [2,3], "dependency" : 4, "degree": 0.250},
+         {"attributes" : [2,-1], "dependency" : 4, "degree": 0.500},
+         {"attributes" : [2,3,-1], "dependency" : 4, "degree": 0.750},
+         {"attributes" : [2,3,-1,-2], "dependency" : 4, "degree": 1.000}]'::pg_dependencies;
+                                                                                                                          pg_dependencies                                                                                                                          
+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ [{"attributes": [2, 3], "dependency": 4, "degree": 0.250000}, {"attributes": [2, -1], "dependency": 4, "degree": 0.500000}, {"attributes": [2, 3, -1], "dependency": 4, "degree": 0.750000}, {"attributes": [2, 3, -1, -2], "dependency": 4, "degree": 1.000000}]
+(1 row)
+
+SELECT * FROM pg_input_error_info('[{"attributes" : [2,3], "dependency" : 4, "degree": 0.250},
+         {"attributes" : [2,-1], "dependency" : 4, "degree": 0.500},
+         {"attributes" : [2,3,-1], "dependency" : 4, "degree": 0.750},
+         {"attributes" : [2,3,-1,-2], "dependency" : 4, "degree": 1.000}]', 'pg_dependencies');
+ message | detail | hint | sql_error_code 
+---------+--------+------+----------------
+         |        |      | 
+(1 row)
+
+-- Partially-covered attribute lists, possible as items with a degree of 0
+-- are discarded.
+SELECT '[{"attributes" : [2,3], "dependency" : 4, "degree": 1.000},
+         {"attributes" : [1,-1], "dependency" : 4, "degree": 1.000},
+         {"attributes" : [2,3,-1], "dependency" : 4, "degree": 1.000},
+         {"attributes" : [2,3,-1,-2], "dependency" : 4, "degree": 1.000}]'::pg_dependencies;
+                                                                                                                          pg_dependencies                                                                                                                          
+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ [{"attributes": [2, 3], "dependency": 4, "degree": 1.000000}, {"attributes": [1, -1], "dependency": 4, "degree": 1.000000}, {"attributes": [2, 3, -1], "dependency": 4, "degree": 1.000000}, {"attributes": [2, 3, -1, -2], "dependency": 4, "degree": 1.000000}]
+(1 row)
+
index f3f0b5f2f317b485a89a134cd79f682d9cd072f8..cc6d799bceaf04ff68f1773a9f8e58f3f25755a0 100644 (file)
@@ -28,7 +28,7 @@ test: strings md5 numerology point lseg line box path polygon circle date time t
 # geometry depends on point, lseg, line, box, path, polygon, circle
 # horology depends on date, time, timetz, timestamp, timestamptz, interval
 # ----------
-test: geometry horology tstypes regex type_sanity opr_sanity misc_sanity comments expressions unicode xid mvcc database stats_import pg_ndistinct
+test: geometry horology tstypes regex type_sanity opr_sanity misc_sanity comments expressions unicode xid mvcc database stats_import pg_ndistinct pg_dependencies
 
 # ----------
 # Load huge amounts of data
diff --git a/src/test/regress/sql/pg_dependencies.sql b/src/test/regress/sql/pg_dependencies.sql
new file mode 100644 (file)
index 0000000..7935c54
--- /dev/null
@@ -0,0 +1,133 @@
+-- Tests for type pg_distinct
+
+-- Invalid inputs
+SELECT 'null'::pg_dependencies;
+SELECT '{"a": 1}'::pg_dependencies;
+SELECT '[]'::pg_dependencies;
+SELECT '{}'::pg_dependencies;
+SELECT '[null]'::pg_dependencies;
+SELECT * FROM pg_input_error_info('null', 'pg_dependencies');
+SELECT * FROM pg_input_error_info('{"a": 1}', 'pg_dependencies');
+SELECT * FROM pg_input_error_info('[]', 'pg_dependencies');
+SELECT * FROM pg_input_error_info('{}', 'pg_dependencies');
+SELECT * FROM pg_input_error_info('[null]', 'pg_dependencies');
+
+-- Invalid keys
+SELECT '[{"attributes_invalid" : [2,3], "dependency" : 4}]'::pg_dependencies;
+SELECT '[{"attributes" : [2,3], "invalid" : 3, "dependency" : 4}]'::pg_dependencies;
+SELECT * FROM pg_input_error_info('[{"attributes_invalid" : [2,3], "dependency" : 4}]', 'pg_dependencies');
+SELECT * FROM pg_input_error_info('[{"attributes" : [2,3], "invalid" : 3, "dependency" : 4}]', 'pg_dependencies');
+
+-- Missing keys
+SELECT '[{"attributes" : [2,3], "dependency" : 4}]'::pg_dependencies;
+SELECT '[{"attributes" : [2,3], "degree" : 1.000}]'::pg_dependencies;
+SELECT '[{"attributes" : [2,3], "dependency" : 4}]'::pg_dependencies;
+SELECT * FROM pg_input_error_info('[{"attributes" : [2,3], "dependency" : 4}]', 'pg_dependencies');
+SELECT * FROM pg_input_error_info('[{"attributes" : [2,3], "degree" : 1.000}]', 'pg_dependencies');
+SELECT * FROM pg_input_error_info('[{"attributes" : [2,3], "dependency" : 4}]', 'pg_dependencies');
+
+-- Valid keys, too many attributes
+SELECT '[{"attributes" : [1,2,3,4,5,6,7,8], "dependency" : 4, "degree": 1.000}]'::pg_dependencies;
+SELECT * FROM pg_input_error_info('[{"attributes" : [1,2,3,4,5,6,7,8], "dependency" : 4, "degree": 1.000}]', 'pg_dependencies');
+
+-- Special characters
+SELECT '[{"attributes" : ["\ud83d",3], "dependency" : 4, "degree": 0.250}]'::pg_dependencies;
+SELECT '[{"attributes" : [2,3], "dependency" : "\ud83d", "degree": 0.250}]'::pg_dependencies;
+SELECT '[{"attributes" : [2,3], "dependency" : 4, "degree": "\ud83d"}]'::pg_dependencies;
+SELECT '[{"\ud83d" : [2,3], "dependency" : 4, "degree": 0.250}]'::pg_dependencies;
+SELECT * FROM pg_input_error_info('[{"attributes" : ["\ud83d",3], "dependency" : 4, "degree": 0.250}]', 'pg_dependencies');
+SELECT * FROM pg_input_error_info('[{"attributes" : [2,3], "dependency" : "\ud83d", "degree": 0.250}]', 'pg_dependencies');
+SELECT * FROM pg_input_error_info('[{"attributes" : [2,3], "dependency" : 4, "degree": "\ud83d"}]', 'pg_dependencies');
+SELECT * FROM pg_input_error_info('[{"\ud83d" : [2,3], "dependency" : 4, "degree": 0.250}]', 'pg_dependencies');
+
+-- Valid keys, invalid values
+SELECT '[{"attributes" : null, "dependency" : 4, "degree": 1.000}]'::pg_dependencies;
+SELECT '[{"attributes" : [2,null], "dependency" : 4, "degree": 1.000}]'::pg_dependencies;
+SELECT '[{"attributes" : [2,3], "dependency" : null, "degree": 1.000}]'::pg_dependencies;
+SELECT '[{"attributes" : [2,"a"], "dependency" : 4, "degree": 1.000}]'::pg_dependencies;
+SELECT '[{"attributes" : [2,3], "dependency" : "a", "degree": 1.000}]'::pg_dependencies;
+SELECT '[{"attributes" : [2,3], "dependency" : [], "degree": 1.000}]'::pg_dependencies;
+SELECT '[{"attributes" : [2,3], "dependency" : [null], "degree": 1.000}]'::pg_dependencies;
+SELECT '[{"attributes" : [2,3], "dependency" : [1,null], "degree": 1.000}]'::pg_dependencies;
+SELECT '[{"attributes" : 1, "dependency" : 4, "degree": 1.000}]'::pg_dependencies;
+SELECT '[{"attributes" : "a", "dependency" : 4, "degree": 1.000}]'::pg_dependencies;
+SELECT '[{"attributes" : [2,3], "dependency" : 4, "degree": NaN}]'::pg_dependencies;
+SELECT * FROM pg_input_error_info('[{"attributes" : null, "dependency" : 4, "degree": 1.000}]', 'pg_dependencies');
+SELECT * FROM pg_input_error_info('[{"attributes" : [2,null], "dependency" : 4, "degree": 1.000}]', 'pg_dependencies');
+SELECT * FROM pg_input_error_info('[{"attributes" : [2,3], "dependency" : null, "degree": 1.000}]', 'pg_dependencies');
+SELECT * FROM pg_input_error_info('[{"attributes" : [2,"a"], "dependency" : 4, "degree": 1.000}]', 'pg_dependencies');
+SELECT * FROM pg_input_error_info('[{"attributes" : [2,3], "dependency" : "a", "degree": 1.000}]', 'pg_dependencies');
+SELECT * FROM pg_input_error_info('[{"attributes" : [2,3], "dependency" : [], "degree": 1.000}]', 'pg_dependencies');
+SELECT * FROM pg_input_error_info('[{"attributes" : [2,3], "dependency" : [null], "degree": 1.000}]', 'pg_dependencies');
+SELECT * FROM pg_input_error_info('[{"attributes" : [2,3], "dependency" : [1,null], "degree": 1.000}]', 'pg_dependencies');
+SELECT * FROM pg_input_error_info('[{"attributes" : 1, "dependency" : 4, "degree": 1.000}]', 'pg_dependencies');
+SELECT * FROM pg_input_error_info('[{"attributes" : "a", "dependency" : 4, "degree": 1.000}]', 'pg_dependencies');
+SELECT * FROM pg_input_error_info('[{"attributes" : [2,3], "dependency" : 4, "degree": NaN}]', 'pg_dependencies');
+
+SELECT '[{"attributes": [], "dependency": 2, "degree": 1}]' ::pg_dependencies;
+SELECT '[{"attributes" : {"a": 1}, "dependency" : 4, "degree": "1.2"}]'::pg_dependencies;
+SELECT * FROM pg_input_error_info('[{"attributes": [], "dependency": 2, "degree": 1}]', 'pg_dependencies');
+SELECT * FROM pg_input_error_info('[{"attributes" : {"a": 1}, "dependency" : 4, "degree": "1.2"}]', 'pg_dependencies');
+
+SELECT '[{"dependency" : 4, "degree": "1.2"}]'::pg_dependencies;
+SELECT '[{"attributes" : [1,2,3,4,5,6,7], "dependency" : 0, "degree": "1.2"}]'::pg_dependencies;
+SELECT '[{"attributes" : [1,2,3,4,5,6,7], "dependency" : -9, "degree": "1.2"}]'::pg_dependencies;
+SELECT '[{"attributes": [1,2], "dependency": 2, "degree": 1}]' ::pg_dependencies;
+SELECT '[{"attributes" : [1, {}], "dependency" : 1, "degree": "1.2"}]'::pg_dependencies;
+SELECT '[{"attributes" : [1,2], "dependency" : {}, "degree": 1.0}]'::pg_dependencies;
+SELECT '[{"attributes" : [1,2], "dependency" : 3, "degree": {}}]'::pg_dependencies;
+SELECT '[{"attributes" : [1,2], "dependency" : 1, "degree": "a"}]'::pg_dependencies;
+SELECT * FROM pg_input_error_info('[{"dependency" : 4, "degree": "1.2"}]', 'pg_dependencies');
+SELECT * FROM pg_input_error_info('[{"attributes" : [1,2,3,4,5,6,7], "dependency" : 0, "degree": "1.2"}]', 'pg_dependencies');
+SELECT * FROM pg_input_error_info('[{"attributes" : [1,2,3,4,5,6,7], "dependency" : -9, "degree": "1.2"}]', 'pg_dependencies');
+SELECT * FROM pg_input_error_info('[{"attributes": [1,2], "dependency": 2, "degree": 1}]' , 'pg_dependencies');
+SELECT * FROM pg_input_error_info('[{"attributes" : [1, {}], "dependency" : 1, "degree": "1.2"}]', 'pg_dependencies');
+SELECT * FROM pg_input_error_info('[{"attributes" : [1,2], "dependency" : {}, "degree": 1.0}]', 'pg_dependencies');
+SELECT * FROM pg_input_error_info('[{"attributes" : [1,2], "dependency" : 3, "degree": {}}]', 'pg_dependencies');
+SELECT * FROM pg_input_error_info('[{"attributes" : [1,2], "dependency" : 1, "degree": "a"}]', 'pg_dependencies');
+
+-- Funky degree values, which do not fail.
+SELECT '[{"attributes" : [2], "dependency" : 4, "degree": "NaN"}]'::pg_dependencies;
+SELECT '[{"attributes" : [2], "dependency" : 4, "degree": "-inf"}]'::pg_dependencies;
+SELECT '[{"attributes" : [2], "dependency" : 4, "degree": "inf"}]'::pg_dependencies;
+SELECT '[{"attributes" : [2], "dependency" : 4, "degree": "-inf"}]'::pg_dependencies::text::pg_dependencies;
+
+-- Duplicated keys
+SELECT '[{"attributes" : [2,3], "attributes": [1,2], "dependency" : 4, "degree": 1.000}]'::pg_dependencies;
+SELECT '[{"attributes" : [2,3], "dependency" : 4, "dependency": 4, "degree": 1.000}]'::pg_dependencies;
+SELECT '[{"attributes" : [2,3], "dependency": 4, "degree": 1.000, "degree": 1.000}]'::pg_dependencies;
+SELECT * FROM pg_input_error_info('[{"attributes" : [2,3], "attributes": [1,2], "dependency" : 4, "degree": 1.000}]', 'pg_dependencies');
+SELECT * FROM pg_input_error_info('[{"attributes" : [2,3], "dependency" : 4, "dependency": 4, "degree": 1.000}]', 'pg_dependencies');
+SELECT * FROM pg_input_error_info('[{"attributes" : [2,3], "dependency": 4, "degree": 1.000, "degree": 1.000}]', 'pg_dependencies');
+
+-- Invalid attnums
+SELECT '[{"attributes" : [0,2], "dependency" : 4, "degree": 0.500}]'::pg_dependencies;
+SELECT '[{"attributes" : [-7,-9], "dependency" : 4, "degree": 0.500}]'::pg_dependencies;
+SELECT * FROM pg_input_error_info('[{"attributes" : [0,2], "dependency" : 4, "degree": 0.500}]', 'pg_dependencies');
+SELECT * FROM pg_input_error_info('[{"attributes" : [-7,-9], "dependency" : 4, "degree": 0.500}]', 'pg_dependencies');
+
+-- Duplicated attributes
+SELECT '[{"attributes" : [2,2], "dependency" : 4, "degree": 0.500}]'::pg_dependencies;
+SELECT * FROM pg_input_error_info('[{"attributes" : [2,2], "dependency" : 4, "degree": 0.500}]', 'pg_dependencies');
+
+-- Duplicated attribute lists.
+SELECT '[{"attributes" : [2,3], "dependency" : 4, "degree": 1.000},
+         {"attributes" : [2,3], "dependency" : 4, "degree": 1.000}]'::pg_dependencies;
+SELECT * FROM pg_input_error_info('[{"attributes" : [2,3], "dependency" : 4, "degree": 1.000},
+         {"attributes" : [2,3], "dependency" : 4, "degree": 1.000}]', 'pg_dependencies');
+
+-- Valid inputs
+SELECT '[{"attributes" : [2,3], "dependency" : 4, "degree": 0.250},
+         {"attributes" : [2,-1], "dependency" : 4, "degree": 0.500},
+         {"attributes" : [2,3,-1], "dependency" : 4, "degree": 0.750},
+         {"attributes" : [2,3,-1,-2], "dependency" : 4, "degree": 1.000}]'::pg_dependencies;
+SELECT * FROM pg_input_error_info('[{"attributes" : [2,3], "dependency" : 4, "degree": 0.250},
+         {"attributes" : [2,-1], "dependency" : 4, "degree": 0.500},
+         {"attributes" : [2,3,-1], "dependency" : 4, "degree": 0.750},
+         {"attributes" : [2,3,-1,-2], "dependency" : 4, "degree": 1.000}]', 'pg_dependencies');
+-- Partially-covered attribute lists, possible as items with a degree of 0
+-- are discarded.
+SELECT '[{"attributes" : [2,3], "dependency" : 4, "degree": 1.000},
+         {"attributes" : [1,-1], "dependency" : 4, "degree": 1.000},
+         {"attributes" : [2,3,-1], "dependency" : 4, "degree": 1.000},
+         {"attributes" : [2,3,-1,-2], "dependency" : 4, "degree": 1.000}]'::pg_dependencies;
index 17e2b40b9cb0d52edf1712753b87688c962b9abe..dfcd619bfee3c15cc166bd5a9aadec4f679f3f07 100644 (file)
@@ -631,6 +631,8 @@ DefaultACLInfo
 DefineStmt
 DefnDumperPtr
 DeleteStmt
+DependenciesParseState
+DependenciesSemanticState
 DependencyGenerator
 DependencyGeneratorData
 DependencyType