v9-0009-Make-pg_dependencies-a-proper-adt.patch

text/x-patch

Filename: v9-0009-Make-pg_dependencies-a-proper-adt.patch
Type: text/x-patch
Part: 8
Message: Re: Extended Statistics set/restore/clear functions.

Patch

Same data as JSON: GET /api/v1/attachments/:id/patch the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes. API reference →
Format: format-patch
Series: patch v9-0009
Subject: Make pg_dependencies a proper adt.
File+
src/backend/statistics/dependencies.c 0 539
src/backend/utils/adt/Makefile 1 0
src/backend/utils/adt/meson.build 1 0
src/backend/utils/adt/pg_dependencies.c 556 0
From 5826e635f06e1e347ae5609ffdffdcf69fc13cd7 Mon Sep 17 00:00:00 2001
From: Corey Huinker <corey.huinker@gmail.com>
Date: Mon, 10 Nov 2025 00:13:14 -0500
Subject: [PATCH v9 9/9] Make pg_dependencies a proper adt.

Move the in/out/send/recv functions for pg_dependencies to pg_dependencies.c,
which allows dependencies.c to focus on the transformation from sample
data to the internal MVDependencies structure.
---
 src/backend/statistics/dependencies.c   | 539 -----------------------
 src/backend/utils/adt/Makefile          |   1 +
 src/backend/utils/adt/meson.build       |   1 +
 src/backend/utils/adt/pg_dependencies.c | 556 ++++++++++++++++++++++++
 4 files changed, 558 insertions(+), 539 deletions(-)
 create mode 100644 src/backend/utils/adt/pg_dependencies.c

diff --git a/src/backend/statistics/dependencies.c b/src/backend/statistics/dependencies.c
index 7b310b9f55d..b1d4e84de21 100644
--- a/src/backend/statistics/dependencies.c
+++ b/src/backend/statistics/dependencies.c
@@ -13,26 +13,18 @@
  */
 #include "postgres.h"
 
-#include "access/attnum.h"
 #include "access/htup_details.h"
 #include "catalog/pg_statistic_ext.h"
 #include "catalog/pg_statistic_ext_data.h"
-#include "common/int.h"
-#include "common/jsonapi.h"
 #include "lib/stringinfo.h"
-#include "mb/pg_wchar.h"
-#include "nodes/miscnodes.h"
 #include "nodes/nodeFuncs.h"
 #include "nodes/nodes.h"
 #include "nodes/pathnodes.h"
-#include "nodes/pg_list.h"
 #include "optimizer/clauses.h"
 #include "optimizer/optimizer.h"
 #include "parser/parsetree.h"
 #include "statistics/extended_stats_internal.h"
 #include "statistics/statistics.h"
-#include "utils/builtins.h"
-#include "utils/float.h"
 #include "utils/fmgroids.h"
 #include "utils/fmgrprotos.h"
 #include "utils/lsyscache.h"
@@ -651,377 +643,6 @@ statext_dependencies_load(Oid mvoid, bool inh)
 	return result;
 }
 
