On Sat, Nov 22, 2025 at 03:26:19AM -0500, Corey Huinker wrote:
> I added a comment debating the feasibility of testing for subsets of
> attribute sets in pg_dependencies. Basically, I think we can't have the
> test at all, but I haven't removed it just yet pending consensus.

+     * Verify that all attnum sets are a proper subset of the first longest
+     * attnum set.
+     *
+     * TODO:
+     *
+     * I'm fairly certain that because statisticsally insignificant dependency
+     * combinations are not stored, there is a chance that the longest 
dependency
+     * does not exist, and therefore this test cannot be done. I have left the
+     * test in place for the time being until the issue can be definitively
+     * settled.

As you have already quoted upthread, statext_dependencies_build()
settles the issue on this one, I think.  It is entirely possible that
any group returned by DependencyGenerator generates a degree value
that would prevent a given group to be stored, and this could as well
be the largest possible group there could be in the set.  So we cannot
do any of that for dependencies, unfortunately.  We can always rely on
the list of attributes when assigning the json blob to the stats
object, at least, cross-checking that each attribute list matches with
the numbers of the stats object.  At least we can check for
duplicates, which is better than nothing at all.

Regarding the suggested check where we'd want to enforce all the
groups of attributes to be listed depending on the longest set we have
found, at the end estimate_multivariate_ndistinct() checks the items
listed one-by-one, giving up if we cannot find something in the list
of items.  I think that I am going to be content with the patch as it
is, without this piece.  Let's add an extra SQL test to treat that as
valid input, though.  So I am feeling OK with the input for ndistinct
at this stage.  I have noticed a couple of issues in passing,
adjusting them.  We are reaching more than 90% of coverage with the
tests, and I am not sure that we can actually reach the rest except if
one of the previous steps failed.

So That's one.  Now into the second patch for the input of the
dependencies.

+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;

Okay, I have to admit that these ones are fun.  I doubt that anybody
would actually do that, and these do not produce valid json objects,
which is what the last case shows.  Hmm, it makes sense to keep these,
and I'm still siding that we should not care too much about applying
checks on the values and complicate the input function more than that,
so fine by me.

There were a couple of things in the tests, missing quite a few soft
errors.  Many typos, grammar mistakes in the whole.  Also, please do
not split the error strings into multiple lines to make these
greppable.  There is also no need for a break after a return.  In some
cases, a return was used where a break made more sense as the default
path returned a failure.. 

The TODO in build_mvdependencies() could be an elog(), but I have left
it untouched for the errdetail().

We're reaching 91% of coverage here, not bad.  The rest does not seem
reachable, as far as I can see.

With that said, a v18 for the first two patches with the input
functions.  Comments and/or opinions?
--
Michael
From b19df83c7be3c9de6174f3d971aac581b45eb440 Mon Sep 17 00:00:00 2001
From: Michael Paquier <[email protected]>
Date: Tue, 25 Nov 2025 15:20:49 +0900
Subject: [PATCH v18 1/2] Add working input function for pg_ndistinct.

This will consume the format that was established when the output
function for pg_ndistinct was recently changed.

This will be needed for importing extended statistics.  With these
changes in place, coverage of pg_ndistinct.c reaches 91%.
---
 src/backend/utils/adt/pg_ndistinct.c       | 760 ++++++++++++++++++++-
 src/test/regress/expected/pg_ndistinct.out | 417 +++++++++++
 src/test/regress/parallel_schedule         |   2 +-
 src/test/regress/sql/pg_ndistinct.sql      | 101 +++
 src/tools/pgindent/typedefs.list           |   2 +
 5 files changed, 1273 insertions(+), 9 deletions(-)
 create mode 100644 src/test/regress/expected/pg_ndistinct.out
 create mode 100644 src/test/regress/sql/pg_ndistinct.sql

diff --git a/src/backend/utils/adt/pg_ndistinct.c b/src/backend/utils/adt/pg_ndistinct.c
index 97efc290ef5e..a730ea6ef2db 100644
--- a/src/backend/utils/adt/pg_ndistinct.c
+++ b/src/backend/utils/adt/pg_ndistinct.c
@@ -14,29 +14,773 @@
 
 #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/fmgrprotos.h"
 
