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