From 5826e635f06e1e347ae5609ffdffdcf69fc13cd7 Mon Sep 17 00:00:00 2001 From: Corey Huinker 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