+/* Parsing state data */
+typedef enum
+{
+	NDIST_EXPECT_START = 0,
+	NDIST_EXPECT_ITEM,
+	NDIST_EXPECT_KEY,
+	NDIST_EXPECT_ATTNUM_LIST,
+	NDIST_EXPECT_ATTNUM,
+	NDIST_EXPECT_NDISTINCT,
+	NDIST_EXPECT_COMPLETE,
+} NDistinctSemanticState;
+
+typedef struct
+{
+	const char *str;
+	NDistinctSemanticState state;
+
+	List	   *distinct_items; /* Accumulated complete MVNDistinctItems */
+	Node	   *escontext;
+
+	bool		found_attributes;	/* Item has "attributes" key */
+	bool		found_ndistinct;	/* Item has "ndistinct" key */
+	List	   *attnum_list;	/* Accumulated attribute numbers */
+	int32		ndistinct;
+} NDistinctParseState;
+
+/*
+ * Invoked at the start of each MVNDistinctItem.
+ *
+ * The entire JSON document should be one array of MVNDistinctItem objects.
+ * If we are anywhere else in the document, it is an error.
+ */
+static JsonParseErrorType
+ndistinct_object_start(void *state)
+{
+	NDistinctParseState *parse = state;
+
+	switch (parse->state)
+	{
+		case NDIST_EXPECT_ITEM:
+			/* Now we expect to see attributes/ndistinct keys */
+			parse->state = NDIST_EXPECT_KEY;
+			return JSON_SUCCESS;
+
+		case NDIST_EXPECT_START:
+			/* pg_ndistinct must begin with a '[' */
+			errsave(parse->escontext,
+					errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+					errmsg("malformed pg_ndistinct: \"%s\"", parse->str),
+					errdetail("Initial element must be an array."));
+			break;
+
+		case NDIST_EXPECT_KEY:
+			/* In an object, expecting key */
+			errsave(parse->escontext,
+					errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+					errmsg("malformed pg_ndistinct: \"%s\"", parse->str),
+					errdetail("Expected an object key."));
+			break;
+
+		case NDIST_EXPECT_ATTNUM_LIST:
+			/* Just followed an "attributes" key */
+			errsave(parse->escontext,
+					errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+					errmsg("malformed pg_ndistinct: \"%s\"", parse->str),
+					errdetail("Value of \"%s\" must be an array of attribute numbers.",
+							  PG_NDISTINCT_KEY_ATTRIBUTES));
+			break;
+
+		case NDIST_EXPECT_ATTNUM:
+			/* In an attribute number list, expect only scalar integers */
+			errsave(parse->escontext,
+					errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+					errmsg("malformed pg_ndistinct: \"%s\"", parse->str),
+					errdetail("Attribute lists can only contain attribute numbers."));
+			break;
+
+		case NDIST_EXPECT_NDISTINCT:
+			/* Just followed an "ndistinct" key */
+			errsave(parse->escontext,
+					errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+					errmsg("malformed pg_ndistinct: \"%s\"", parse->str),
+					errdetail("Value of \"%s\" must be an integer.",
+							  PG_NDISTINCT_KEY_NDISTINCT));
+			break;
+
+		default:
+			errsave(parse->escontext,
+					errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+					errmsg("malformed pg_ndistinct: \"%s\"", parse->str),
+					errdetail("Unexpected parse state: %d", (int) parse->state));
+			break;
+	}
+
+	return JSON_SEM_ACTION_FAILED;
+}
+
+/*
+ * Invoked at the end of an object.
+ *
+ * Check to ensure that it was a complete MVNDistinctItem
+ */
+static JsonParseErrorType
+ndistinct_object_end(void *state)
+{
+	NDistinctParseState *parse = state;
+
+	int			natts = 0;
+
+	MVNDistinctItem *item;
+
+	if (parse->state != NDIST_EXPECT_KEY)
+	{
+		errsave(parse->escontext,
+				errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+				errmsg("malformed pg_ndistinct: \"%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_ndistinct: \"%s\"", parse->str),
+				errdetail("Item must contain \"%s\" key.",
+						  PG_NDISTINCT_KEY_ATTRIBUTES));
+		return JSON_SEM_ACTION_FAILED;
+	}
+
+	if (!parse->found_ndistinct)
+	{
+		errsave(parse->escontext,
+				errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+				errmsg("malformed pg_ndistinct: \"%s\"", parse->str),
+				errdetail("Item must contain \"%s\" key.",
+						  PG_NDISTINCT_KEY_NDISTINCT));
+		return JSON_SEM_ACTION_FAILED;
+	}
+
+	/*
+	 * We need at least two attribute numbers for a ndistinct item, anything
+	 * less is malformed.
+	 */
+	natts = list_length(parse->attnum_list);
+	if ((natts < 2) || (natts > STATS_MAX_DIMENSIONS))
+	{
+		errsave(parse->escontext,
+				errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+				errmsg("malformed pg_ndistinct: \"%s\"", parse->str),
+				errdetail("The \"%s\" key must contain an array of at least %d "
+						  "and no more than %d attributes.",
+						  PG_NDISTINCT_KEY_ATTRIBUTES, 2, STATS_MAX_DIMENSIONS));
+		return JSON_SEM_ACTION_FAILED;
+	}
+
+	/* Create the MVNDistinctItem */
+	item = palloc(sizeof(MVNDistinctItem));
+	item->nattributes = natts;
+	item->attributes = palloc0(natts * sizeof(AttrNumber));
+	item->ndistinct = (double) parse->ndistinct;
+
+	for (int i = 0; i < natts; i++)
+		item->attributes[i] = (AttrNumber) list_nth_int(parse->attnum_list, i);
+
+	parse->distinct_items = lappend(parse->distinct_items, (void *) item);
+
+	/* reset item state vars */
+	list_free(parse->attnum_list);
+	parse->attnum_list = NIL;
+	parse->ndistinct = 0;
+	parse->found_attributes = false;
+	parse->found_ndistinct = false;
+
+	/* Now we are looking for the next MVNDistinctItem */
+	parse->state = NDIST_EXPECT_ITEM;
+	return JSON_SUCCESS;
+}
+
 
 /*
- * pg_ndistinct_in
- *		input routine for type pg_ndistinct
+ * ndistinct input format has two types of arrays, the outer MVNDistinctItem
+ * array and the attribute number array within each MVNDistinctItem.
+ */
+static JsonParseErrorType
+ndistinct_array_start(void *state)
+{
+	NDistinctParseState *parse = state;
+
+	switch (parse->state)
+	{
+		case NDIST_EXPECT_ATTNUM_LIST:
+			parse->state = NDIST_EXPECT_ATTNUM;
+			break;
+
+		case NDIST_EXPECT_START:
+			parse->state = NDIST_EXPECT_ITEM;
+			break;
+
+		default:
+			errsave(parse->escontext,
+					errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+					errmsg("malformed pg_ndistinct: \"%s\"", parse->str),
+					errdetail("Array found in unexpected place."));
+			return JSON_SEM_ACTION_FAILED;
+	}
+
+	return JSON_SUCCESS;
+}
+
+
+/*
+ * Arrays can never be empty.
+ */
+static JsonParseErrorType
+ndistinct_array_end(void *state)
+{
+	NDistinctParseState *parse = state;
+
+	switch (parse->state)
+	{
+		case NDIST_EXPECT_ATTNUM:
+			if (list_length(parse->attnum_list) > 0)
+			{
+				/*
+				 * The attribute number list is complete, look for more
+				 * MVNDistinctItem keys.
+				 */
+				parse->state = NDIST_EXPECT_KEY;
+				return JSON_SUCCESS;
+			}
+
+			errsave(parse->escontext,
+					errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+					errmsg("malformed pg_ndistinct: \"%s\"", parse->str),
+					errdetail("The \"%s\" key must be a non-empty array.",
+							  PG_NDISTINCT_KEY_ATTRIBUTES));
+			break;
+
+		case NDIST_EXPECT_ITEM:
+			if (list_length(parse->distinct_items) > 0)
+			{
+				/* Item list is complete, we are done. */
+				parse->state = NDIST_EXPECT_COMPLETE;
+				return JSON_SUCCESS;
+			}
+
+			errsave(parse->escontext,
+					errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+					errmsg("malformed pg_ndistinct: \"%s\"", parse->str),
+					errdetail("Item array cannot be empty."));
+			break;
+		default:
+
+			/*
+			 * This can only happen if a case was missed in
+			 * ndistinct_array_start().
+			 */
+			errsave(parse->escontext,
+					errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+					errmsg("malformed pg_ndistinct: \"%s\"", parse->str),
+					errdetail("Array found in unexpected place."));
+			break;
+	}
+
+	return JSON_SEM_ACTION_FAILED;
+}
+
+/*
+ * The valid keys for the MVNDistinctItem object are:
+ *   - attributes
+ *   - ndistinct
+ */
+static JsonParseErrorType
+ndistinct_object_field_start(void *state, char *fname, bool isnull)
+{
+	NDistinctParseState *parse = state;
+
+	if (strcmp(fname, PG_NDISTINCT_KEY_ATTRIBUTES) == 0)
+	{
+		if (parse->found_attributes)
+		{
+			errsave(parse->escontext,
+					errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+					errmsg("malformed pg_ndistinct: \"%s\"", parse->str),
+					errdetail("Multiple \"%s\" keys are not allowed.",
+							  PG_NDISTINCT_KEY_ATTRIBUTES));
+			return JSON_SEM_ACTION_FAILED;
+		}
+		parse->found_attributes = true;
+		parse->state = NDIST_EXPECT_ATTNUM_LIST;
+		return JSON_SUCCESS;
+	}
+
+	if (strcmp(fname, PG_NDISTINCT_KEY_NDISTINCT) == 0)
+	{
+		if (parse->found_ndistinct)
+		{
+			errsave(parse->escontext,
+					errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+					errmsg("malformed pg_ndistinct: \"%s\"", parse->str),
+					errdetail("Multiple \"%s\" keys are not allowed.",
+							  PG_NDISTINCT_KEY_NDISTINCT));
+			return JSON_SEM_ACTION_FAILED;
+		}
+		parse->found_ndistinct = true;
+		parse->state = NDIST_EXPECT_NDISTINCT;
+		return JSON_SUCCESS;
+	}
+
+	errsave(parse->escontext,
+			errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+			errmsg("malformed pg_ndistinct: \"%s\"", parse->str),
+			errdetail("Only allowed keys are \"%s\" and \"%s\".",
+					  PG_NDISTINCT_KEY_ATTRIBUTES,
+					  PG_NDISTINCT_KEY_NDISTINCT));
+	return JSON_SEM_ACTION_FAILED;
+}
+
+/*
+ * The overall structure of the datatype is an array, but there are also
+ * arrays as the value of every attributes key.
+ */
+static JsonParseErrorType
+ndistinct_array_element_start(void *state, bool isnull)
+{
+	const NDistinctParseState *parse = state;
+
+	switch (parse->state)
+	{
+		case NDIST_EXPECT_ATTNUM:
+			if (!isnull)
+				return JSON_SUCCESS;
+
+			errsave(parse->escontext,
+					errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+					errmsg("malformed pg_ndistinct: \"%s\"", parse->str),
+					errdetail("Attribute number array cannot be null."));
+			break;
+
+		case NDIST_EXPECT_ITEM:
+			if (!isnull)
+				return JSON_SUCCESS;
+
+			errsave(parse->escontext,
+					errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+					errmsg("malformed pg_ndistinct: \"%s\"", parse->str),
+					errdetail("Item list elements cannot be null."));
+
+			break;
+
+		default:
+			errsave(parse->escontext,
+					errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+					errmsg("malformed pg_ndistinct: \"%s\"", parse->str),
+					errdetail("Unexpected array element."));
+			break;
+	}
+
+	return JSON_SEM_ACTION_FAILED;
+}
+
+/*
+ * Test for valid subsequent attribute number.
  *
- * pg_ndistinct is real enough to be a table column, but it has no
- * operations of its own, and disallows input (just like pg_node_tree).
+ * 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 obviously not allowed, but that is already covered
+ * by the rules listed above.
+ */
+static bool
+valid_subsequent_attnum(AttrNumber prev, AttrNumber cur)
+{
+	Assert(prev != 0);
+
+	if (prev > 0)
+		return ((cur > prev) || (cur < 0));
+
+	return (cur < prev);
+}
+
+/*
+ * Handle scalar events from the ndistinct input parser.
+ *
+ * Override integer parse error messages and replace them with errors
+ * specific to the context.
+ */
+static JsonParseErrorType
+ndistinct_scalar(void *state, char *token, JsonTokenType tokentype)
+{
+	NDistinctParseState *parse = state;
+	AttrNumber	attnum;
+	ErrorSaveContext escontext = {T_ErrorSaveContext};
+
+	switch (parse->state)
+	{
+		case NDIST_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_ndistinct: \"%s\"", parse->str),
+						errdetail("Invalid \"%s\" value.", PG_NDISTINCT_KEY_ATTRIBUTES));
+				return JSON_SEM_ACTION_FAILED;
+			}
+
+			/*
+			 * The attribute number cannot be zero 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_ndistinct: \"%s\"", parse->str),
+						errdetail("Invalid \"%s\" element: %d.",
+								  PG_NDISTINCT_KEY_ATTRIBUTES, attnum));
+				return JSON_SEM_ACTION_FAILED;
+			}
+
+			if (list_length(parse->attnum_list) > 0)
+			{
+				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_ndistinct: \"%s\"", parse->str),
+							errdetail("Invalid \"%s\" element: %d cannot follow %d.",
+									  PG_NDISTINCT_KEY_ATTRIBUTES, attnum, prev));
+					return JSON_SEM_ACTION_FAILED;
+				}
+			}
+
+			parse->attnum_list = lappend_int(parse->attnum_list, (int) attnum);
+			return JSON_SUCCESS;
+
+		case NDIST_EXPECT_NDISTINCT:
+
+			/*
+			 * While the structure dictates that ndistinct is a double
+			 * precision floating point, it has always been an integer in the
+			 * output generated.  Therefore, we parse it as an integer here.
+			 */
+			parse->ndistinct = pg_strtoint32_safe(token, (Node *) &escontext);
+
+			if (!SOFT_ERROR_OCCURRED(&escontext))
+			{
+				parse->state = NDIST_EXPECT_KEY;
+				return JSON_SUCCESS;
+			}
+			errsave(parse->escontext,
+					errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+					errmsg("malformed pg_ndistinct: \"%s\"", parse->str),
+					errdetail("Invalid \"%s\" value.",
+							  PG_NDISTINCT_KEY_NDISTINCT));
+			break;
+
+		default:
+			errsave(parse->escontext,
+					errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+					errmsg("malformed pg_ndistinct: \"%s\"", parse->str),
+					errdetail("Unexpected scalar."));
+			break;
+	}
+
+	return JSON_SEM_ACTION_FAILED;
+}
+
+/*
+ * Compare the attribute arrays of two MVNDistinctItem values,
+ * looking for duplicate sets. Return true if a duplicate set is found.
+ *
+ * The arrays are required to be in canonical order (all positive numbers
+ * in ascending order first, followed by all negative numbers in descending
+ * order) so it's safe to compare the attrnums in order, stopping at the
+ * first difference.
+ */
+static bool
+item_attributes_eq(const MVNDistinctItem *a, const MVNDistinctItem *b)
+{
+	if (a->nattributes != b->nattributes)
+		return false;
+
+	for (int i = 0; i < a->nattributes; i++)
+	{
+		if (a->attributes[i] != b->attributes[i])
+			return false;
+	}
+
+	return true;
+}
+
+/*
+ * Ensure that an attribute number appears as one of the attribute numbers
+ * in a MVNDistinctItem.
+ */
+static bool
+item_has_attnum(const MVNDistinctItem *item, AttrNumber attnum)
+{
+	for (int i = 0; i < item->nattributes; i++)
+	{
+		if (attnum == item->attributes[i])
+			return true;
+	}
+	return false;
+}
+
+/*
+ * Ensure that the attributes in MVNDistinctItem A are a subset of the
+ * reference MVNDistinctItem B.
+ */
+static bool
+item_is_attnum_subset(const MVNDistinctItem *item,
+					  const MVNDistinctItem *refitem)
+{
+	for (int i = 0; i < item->nattributes; i++)
+	{
+		if (!item_has_attnum(refitem, item->attributes[i]))
+			return false;
+	}
+	return true;
+}
+
+/*
+ * Generate a string representing an array of attribute numbers.
+ *
+ * Freeing the allocated string is the responsibility of the caller.
+ */
+static char *
+item_attnum_list(const MVNDistinctItem *item)
+{
+	StringInfoData str;
+
+	initStringInfo(&str);
+
+	appendStringInfo(&str, "%d", item->attributes[0]);
+
+	for (int i = 1; i < item->nattributes; i++)
+		appendStringInfo(&str, ", %d", item->attributes[i]);
+
+	return str.data;
+}
+
+/*
+ * Attempt to build and serialize the MVNDistinct 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_mvndistinct(NDistinctParseState *parse, char *str)
+{
+	MVNDistinct *ndistinct;
+	int			nitems = list_length(parse->distinct_items);
+	bytea	   *bytes;
+	int			item_most_attrs = 0;
+	int			item_most_attrs_idx = 0;
+
+	switch (parse->state)
+	{
+		case NDIST_EXPECT_COMPLETE:
+
+			/*
+			 * Parsing has ended correctly and we should have a list of items.
+			 * If we don't, something has been done wrong in one of the
+			 * earlier parsing steps.
+			 */
+			if (nitems == 0)
+				elog(ERROR,
+					 "cannot have empty item list after parsing success.");
+			break;
+
+		case NDIST_EXPECT_START:
+			/* blank */
+			errsave(parse->escontext,
+					errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+					errmsg("malformed pg_ndistinct: \"%s\"", str),
+					errdetail("Value cannot be empty."));
+			return NULL;
+
+		default:
+			/* Unexpected end-state. */
+			errsave(parse->escontext,
+					errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+					errmsg("malformed pg_ndistinct: \"%s\"", str),
+					errdetail("Unexpected end state %d.", parse->state));
+			return NULL;
+	}
+
+	ndistinct = palloc(offsetof(MVNDistinct, items) +
+					   nitems * sizeof(MVNDistinctItem));
+
+	ndistinct->magic = STATS_NDISTINCT_MAGIC;
+	ndistinct->type = STATS_NDISTINCT_TYPE_BASIC;
+	ndistinct->nitems = nitems;
+
+	for (int i = 0; i < nitems; i++)
+	{
+		MVNDistinctItem *item = list_nth(parse->distinct_items, i);
+
+		/*
+		 * Ensure that this item does not duplicate the attributes of any
+		 * pre-existing item.
+		 */
+		for (int j = 0; j < i; j++)
+		{
+			if (item_attributes_eq(item, &ndistinct->items[j]))
+			{
+				char	   *s = item_attnum_list(item);
+
+				errsave(parse->escontext,
+						errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+						errmsg("malformed pg_ndistinct: \"%s\"", str),
+						errdetail("Duplicated \"%s\" array found: [%s]",
+								  PG_NDISTINCT_KEY_ATTRIBUTES, s));
+				pfree(s);
+				return NULL;
+			}
+		}
+
+		ndistinct->items[i].ndistinct = item->ndistinct;
+		ndistinct->items[i].nattributes = item->nattributes;
+
+		/*
+		 * This transfers free-ing responsibility from the distinct_items list
+		 * to the ndistinct object.
+		 */
+		ndistinct->items[i].attributes = item->attributes;
+
+		/*
+		 * Keep track of the first longest attribute list. All other attribute
+		 * lists must be a subset of this list.
+		 */
+		if (item->nattributes > item_most_attrs)
+		{
+			item_most_attrs = item->nattributes;
+			item_most_attrs_idx = i;
+		}
+	}
+
+	/*
+	 * Verify that all the sets of attribute numbers are a proper subset of
+	 * the longest set recorded.  This acts as an extra sanity check based on
+	 * the input given.  Note that this still needs to be cross-checked with
+	 * the extended statistics objects this would be assigned to, but it
+	 * provides one extra layer of protection.
+	 */
+	for (int i = 0; i < nitems; i++)
+	{
+		if (i == item_most_attrs_idx)
+			continue;
+
+		if (!item_is_attnum_subset(&ndistinct->items[i],
+								   &ndistinct->items[item_most_attrs_idx]))
+		{
+			const MVNDistinctItem *item = &ndistinct->items[i];
+			const MVNDistinctItem *refitem = &ndistinct->items[item_most_attrs_idx];
+			char	   *item_list = item_attnum_list(item);
+			char	   *refitem_list = item_attnum_list(refitem);
+
+			errsave(parse->escontext,
+					errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+					errmsg("malformed pg_ndistinct: \"%s\"", str),
+					errdetail("\"%s\" array: [%s] must be a subset of array: [%s]",
+							  PG_NDISTINCT_KEY_ATTRIBUTES,
+							  item_list, refitem_list));
+			pfree(item_list);
+			pfree(refitem_list);
+			return NULL;
+		}
+	}
+
+	bytes = statext_ndistinct_serialize(ndistinct);
+
+	/*
+	 * Free the attribute lists, before the ndistinct itself.
+	 */
+	for (int i = 0; i < nitems; i++)
+		pfree(ndistinct->items[i].attributes);
+	pfree(ndistinct);
+
+	return bytes;
+}
+
+/*
+ * pg_ndistinct_in
+ *		input routine for type pg_ndistinct.
  */
 Datum
 pg_ndistinct_in(PG_FUNCTION_ARGS)
 {
-	ereport(ERROR,
-			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-			 errmsg("cannot accept a value of type %s", "pg_ndistinct")));
+	char	   *str = PG_GETARG_CSTRING(0);
+	NDistinctParseState parse_state;
+	JsonParseErrorType result;
+	JsonLexContext *lex;
+	JsonSemAction sem_action;
+	bytea	   *bytes = NULL;
 