-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
-}			depsParseSemanticState;
-
-typedef struct
-{
-	const char *str;
-	depsParseSemanticState 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 attributes attnums */
-	AttrNumber	dependency;
-	double		degree;
-}			dependenciesParseState;
-
-/*
- * Invoked at the start of each MVDependency object.
- *
- * The entire JSON document shoul be one array of MVDependency objects.
- *
- * If we're anywhere else in the document, it's an error.
- */
-static JsonParseErrorType
-dependencies_object_start(void *state)
-{
-	dependenciesParseState *parse = state;
-
-	if (parse->state != DEPS_EXPECT_ITEM)
-	{
-		ereturn(parse->escontext, (Datum) 0,
-				(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
-				 errmsg("malformed pg_dependencies: \"%s\"", parse->str),
-				 errdetail("Expected Item object")));
-		return JSON_SEM_ACTION_FAILED;
-	}
-
-	/* Now we expect to see attributes/dependency/degree keys */
-	parse->state = DEPS_EXPECT_KEY;
-	return JSON_SUCCESS;
-}
-
-static int
-attnum_compare(const void *aptr, const void *bptr)
-{
-	AttrNumber	a = *(const AttrNumber *) aptr;
-	AttrNumber	b = *(const AttrNumber *) bptr;
-
-	return pg_cmp_s16(a, b);
-}
-
-static JsonParseErrorType
-dependencies_object_end(void *state)
-{
-	dependenciesParseState *parse = state;
-
-	MVDependency *dep;
-	AttrNumber *attrsort;
-
-	int			natts = 0;
-
-	if (!parse->found_attributes)
-	{
-		ereturn(parse->escontext, (Datum) 0,
-				(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
-				 errmsg("malformed pg_dependencies: \"%s\"", parse->str),
-				 errdetail("Item must contain \"attributes\" key")));
-		return JSON_SEM_ACTION_FAILED;
-	}
-
-	if (!parse->found_dependency)
-	{
-		ereturn(parse->escontext, (Datum) 0,
-				(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
-				 errmsg("malformed pg_dependencies: \"%s\"", parse->str),
-				 errdetail("Item must contain \"dependencies\" key")));
-		return JSON_SEM_ACTION_FAILED;
-	}
-
-	if (!parse->found_degree)
-	{
-		ereturn(parse->escontext, (Datum) 0,
-				(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
-				 errmsg("malformed pg_dependencies: \"%s\"", parse->str),
-				 errdetail("Item must contain \"degree\" key")));
-		return JSON_SEM_ACTION_FAILED;
-	}
-
-	if (parse->attnum_list == NIL)
-	{
-		ereturn(parse->escontext, (Datum) 0,
-				(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
-				 errmsg("malformed pg_dependencies: \"%s\"", parse->str),
-				 errdetail("The \"attributes\" key must be an non-empty array")));
-		return JSON_SEM_ACTION_FAILED;
-	}
-
-	/*
-	 * We need at least 1 attnum for a dependencies item, anything less is
-	 * malformed.
-	 */
-	natts = parse->attnum_list->length;
-	if (natts < 1)
-	{
-		ereturn(parse->escontext, (Datum) 0,
-				(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
-				 errmsg("malformed pg_dependencies: \"%s\"", parse->str),
-				 errdetail("The attributes key must contain an array of at least one attnum")));
-
-		return JSON_SEM_ACTION_FAILED;
-	}
-	attrsort = palloc0(natts * sizeof(AttrNumber));
-
-	/*
-	 * Allocate enough space for the dependency, the attnums in the list, plus
-	 * the final attnum
-	 */
-	dep = palloc0(offsetof(MVDependency, attributes) + ((natts + 1) * sizeof(AttrNumber)));
-	dep->nattributes = natts + 1;
-
-	dep->attributes[natts] = parse->dependency;
-	dep->degree = parse->degree;
-
-	attrsort = palloc0(dep->nattributes * sizeof(AttrNumber));
-	attrsort[natts] = parse->dependency;
-
-	for (int i = 0; i < natts; i++)
-	{
-		attrsort[i] = (AttrNumber) parse->attnum_list->elements[i].int_value;
-		dep->attributes[i] = attrsort[i];
-	}
-
-	/* Check attrsort for uniqueness */
-	qsort(attrsort, natts + 1, sizeof(AttrNumber), attnum_compare);
-	for (int i = 1; i < dep->nattributes; i++)
-		if (attrsort[i] == attrsort[i - 1])
-		{
-			ereturn(parse->escontext, (Datum) 0,
-					(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
-					 errmsg("malformed pg_dependencies: \"%s\"", parse->str),
-					 errdetail("attnum list duplicate value found: %d", attrsort[i])));
-
-			return JSON_SEM_ACTION_FAILED;
-		}
-	pfree(attrsort);
-
-	parse->dependency_list = lappend(parse->dependency_list, (void *) dep);
-
-	/* reset dep item state vars */
-	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;
-
-	/* Now we are looking for the next MVDependency */
-	parse->state = DEPS_EXPECT_ITEM;
-	return JSON_SUCCESS;
-}
-
-/*
- * dependencies 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:
-			ereturn(parse->escontext, (Datum) 0,
-					(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 attnum list or the whole object
- */
-static JsonParseErrorType
-dependencies_array_end(void *state)
-{
-	dependenciesParseState *parse = state;
-
-	switch (parse->state)
-	{
-		case DEPS_EXPECT_ATTNUM:
-			parse->state = DEPS_EXPECT_KEY;
-			break;
-
-		case DEPS_EXPECT_ITEM:
-			parse->state = DEPS_PARSE_COMPLETE;
-			break;
-
-		default:
-			ereturn(parse->escontext, (Datum) 0,
-					(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;
-}
-
-/*
- * The valid keys for the MVDependency object are:
- *   - attributes
- *   - depeendency
- *   - degree
- */
-static JsonParseErrorType
-dependencies_object_field_start(void *state, char *fname, bool isnull)
-{
-	dependenciesParseState *parse = state;
-
-	const char *attributes = "attributes";
-	const char *dependency = "dependency";
-	const char *degree = "degree";
-
-	if (strcmp(fname, attributes) == 0)
-	{
-		parse->found_attributes = true;
-		parse->state = DEPS_EXPECT_ATTNUM_LIST;
-		return JSON_SUCCESS;
-	}
-
-	if (strcmp(fname, dependency) == 0)
-	{
-		parse->found_dependency = true;
-		parse->state = DEPS_EXPECT_DEPENDENCY;
-		return JSON_SUCCESS;
-	}
-
-	if (strcmp(fname, degree) == 0)
-	{
-		parse->found_degree = true;
-		parse->state = DEPS_EXPECT_DEGREE;
-		return JSON_SUCCESS;
-	}
-
-	ereturn(parse->escontext, (Datum) 0,
-			(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
-			 errmsg("malformed pg_dependencies: \"%s\"", parse->str),
-			 errdetail("Only allowed keys are \%s\", \"%s\" and \%s\".",
-					   attributes, dependency, degree)));
-	return JSON_SEM_ACTION_FAILED;
-}
-
-/*
- * ndsitinct 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;
-
-	if (parse->state == DEPS_EXPECT_ATTNUM)
-	{
-		if (isnull)
-		{
-			ereturn(parse->escontext, (Datum) 0,
-					(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
-					 errmsg("malformed pg_dependencies: \"%s\"", parse->str),
-					 errdetail("Attnum list elements cannot be null.")));
-
-			return JSON_SEM_ACTION_FAILED;
-		}
-		return JSON_SUCCESS;
-	}
-
-	if (parse->state == DEPS_EXPECT_ITEM)
-	{
-		if (isnull)
-		{
-			ereturn(parse->escontext, (Datum) 0,
-					(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
-					 errmsg("malformed pg_dependencies: \"%s\"", parse->str),
-					 errdetail("Item list elements cannot be null.")));
-
-			return JSON_SEM_ACTION_FAILED;
-		}
-
-		return JSON_SUCCESS;
-	}
-
-	ereturn(parse->escontext, (Datum) 0,
-			(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
-			 errmsg("malformed pg_dependencies: \"%s\"", parse->str),
-			 errdetail("Unexpected array element.")));
-
-	return JSON_SEM_ACTION_FAILED;
-}
-
-/*
- * 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;
-
-	if (parse->state == DEPS_EXPECT_ATTNUM)
-	{
-		AttrNumber	attnum = pg_strtoint16_safe(token, parse->escontext);
-
-		if (SOFT_ERROR_OCCURRED(parse->escontext))
-			return JSON_SEM_ACTION_FAILED;
-
-		parse->attnum_list = lappend_int(parse->attnum_list, (int) attnum);
-		return JSON_SUCCESS;
-	}
-
-	if (parse->state == DEPS_EXPECT_DEPENDENCY)
-	{
-		parse->dependency = (AttrNumber) pg_strtoint16_safe(token, parse->escontext);
-
-		if (SOFT_ERROR_OCCURRED(parse->escontext))
-			return JSON_SEM_ACTION_FAILED;
-
-		return JSON_SUCCESS;
-	}
-
-
-	if (parse->state == DEPS_EXPECT_DEGREE)
-	{
-		parse->degree = float8in_internal(token, NULL, "double",
-										  token, parse->escontext);
-
-		if (SOFT_ERROR_OCCURRED(parse->escontext))
-			return JSON_SEM_ACTION_FAILED;
-
-		return JSON_SUCCESS;
-	}
-
-	ereturn(parse->escontext, (Datum) 0,
-			(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
-			 errmsg("malformed pg_dependencies: \"%s\"", parse->str),
-			 errdetail("Unexpected scalar.")));
-	return JSON_SEM_ACTION_FAILED;
-}
-
 /*
  * Validate an MVDependencies against the extended statistics object definition.
  *
@@ -1071,166 +692,6 @@ pg_dependencies_validate_deps(MVDependencies *dependencies, int2vector *stxkeys,
 	return true;
 }
 
-/*
- * pg_dependencies_in		- input routine for type pg_dependencies.
- *
- * This format is valid JSON, with the expected format:
- *    [{"attributes": [1,2], "dependency": -1, "degree": 1.0000},
- *     {"attributes": [1,-1], "dependency": 2, "degree": 0.0000},
- *     {"attributes": [2,-1], "dependency": 1, "degree": 1.0000}]
- *
- */
-Datum
-pg_dependencies_in(PG_FUNCTION_ARGS)
-{
-	char	   *str = PG_GETARG_CSTRING(0);
-
-	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)
-	{
-		List	   *list = parse_state.dependency_list;
-		int			ndeps = list->length;
-		MVDependencies *mvdeps;
-		bytea	   *bytes;
-
-		mvdeps = palloc0(offsetof(MVDependencies, deps) + ndeps * sizeof(MVDependency));
-		mvdeps->magic = STATS_DEPS_MAGIC;
-		mvdeps->type = STATS_DEPS_TYPE_BASIC;
-		mvdeps->ndeps = ndeps;
-
-		/* copy MVDependency structs out of the list into the MVDependencies */
-		for (int i = 0; i < ndeps; i++)
-			mvdeps->deps[i] = list->elements[i].ptr_value;
-		bytes = statext_dependencies_serialize(mvdeps);
-
-		list_free(list);
-		for (int i = 0; i < ndeps; i++)
-			pfree(mvdeps->deps[i]);
-		pfree(mvdeps);
-
-		PG_RETURN_BYTEA_P(bytes);
-	}
-	else if (result == JSON_SEM_ACTION_FAILED)
-		PG_RETURN_NULL();
-
-	/* Anything else is a generic JSON parse error */
-	ereturn(parse_state.escontext, (Datum) 0,
-			(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
-			 errmsg("malformed pg_dependencies: \"%s\"", str),
-			 errdetail("Must be valid JSON.")));
-
-	PG_RETURN_NULL();			/* keep compiler quiet */
-}
-
-/*
- * Free allocations of an MVNDistinct
- */
-void
-free_pg_dependencies(MVDependencies *dependencies)
-{
-	for (int i = 0; i < dependencies->ndeps; i++)
-		pfree(dependencies->deps[i]);
-
-	pfree(dependencies);
-}
-
-/*
- * pg_dependencies		- output routine for type pg_dependencies.
- */
-Datum
-pg_dependencies_out(PG_FUNCTION_ARGS)
-{
-	bytea	   *data = PG_GETARG_BYTEA_PP(0);
-	MVDependencies *dependencies = statext_dependencies_deserialize(data);
-	StringInfoData str;
-
-	initStringInfo(&str);
-	appendStringInfoChar(&str, '[');
-
-	for (int i = 0; i < dependencies->ndeps; i++)
-	{
-		MVDependency *dependency = dependencies->deps[i];
-
-		if (i > 0)
-			appendStringInfoString(&str, ", ");
-
-		if (dependency->nattributes <= 1)
-			elog(ERROR, "invalid zero-length nattributes array in MVDependencies");
-
-		appendStringInfo(&str, "{\"attributes\": [%d",
-						 dependency->attributes[0]);
-
-		for (int j = 1; j < dependency->nattributes - 1; j++)
-			appendStringInfo(&str, ", %d", dependency->attributes[j]);
-
-		appendStringInfo(&str, "], \"dependency\": %d, \"degree\": %f}",
-						 dependency->attributes[dependency->nattributes - 1],
-						 dependency->degree);
-	}
-
-	appendStringInfoChar(&str, ']');
-
-	PG_RETURN_CSTRING(str.data);
-}
-
-/*
- * pg_dependencies_recv		- binary input routine for type pg_dependencies.
- */
-Datum
-pg_dependencies_recv(PG_FUNCTION_ARGS)
-{
-	ereport(ERROR,
-			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-			 errmsg("cannot accept a value of type %s", "pg_dependencies")));
-
-	PG_RETURN_VOID();			/* keep compiler quiet */
-}
-
-/*
- * pg_dependencies_send		- binary output routine for type pg_dependencies.
- *
- * Functional dependencies are serialized in a bytea value (although the type
- * is named differently), so let's just send that.
- */
-Datum
-pg_dependencies_send(PG_FUNCTION_ARGS)
-{
-	return byteasend(fcinfo);
-}
-
 /*
  * dependency_is_compatible_clause
  *		Determines if the clause is compatible with functional dependencies
diff --git a/src/backend/utils/adt/Makefile b/src/backend/utils/adt/Makefile
index 70ff8e45516..ba40ada11ca 100644
--- a/src/backend/utils/adt/Makefile
+++ b/src/backend/utils/adt/Makefile
@@ -80,6 +80,7 @@ OBJS = \
 	oracle_compat.o \
 	orderedsetaggs.o \
 	partitionfuncs.o \
+	pg_dependencies.o \
 	pg_locale.o \
 	pg_locale_builtin.o \
 	pg_locale_icu.o \
diff --git a/src/backend/utils/adt/meson.build b/src/backend/utils/adt/meson.build
index b6b642c77a0..9c4c62d41da 100644
--- a/src/backend/utils/adt/meson.build
+++ b/src/backend/utils/adt/meson.build
@@ -76,6 +76,7 @@ backend_sources += files(
   'oracle_compat.c',
   'orderedsetaggs.c',
   'partitionfuncs.c',
+  'pg_dependencies.c',
   'pg_locale.c',
   'pg_locale_builtin.c',
   'pg_locale_icu.c',
diff --git a/src/backend/utils/adt/pg_dependencies.c b/src/backend/utils/adt/pg_dependencies.c
new file mode 100644
index 00000000000..722c000eeef
--- /dev/null
+++ b/src/backend/utils/adt/pg_dependencies.c
@@ -0,0 +1,556 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_dependencies.c
+ *		pg_dependencies data type support.
+ *
+ * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ *	  src/backend/utils/adt/pg_dependencies.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/attnum.h"
+#include "common/int.h"
+#include "common/jsonapi.h"
+#include "mb/pg_wchar.h"
+#include "nodes/miscnodes.h"
+#include "nodes/pg_list.h"
+#include "statistics/extended_stats_internal.h"
+#include "utils/builtins.h"
+#include "utils/float.h"
+
+/*
+ * The valid keys for the pg_dependencies object:
+ */
+static const char *attributes = "attributes";
+static const char *dependency = "dependency";
+static const char *degree = "degree";
+
+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
+}			depsParseSemanticState;
+
+typedef struct
+{
+	const char *str;
+	depsParseSemanticState 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 attributes attnums */
+	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're anywhere else in the document, it's an error.
+ */
+static JsonParseErrorType
+dependencies_object_start(void *state)
+{
+	dependenciesParseState *parse = state;
+
+	if (parse->state != DEPS_EXPECT_ITEM)
+	{
+		ereturn(parse->escontext, (Datum) 0,
+				(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+				 errmsg("malformed pg_dependencies: \"%s\"", parse->str),
+				 errdetail("Expected Item object")));
+		return JSON_SEM_ACTION_FAILED;
+	}
+
+	/* Now we expect to see attributes/dependency/degree keys */
+	parse->state = DEPS_EXPECT_KEY;
+	return JSON_SUCCESS;
+}
+
+static int
+attnum_compare(const void *aptr, const void *bptr)
+{
+	AttrNumber	a = *(const AttrNumber *) aptr;
+	AttrNumber	b = *(const AttrNumber *) bptr;
+
+	return pg_cmp_s16(a, b);
+}
+
+static JsonParseErrorType
+dependencies_object_end(void *state)
+{
+	dependenciesParseState *parse = state;
+
+	MVDependency *dep;
+	AttrNumber *attrsort;
+
+	int			natts = 0;
+
+	if (!parse->found_attributes)
+	{
+		ereturn(parse->escontext, (Datum) 0,
+				(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+				 errmsg("malformed pg_dependencies: \"%s\"", parse->str),
+				 errdetail("Item must contain \"%s\" key", attributes)));
+		return JSON_SEM_ACTION_FAILED;
+	}
+
+	if (!parse->found_dependency)
+	{
+		ereturn(parse->escontext, (Datum) 0,
+				(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+				 errmsg("malformed pg_dependencies: \"%s\"", parse->str),
+				 errdetail("Item must contain \"%s\" key", dependency)));
+		return JSON_SEM_ACTION_FAILED;
+	}
+
+	if (!parse->found_degree)
+	{
+		ereturn(parse->escontext, (Datum) 0,
+				(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+				 errmsg("malformed pg_dependencies: \"%s\"", parse->str),
+				 errdetail("Item must contain \"%s\" key", degree)));
+		return JSON_SEM_ACTION_FAILED;
+	}
+
+	if (parse->attnum_list == NIL)
+	{
+		ereturn(parse->escontext, (Datum) 0,
+				(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+				 errmsg("malformed pg_dependencies: \"%s\"", parse->str),
+				 errdetail("The \"%s\" key must be an non-empty array", attributes)));
+		return JSON_SEM_ACTION_FAILED;
+	}
+
+	/*
+	 * We need at least 1 attnum for a dependencies item, anything less is
+	 * malformed.
+	 */
+	natts = parse->attnum_list->length;
+	if (natts < 1)
+	{
+		ereturn(parse->escontext, (Datum) 0,
+				(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+				 errmsg("malformed pg_dependencies: \"%s\"", parse->str),
+				 errdetail("The %s key must contain an array of at least one attnum", attributes)));
+
+		return JSON_SEM_ACTION_FAILED;
+	}
+	attrsort = palloc0(natts * sizeof(AttrNumber));
+
+	/*
+	 * Allocate enough space for the dependency, the attnums in the list, plus
+	 * the final attnum
+	 */
+	dep = palloc0(offsetof(MVDependency, attributes) + ((natts + 1) * sizeof(AttrNumber)));
+	dep->nattributes = natts + 1;
+
+	dep->attributes[natts] = parse->dependency;
+	dep->degree = parse->degree;
+
+	attrsort = palloc0(dep->nattributes * sizeof(AttrNumber));
+	attrsort[natts] = parse->dependency;
+
+	for (int i = 0; i < natts; i++)
+	{
+		attrsort[i] = (AttrNumber) parse->attnum_list->elements[i].int_value;
+		dep->attributes[i] = attrsort[i];
+	}
+
+	/* Check attrsort for uniqueness */
+	qsort(attrsort, natts + 1, sizeof(AttrNumber), attnum_compare);
+	for (int i = 1; i < dep->nattributes; i++)
+		if (attrsort[i] == attrsort[i - 1])
+		{
+			ereturn(parse->escontext, (Datum) 0,
+					(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+					 errmsg("malformed pg_dependencies: \"%s\"", parse->str),
+					 errdetail("attnum list duplicate value found: %d", attrsort[i])));
+
+			return JSON_SEM_ACTION_FAILED;
+		}
+	pfree(attrsort);
+
+	parse->dependency_list = lappend(parse->dependency_list, (void *) dep);
+
+	/* reset dep item state vars */
+	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;
+
+	/* Now we are looking for the next MVDependency */
+	parse->state = DEPS_EXPECT_ITEM;
+	return JSON_SUCCESS;
+}
+
+/*
+ * dependencies 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:
+			ereturn(parse->escontext, (Datum) 0,
+					(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 attnum list or the whole object
+ */
+static JsonParseErrorType
+dependencies_array_end(void *state)
+{
+	dependenciesParseState *parse = state;
+
+	switch (parse->state)
+	{
+		case DEPS_EXPECT_ATTNUM:
+			parse->state = DEPS_EXPECT_KEY;
+			break;
+
+		case DEPS_EXPECT_ITEM:
+			parse->state = DEPS_PARSE_COMPLETE;
+			break;
+
+		default:
+			ereturn(parse->escontext, (Datum) 0,
+					(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;
+}
+
+/*
+ *
+ */
+static JsonParseErrorType
+dependencies_object_field_start(void *state, char *fname, bool isnull)
+{
+	dependenciesParseState *parse = state;
+
+	if (strcmp(fname, attributes) == 0)
+	{
+		parse->found_attributes = true;
+		parse->state = DEPS_EXPECT_ATTNUM_LIST;
+		return JSON_SUCCESS;
+	}
+
+	if (strcmp(fname, dependency) == 0)
+	{
+		parse->found_dependency = true;
+		parse->state = DEPS_EXPECT_DEPENDENCY;
+		return JSON_SUCCESS;
+	}
+
+	if (strcmp(fname, degree) == 0)
+	{
+		parse->found_degree = true;
+		parse->state = DEPS_EXPECT_DEGREE;
+		return JSON_SUCCESS;
+	}
+
+	ereturn(parse->escontext, (Datum) 0,
+			(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+			 errmsg("malformed pg_dependencies: \"%s\"", parse->str),
+			 errdetail("Only allowed keys are \%s\", \"%s\" and \%s\".",
+					   attributes, dependency, degree)));
+	return JSON_SEM_ACTION_FAILED;
+}
+
+/*
+ * ndsitinct 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;
+
+	if (parse->state == DEPS_EXPECT_ATTNUM)
+	{
+		if (isnull)
+		{
+			ereturn(parse->escontext, (Datum) 0,
+					(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+					 errmsg("malformed pg_dependencies: \"%s\"", parse->str),
+					 errdetail("Attnum list elements cannot be null.")));
+
+			return JSON_SEM_ACTION_FAILED;
+		}
+		return JSON_SUCCESS;
+	}
+
+	if (parse->state == DEPS_EXPECT_ITEM)
+	{
+		if (isnull)
+		{
+			ereturn(parse->escontext, (Datum) 0,
+					(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+					 errmsg("malformed pg_dependencies: \"%s\"", parse->str),
+					 errdetail("Item list elements cannot be null.")));
+
+			return JSON_SEM_ACTION_FAILED;
+		}
+
+		return JSON_SUCCESS;
+	}
+
+	ereturn(parse->escontext, (Datum) 0,
+			(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+			 errmsg("malformed pg_dependencies: \"%s\"", parse->str),
+			 errdetail("Unexpected array element.")));
+
+	return JSON_SEM_ACTION_FAILED;
+}
+
+/*
+ * 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;
+
+	if (parse->state == DEPS_EXPECT_ATTNUM)
+	{
+		AttrNumber	attnum = pg_strtoint16_safe(token, parse->escontext);
+
+		if (SOFT_ERROR_OCCURRED(parse->escontext))
+			return JSON_SEM_ACTION_FAILED;
+
+		parse->attnum_list = lappend_int(parse->attnum_list, (int) attnum);
+		return JSON_SUCCESS;
+	}
+
+	if (parse->state == DEPS_EXPECT_DEPENDENCY)
+	{
+		parse->dependency = (AttrNumber) pg_strtoint16_safe(token, parse->escontext);
+
+		if (SOFT_ERROR_OCCURRED(parse->escontext))
+			return JSON_SEM_ACTION_FAILED;
+
+		return JSON_SUCCESS;
+	}
+
+
+	if (parse->state == DEPS_EXPECT_DEGREE)
+	{
+		parse->degree = float8in_internal(token, NULL, "double",
+										  token, parse->escontext);
+
+		if (SOFT_ERROR_OCCURRED(parse->escontext))
+			return JSON_SEM_ACTION_FAILED;
+
+		return JSON_SUCCESS;
+	}
+
+	ereturn(parse->escontext, (Datum) 0,
+			(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+			 errmsg("malformed pg_dependencies: \"%s\"", parse->str),
+			 errdetail("Unexpected scalar.")));
+	return JSON_SEM_ACTION_FAILED;
+}
+
+/*
+ * pg_dependencies_in		- input routine for type pg_dependencies.
+ *
+ * This format is valid JSON, with the expected format:
+ *    [{"attributes": [1,2], "dependency": -1, "degree": 1.0000},
+ *     {"attributes": [1,-1], "dependency": 2, "degree": 0.0000},
+ *     {"attributes": [2,-1], "dependency": 1, "degree": 1.0000}]
+ *
+ */
+Datum
+pg_dependencies_in(PG_FUNCTION_ARGS)
+{
+	char	   *str = PG_GETARG_CSTRING(0);
+
+	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)
+	{
+		List	   *list = parse_state.dependency_list;
+		int			ndeps = list->length;
+		MVDependencies *mvdeps;
+		bytea	   *bytes;
+
+		mvdeps = palloc0(offsetof(MVDependencies, deps) + ndeps * sizeof(MVDependency));
+		mvdeps->magic = STATS_DEPS_MAGIC;
+		mvdeps->type = STATS_DEPS_TYPE_BASIC;
+		mvdeps->ndeps = ndeps;
+
+		/* copy MVDependency structs out of the list into the MVDependencies */
+		for (int i = 0; i < ndeps; i++)
+			mvdeps->deps[i] = list->elements[i].ptr_value;
+		bytes = statext_dependencies_serialize(mvdeps);
+
+		list_free(list);
+		for (int i = 0; i < ndeps; i++)
+			pfree(mvdeps->deps[i]);
+		pfree(mvdeps);
+
+		PG_RETURN_BYTEA_P(bytes);
+	}
+	else if (result == JSON_SEM_ACTION_FAILED)
+		PG_RETURN_NULL();
+
+	/* Anything else is a generic JSON parse error */
+	ereturn(parse_state.escontext, (Datum) 0,
+			(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+			 errmsg("malformed pg_dependencies: \"%s\"", str),
+			 errdetail("Must be valid JSON.")));
+
+	PG_RETURN_NULL();			/* keep compiler quiet */
+}
+
+/*
+ * Free allocations of an MVNDistinct
+ */
+void
+free_pg_dependencies(MVDependencies *dependencies)
+{
+	for (int i = 0; i < dependencies->ndeps; i++)
+		pfree(dependencies->deps[i]);
+
+	pfree(dependencies);
+}
+
+/*
+ * pg_dependencies		- output routine for type pg_dependencies.
+ */
+Datum
+pg_dependencies_out(PG_FUNCTION_ARGS)
+{
+	bytea	   *data = PG_GETARG_BYTEA_PP(0);
+	MVDependencies *dependencies = statext_dependencies_deserialize(data);
+	StringInfoData str;
+
+	initStringInfo(&str);
+	appendStringInfoChar(&str, '[');
+
+	for (int i = 0; i < dependencies->ndeps; i++)
+	{
+		MVDependency *dep = dependencies->deps[i];
+
+		if (i > 0)
+			appendStringInfoString(&str, ", ");
+
+		if (dep->nattributes <= 1)
+			elog(ERROR, "invalid zero-length nattributes array in MVDependencies");
+
+		appendStringInfo(&str, "{\"%s\": [%d",
+				   		 attributes,
+						 dep->attributes[0]);
+
+		for (int j = 1; j < dep->nattributes - 1; j++)
+			appendStringInfo(&str, ", %d", dep->attributes[j]);
+
+		appendStringInfo(&str, "], \"%s\": %d, \"%s\": %f}",
+						 dependency, dep->attributes[dep->nattributes - 1],
+						 degree, dep->degree);
+	}
+
+	appendStringInfoChar(&str, ']');
+
+	PG_RETURN_CSTRING(str.data);
+}
+
+/*
+ * pg_dependencies_recv		- binary input routine for type pg_dependencies.
+ */
+Datum
+pg_dependencies_recv(PG_FUNCTION_ARGS)
+{
+	ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			 errmsg("cannot accept a value of type %s", "pg_dependencies")));
+
+	PG_RETURN_VOID();			/* keep compiler quiet */
+}
+
+/*
+ * pg_dependencies_send		- binary output routine for type pg_dependencies.
+ *
+ * Functional dependencies are serialized in a bytea value (although the type
+ * is named differently), so let's just send that.
+ */
+Datum
+pg_dependencies_send(PG_FUNCTION_ARGS)
+{
+	return byteasend(fcinfo);
+}
-- 
2.51.1