From 20210adfe9b9e78a5e44c120bc317737fb7fa783 Mon Sep 17 00:00:00 2001 From: Michael Paquier Date: Tue, 11 Nov 2025 16:47:00 +0900 Subject: [PATCH v11 5/9] Add working input function for pg_ndistinct. This will consume the format that was established when the output function for pg_ndistinct was recently changed. This will be needed for importing extended statistics. --- src/backend/utils/adt/pg_ndistinct.c | 595 ++++++++++++++++++++- src/test/regress/expected/pg_ndistinct.out | 109 ++++ src/test/regress/parallel_schedule | 2 +- src/test/regress/sql/pg_ndistinct.sql | 34 ++ 4 files changed, 732 insertions(+), 8 deletions(-) create mode 100644 src/test/regress/expected/pg_ndistinct.out create mode 100644 src/test/regress/sql/pg_ndistinct.sql diff --git a/src/backend/utils/adt/pg_ndistinct.c b/src/backend/utils/adt/pg_ndistinct.c index 56b17758ea5..0bed344f41d 100644 --- a/src/backend/utils/adt/pg_ndistinct.c +++ b/src/backend/utils/adt/pg_ndistinct.c @@ -14,34 +14,615 @@ #include "postgres.h" +#include "common/int.h" +#include "common/jsonapi.h" #include "lib/stringinfo.h" +#include "mb/pg_wchar.h" +#include "nodes/miscnodes.h" #include "statistics/extended_stats_internal.h" #include "statistics/statistics_format.h" +#include "utils/builtins.h" #include "utils/fmgrprotos.h" +typedef enum +{ + NDIST_EXPECT_START = 0, + NDIST_EXPECT_ITEM, + NDIST_EXPECT_KEY, + NDIST_EXPECT_ATTNUM_LIST, + NDIST_EXPECT_ATTNUM, + NDIST_EXPECT_NDISTINCT, + NDIST_EXPECT_COMPLETE +} NDistinctSemanticState; + +typedef struct +{ + const char *str; + NDistinctSemanticState state; + + List *distinct_items; /* Accumulated complete MVNDistinctItems */ + Node *escontext; + + bool found_attributes; /* Item has an attributes key */ + bool found_ndistinct; /* Item has ndistinct key */ + List *attnum_list; /* Accumulated attributes attnums */ + int64 ndistinct; +} NDistinctParseState; + +/* + * Invoked at the start of each MVNDistinctItem. + * + * The entire JSON document shoul be one array of MVNDistinctItem objects. + * + * If we're anywhere else in the document, it's an error. + */ +static JsonParseErrorType +ndistinct_object_start(void *state) +{ + NDistinctParseState *parse = state; + + switch(parse->state) + { + case NDIST_EXPECT_ITEM: + /* Now we expect to see attributes/ndistinct keys */ + parse->state = NDIST_EXPECT_KEY; + return JSON_SUCCESS; + break; + + default: + ereturn(parse->escontext, (Datum) 0, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("malformed pg_ndistinct: \"%s\"", parse->str), + errdetail("Expected Item object."))); + } + + return JSON_SEM_ACTION_FAILED; +} + +/* + * Routine to allow qsorting of AttNumbers + */ +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); +} + + +/* + * Invoked at the end of an object. + * + * Check to ensure that it was a complete MVNDistinctItem + * + */ +static JsonParseErrorType +ndistinct_object_end(void *state) +{ + NDistinctParseState *parse = state; + + int natts = 0; + AttrNumber *attrsort; + + MVNDistinctItem *item; + + if (!parse->found_attributes) + { + ereturn(parse->escontext, (Datum) 0, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("malformed pg_ndistinct: \"%s\"", parse->str), + errdetail("Item must contain \"" PG_NDISTINCT_KEY_ATTRIBUTES "\" key."))); + return JSON_SEM_ACTION_FAILED; + } + + if (!parse->found_ndistinct) + { + ereturn(parse->escontext, (Datum) 0, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("malformed pg_ndistinct: \"%s\"", parse->str), + errdetail("Item must contain \"" PG_NDISTINCT_KEY_NDISTINCT "\" key."))); + return JSON_SEM_ACTION_FAILED; + } + + /* + * We need at least 2 attnums for a ndistinct item, anything less is + * malformed. + */ + natts = parse->attnum_list->length; + if (natts < 2) + { + ereturn(parse->escontext, (Datum) 0, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("malformed pg_ndistinct: \"%s\"", parse->str), + errdetail("The \"" PG_NDISTINCT_KEY_ATTRIBUTES + "\" key must contain an array of at least two attnums."))); + + return JSON_SEM_ACTION_FAILED; + } + attrsort = palloc0(natts * sizeof(AttrNumber)); + + /* Create the MVNDistinctItem */ + item = palloc(sizeof(MVNDistinctItem)); + item->nattributes = natts; + item->attributes = palloc0(natts * sizeof(AttrNumber)); + item->ndistinct = (double) parse->ndistinct; + + /* fill out both attnum list and sortable list */ + for (int i = 0; i < natts; i++) + { + attrsort[i] = (AttrNumber) parse->attnum_list->elements[i].int_value; + item->attributes[i] = attrsort[i]; + } + + /* Check attrsort for uniqueness */ + qsort(attrsort, natts, sizeof(AttrNumber), attnum_compare); + for (int i = 1; i < natts; i++) + { + if (attrsort[i] == attrsort[i - 1]) + { + ereturn(parse->escontext, (Datum) 0, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("malformed pg_ndistinct: \"%s\"", parse->str), + errdetail("attnum list duplicate value found: %d.", attrsort[i]))); + + return JSON_SEM_ACTION_FAILED; + } + } + pfree(attrsort); + + parse->distinct_items = lappend(parse->distinct_items, (void *) item); + + /* reset item state vars */ + list_free(parse->attnum_list); + parse->attnum_list = NIL; + parse->ndistinct = 0; + parse->found_attributes = false; + parse->found_ndistinct = false; + + /* Now we are looking for the next MVNDistinctItem */ + parse->state = NDIST_EXPECT_ITEM; + return JSON_SUCCESS; +} + + +/* + * ndsitinct input format has two types of arrays, the outer MVNDistinctItem + * array, and the attnum list array within each MVNDistinctItem. + */ +static JsonParseErrorType +ndistinct_array_start(void *state) +{ + NDistinctParseState *parse = state; + + switch (parse->state) + { + case NDIST_EXPECT_ATTNUM_LIST: + parse->state = NDIST_EXPECT_ATTNUM; + break; + + case NDIST_EXPECT_START: + parse->state = NDIST_EXPECT_ITEM; + break; + + default: + ereturn(parse->escontext, (Datum) 0, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("malformed pg_ndistinct: \"%s\"", parse->str), + errdetail("Array found in unexpected place."))); + return JSON_SEM_ACTION_FAILED; + } + + return JSON_SUCCESS; +} + + +static JsonParseErrorType +ndistinct_array_end(void *state) +{ + NDistinctParseState *parse = state; + + switch (parse->state) + { + case NDIST_EXPECT_ATTNUM: + if (parse->attnum_list != NIL) + { + /* The attnum list is complete, look for more MVNDistinctItem keys */ + parse->state = NDIST_EXPECT_KEY; + return JSON_SUCCESS; + } + + ereturn(parse->escontext, (Datum) 0, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("malformed pg_ndistinct: \"%s\"", parse->str), + errdetail("The \"" PG_NDISTINCT_KEY_ATTRIBUTES + "\" key must be an non-empty array."))); + return JSON_SEM_ACTION_FAILED; + break; + + case NDIST_EXPECT_ITEM: + if (parse->distinct_items != NIL) + { + /* Item list is complete, we're done. */ + parse->state = NDIST_EXPECT_COMPLETE; + return JSON_SUCCESS; + } + + ereturn(parse->escontext, (Datum) 0, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("malformed pg_ndistinct: \"%s\"", parse->str), + errdetail("Item array cannot be empty."))); + + return JSON_SEM_ACTION_FAILED; + break; + default: + ereturn(parse->escontext, (Datum) 0, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("malformed pg_ndistinct: \"%s\"", parse->str), + errdetail("Array found in unexpected place."))); + } + return JSON_SEM_ACTION_FAILED; +} + + +/* + * The valid keys for the MVNDistinctItem object are: + * - attributes + * - ndistinct + */ +static JsonParseErrorType +ndistinct_object_field_start(void *state, char *fname, bool isnull) +{ + NDistinctParseState *parse = state; + + if (strcmp(fname, PG_NDISTINCT_KEY_ATTRIBUTES) == 0) + { + parse->found_attributes = true; + parse->state = NDIST_EXPECT_ATTNUM_LIST; + return JSON_SUCCESS; + } + + if (strcmp(fname, PG_NDISTINCT_KEY_NDISTINCT) == 0) + { + parse->found_ndistinct = true; + parse->state = NDIST_EXPECT_NDISTINCT; + return JSON_SUCCESS; + } + + ereturn(parse->escontext, (Datum) 0, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("malformed pg_ndistinct: \"%s\"", parse->str), + errdetail("Invalid key \"%s\". Only allowed keys are \"" + PG_NDISTINCT_KEY_ATTRIBUTES "\" and \"" + PG_NDISTINCT_KEY_NDISTINCT "\".", fname))); + return JSON_SEM_ACTION_FAILED; +} + +/* + * + */ +static JsonParseErrorType +ndistinct_array_element_start(void *state, bool isnull) +{ + NDistinctParseState *parse = state; + + switch(parse->state) + { + case NDIST_EXPECT_ATTNUM: + if (!isnull) + return JSON_SUCCESS; + + ereturn(parse->escontext, (Datum) 0, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("malformed pg_ndistinct: \"%s\"", parse->str), + errdetail("Attnum list elements cannot be null."))); + + break; + + case NDIST_EXPECT_ITEM: + if (!isnull) + return JSON_SUCCESS; + + ereturn(parse->escontext, (Datum) 0, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("malformed pg_ndistinct: \"%s\"", parse->str), + errdetail("Item list elements cannot be null."))); + + break; + + default: + ereturn(parse->escontext, (Datum) 0, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("malformed pg_ndistinct: \"%s\"", parse->str), + errdetail("Unexpected array element."))); + } + + return JSON_SEM_ACTION_FAILED; +} + +/* + * Handle scalar events from the ndistinct input parser. + * + */ +static JsonParseErrorType +ndistinct_scalar(void *state, char *token, JsonTokenType tokentype) +{ + NDistinctParseState *parse = state; + AttrNumber attnum; + + switch(parse->state) + { + case NDIST_EXPECT_ATTNUM: + 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; + break; + + case NDIST_EXPECT_NDISTINCT: + /* + * While the structure dictates that ndistinct in a double precision + * floating point, in practice it has always been an integer, and it + * is output as such. Therefore, we follow usage precendent over the + * actual storage structure, and read it in as an integer. + */ + parse->ndistinct = pg_strtoint64_safe(token, parse->escontext); + + if (SOFT_ERROR_OCCURRED(parse->escontext)) + return JSON_SEM_ACTION_FAILED; + + parse->state = NDIST_EXPECT_KEY; + return JSON_SUCCESS; + break; + + default: + ereturn(parse->escontext, (Datum) 0, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("malformed pg_ndistinct: \"%s\"", parse->str), + errdetail("Unexpected scalar."))); + } + + return JSON_SEM_ACTION_FAILED; +} + +/* + * Compare the attribute arrays of two MVNDistinctItem values, + * looking for duplicate sets. + */ +static +bool has_duplicate_attributes(const MVNDistinctItem *a, + const MVNDistinctItem *b) +{ + int i; + + if (a->nattributes != b->nattributes) + return false; + + for (i = 0; i < a->nattributes; i++) + { + if (a->attributes[i] != b->attributes[i]) + return false; + } + + return true; +} + +/* + * Ensure that an attnum appears as one of the attnums in a given + * MVNDistinctItem. + */ +static +bool item_has_attnum(const MVNDistinctItem *item, AttrNumber attnum) +{ + for (int i = 0; i < item->nattributes; i++) + { + if (attnum == item->attributes[i]) + return true; + } + return false; +} + +/* + * Ensure that the attributes of one MVNDistinctItem A are a proper subset + * of the reference MVNDistinctItem B. + */ +static +bool item_is_attnum_subset(const MVNDistinctItem *item, + const MVNDistinctItem *refitem) +{ + for (int i = 0; i < item->nattributes; i++) + { + if (!item_has_attnum(refitem,item->attributes[i])) + return false; + } + return true; +} + +/* + * Generate a string representing an array of attnum. + * + * Freeing the allocated string is responsibility of the caller. + */ +static +const char *item_attnum_list(const MVNDistinctItem *item) +{ + StringInfoData str; + + initStringInfo(&str); + + appendStringInfo(&str, "%d", item->attributes[0]); + + for (int i = 1; i < item->nattributes; i++) + appendStringInfo(&str, ", %d", item->attributes[i]); + + return str.data; +} /* * pg_ndistinct_in * input routine for type pg_ndistinct * - * pg_ndistinct is real enough to be a table column, but it has no - * operations of its own, and disallows input (just like pg_node_tree). + * example input: + * [{"attributes": [6, -1], "ndistinct": 14}, + * {"attributes": [6, -2], "ndistinct": 9143}, + * {"attributes": [-1,-2], "ndistinct": 13454}, + * {"attributes": [6, -1, -2], "ndistinct": 14549}] */ Datum pg_ndistinct_in(PG_FUNCTION_ARGS) { - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("cannot accept a value of type %s", "pg_ndistinct"))); + char *str = PG_GETARG_CSTRING(0); - PG_RETURN_VOID(); /* keep compiler quiet */ + NDistinctParseState parse_state; + JsonParseErrorType result; + JsonLexContext *lex; + JsonSemAction sem_action; + + int item_most_attrs = 0; + int item_most_attrs_idx = 0; + + /* initialize semantic state */ + parse_state.str = str; + parse_state.state = NDIST_EXPECT_START; + parse_state.distinct_items = NIL; + parse_state.escontext = fcinfo->context; + parse_state.found_attributes = false; + parse_state.found_ndistinct = false; + parse_state.attnum_list = NIL; + parse_state.ndistinct = 0; + + /* set callbacks */ + sem_action.semstate = (void *) &parse_state; + sem_action.object_start = ndistinct_object_start; + sem_action.object_end = ndistinct_object_end; + sem_action.array_start = ndistinct_array_start; + sem_action.array_end = ndistinct_array_end; + sem_action.object_field_start = ndistinct_object_field_start; + sem_action.object_field_end = NULL; + sem_action.array_element_start = ndistinct_array_element_start; + sem_action.array_element_end = NULL; + sem_action.scalar = ndistinct_scalar; + + lex = makeJsonLexContextCstringLen(NULL, str, strlen(str), + PG_UTF8, true); + result = pg_parse_json(lex, &sem_action); + freeJsonLexContext(lex); + + if (result == JSON_SUCCESS && + parse_state.distinct_items != NIL) + { + MVNDistinct *ndistinct; + int nitems = parse_state.distinct_items->length; + bytea *bytes; + + + ndistinct = palloc(offsetof(MVNDistinct, items) + + nitems * sizeof(MVNDistinctItem)); + + ndistinct->magic = STATS_NDISTINCT_MAGIC; + ndistinct->type = STATS_NDISTINCT_TYPE_BASIC; + ndistinct->nitems = nitems; + + for (int i = 0; i < nitems; i++) + { + MVNDistinctItem *item = parse_state.distinct_items->elements[i].ptr_value; + + /* + * Ensure that this item does not duplicate the attributes of any + * pre-existing item. + */ + for (int j = 0; j < i; j++) + { + if (has_duplicate_attributes(item, &ndistinct->items[j])) + { + ereturn(parse_state.escontext, (Datum) 0, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("malformed pg_ndistinct: \"%s\"", str), + errdetail("Duplicate \"" PG_NDISTINCT_KEY_ATTRIBUTES "\" array : [%s]", + item_attnum_list(item)))); + PG_RETURN_NULL(); + } + } + + ndistinct->items[i].ndistinct = item->ndistinct; + ndistinct->items[i].nattributes = item->nattributes; + ndistinct->items[i].attributes = item->attributes; + + /* + * Keep track of the first longest attribute list. All other attribute + * lists must be a subset of this list. + */ + if (item->nattributes > item_most_attrs) + { + item_most_attrs = item->nattributes; + item_most_attrs_idx = i; + } + + /* + * Free the MVNDistinctItem, but not the attributes we're still + * using. + */ + pfree(item); + } + + /* + * Verify that all attnum sets are a proper subset of the first longest + * attnum set. + */ + for (int i = 0; i < nitems; i++) + { + if (i == item_most_attrs_idx) + continue; + + if (!item_is_attnum_subset(&ndistinct->items[i], + &ndistinct->items[item_most_attrs_idx])) + { + const MVNDistinctItem *item = &ndistinct->items[i]; + const MVNDistinctItem *refitem = &ndistinct->items[item_most_attrs_idx]; + const char *item_list = item_attnum_list(item); + const char *refitem_list = item_attnum_list(refitem); + + ereturn(parse_state.escontext, (Datum) 0, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("malformed pg_ndistinct: \"%s\"", str), + errdetail("\"" PG_NDISTINCT_KEY_ATTRIBUTES "\" array: [%s]" + "must be a subset of array: [%s]", + item_list, refitem_list))); + PG_RETURN_NULL(); + } + } + + bytes = statext_ndistinct_serialize(ndistinct); + + list_free(parse_state.distinct_items); + for (int i = 0; i < nitems; i++) + pfree(ndistinct->items[i].attributes); + pfree(ndistinct); + + PG_RETURN_BYTEA_P(bytes); + } + else if (result == JSON_SEM_ACTION_FAILED) + PG_RETURN_NULL(); /* escontext already set */ + + /* Anything else is a generic JSON parse error */ + ereturn(parse_state.escontext, (Datum) 0, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("malformed pg_ndistinct: \"%s\"", str), + errdetail("Must be valid JSON."))); + PG_RETURN_NULL(); } /* * pg_ndistinct * output routine for type pg_ndistinct * - * Produces a human-readable representation of the value. + * Produces a human-readable representation of the value, in the format: + * [{"attributes": [attnum,. ..], "ndistinct": int}, ...] + * */ Datum pg_ndistinct_out(PG_FUNCTION_ARGS) diff --git a/src/test/regress/expected/pg_ndistinct.out b/src/test/regress/expected/pg_ndistinct.out new file mode 100644 index 00000000000..d99e84a2bce --- /dev/null +++ b/src/test/regress/expected/pg_ndistinct.out @@ -0,0 +1,109 @@ +-- Tests for type pg_distinct +-- Invalid inputs +SELECT '[]'::pg_ndistinct; +ERROR: malformed pg_ndistinct: "[]" +LINE 1: SELECT '[]'::pg_ndistinct; + ^ +DETAIL: Item array cannot be empty. +SELECT '[null]'::pg_ndistinct; +ERROR: malformed pg_ndistinct: "[null]" +LINE 1: SELECT '[null]'::pg_ndistinct; + ^ +DETAIL: Item list elements cannot be null. +-- Invalid keys +SELECT '[{"attributes_invalid" : [2,3], "ndistinct" : 4}]'::pg_ndistinct; +ERROR: malformed pg_ndistinct: "[{"attributes_invalid" : [2,3], "ndistinct" : 4}]" +LINE 1: SELECT '[{"attributes_invalid" : [2,3], "ndistinct" : 4}]'::... + ^ +DETAIL: Invalid key "attributes_invalid". Only allowed keys are "attributes" and "ndistinct". +SELECT '[{"attributes" : [2,3], "invalid" : 3, "ndistinct" : 4}]'::pg_ndistinct; +ERROR: malformed pg_ndistinct: "[{"attributes" : [2,3], "invalid" : 3, "ndistinct" : 4}]" +LINE 1: SELECT '[{"attributes" : [2,3], "invalid" : 3, "ndistinct" :... + ^ +DETAIL: Invalid key "invalid". Only allowed keys are "attributes" and "ndistinct". +-- Missing key +SELECT '[{"attributes" : [2,3]}]'::pg_ndistinct; +ERROR: malformed pg_ndistinct: "[{"attributes" : [2,3]}]" +LINE 1: SELECT '[{"attributes" : [2,3]}]'::pg_ndistinct; + ^ +DETAIL: Item must contain "ndistinct" key. +SELECT '[{"ndistinct" : 4}]'::pg_ndistinct; +ERROR: malformed pg_ndistinct: "[{"ndistinct" : 4}]" +LINE 1: SELECT '[{"ndistinct" : 4}]'::pg_ndistinct; + ^ +DETAIL: Item must contain "attributes" key. +-- Valid keys, invalid values +SELECT '[{"attributes" : null, "ndistinct" : 4}]'::pg_ndistinct; +ERROR: malformed pg_ndistinct: "[{"attributes" : null, "ndistinct" : 4}]" +LINE 1: SELECT '[{"attributes" : null, "ndistinct" : 4}]'::pg_ndisti... + ^ +DETAIL: Unexpected scalar. +SELECT '[{"attributes" : [2,null], "ndistinct" : 4}]'::pg_ndistinct; +ERROR: malformed pg_ndistinct: "[{"attributes" : [2,null], "ndistinct" : 4}]" +LINE 1: SELECT '[{"attributes" : [2,null], "ndistinct" : 4}]'::pg_nd... + ^ +DETAIL: Attnum list elements cannot be null. +SELECT '[{"attributes" : [2,3], "ndistinct" : null}]'::pg_ndistinct; +ERROR: invalid input syntax for type bigint: "null" +LINE 1: SELECT '[{"attributes" : [2,3], "ndistinct" : null}]'::pg_nd... + ^ +SELECT '[{"attributes" : [2,"a"], "ndistinct" : 4}]'::pg_ndistinct; +ERROR: invalid input syntax for type smallint: "a" +LINE 1: SELECT '[{"attributes" : [2,"a"], "ndistinct" : 4}]'::pg_ndi... + ^ +SELECT '[{"attributes" : [2,3], "ndistinct" : "a"}]'::pg_ndistinct; +ERROR: invalid input syntax for type bigint: "a" +LINE 1: SELECT '[{"attributes" : [2,3], "ndistinct" : "a"}]'::pg_ndi... + ^ +SELECT '[{"attributes" : [2,3], "ndistinct" : []}]'::pg_ndistinct; +ERROR: malformed pg_ndistinct: "[{"attributes" : [2,3], "ndistinct" : []}]" +LINE 1: SELECT '[{"attributes" : [2,3], "ndistinct" : []}]'::pg_ndis... + ^ +DETAIL: Array found in unexpected place. +SELECT '[{"attributes" : [2,3], "ndistinct" : [null]}]'::pg_ndistinct; +ERROR: malformed pg_ndistinct: "[{"attributes" : [2,3], "ndistinct" : [null]}]" +LINE 1: SELECT '[{"attributes" : [2,3], "ndistinct" : [null]}]'::pg_... + ^ +DETAIL: Array found in unexpected place. +SELECT '[{"attributes" : [2,3], "ndistinct" : [1,null]}]'::pg_ndistinct; +ERROR: malformed pg_ndistinct: "[{"attributes" : [2,3], "ndistinct" : [1,null]}]" +LINE 1: SELECT '[{"attributes" : [2,3], "ndistinct" : [1,null]}]'::p... + ^ +DETAIL: Array found in unexpected place. +SELECT '[{"attributes" : 1, "ndistinct" : 4}]'::pg_ndistinct; +ERROR: malformed pg_ndistinct: "[{"attributes" : 1, "ndistinct" : 4}]" +LINE 1: SELECT '[{"attributes" : 1, "ndistinct" : 4}]'::pg_ndistinct... + ^ +DETAIL: Unexpected scalar. +SELECT '[{"attributes" : "a", "ndistinct" : 4}]'::pg_ndistinct; +ERROR: malformed pg_ndistinct: "[{"attributes" : "a", "ndistinct" : 4}]" +LINE 1: SELECT '[{"attributes" : "a", "ndistinct" : 4}]'::pg_ndistin... + ^ +DETAIL: Unexpected scalar. +-- Duplicated attributes +SELECT '[{"attributes" : [2,2], "ndistinct" : 4}]'::pg_ndistinct; +ERROR: malformed pg_ndistinct: "[{"attributes" : [2,2], "ndistinct" : 4}]" +LINE 1: SELECT '[{"attributes" : [2,2], "ndistinct" : 4}]'::pg_ndist... + ^ +DETAIL: attnum list duplicate value found: 2. +-- Valid inputs +-- Duplicated attribute lists. +SELECT '[{"attributes" : [2,3], "ndistinct" : 4}, + {"attributes" : [2,3], "ndistinct" : 4}]'::pg_ndistinct; +ERROR: malformed pg_ndistinct: "[{"attributes" : [2,3], "ndistinct" : 4}, + {"attributes" : [2,3], "ndistinct" : 4}]" +LINE 1: SELECT '[{"attributes" : [2,3], "ndistinct" : 4}, + ^ +DETAIL: Duplicate "attributes" array : [2, 3] +-- Partially-covered attribute lists. +SELECT '[{"attributes" : [2,3], "ndistinct" : 4}, + {"attributes" : [2,-1], "ndistinct" : 4}, + {"attributes" : [2,3,-1], "ndistinct" : 4}, + {"attributes" : [1,3,-1,-2], "ndistinct" : 4}]'::pg_ndistinct; +ERROR: malformed pg_ndistinct: "[{"attributes" : [2,3], "ndistinct" : 4}, + {"attributes" : [2,-1], "ndistinct" : 4}, + {"attributes" : [2,3,-1], "ndistinct" : 4}, + {"attributes" : [1,3,-1,-2], "ndistinct" : 4}]" +LINE 1: SELECT '[{"attributes" : [2,3], "ndistinct" : 4}, + ^ +DETAIL: "attributes" array: [2, 3]must be a subset of array: [1, 3, -1, -2] diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule index f56482fb9f1..f3f0b5f2f31 100644 --- a/src/test/regress/parallel_schedule +++ b/src/test/regress/parallel_schedule @@ -28,7 +28,7 @@ test: strings md5 numerology point lseg line box path polygon circle date time t # geometry depends on point, lseg, line, box, path, polygon, circle # horology depends on date, time, timetz, timestamp, timestamptz, interval # ---------- -test: geometry horology tstypes regex type_sanity opr_sanity misc_sanity comments expressions unicode xid mvcc database stats_import +test: geometry horology tstypes regex type_sanity opr_sanity misc_sanity comments expressions unicode xid mvcc database stats_import pg_ndistinct # ---------- # Load huge amounts of data diff --git a/src/test/regress/sql/pg_ndistinct.sql b/src/test/regress/sql/pg_ndistinct.sql new file mode 100644 index 00000000000..ca89fed6fe2 --- /dev/null +++ b/src/test/regress/sql/pg_ndistinct.sql @@ -0,0 +1,34 @@ +-- Tests for type pg_distinct + +-- Invalid inputs +SELECT '[]'::pg_ndistinct; +SELECT '[null]'::pg_ndistinct; +-- Invalid keys +SELECT '[{"attributes_invalid" : [2,3], "ndistinct" : 4}]'::pg_ndistinct; +SELECT '[{"attributes" : [2,3], "invalid" : 3, "ndistinct" : 4}]'::pg_ndistinct; +-- Missing key +SELECT '[{"attributes" : [2,3]}]'::pg_ndistinct; +SELECT '[{"ndistinct" : 4}]'::pg_ndistinct; +-- Valid keys, invalid values +SELECT '[{"attributes" : null, "ndistinct" : 4}]'::pg_ndistinct; +SELECT '[{"attributes" : [2,null], "ndistinct" : 4}]'::pg_ndistinct; +SELECT '[{"attributes" : [2,3], "ndistinct" : null}]'::pg_ndistinct; +SELECT '[{"attributes" : [2,"a"], "ndistinct" : 4}]'::pg_ndistinct; +SELECT '[{"attributes" : [2,3], "ndistinct" : "a"}]'::pg_ndistinct; +SELECT '[{"attributes" : [2,3], "ndistinct" : []}]'::pg_ndistinct; +SELECT '[{"attributes" : [2,3], "ndistinct" : [null]}]'::pg_ndistinct; +SELECT '[{"attributes" : [2,3], "ndistinct" : [1,null]}]'::pg_ndistinct; +SELECT '[{"attributes" : 1, "ndistinct" : 4}]'::pg_ndistinct; +SELECT '[{"attributes" : "a", "ndistinct" : 4}]'::pg_ndistinct; +-- Duplicated attributes +SELECT '[{"attributes" : [2,2], "ndistinct" : 4}]'::pg_ndistinct; + +-- Valid inputs +-- Duplicated attribute lists. +SELECT '[{"attributes" : [2,3], "ndistinct" : 4}, + {"attributes" : [2,3], "ndistinct" : 4}]'::pg_ndistinct; +-- Partially-covered attribute lists. +SELECT '[{"attributes" : [2,3], "ndistinct" : 4}, + {"attributes" : [2,-1], "ndistinct" : 4}, + {"attributes" : [2,3,-1], "ndistinct" : 4}, + {"attributes" : [1,3,-1,-2], "ndistinct" : 4}]'::pg_ndistinct; -- 2.51.1