-	PG_RETURN_VOID();			/* keep compiler quiet */
+	/* initialize semantic state */
+	parse_state.str = str;
+	parse_state.state = NDIST_EXPECT_START;
+	parse_state.distinct_items = NIL;
+	parse_state.escontext = fcinfo->context;
+	parse_state.found_attributes = false;
+	parse_state.found_ndistinct = false;
+	parse_state.attnum_list = NIL;
+	parse_state.ndistinct = 0;
+
+	/* set callbacks */
+	sem_action.semstate = (void *) &parse_state;
+	sem_action.object_start = ndistinct_object_start;
+	sem_action.object_end = ndistinct_object_end;
+	sem_action.array_start = ndistinct_array_start;
+	sem_action.array_end = ndistinct_array_end;
+	sem_action.object_field_start = ndistinct_object_field_start;
+	sem_action.object_field_end = NULL;
+	sem_action.array_element_start = ndistinct_array_element_start;
+	sem_action.array_element_end = NULL;
+	sem_action.scalar = ndistinct_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_mvndistinct(&parse_state, str);
+
+	list_free(parse_state.attnum_list);
+	list_free_deep(parse_state.distinct_items);
+
+	if (bytes)
+		PG_RETURN_BYTEA_P(bytes);
+
+	/*
+	 * If escontext already set, just use that. Anything else is a generic
+	 * JSON parse error.
+	 */
+	if (!SOFT_ERROR_OCCURRED(parse_state.escontext))
+		errsave(parse_state.escontext,
+				errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+				errmsg("malformed pg_ndistinct: \"%s\"", str),
+				errdetail("Must be valid JSON."));
+
+	PG_RETURN_NULL();
 }
 
