v5-0004-Add-working-input-function-for-pg_dependencies.patch
text/x-patch
Filename: v5-0004-Add-working-input-function-for-pg_dependencies.patch
Type: text/x-patch
Part: 3
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 v5-0004
Subject: Add working input function for pg_dependencies.
| File | + | − |
|---|---|---|
| src/backend/statistics/dependencies.c | 453 | 10 |
| src/test/regress/expected/stats_ext.out | 22 | 0 |
| src/test/regress/sql/stats_ext.sql | 12 | 0 |
From 5d39e2bae4d952205c4952e23a41220b940ccbd4 Mon Sep 17 00:00:00 2001
From: Corey Huinker <corey.huinker@gmail.com>
Date: Tue, 4 Nov 2025 16:12:44 -0500
Subject: [PATCH v5 4/7] 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/statistics/dependencies.c | 463 +++++++++++++++++++++++-
src/test/regress/expected/stats_ext.out | 22 ++
src/test/regress/sql/stats_ext.sql | 12 +
3 files changed, 487 insertions(+), 10 deletions(-)
diff --git a/src/backend/statistics/dependencies.c b/src/backend/statistics/dependencies.c
index c150ddfb594..573d0f722de 100644
--- a/src/backend/statistics/dependencies.c
+++ b/src/backend/statistics/dependencies.c
@@ -13,18 +13,26 @@
*/
#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"
@@ -643,24 +651,459 @@ 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;
+}
+
/*
* 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);
- 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)
+ {
+ 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 */
}
/*
diff --git a/src/test/regress/expected/stats_ext.out b/src/test/regress/expected/stats_ext.out
index 8cea29de314..6219b9674b0 100644
--- a/src/test/regress/expected/stats_ext.out
+++ b/src/test/regress/expected/stats_ext.out
@@ -3788,6 +3788,28 @@ ERROR: malformed pg_ndistinct: "[{"attributes" : [2,3], "ndistinct" : 4},
LINE 1: SELECT '[{"attributes" : [2,3], "ndistinct" : 4},
^
DETAIL: attnum list duplicate value found: 2
+-- Test input function of pg_dependencies.
+SELECT '[{"attributes" : [2,3], "dependency" : 4, "degree": 1.0000},
+ {"attributes" : [2,-1], "dependency" : 4, "degree": 0.0000},
+ {"attributes" : [2,3,-1], "dependency" : 4, "degree": 0.5000},
+ {"attributes" : [1,3,-1,-2], "dependency" : 4, "degree": 1.0000}]'::pg_dependencies;
+ pg_dependencies
+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ [{"attributes": [2, 3], "dependency": 4, "degree": 1.000000}, {"attributes": [2, -1], "dependency": 4, "degree": 0.000000}, {"attributes": [2, 3, -1], "dependency": 4, "degree": 0.500000}, {"attributes": [1, 3, -1, -2], "dependency": 4, "degree": 1.000000}]
+(1 row)
+
+-- error, cannot duplicate attribute
+SELECT '[{"attributes": [6], "dependency": 6, "degree": 0.292508},
+ {"attributes": [-2], "dependency": -1, "degree": 0.113999},
+ {"attributes": [6, -2], "dependency": -1, "degree": 0.348479},
+ {"attributes": [-1, -2], "dependency": 6, "degree": 0.839691}]'::pg_dependencies;
+ERROR: malformed pg_dependencies: "[{"attributes": [6], "dependency": 6, "degree": 0.292508},
+ {"attributes": [-2], "dependency": -1, "degree": 0.113999},
+ {"attributes": [6, -2], "dependency": -1, "degree": 0.348479},
+ {"attributes": [-1, -2], "dependency": 6, "degree": 0.839691}]"
+LINE 1: SELECT '[{"attributes": [6], "dependency": 6, "degree": 0.29...
+ ^
+DETAIL: attnum list duplicate value found: 6
-- Tidy up
DROP TABLE sb_1, sb_2 CASCADE;
DROP FUNCTION extstat_small(x numeric);
diff --git a/src/test/regress/sql/stats_ext.sql b/src/test/regress/sql/stats_ext.sql
index e8aa6b58009..6a5fbfd31ad 100644
--- a/src/test/regress/sql/stats_ext.sql
+++ b/src/test/regress/sql/stats_ext.sql
@@ -1844,6 +1844,18 @@ SELECT '[{"attributes" : [2,3], "ndistinct" : 4},
{"attributes" : [2,3,2], "ndistinct" : 4},
{"attributes" : [1,3,-1,-2], "ndistinct" : 4}]'::pg_ndistinct;
+-- Test input function of pg_dependencies.
+SELECT '[{"attributes" : [2,3], "dependency" : 4, "degree": 1.0000},
+ {"attributes" : [2,-1], "dependency" : 4, "degree": 0.0000},
+ {"attributes" : [2,3,-1], "dependency" : 4, "degree": 0.5000},
+ {"attributes" : [1,3,-1,-2], "dependency" : 4, "degree": 1.0000}]'::pg_dependencies;
+
+-- error, cannot duplicate attribute
+SELECT '[{"attributes": [6], "dependency": 6, "degree": 0.292508},
+ {"attributes": [-2], "dependency": -1, "degree": 0.113999},
+ {"attributes": [6, -2], "dependency": -1, "degree": 0.348479},
+ {"attributes": [-1, -2], "dependency": 6, "degree": 0.839691}]'::pg_dependencies;
+
-- Tidy up
DROP TABLE sb_1, sb_2 CASCADE;
DROP FUNCTION extstat_small(x numeric);
--
2.51.1