+
 /*
  * pg_ndistinct_out
  *		output routine for type pg_ndistinct
diff --git a/src/test/regress/expected/pg_ndistinct.out b/src/test/regress/expected/pg_ndistinct.out
new file mode 100644
index 000000000000..6e8c94e4fa5b
--- /dev/null
+++ b/src/test/regress/expected/pg_ndistinct.out
@@ -0,0 +1,417 @@
+-- Tests for type pg_ndistinct
+-- Invalid inputs
+SELECT 'null'::pg_ndistinct;
+ERROR:  malformed pg_ndistinct: "null"
+LINE 1: SELECT 'null'::pg_ndistinct;
+               ^
+DETAIL:  Unexpected scalar.
+SELECT '{"a": 1}'::pg_ndistinct;
+ERROR:  malformed pg_ndistinct: "{"a": 1}"
+LINE 1: SELECT '{"a": 1}'::pg_ndistinct;
+               ^
+DETAIL:  Initial element must be an array.
+SELECT '[]'::pg_ndistinct;
+ERROR:  malformed pg_ndistinct: "[]"
+LINE 1: SELECT '[]'::pg_ndistinct;
+               ^
+DETAIL:  Item array cannot be empty.
+SELECT '{}'::pg_ndistinct;
+ERROR:  malformed pg_ndistinct: "{}"
+LINE 1: SELECT '{}'::pg_ndistinct;
+               ^
+DETAIL:  Initial element must be an array.
+SELECT '[null]'::pg_ndistinct;
+ERROR:  malformed pg_ndistinct: "[null]"
+LINE 1: SELECT '[null]'::pg_ndistinct;
+               ^
+DETAIL:  Item list elements cannot be null.
+SELECT * FROM pg_input_error_info('null', 'pg_ndistinct');
+            message             |       detail       | hint | sql_error_code 
+--------------------------------+--------------------+------+----------------
+ malformed pg_ndistinct: "null" | Unexpected scalar. |      | 22P02
+(1 row)
+
+SELECT * FROM pg_input_error_info('{"a": 1}', 'pg_ndistinct');
+              message               |              detail               | hint | sql_error_code 
+------------------------------------+-----------------------------------+------+----------------
+ malformed pg_ndistinct: "{"a": 1}" | Initial element must be an array. |      | 22P02
+(1 row)
+
+SELECT * FROM pg_input_error_info('[]', 'pg_ndistinct');
+           message            |           detail            | hint | sql_error_code 
+------------------------------+-----------------------------+------+----------------
+ malformed pg_ndistinct: "[]" | Item array cannot be empty. |      | 22P02
+(1 row)
+
+SELECT * FROM pg_input_error_info('{}', 'pg_ndistinct');
+           message            |              detail               | hint | sql_error_code 
+------------------------------+-----------------------------------+------+----------------
+ malformed pg_ndistinct: "{}" | Initial element must be an array. |      | 22P02
+(1 row)
+
+SELECT * FROM pg_input_error_info('[null]', 'pg_ndistinct');
+             message              |               detail               | hint | sql_error_code 
+----------------------------------+------------------------------------+------+----------------
+ malformed pg_ndistinct: "[null]" | Item list elements cannot be null. |      | 22P02
+(1 row)
+
+-- Invalid keys
+SELECT '[{"attributes_invalid" : [2,3], "ndistinct" : 4}]'::pg_ndistinct;
+ERROR:  malformed pg_ndistinct: "[{"attributes_invalid" : [2,3], "ndistinct" : 4}]"
+LINE 1: SELECT '[{"attributes_invalid" : [2,3], "ndistinct" : 4}]'::...
+               ^
+DETAIL:  Only allowed keys are "attributes" and "ndistinct".
+SELECT '[{"attributes" : [2,3], "invalid" : 3, "ndistinct" : 4}]'::pg_ndistinct;
+ERROR:  malformed pg_ndistinct: "[{"attributes" : [2,3], "invalid" : 3, "ndistinct" : 4}]"
+LINE 1: SELECT '[{"attributes" : [2,3], "invalid" : 3, "ndistinct" :...
+               ^
+DETAIL:  Only allowed keys are "attributes" and "ndistinct".
+SELECT '[{"attributes" : [2,3], "attributes" : [1,3], "ndistinct" : 4}]'::pg_ndistinct;
+ERROR:  malformed pg_ndistinct: "[{"attributes" : [2,3], "attributes" : [1,3], "ndistinct" : 4}]"
+LINE 1: SELECT '[{"attributes" : [2,3], "attributes" : [1,3], "ndist...
+               ^
+DETAIL:  Multiple "attributes" keys are not allowed.
+SELECT '[{"attributes" : [2,3], "ndistinct" : 4, "ndistinct" : 4}]'::pg_ndistinct;
+ERROR:  malformed pg_ndistinct: "[{"attributes" : [2,3], "ndistinct" : 4, "ndistinct" : 4}]"
+LINE 1: SELECT '[{"attributes" : [2,3], "ndistinct" : 4, "ndistinct"...
+               ^
+DETAIL:  Multiple "ndistinct" keys are not allowed.
+SELECT * FROM pg_input_error_info('[{"attributes_invalid" : [2,3], "ndistinct" : 4}]', 'pg_ndistinct');
+                                   message                                   |                       detail                        | hint | sql_error_code 
+-----------------------------------------------------------------------------+-----------------------------------------------------+------+----------------
+ malformed pg_ndistinct: "[{"attributes_invalid" : [2,3], "ndistinct" : 4}]" | Only allowed keys are "attributes" and "ndistinct". |      | 22P02
+(1 row)
+
+SELECT * FROM pg_input_error_info('[{"attributes" : [2,3], "invalid" : 3, "ndistinct" : 4}]', 'pg_ndistinct');
+                                      message                                       |                       detail                        | hint | sql_error_code 
+------------------------------------------------------------------------------------+-----------------------------------------------------+------+----------------
+ malformed pg_ndistinct: "[{"attributes" : [2,3], "invalid" : 3, "ndistinct" : 4}]" | Only allowed keys are "attributes" and "ndistinct". |      | 22P02
+(1 row)
+
+SELECT * FROM pg_input_error_info('[{"attributes" : [2,3], "attributes" : [1,3], "ndistinct" : 4}]', 'pg_ndistinct');
+                                          message                                          |                   detail                    | hint | sql_error_code 
+-------------------------------------------------------------------------------------------+---------------------------------------------+------+----------------
+ malformed pg_ndistinct: "[{"attributes" : [2,3], "attributes" : [1,3], "ndistinct" : 4}]" | Multiple "attributes" keys are not allowed. |      | 22P02
+(1 row)
+
+SELECT * FROM pg_input_error_info('[{"attributes" : [2,3], "ndistinct" : 4, "ndistinct" : 4}]', 'pg_ndistinct');
+                                       message                                        |                   detail                   | hint | sql_error_code 
+--------------------------------------------------------------------------------------+--------------------------------------------+------+----------------
+ malformed pg_ndistinct: "[{"attributes" : [2,3], "ndistinct" : 4, "ndistinct" : 4}]" | Multiple "ndistinct" keys are not allowed. |      | 22P02
+(1 row)
+
+-- Missing key
+SELECT '[{"attributes" : [2,3]}]'::pg_ndistinct;
+ERROR:  malformed pg_ndistinct: "[{"attributes" : [2,3]}]"
+LINE 1: SELECT '[{"attributes" : [2,3]}]'::pg_ndistinct;
+               ^
+DETAIL:  Item must contain "ndistinct" key.
+SELECT '[{"ndistinct" : 4}]'::pg_ndistinct;
+ERROR:  malformed pg_ndistinct: "[{"ndistinct" : 4}]"
+LINE 1: SELECT '[{"ndistinct" : 4}]'::pg_ndistinct;
+               ^
+DETAIL:  Item must contain "attributes" key.
+SELECT * FROM pg_input_error_info('[{"attributes" : [2,3]}]', 'pg_ndistinct');
+                      message                       |               detail               | hint | sql_error_code 
+----------------------------------------------------+------------------------------------+------+----------------
+ malformed pg_ndistinct: "[{"attributes" : [2,3]}]" | Item must contain "ndistinct" key. |      | 22P02
+(1 row)
+
+SELECT * FROM pg_input_error_info('[{"ndistinct" : 4}]', 'pg_ndistinct');
+                    message                    |               detail                | hint | sql_error_code 
+-----------------------------------------------+-------------------------------------+------+----------------
+ malformed pg_ndistinct: "[{"ndistinct" : 4}]" | Item must contain "attributes" key. |      | 22P02
+(1 row)
+
+-- Valid keys, too many attributes
+SELECT '[{"attributes" : [1,2,3,4,5,6,7,8,9], "ndistinct" : 4}]'::pg_ndistinct;
+ERROR:  malformed pg_ndistinct: "[{"attributes" : [1,2,3,4,5,6,7,8,9], "ndistinct" : 4}]"
+LINE 1: SELECT '[{"attributes" : [1,2,3,4,5,6,7,8,9], "ndistinct" : ...
+               ^
+DETAIL:  The "attributes" key must contain an array of at least 2 and no more than 8 attributes.
+-- Special characters
+SELECT '[{"\ud83d\ude04\ud83d\udc36" : [1, 2], "ndistinct" : 4}]'::pg_ndistinct;
+ERROR:  malformed pg_ndistinct: "[{"\ud83d\ude04\ud83d\udc36" : [1, 2], "ndistinct" : 4}]"
+LINE 1: SELECT '[{"\ud83d\ude04\ud83d\udc36" : [1, 2], "ndistinct" :...
+               ^
+DETAIL:  Only allowed keys are "attributes" and "ndistinct".
+SELECT '[{"attributes" : [1, 2], "\ud83d\ude04\ud83d\udc36" : 4}]'::pg_ndistinct;
+ERROR:  malformed pg_ndistinct: "[{"attributes" : [1, 2], "\ud83d\ude04\ud83d\udc36" : 4}]"
+LINE 1: SELECT '[{"attributes" : [1, 2], "\ud83d\ude04\ud83d\udc36" ...
+               ^
+DETAIL:  Only allowed keys are "attributes" and "ndistinct".
+SELECT '[{"attributes" : [1, 2], "ndistinct" : "\ud83d\ude04\ud83d\udc36"}]'::pg_ndistinct;
+ERROR:  malformed pg_ndistinct: "[{"attributes" : [1, 2], "ndistinct" : "\ud83d\ude04\ud83d\udc36"}]"
+LINE 1: SELECT '[{"attributes" : [1, 2], "ndistinct" : "\ud83d\ude04...
+               ^
+DETAIL:  Invalid "ndistinct" value.
+SELECT '[{"attributes" : ["\ud83d\ude04\ud83d\udc36", 2], "ndistinct" : 1}]'::pg_ndistinct;
+ERROR:  malformed pg_ndistinct: "[{"attributes" : ["\ud83d\ude04\ud83d\udc36", 2], "ndistinct" : 1}]"
+LINE 1: SELECT '[{"attributes" : ["\ud83d\ude04\ud83d\udc36", 2], "n...
+               ^
+DETAIL:  Invalid "attributes" value.
+-- Valid keys, invalid values
+SELECT '[{"attributes" : null, "ndistinct" : 4}]'::pg_ndistinct;
+ERROR:  malformed pg_ndistinct: "[{"attributes" : null, "ndistinct" : 4}]"
+LINE 1: SELECT '[{"attributes" : null, "ndistinct" : 4}]'::pg_ndisti...
+               ^
+DETAIL:  Unexpected scalar.
+SELECT '[{"attributes" : [], "ndistinct" : 1}]'::pg_ndistinct;
+ERROR:  malformed pg_ndistinct: "[{"attributes" : [], "ndistinct" : 1}]"
+LINE 1: SELECT '[{"attributes" : [], "ndistinct" : 1}]'::pg_ndistinc...
+               ^
+DETAIL:  The "attributes" key must be a non-empty array.
+SELECT '[{"attributes" : [2], "ndistinct" : 4}]'::pg_ndistinct;
+ERROR:  malformed pg_ndistinct: "[{"attributes" : [2], "ndistinct" : 4}]"
+LINE 1: SELECT '[{"attributes" : [2], "ndistinct" : 4}]'::pg_ndistin...
+               ^
+DETAIL:  The "attributes" key must contain an array of at least 2 and no more than 8 attributes.
+SELECT '[{"attributes" : [2,null], "ndistinct" : 4}]'::pg_ndistinct;
+ERROR:  malformed pg_ndistinct: "[{"attributes" : [2,null], "ndistinct" : 4}]"
+LINE 1: SELECT '[{"attributes" : [2,null], "ndistinct" : 4}]'::pg_nd...
+               ^
+DETAIL:  Attribute number array cannot be null.
+SELECT '[{"attributes" : [2,3], "ndistinct" : null}]'::pg_ndistinct;
+ERROR:  malformed pg_ndistinct: "[{"attributes" : [2,3], "ndistinct" : null}]"
+LINE 1: SELECT '[{"attributes" : [2,3], "ndistinct" : null}]'::pg_nd...
+               ^
+DETAIL:  Invalid "ndistinct" value.
+SELECT '[{"attributes" : [2,"a"], "ndistinct" : 4}]'::pg_ndistinct;
+ERROR:  malformed pg_ndistinct: "[{"attributes" : [2,"a"], "ndistinct" : 4}]"
+LINE 1: SELECT '[{"attributes" : [2,"a"], "ndistinct" : 4}]'::pg_ndi...
+               ^
+DETAIL:  Invalid "attributes" value.
+SELECT '[{"attributes" : [2,3], "ndistinct" : "a"}]'::pg_ndistinct;
+ERROR:  malformed pg_ndistinct: "[{"attributes" : [2,3], "ndistinct" : "a"}]"
+LINE 1: SELECT '[{"attributes" : [2,3], "ndistinct" : "a"}]'::pg_ndi...
+               ^
+DETAIL:  Invalid "ndistinct" value.
+SELECT '[{"attributes" : [2,3], "ndistinct" : []}]'::pg_ndistinct;
+ERROR:  malformed pg_ndistinct: "[{"attributes" : [2,3], "ndistinct" : []}]"
+LINE 1: SELECT '[{"attributes" : [2,3], "ndistinct" : []}]'::pg_ndis...
+               ^
+DETAIL:  Array found in unexpected place.
+SELECT '[{"attributes" : [2,3], "ndistinct" : [null]}]'::pg_ndistinct;
+ERROR:  malformed pg_ndistinct: "[{"attributes" : [2,3], "ndistinct" : [null]}]"
+LINE 1: SELECT '[{"attributes" : [2,3], "ndistinct" : [null]}]'::pg_...
+               ^
+DETAIL:  Array found in unexpected place.
+SELECT '[{"attributes" : [2,3], "ndistinct" : [1,null]}]'::pg_ndistinct;
+ERROR:  malformed pg_ndistinct: "[{"attributes" : [2,3], "ndistinct" : [1,null]}]"
+LINE 1: SELECT '[{"attributes" : [2,3], "ndistinct" : [1,null]}]'::p...
+               ^
+DETAIL:  Array found in unexpected place.
+SELECT '[{"attributes" : [2,3], "ndistinct" : {"a": 1}}]'::pg_ndistinct;
+ERROR:  malformed pg_ndistinct: "[{"attributes" : [2,3], "ndistinct" : {"a": 1}}]"
+LINE 1: SELECT '[{"attributes" : [2,3], "ndistinct" : {"a": 1}}]'::p...
+               ^
+DETAIL:  Value of "ndistinct" must be an integer.
+SELECT '[{"attributes" : [0,1], "ndistinct" : 1}]'::pg_ndistinct;
+ERROR:  malformed pg_ndistinct: "[{"attributes" : [0,1], "ndistinct" : 1}]"
+LINE 1: SELECT '[{"attributes" : [0,1], "ndistinct" : 1}]'::pg_ndist...
+               ^
+DETAIL:  Invalid "attributes" element: 0.
+SELECT '[{"attributes" : [-7,-9], "ndistinct" : 1}]'::pg_ndistinct;
+ERROR:  malformed pg_ndistinct: "[{"attributes" : [-7,-9], "ndistinct" : 1}]"
+LINE 1: SELECT '[{"attributes" : [-7,-9], "ndistinct" : 1}]'::pg_ndi...
+               ^
+DETAIL:  Invalid "attributes" element: -9.
+SELECT '[{"attributes" : 1, "ndistinct" : 4}]'::pg_ndistinct;
+ERROR:  malformed pg_ndistinct: "[{"attributes" : 1, "ndistinct" : 4}]"
+LINE 1: SELECT '[{"attributes" : 1, "ndistinct" : 4}]'::pg_ndistinct...
+               ^
+DETAIL:  Unexpected scalar.
+SELECT '[{"attributes" : "a", "ndistinct" : 4}]'::pg_ndistinct;
+ERROR:  malformed pg_ndistinct: "[{"attributes" : "a", "ndistinct" : 4}]"
+LINE 1: SELECT '[{"attributes" : "a", "ndistinct" : 4}]'::pg_ndistin...
+               ^
+DETAIL:  Unexpected scalar.
+SELECT '[{"attributes" : {"a": 1}, "ndistinct" : 1}]'::pg_ndistinct;
+ERROR:  malformed pg_ndistinct: "[{"attributes" : {"a": 1}, "ndistinct" : 1}]"
+LINE 1: SELECT '[{"attributes" : {"a": 1}, "ndistinct" : 1}]'::pg_nd...
+               ^
+DETAIL:  Value of "attributes" must be an array of attribute numbers.
+SELECT '[{"attributes" : [1, {"a": 1}], "ndistinct" : 1}]'::pg_ndistinct;
+ERROR:  malformed pg_ndistinct: "[{"attributes" : [1, {"a": 1}], "ndistinct" : 1}]"
+LINE 1: SELECT '[{"attributes" : [1, {"a": 1}], "ndistinct" : 1}]'::...
+               ^
+DETAIL:  Attribute lists can only contain attribute numbers.
+SELECT * FROM pg_input_error_info('[{"attributes" : null, "ndistinct" : 4}]', 'pg_ndistinct');
+                              message                               |       detail       | hint | sql_error_code 
+--------------------------------------------------------------------+--------------------+------+----------------
+ malformed pg_ndistinct: "[{"attributes" : null, "ndistinct" : 4}]" | Unexpected scalar. |      | 22P02
+(1 row)
+
+SELECT * FROM pg_input_error_info('[{"attributes" : [], "ndistinct" : 1}]', 'pg_ndistinct');
+                             message                              |                     detail                      | hint | sql_error_code 
+------------------------------------------------------------------+-------------------------------------------------+------+----------------
+ malformed pg_ndistinct: "[{"attributes" : [], "ndistinct" : 1}]" | The "attributes" key must be a non-empty array. |      | 22P02
+(1 row)
+
+SELECT * FROM pg_input_error_info('[{"attributes" : [2], "ndistinct" : 4}]', 'pg_ndistinct');
+                              message                              |                                         detail                                          | hint | sql_error_code 
+-------------------------------------------------------------------+-----------------------------------------------------------------------------------------+------+----------------
+ malformed pg_ndistinct: "[{"attributes" : [2], "ndistinct" : 4}]" | The "attributes" key must contain an array of at least 2 and no more than 8 attributes. |      | 22P02
+(1 row)
+
+SELECT * FROM pg_input_error_info('[{"attributes" : [2,null], "ndistinct" : 4}]', 'pg_ndistinct');
+                                message                                 |                 detail                 | hint | sql_error_code 
+------------------------------------------------------------------------+----------------------------------------+------+----------------
+ malformed pg_ndistinct: "[{"attributes" : [2,null], "ndistinct" : 4}]" | Attribute number array cannot be null. |      | 22P02
+(1 row)
+
+SELECT * FROM pg_input_error_info('[{"attributes" : [2,3], "ndistinct" : null}]', 'pg_ndistinct');
+                                message                                 |           detail           | hint | sql_error_code 
+------------------------------------------------------------------------+----------------------------+------+----------------
+ malformed pg_ndistinct: "[{"attributes" : [2,3], "ndistinct" : null}]" | Invalid "ndistinct" value. |      | 22P02
+(1 row)
+
+SELECT * FROM pg_input_error_info('[{"attributes" : [2,"a"], "ndistinct" : 4}]', 'pg_ndistinct');
+                                message                                |           detail            | hint | sql_error_code 
+-----------------------------------------------------------------------+-----------------------------+------+----------------
+ malformed pg_ndistinct: "[{"attributes" : [2,"a"], "ndistinct" : 4}]" | Invalid "attributes" value. |      | 22P02
+(1 row)
+
+SELECT * FROM pg_input_error_info('[{"attributes" : [2,3], "ndistinct" : "a"}]', 'pg_ndistinct');
+                                message                                |           detail           | hint | sql_error_code 
+-----------------------------------------------------------------------+----------------------------+------+----------------
+ malformed pg_ndistinct: "[{"attributes" : [2,3], "ndistinct" : "a"}]" | Invalid "ndistinct" value. |      | 22P02
+(1 row)
+
+SELECT * FROM pg_input_error_info('[{"attributes" : [2,3], "ndistinct" : []}]', 'pg_ndistinct');
+                               message                                |              detail              | hint | sql_error_code 
+----------------------------------------------------------------------+----------------------------------+------+----------------
+ malformed pg_ndistinct: "[{"attributes" : [2,3], "ndistinct" : []}]" | Array found in unexpected place. |      | 22P02
+(1 row)
+
+SELECT * FROM pg_input_error_info('[{"attributes" : [2,3], "ndistinct" : [null]}]', 'pg_ndistinct');
+                                 message                                  |              detail              | hint | sql_error_code 
+--------------------------------------------------------------------------+----------------------------------+------+----------------
+ malformed pg_ndistinct: "[{"attributes" : [2,3], "ndistinct" : [null]}]" | Array found in unexpected place. |      | 22P02
+(1 row)
+
+SELECT * FROM pg_input_error_info('[{"attributes" : [2,3], "ndistinct" : [1,null]}]', 'pg_ndistinct');
+                                  message                                   |              detail              | hint | sql_error_code 
+----------------------------------------------------------------------------+----------------------------------+------+----------------
+ malformed pg_ndistinct: "[{"attributes" : [2,3], "ndistinct" : [1,null]}]" | Array found in unexpected place. |      | 22P02
+(1 row)
+
+SELECT * FROM pg_input_error_info('[{"attributes" : [2,3], "ndistinct" : {"a": 1}}]', 'pg_ndistinct');
+                                  message                                   |                  detail                  | hint | sql_error_code 
+----------------------------------------------------------------------------+------------------------------------------+------+----------------
+ malformed pg_ndistinct: "[{"attributes" : [2,3], "ndistinct" : {"a": 1}}]" | Value of "ndistinct" must be an integer. |      | 22P02
+(1 row)
+
+SELECT * FROM pg_input_error_info('[{"attributes" : 1, "ndistinct" : 4}]', 'pg_ndistinct');
+                             message                             |       detail       | hint | sql_error_code 
+-----------------------------------------------------------------+--------------------+------+----------------
+ malformed pg_ndistinct: "[{"attributes" : 1, "ndistinct" : 4}]" | Unexpected scalar. |      | 22P02
+(1 row)
+
+SELECT * FROM pg_input_error_info('[{"attributes" : [-7,-9], "ndistinct" : 1}]', 'pg_ndistinct');
+                                message                                |              detail               | hint | sql_error_code 
+-----------------------------------------------------------------------+-----------------------------------+------+----------------
+ malformed pg_ndistinct: "[{"attributes" : [-7,-9], "ndistinct" : 1}]" | Invalid "attributes" element: -9. |      | 22P02
+(1 row)
+
+SELECT * FROM pg_input_error_info('[{"attributes" : 1, "ndistinct" : 4}]', 'pg_ndistinct');
+                             message                             |       detail       | hint | sql_error_code 
+-----------------------------------------------------------------+--------------------+------+----------------
+ malformed pg_ndistinct: "[{"attributes" : 1, "ndistinct" : 4}]" | Unexpected scalar. |      | 22P02
+(1 row)
+
+SELECT * FROM pg_input_error_info('[{"attributes" : "a", "ndistinct" : 4}]', 'pg_ndistinct');
+                              message                              |       detail       | hint | sql_error_code 
+-------------------------------------------------------------------+--------------------+------+----------------
+ malformed pg_ndistinct: "[{"attributes" : "a", "ndistinct" : 4}]" | Unexpected scalar. |      | 22P02
+(1 row)
+
+SELECT * FROM pg_input_error_info('[{"attributes" : {"a": 1}, "ndistinct" : 1}]', 'pg_ndistinct');
+                                message                                 |                            detail                            | hint | sql_error_code 
+------------------------------------------------------------------------+--------------------------------------------------------------+------+----------------
+ malformed pg_ndistinct: "[{"attributes" : {"a": 1}, "ndistinct" : 1}]" | Value of "attributes" must be an array of attribute numbers. |      | 22P02
+(1 row)
+
+SELECT * FROM pg_input_error_info('[{"attributes" : [1, {"a": 1}], "ndistinct" : 1}]', 'pg_ndistinct');
+                                   message                                   |                       detail                        | hint | sql_error_code 
+-----------------------------------------------------------------------------+-----------------------------------------------------+------+----------------
+ malformed pg_ndistinct: "[{"attributes" : [1, {"a": 1}], "ndistinct" : 1}]" | Attribute lists can only contain attribute numbers. |      | 22P02
+(1 row)
+
+-- Duplicated attributes
+SELECT '[{"attributes" : [2,2], "ndistinct" : 4}]'::pg_ndistinct;
+ERROR:  malformed pg_ndistinct: "[{"attributes" : [2,2], "ndistinct" : 4}]"
+LINE 1: SELECT '[{"attributes" : [2,2], "ndistinct" : 4}]'::pg_ndist...
+               ^
+DETAIL:  Invalid "attributes" element: 2 cannot follow 2.
+SELECT * FROM pg_input_error_info('[{"attributes" : [2,2], "ndistinct" : 4}]', 'pg_ndistinct');
+                               message                               |                      detail                      | hint | sql_error_code 
+---------------------------------------------------------------------+--------------------------------------------------+------+----------------
+ malformed pg_ndistinct: "[{"attributes" : [2,2], "ndistinct" : 4}]" | Invalid "attributes" element: 2 cannot follow 2. |      | 22P02
+(1 row)
+
+-- Duplicated attribute lists.
+SELECT '[{"attributes" : [2,3], "ndistinct" : 4},
+         {"attributes" : [2,3], "ndistinct" : 4}]'::pg_ndistinct;
+ERROR:  malformed pg_ndistinct: "[{"attributes" : [2,3], "ndistinct" : 4},
+         {"attributes" : [2,3], "ndistinct" : 4}]"
+LINE 1: SELECT '[{"attributes" : [2,3], "ndistinct" : 4},
+               ^
+DETAIL:  Duplicated "attributes" array found: [2, 3]
+SELECT * FROM pg_input_error_info('[{"attributes" : [2,3], "ndistinct" : 4},
+         {"attributes" : [2,3], "ndistinct" : 4}]', 'pg_ndistinct');
+                              message                               |                   detail                    | hint | sql_error_code 
+--------------------------------------------------------------------+---------------------------------------------+------+----------------
+ malformed pg_ndistinct: "[{"attributes" : [2,3], "ndistinct" : 4},+| Duplicated "attributes" array found: [2, 3] |      | 22P02
+          {"attributes" : [2,3], "ndistinct" : 4}]"                 |                                             |      | 
+(1 row)
+
+-- Partially-covered attribute lists.
+SELECT '[{"attributes" : [2,3], "ndistinct" : 4},
+         {"attributes" : [2,-1], "ndistinct" : 4},
+         {"attributes" : [2,3,-1], "ndistinct" : 4},
+         {"attributes" : [1,3,-1,-2], "ndistinct" : 4}]'::pg_ndistinct;
+ERROR:  malformed pg_ndistinct: "[{"attributes" : [2,3], "ndistinct" : 4},
+         {"attributes" : [2,-1], "ndistinct" : 4},
+         {"attributes" : [2,3,-1], "ndistinct" : 4},
+         {"attributes" : [1,3,-1,-2], "ndistinct" : 4}]"
+LINE 1: SELECT '[{"attributes" : [2,3], "ndistinct" : 4},
+               ^
+DETAIL:  "attributes" array: [2, 3] must be a subset of array: [1, 3, -1, -2]
+SELECT * FROM pg_input_error_info('[{"attributes" : [2,3], "ndistinct" : 4},
+         {"attributes" : [2,-1], "ndistinct" : 4},
+         {"attributes" : [2,3,-1], "ndistinct" : 4},
+         {"attributes" : [1,3,-1,-2], "ndistinct" : 4}]', 'pg_ndistinct');
+                              message                               |                                detail                                | hint | sql_error_code 
+--------------------------------------------------------------------+----------------------------------------------------------------------+------+----------------
+ malformed pg_ndistinct: "[{"attributes" : [2,3], "ndistinct" : 4},+| "attributes" array: [2, 3] must be a subset of array: [1, 3, -1, -2] |      | 22P02
+          {"attributes" : [2,-1], "ndistinct" : 4},                +|                                                                      |      | 
+          {"attributes" : [2,3,-1], "ndistinct" : 4},              +|                                                                      |      | 
+          {"attributes" : [1,3,-1,-2], "ndistinct" : 4}]"           |                                                                      |      | 
+(1 row)
+
+-- Valid inputs
+-- Two attributes.
+SELECT '[{"attributes" : [1,2], "ndistinct" : 4}]'::pg_ndistinct;
+               pg_ndistinct               
+------------------------------------------
+ [{"attributes": [1, 2], "ndistinct": 4}]
+(1 row)
+
+-- Three attributes.
+SELECT '[{"attributes" : [2,-1], "ndistinct" : 1},
+         {"attributes" : [3,-1], "ndistinct" : 2},
+         {"attributes" : [2,3,-1], "ndistinct" : 3}]'::pg_ndistinct;
+                                                          pg_ndistinct                                                          
+--------------------------------------------------------------------------------------------------------------------------------
+ [{"attributes": [2, -1], "ndistinct": 1}, {"attributes": [3, -1], "ndistinct": 2}, {"attributes": [2, 3, -1], "ndistinct": 3}]
+(1 row)
+
+-- Three attributes with only two items.
+SELECT '[{"attributes" : [2,-1], "ndistinct" : 1},
+         {"attributes" : [2,3,-1], "ndistinct" : 3}]'::pg_ndistinct;
+                                     pg_ndistinct                                      
+---------------------------------------------------------------------------------------
+ [{"attributes": [2, -1], "ndistinct": 1}, {"attributes": [2, 3, -1], "ndistinct": 3}]
+(1 row)
+
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index f56482fb9f12..f3f0b5f2f317 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -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
+test: geometry horology tstypes regex type_sanity opr_sanity misc_sanity comments expressions unicode xid mvcc database stats_import pg_ndistinct
 
 # ----------
 # Load huge amounts of data
diff --git a/src/test/regress/sql/pg_ndistinct.sql b/src/test/regress/sql/pg_ndistinct.sql
new file mode 100644
index 000000000000..e2bd19eaa5ef
--- /dev/null
+++ b/src/test/regress/sql/pg_ndistinct.sql
@@ -0,0 +1,101 @@
+-- Tests for type pg_ndistinct
+
+-- Invalid inputs
+SELECT 'null'::pg_ndistinct;
+SELECT '{"a": 1}'::pg_ndistinct;
+SELECT '[]'::pg_ndistinct;
+SELECT '{}'::pg_ndistinct;
+SELECT '[null]'::pg_ndistinct;
+SELECT * FROM pg_input_error_info('null', 'pg_ndistinct');
+SELECT * FROM pg_input_error_info('{"a": 1}', 'pg_ndistinct');
+SELECT * FROM pg_input_error_info('[]', 'pg_ndistinct');
+SELECT * FROM pg_input_error_info('{}', 'pg_ndistinct');
+SELECT * FROM pg_input_error_info('[null]', 'pg_ndistinct');
+-- Invalid keys
+SELECT '[{"attributes_invalid" : [2,3], "ndistinct" : 4}]'::pg_ndistinct;
+SELECT '[{"attributes" : [2,3], "invalid" : 3, "ndistinct" : 4}]'::pg_ndistinct;
+SELECT '[{"attributes" : [2,3], "attributes" : [1,3], "ndistinct" : 4}]'::pg_ndistinct;
+SELECT '[{"attributes" : [2,3], "ndistinct" : 4, "ndistinct" : 4}]'::pg_ndistinct;
+SELECT * FROM pg_input_error_info('[{"attributes_invalid" : [2,3], "ndistinct" : 4}]', 'pg_ndistinct');
+SELECT * FROM pg_input_error_info('[{"attributes" : [2,3], "invalid" : 3, "ndistinct" : 4}]', 'pg_ndistinct');
+SELECT * FROM pg_input_error_info('[{"attributes" : [2,3], "attributes" : [1,3], "ndistinct" : 4}]', 'pg_ndistinct');
+SELECT * FROM pg_input_error_info('[{"attributes" : [2,3], "ndistinct" : 4, "ndistinct" : 4}]', 'pg_ndistinct');
+
+-- Missing key
+SELECT '[{"attributes" : [2,3]}]'::pg_ndistinct;
+SELECT '[{"ndistinct" : 4}]'::pg_ndistinct;
+SELECT * FROM pg_input_error_info('[{"attributes" : [2,3]}]', 'pg_ndistinct');
+SELECT * FROM pg_input_error_info('[{"ndistinct" : 4}]', 'pg_ndistinct');
+
+-- Valid keys, too many attributes
+SELECT '[{"attributes" : [1,2,3,4,5,6,7,8,9], "ndistinct" : 4}]'::pg_ndistinct;
+
+-- Special characters
+SELECT '[{"\ud83d\ude04\ud83d\udc36" : [1, 2], "ndistinct" : 4}]'::pg_ndistinct;
+SELECT '[{"attributes" : [1, 2], "\ud83d\ude04\ud83d\udc36" : 4}]'::pg_ndistinct;
+SELECT '[{"attributes" : [1, 2], "ndistinct" : "\ud83d\ude04\ud83d\udc36"}]'::pg_ndistinct;
+SELECT '[{"attributes" : ["\ud83d\ude04\ud83d\udc36", 2], "ndistinct" : 1}]'::pg_ndistinct;
+
+-- Valid keys, invalid values
+SELECT '[{"attributes" : null, "ndistinct" : 4}]'::pg_ndistinct;
+SELECT '[{"attributes" : [], "ndistinct" : 1}]'::pg_ndistinct;
+SELECT '[{"attributes" : [2], "ndistinct" : 4}]'::pg_ndistinct;
+SELECT '[{"attributes" : [2,null], "ndistinct" : 4}]'::pg_ndistinct;
+SELECT '[{"attributes" : [2,3], "ndistinct" : null}]'::pg_ndistinct;
+SELECT '[{"attributes" : [2,"a"], "ndistinct" : 4}]'::pg_ndistinct;
+SELECT '[{"attributes" : [2,3], "ndistinct" : "a"}]'::pg_ndistinct;
+SELECT '[{"attributes" : [2,3], "ndistinct" : []}]'::pg_ndistinct;
+SELECT '[{"attributes" : [2,3], "ndistinct" : [null]}]'::pg_ndistinct;
+SELECT '[{"attributes" : [2,3], "ndistinct" : [1,null]}]'::pg_ndistinct;
+SELECT '[{"attributes" : [2,3], "ndistinct" : {"a": 1}}]'::pg_ndistinct;
+SELECT '[{"attributes" : [0,1], "ndistinct" : 1}]'::pg_ndistinct;
+SELECT '[{"attributes" : [-7,-9], "ndistinct" : 1}]'::pg_ndistinct;
+SELECT '[{"attributes" : 1, "ndistinct" : 4}]'::pg_ndistinct;
+SELECT '[{"attributes" : "a", "ndistinct" : 4}]'::pg_ndistinct;
+SELECT '[{"attributes" : {"a": 1}, "ndistinct" : 1}]'::pg_ndistinct;
+SELECT '[{"attributes" : [1, {"a": 1}], "ndistinct" : 1}]'::pg_ndistinct;
+SELECT * FROM pg_input_error_info('[{"attributes" : null, "ndistinct" : 4}]', 'pg_ndistinct');
+SELECT * FROM pg_input_error_info('[{"attributes" : [], "ndistinct" : 1}]', 'pg_ndistinct');
+SELECT * FROM pg_input_error_info('[{"attributes" : [2], "ndistinct" : 4}]', 'pg_ndistinct');
+SELECT * FROM pg_input_error_info('[{"attributes" : [2,null], "ndistinct" : 4}]', 'pg_ndistinct');
+SELECT * FROM pg_input_error_info('[{"attributes" : [2,3], "ndistinct" : null}]', 'pg_ndistinct');
+SELECT * FROM pg_input_error_info('[{"attributes" : [2,"a"], "ndistinct" : 4}]', 'pg_ndistinct');
+SELECT * FROM pg_input_error_info('[{"attributes" : [2,3], "ndistinct" : "a"}]', 'pg_ndistinct');
+SELECT * FROM pg_input_error_info('[{"attributes" : [2,3], "ndistinct" : []}]', 'pg_ndistinct');
+SELECT * FROM pg_input_error_info('[{"attributes" : [2,3], "ndistinct" : [null]}]', 'pg_ndistinct');
+SELECT * FROM pg_input_error_info('[{"attributes" : [2,3], "ndistinct" : [1,null]}]', 'pg_ndistinct');
+SELECT * FROM pg_input_error_info('[{"attributes" : [2,3], "ndistinct" : {"a": 1}}]', 'pg_ndistinct');
+SELECT * FROM pg_input_error_info('[{"attributes" : 1, "ndistinct" : 4}]', 'pg_ndistinct');
+SELECT * FROM pg_input_error_info('[{"attributes" : [-7,-9], "ndistinct" : 1}]', 'pg_ndistinct');
+SELECT * FROM pg_input_error_info('[{"attributes" : 1, "ndistinct" : 4}]', 'pg_ndistinct');
+SELECT * FROM pg_input_error_info('[{"attributes" : "a", "ndistinct" : 4}]', 'pg_ndistinct');
+SELECT * FROM pg_input_error_info('[{"attributes" : {"a": 1}, "ndistinct" : 1}]', 'pg_ndistinct');
+SELECT * FROM pg_input_error_info('[{"attributes" : [1, {"a": 1}], "ndistinct" : 1}]', 'pg_ndistinct');
+-- Duplicated attributes
+SELECT '[{"attributes" : [2,2], "ndistinct" : 4}]'::pg_ndistinct;
+SELECT * FROM pg_input_error_info('[{"attributes" : [2,2], "ndistinct" : 4}]', 'pg_ndistinct');
+-- Duplicated attribute lists.
+SELECT '[{"attributes" : [2,3], "ndistinct" : 4},
+         {"attributes" : [2,3], "ndistinct" : 4}]'::pg_ndistinct;
+SELECT * FROM pg_input_error_info('[{"attributes" : [2,3], "ndistinct" : 4},
+         {"attributes" : [2,3], "ndistinct" : 4}]', 'pg_ndistinct');
+-- Partially-covered attribute lists.
+SELECT '[{"attributes" : [2,3], "ndistinct" : 4},
+         {"attributes" : [2,-1], "ndistinct" : 4},
+         {"attributes" : [2,3,-1], "ndistinct" : 4},
+         {"attributes" : [1,3,-1,-2], "ndistinct" : 4}]'::pg_ndistinct;
+SELECT * FROM pg_input_error_info('[{"attributes" : [2,3], "ndistinct" : 4},
+         {"attributes" : [2,-1], "ndistinct" : 4},
+         {"attributes" : [2,3,-1], "ndistinct" : 4},
+         {"attributes" : [1,3,-1,-2], "ndistinct" : 4}]', 'pg_ndistinct');
+
+-- Valid inputs
+-- Two attributes.
+SELECT '[{"attributes" : [1,2], "ndistinct" : 4}]'::pg_ndistinct;
+-- Three attributes.
+SELECT '[{"attributes" : [2,-1], "ndistinct" : 1},
+         {"attributes" : [3,-1], "ndistinct" : 2},
+         {"attributes" : [2,3,-1], "ndistinct" : 3}]'::pg_ndistinct;
+-- Three attributes with only two items.
+SELECT '[{"attributes" : [2,-1], "ndistinct" : 1},
+         {"attributes" : [2,3,-1], "ndistinct" : 3}]'::pg_ndistinct;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 57a8f0366a55..17e2b40b9cb0 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1732,6 +1732,8 @@ MultirangeIOData
 MultirangeParseState
 MultirangeType
 NDBOX
+NDistinctParseState
+NDistinctSemanticState
 NLSVERSIONINFOEX
 NODE
 NTSTATUS
-- 
2.51.0

From 29f98ba149aeb006f61bb2c8a562732c264875c0 Mon Sep 17 00:00:00 2001
From: Michael Paquier <[email protected]>
Date: Tue, 25 Nov 2025 16:13:44 +0900
Subject: [PATCH v18 2/2] Add working input function for pg_dependencies.

This will consume the format that was established when the output
function for pg_dependencies was recently changed.

This will be needed for importing extended statistics.
---
 src/backend/utils/adt/pg_dependencies.c       | 785 +++++++++++++++++-
 src/test/regress/expected/pg_dependencies.out | 495 +++++++++++
 src/test/regress/parallel_schedule            |   2 +-
 src/test/regress/sql/pg_dependencies.sql      | 123 +++
 src/tools/pgindent/typedefs.list              |   2 +
 5 files changed, 1396 insertions(+), 11 deletions(-)
 create mode 100644 src/test/regress/expected/pg_dependencies.out
 create mode 100644 src/test/regress/sql/pg_dependencies.sql

diff --git a/src/backend/utils/adt/pg_dependencies.c b/src/backend/utils/adt/pg_dependencies.c
index 87181aa00e9a..0fd8a2476459 100644
--- a/src/backend/utils/adt/pg_dependencies.c
+++ b/src/backend/utils/adt/pg_dependencies.c
@@ -14,31 +14,796 @@
 
 #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;
+}
+
+/*
+ * 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;
+}
+
+/*
+ * 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;
+}
+
+/*
+ * 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;
+}
+
+/*
+ * 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;
+}
+
+/*
+ * 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)
 {
-	/*
-	 * pg_node_list stores the data in binary form and parsing text input is
-	 * not needed, so disallow this.
-	 */
-	ereport(ERROR,
-			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-			 errmsg("cannot accept a value of type %s", "pg_dependencies")));
+	char	   *str = PG_GETARG_CSTRING(0);
+	bytea	   *bytes = NULL;
 
-	PG_RETURN_VOID();			/* keep compiler quiet */
+	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);
+
+	/*
+	 * If escontext already set, just use that. Anything else is a generic
+	 * JSON parse error.
+	 */
+	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_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
index 000000000000..556e3dad00fb
--- /dev/null
+++ b/src/test/regress/expected/pg_dependencies.out
@@ -0,0 +1,495 @@
+-- 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)
+
+-- 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)
+
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index f3f0b5f2f317..cc6d799bceaf 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -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
index 000000000000..ccc8ee277126
--- /dev/null
+++ b/src/test/regress/sql/pg_dependencies.sql
@@ -0,0 +1,123 @@
+-- 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');
+
+-- 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;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 17e2b40b9cb0..dfcd619bfee3 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -631,6 +631,8 @@ DefaultACLInfo
 DefineStmt
 DefnDumperPtr
 DeleteStmt
+DependenciesParseState
+DependenciesSemanticState
 DependencyGenerator
 DependencyGeneratorData
 DependencyType
-- 
2.51.0

Attachment: signature.asc
Description: PGP signature

Reply via email to