diff options
author | Alvaro Herrera | 2023-03-31 20:34:04 +0000 |
---|---|---|
committer | Alvaro Herrera | 2023-03-31 20:34:04 +0000 |
commit | 6ee30209a6f161d0a267a33f090c70c579c87c00 (patch) | |
tree | eda2b3a9f0a61f3fc484819b39abf1eb130e0d88 /src/backend | |
parent | a2a0c7c29e47f39da905577659e66b0086b769cc (diff) |
SQL/JSON: support the IS JSON predicate
This patch introduces the SQL standard IS JSON predicate. It operates
on text and bytea values representing JSON, as well as on the json and
jsonb types. Each test has IS and IS NOT variants and supports a WITH
UNIQUE KEYS flag. The tests are:
IS JSON [VALUE]
IS JSON ARRAY
IS JSON OBJECT
IS JSON SCALAR
These should be self-explanatory.
The WITH UNIQUE KEYS flag makes these return false when duplicate keys
exist in any object within the value, not necessarily directly contained
in the outermost object.
Author: Nikita Glukhov <n.gluhov@postgrespro.ru>
Author: Teodor Sigaev <teodor@sigaev.ru>
Author: Oleg Bartunov <obartunov@gmail.com>
Author: Alexander Korotkov <aekorotkov@gmail.com>
Author: Amit Langote <amitlangote09@gmail.com>
Author: Andrew Dunstan <andrew@dunslane.net>
Reviewers have included (in no particular order) Andres Freund, Alexander
Korotkov, Pavel Stehule, Andrew Alsup, Erik Rijkers, Zihong Yu,
Himanshu Upadhyaya, Daniel Gustafsson, Justin Pryzby.
Discussion: https://postgr.es/m/CAF4Au4w2x-5LTnN_bxky-mq4=WOqsGsxSpENCzHRAzSnEd8+WQ@mail.gmail.com
Discussion: https://postgr.es/m/cd0bb935-0158-78a7-08b5-904886deac4b@postgrespro.ru
Discussion: https://postgr.es/m/20220616233130.rparivafipt6doj3@alap3.anarazel.de
Discussion: https://postgr.es/m/abd9b83b-aa66-f230-3d6d-734817f0995d%40postgresql.org
Diffstat (limited to 'src/backend')
-rw-r--r-- | src/backend/executor/execExpr.c | 13 | ||||
-rw-r--r-- | src/backend/executor/execExprInterp.c | 99 | ||||
-rw-r--r-- | src/backend/jit/llvm/llvmjit_expr.c | 6 | ||||
-rw-r--r-- | src/backend/jit/llvm/llvmjit_types.c | 1 | ||||
-rw-r--r-- | src/backend/nodes/makefuncs.c | 19 | ||||
-rw-r--r-- | src/backend/nodes/nodeFuncs.c | 26 | ||||
-rw-r--r-- | src/backend/parser/gram.y | 68 | ||||
-rw-r--r-- | src/backend/parser/parse_expr.c | 76 | ||||
-rw-r--r-- | src/backend/utils/adt/json.c | 132 | ||||
-rw-r--r-- | src/backend/utils/adt/jsonfuncs.c | 20 | ||||
-rw-r--r-- | src/backend/utils/adt/ruleutils.c | 37 |
11 files changed, 484 insertions, 13 deletions
diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c index 6c5a3780292..4c6700de045 100644 --- a/src/backend/executor/execExpr.c +++ b/src/backend/executor/execExpr.c @@ -2370,6 +2370,19 @@ ExecInitExprRec(Expr *node, ExprState *state, } break; + case T_JsonIsPredicate: + { + JsonIsPredicate *pred = (JsonIsPredicate *) node; + + ExecInitExprRec((Expr *) pred->expr, state, resv, resnull); + + scratch.opcode = EEOP_IS_JSON; + scratch.d.is_json.pred = pred; + + ExprEvalPushStep(state, &scratch); + break; + } + case T_NullTest: { NullTest *ntest = (NullTest *) node; diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c index 5e55592f801..4cd46f17176 100644 --- a/src/backend/executor/execExprInterp.c +++ b/src/backend/executor/execExprInterp.c @@ -73,6 +73,7 @@ #include "utils/expandedrecord.h" #include "utils/json.h" #include "utils/jsonb.h" +#include "utils/jsonfuncs.h" #include "utils/lsyscache.h" #include "utils/memutils.h" #include "utils/timestamp.h" @@ -477,6 +478,7 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull) &&CASE_EEOP_HASHED_SCALARARRAYOP, &&CASE_EEOP_XMLEXPR, &&CASE_EEOP_JSON_CONSTRUCTOR, + &&CASE_EEOP_IS_JSON, &&CASE_EEOP_AGGREF, &&CASE_EEOP_GROUPING_FUNC, &&CASE_EEOP_WINDOW_FUNC, @@ -1521,6 +1523,14 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull) EEO_NEXT(); } + EEO_CASE(EEOP_IS_JSON) + { + /* too complex for an inline implementation */ + ExecEvalJsonIsPredicate(state, op); + + EEO_NEXT(); + } + EEO_CASE(EEOP_AGGREF) { /* @@ -3922,6 +3932,95 @@ ExecEvalJsonConstructor(ExprState *state, ExprEvalStep *op, } /* + * Evaluate a IS JSON predicate. + */ +void +ExecEvalJsonIsPredicate(ExprState *state, ExprEvalStep *op) +{ + JsonIsPredicate *pred = op->d.is_json.pred; + Datum js = *op->resvalue; + Oid exprtype; + bool res; + + if (*op->resnull) + { + *op->resvalue = BoolGetDatum(false); + return; + } + + exprtype = exprType(pred->expr); + + if (exprtype == TEXTOID || exprtype == JSONOID) + { + text *json = DatumGetTextP(js); + + if (pred->item_type == JS_TYPE_ANY) + res = true; + else + { + switch (json_get_first_token(json, false)) + { + case JSON_TOKEN_OBJECT_START: + res = pred->item_type == JS_TYPE_OBJECT; + break; + case JSON_TOKEN_ARRAY_START: + res = pred->item_type == JS_TYPE_ARRAY; + break; + case JSON_TOKEN_STRING: + case JSON_TOKEN_NUMBER: + case JSON_TOKEN_TRUE: + case JSON_TOKEN_FALSE: + case JSON_TOKEN_NULL: + res = pred->item_type == JS_TYPE_SCALAR; + break; + default: + res = false; + break; + } + } + + /* + * Do full parsing pass only for uniqueness check or for JSON text + * validation. + */ + if (res && (pred->unique_keys || exprtype == TEXTOID)) + res = json_validate(json, pred->unique_keys, false); + } + else if (exprtype == JSONBOID) + { + if (pred->item_type == JS_TYPE_ANY) + res = true; + else + { + Jsonb *jb = DatumGetJsonbP(js); + + switch (pred->item_type) + { + case JS_TYPE_OBJECT: + res = JB_ROOT_IS_OBJECT(jb); + break; + case JS_TYPE_ARRAY: + res = JB_ROOT_IS_ARRAY(jb) && !JB_ROOT_IS_SCALAR(jb); + break; + case JS_TYPE_SCALAR: + res = JB_ROOT_IS_ARRAY(jb) && JB_ROOT_IS_SCALAR(jb); + break; + default: + res = false; + break; + } + } + + /* Key uniqueness check is redundant for jsonb */ + } + else + res = false; + + *op->resvalue = BoolGetDatum(res); +} + + +/* * ExecEvalGroupingFunc * * Computes a bitmask with a bit for each (unevaluated) argument expression diff --git a/src/backend/jit/llvm/llvmjit_expr.c b/src/backend/jit/llvm/llvmjit_expr.c index 12d39394b31..daefe66f40a 100644 --- a/src/backend/jit/llvm/llvmjit_expr.c +++ b/src/backend/jit/llvm/llvmjit_expr.c @@ -1848,6 +1848,12 @@ llvm_compile_expr(ExprState *state) LLVMBuildBr(b, opblocks[opno + 1]); break; + case EEOP_IS_JSON: + build_EvalXFunc(b, mod, "ExecEvalJsonIsPredicate", + v_state, op); + LLVMBuildBr(b, opblocks[opno + 1]); + break; + case EEOP_AGGREF: { LLVMValueRef v_aggno; diff --git a/src/backend/jit/llvm/llvmjit_types.c b/src/backend/jit/llvm/llvmjit_types.c index 315eeb11720..f61d9390ee8 100644 --- a/src/backend/jit/llvm/llvmjit_types.c +++ b/src/backend/jit/llvm/llvmjit_types.c @@ -133,6 +133,7 @@ void *referenced_functions[] = ExecEvalWholeRowVar, ExecEvalXmlExpr, ExecEvalJsonConstructor, + ExecEvalJsonIsPredicate, MakeExpandedObjectReadOnlyInternal, slot_getmissingattrs, slot_getsomeattrs_int, diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c index 23c71528065..39e1884cf43 100644 --- a/src/backend/nodes/makefuncs.c +++ b/src/backend/nodes/makefuncs.c @@ -894,3 +894,22 @@ makeJsonKeyValue(Node *key, Node *value) return (Node *) n; } + +/* + * makeJsonIsPredicate - + * creates a JsonIsPredicate node + */ +Node * +makeJsonIsPredicate(Node *expr, JsonFormat *format, JsonValueType item_type, + bool unique_keys, int location) +{ + JsonIsPredicate *n = makeNode(JsonIsPredicate); + + n->expr = expr; + n->format = format; + n->item_type = item_type; + n->unique_keys = unique_keys; + n->location = location; + + return (Node *) n; +} diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c index 69907fbcde8..fdb0c6b3fef 100644 --- a/src/backend/nodes/nodeFuncs.c +++ b/src/backend/nodes/nodeFuncs.c @@ -261,6 +261,9 @@ exprType(const Node *expr) case T_JsonConstructorExpr: type = ((const JsonConstructorExpr *) expr)->returning->typid; break; + case T_JsonIsPredicate: + type = BOOLOID; + break; default: elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr)); type = InvalidOid; /* keep compiler quiet */ @@ -983,6 +986,9 @@ exprCollation(const Node *expr) coll = InvalidOid; } break; + case T_JsonIsPredicate: + coll = InvalidOid; /* result is always an boolean type */ + break; default: elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr)); coll = InvalidOid; /* keep compiler quiet */ @@ -1205,6 +1211,9 @@ exprSetCollation(Node *expr, Oid collation) * json[b] type */ } break; + case T_JsonIsPredicate: + Assert(!OidIsValid(collation)); /* result is always boolean */ + break; default: elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr)); break; @@ -1653,6 +1662,9 @@ exprLocation(const Node *expr) case T_JsonConstructorExpr: loc = ((const JsonConstructorExpr *) expr)->location; break; + case T_JsonIsPredicate: + loc = ((const JsonIsPredicate *) expr)->location; + break; default: /* for any other node type it's just unknown... */ loc = -1; @@ -2406,6 +2418,8 @@ expression_tree_walker_impl(Node *node, return true; } break; + case T_JsonIsPredicate: + return walker(((JsonIsPredicate *) node)->expr, context); default: elog(ERROR, "unrecognized node type: %d", (int) nodeTag(node)); @@ -3415,6 +3429,16 @@ expression_tree_mutator_impl(Node *node, return (Node *) newnode; } + case T_JsonIsPredicate: + { + JsonIsPredicate *pred = (JsonIsPredicate *) node; + JsonIsPredicate *newnode; + + FLATCOPY(newnode, pred, JsonIsPredicate); + MUTATE(newnode->expr, pred->expr, Node *); + + return (Node *) newnode; + } default: elog(ERROR, "unrecognized node type: %d", (int) nodeTag(node)); @@ -4261,6 +4285,8 @@ raw_expression_tree_walker_impl(Node *node, return true; } break; + case T_JsonIsPredicate: + return walker(((JsonIsPredicate *) node)->expr, context); default: elog(ERROR, "unrecognized node type: %d", (int) nodeTag(node)); diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index f3ddc34afbe..1b5daf6734b 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -655,6 +655,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); json_array_aggregate_order_by_clause_opt %type <ival> json_encoding_clause_opt + json_predicate_type_constraint %type <boolean> json_key_uniqueness_constraint_opt json_object_constructor_null_clause_opt @@ -754,7 +755,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); RESET RESTART RESTRICT RETURN RETURNING RETURNS REVOKE RIGHT ROLE ROLLBACK ROLLUP ROUTINE ROUTINES ROW ROWS RULE - SAVEPOINT SCHEMA SCHEMAS SCROLL SEARCH SECOND_P SECURITY SELECT SEQUENCE SEQUENCES + SAVEPOINT SCALAR SCHEMA SCHEMAS SCROLL SEARCH SECOND_P SECURITY SELECT + SEQUENCE SEQUENCES SERIALIZABLE SERVER SESSION SESSION_USER SET SETS SETOF SHARE SHOW SIMILAR SIMPLE SKIP SMALLINT SNAPSHOT SOME SQL_P STABLE STANDALONE_P START STATEMENT STATISTICS STDIN STDOUT STORAGE STORED STRICT_P STRIP_P @@ -818,6 +820,12 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); %nonassoc '<' '>' '=' LESS_EQUALS GREATER_EQUALS NOT_EQUALS %nonassoc BETWEEN IN_P LIKE ILIKE SIMILAR NOT_LA %nonassoc ESCAPE /* ESCAPE must be just above LIKE/ILIKE/SIMILAR */ + +/* SQL/JSON related keywords */ +%nonassoc UNIQUE JSON +%nonassoc KEYS OBJECT_P SCALAR VALUE_P +%nonassoc WITH WITHOUT_LA + /* * To support target_el without AS, it used to be necessary to assign IDENT an * explicit precedence just less than Op. While that's not really necessary @@ -14850,6 +14858,44 @@ a_expr: c_expr { $$ = $1; } @2), @2); } + | a_expr IS json_predicate_type_constraint + json_key_uniqueness_constraint_opt %prec IS + { + JsonFormat *format = makeJsonFormat(JS_FORMAT_DEFAULT, JS_ENC_DEFAULT, -1); + + $$ = makeJsonIsPredicate($1, format, $3, $4, @1); + } + /* + * Required by SQL/JSON, but there are conflicts + | a_expr + FORMAT_LA JSON json_encoding_clause_opt + IS json_predicate_type_constraint + json_key_uniqueness_constraint_opt %prec IS + { + $3.location = @2; + $$ = makeJsonIsPredicate($1, $3, $5, $6, @1); + } + */ + | a_expr IS NOT + json_predicate_type_constraint + json_key_uniqueness_constraint_opt %prec IS + { + JsonFormat *format = makeJsonFormat(JS_FORMAT_DEFAULT, JS_ENC_DEFAULT, -1); + + $$ = makeNotExpr(makeJsonIsPredicate($1, format, $4, $5, @1), @1); + } + /* + * Required by SQL/JSON, but there are conflicts + | a_expr + FORMAT_LA JSON json_encoding_clause_opt + IS NOT + json_predicate_type_constraint + json_key_uniqueness_constraint_opt %prec IS + { + $3.location = @2; + $$ = makeNotExpr(makeJsonIsPredicate($1, $3, $6, $7, @1), @1); + } + */ | DEFAULT { /* @@ -16406,13 +16452,21 @@ json_output_clause_opt: | /* EMPTY */ { $$ = NULL; } ; +json_predicate_type_constraint: + JSON { $$ = JS_TYPE_ANY; } + | JSON VALUE_P { $$ = JS_TYPE_ANY; } + | JSON ARRAY { $$ = JS_TYPE_ARRAY; } + | JSON OBJECT_P { $$ = JS_TYPE_OBJECT; } + | JSON SCALAR { $$ = JS_TYPE_SCALAR; } + ; + /* KEYS is a noise word here */ json_key_uniqueness_constraint_opt: - WITH UNIQUE KEYS { $$ = true; } - | WITH UNIQUE { $$ = true; } - | WITHOUT_LA UNIQUE KEYS { $$ = false; } - | WITHOUT_LA UNIQUE { $$ = false; } - | /* EMPTY */ { $$ = false; } + WITH UNIQUE KEYS { $$ = true; } + | WITH UNIQUE { $$ = true; } + | WITHOUT_LA UNIQUE KEYS { $$ = false; } + | WITHOUT_LA UNIQUE { $$ = false; } + | /* EMPTY */ %prec KEYS { $$ = false; } ; json_name_and_value_list: @@ -17182,6 +17236,7 @@ unreserved_keyword: | ROWS | RULE | SAVEPOINT + | SCALAR | SCHEMA | SCHEMAS | SCROLL @@ -17784,6 +17839,7 @@ bare_label_keyword: | ROWS | RULE | SAVEPOINT + | SCALAR | SCHEMA | SCHEMAS | SCROLL diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c index 737b479f541..4c99dd1dec3 100644 --- a/src/backend/parser/parse_expr.c +++ b/src/backend/parser/parse_expr.c @@ -83,6 +83,7 @@ static Node *transformJsonArrayQueryConstructor(ParseState *pstate, JsonArrayQueryConstructor *ctor); static Node *transformJsonObjectAgg(ParseState *pstate, JsonObjectAgg *agg); static Node *transformJsonArrayAgg(ParseState *pstate, JsonArrayAgg *agg); +static Node *transformJsonIsPredicate(ParseState *pstate, JsonIsPredicate *p); static Node *make_row_comparison_op(ParseState *pstate, List *opname, List *largs, List *rargs, int location); static Node *make_row_distinct_op(ParseState *pstate, List *opname, @@ -325,6 +326,10 @@ transformExprRecurse(ParseState *pstate, Node *expr) result = transformJsonArrayAgg(pstate, (JsonArrayAgg *) expr); break; + case T_JsonIsPredicate: + result = transformJsonIsPredicate(pstate, (JsonIsPredicate *) expr); + break; + default: /* should not reach here */ elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr)); @@ -3818,3 +3823,74 @@ transformJsonArrayConstructor(ParseState *pstate, JsonArrayConstructor *ctor) returning, false, ctor->absent_on_null, ctor->location); } + +static Node * +transformJsonParseArg(ParseState *pstate, Node *jsexpr, JsonFormat *format, + Oid *exprtype) +{ + Node *raw_expr = transformExprRecurse(pstate, jsexpr); + Node *expr = raw_expr; + + *exprtype = exprType(expr); + + /* prepare input document */ + if (*exprtype == BYTEAOID) + { + JsonValueExpr *jve; + + expr = makeCaseTestExpr(raw_expr); + expr = makeJsonByteaToTextConversion(expr, format, exprLocation(expr)); + *exprtype = TEXTOID; + + jve = makeJsonValueExpr((Expr *) raw_expr, format); + + jve->formatted_expr = (Expr *) expr; + expr = (Node *) jve; + } + else + { + char typcategory; + bool typispreferred; + + get_type_category_preferred(*exprtype, &typcategory, &typispreferred); + + if (*exprtype == UNKNOWNOID || typcategory == TYPCATEGORY_STRING) + { + expr = coerce_to_target_type(pstate, (Node *) expr, *exprtype, + TEXTOID, -1, + COERCION_IMPLICIT, + COERCE_IMPLICIT_CAST, -1); + *exprtype = TEXTOID; + } + + if (format->encoding != JS_ENC_DEFAULT) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + parser_errposition(pstate, format->location), + errmsg("cannot use JSON FORMAT ENCODING clause for non-bytea input types"))); + } + + return expr; +} + +/* + * Transform IS JSON predicate. + */ +static Node * +transformJsonIsPredicate(ParseState *pstate, JsonIsPredicate *pred) +{ + Oid exprtype; + Node *expr = transformJsonParseArg(pstate, pred->expr, pred->format, + &exprtype); + + /* make resulting expression */ + if (exprtype != TEXTOID && exprtype != JSONOID && exprtype != JSONBOID) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("cannot use type %s in IS JSON predicate", + format_type_be(exprtype)))); + + /* This intentionally(?) drops the format clause. */ + return makeJsonIsPredicate(expr, NULL, pred->item_type, + pred->unique_keys, pred->location); +} diff --git a/src/backend/utils/adt/json.c b/src/backend/utils/adt/json.c index dcd2bb2234a..49080e5fbff 100644 --- a/src/backend/utils/adt/json.c +++ b/src/backend/utils/adt/json.c @@ -62,6 +62,23 @@ typedef struct JsonUniqueHashEntry int object_id; } JsonUniqueHashEntry; +/* Stack element for key uniqueness check during JSON parsing */ +typedef struct JsonUniqueStackEntry +{ + struct JsonUniqueStackEntry *parent; + int object_id; +} JsonUniqueStackEntry; + +/* Context struct for key uniqueness check during JSON parsing */ +typedef struct JsonUniqueParsingState +{ + JsonLexContext *lex; + JsonUniqueCheckState check; + JsonUniqueStackEntry *stack; + int id_counter; + bool unique; +} JsonUniqueParsingState; + /* Context struct for key uniqueness check during JSON building */ typedef struct JsonUniqueBuilderState { @@ -1648,6 +1665,110 @@ escape_json(StringInfo buf, const char *str) appendStringInfoCharMacro(buf, '"'); } +/* Semantic actions for key uniqueness check */ +static JsonParseErrorType +json_unique_object_start(void *_state) +{ + JsonUniqueParsingState *state = _state; + JsonUniqueStackEntry *entry; + + if (!state->unique) + return JSON_SUCCESS; + + /* push object entry to stack */ + entry = palloc(sizeof(*entry)); + entry->object_id = state->id_counter++; + entry->parent = state->stack; + state->stack = entry; + + return JSON_SUCCESS; +} + +static JsonParseErrorType +json_unique_object_end(void *_state) +{ + JsonUniqueParsingState *state = _state; + JsonUniqueStackEntry *entry; + + if (!state->unique) + return JSON_SUCCESS; + + entry = state->stack; + state->stack = entry->parent; /* pop object from stack */ + pfree(entry); + return JSON_SUCCESS; +} + +static JsonParseErrorType +json_unique_object_field_start(void *_state, char *field, bool isnull) +{ + JsonUniqueParsingState *state = _state; + JsonUniqueStackEntry *entry; + + if (!state->unique) + return JSON_SUCCESS; + + /* find key collision in the current object */ + if (json_unique_check_key(&state->check, field, state->stack->object_id)) + return JSON_SUCCESS; + + state->unique = false; + + /* pop all objects entries */ + while ((entry = state->stack)) + { + state->stack = entry->parent; + pfree(entry); + } + return JSON_SUCCESS; +} + +/* Validate JSON text and additionally check key uniqueness */ +bool +json_validate(text *json, bool check_unique_keys, bool throw_error) +{ + JsonLexContext *lex = makeJsonLexContext(json, check_unique_keys); + JsonSemAction uniqueSemAction = {0}; + JsonUniqueParsingState state; + JsonParseErrorType result; + + if (check_unique_keys) + { + state.lex = lex; + state.stack = NULL; + state.id_counter = 0; + state.unique = true; + json_unique_check_init(&state.check); + + uniqueSemAction.semstate = &state; + uniqueSemAction.object_start = json_unique_object_start; + uniqueSemAction.object_field_start = json_unique_object_field_start; + uniqueSemAction.object_end = json_unique_object_end; + } + + result = pg_parse_json(lex, check_unique_keys ? &uniqueSemAction : &nullSemAction); + + if (result != JSON_SUCCESS) + { + if (throw_error) + json_errsave_error(result, lex, NULL); + + return false; /* invalid json */ + } + + if (check_unique_keys && !state.unique) + { + if (throw_error) + ereport(ERROR, + (errcode(ERRCODE_DUPLICATE_JSON_OBJECT_KEY_VALUE), + errmsg("duplicate JSON object key value"))); + + return false; /* not unique keys */ + } + + return true; /* ok */ +} + /* * SQL function json_typeof(json) -> text * @@ -1663,21 +1784,18 @@ escape_json(StringInfo buf, const char *str) Datum json_typeof(PG_FUNCTION_ARGS) { - text *json; - - JsonLexContext *lex; - JsonTokenType tok; + text *json = PG_GETARG_TEXT_PP(0); + JsonLexContext *lex = makeJsonLexContext(json, false); char *type; + JsonTokenType tok; JsonParseErrorType result; - json = PG_GETARG_TEXT_PP(0); - lex = makeJsonLexContext(json, false); - /* Lex exactly one token from the input and check its type. */ result = json_lex(lex); if (result != JSON_SUCCESS) json_errsave_error(result, lex, NULL); tok = lex->token_type; + switch (tok) { case JSON_TOKEN_OBJECT_START: diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c index 7a36f74dad4..4c5abaff257 100644 --- a/src/backend/utils/adt/jsonfuncs.c +++ b/src/backend/utils/adt/jsonfuncs.c @@ -5665,3 +5665,23 @@ transform_string_values_scalar(void *state, char *token, JsonTokenType tokentype return JSON_SUCCESS; } + +JsonTokenType +json_get_first_token(text *json, bool throw_error) +{ + JsonLexContext *lex; + JsonParseErrorType result; + + lex = makeJsonLexContext(json, false); + + /* Lex exactly one token from the input and check its type. */ + result = json_lex(lex); + + if (result == JSON_SUCCESS) + return lex->token_type; + + if (throw_error) + json_errsave_error(result, lex, NULL); + + return JSON_TOKEN_INVALID; /* invalid json */ +} diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c index 1c078d700d4..461735e84f0 100644 --- a/src/backend/utils/adt/ruleutils.c +++ b/src/backend/utils/adt/ruleutils.c @@ -8227,6 +8227,7 @@ isSimpleNode(Node *node, Node *parentNode, int prettyFlags) case T_NullTest: case T_BooleanTest: case T_DistinctExpr: + case T_JsonIsPredicate: switch (nodeTag(parentNode)) { case T_FuncExpr: @@ -9530,6 +9531,42 @@ get_rule_expr(Node *node, deparse_context *context, get_json_constructor((JsonConstructorExpr *) node, context, false); break; + case T_JsonIsPredicate: + { + JsonIsPredicate *pred = (JsonIsPredicate *) node; + + if (!PRETTY_PAREN(context)) + appendStringInfoChar(context->buf, '('); + + get_rule_expr_paren(pred->expr, context, true, node); + + appendStringInfoString(context->buf, " IS JSON"); + + /* TODO: handle FORMAT clause */ + + switch (pred->item_type) + { + case JS_TYPE_SCALAR: + appendStringInfoString(context->buf, " SCALAR"); + break; + case JS_TYPE_ARRAY: + appendStringInfoString(context->buf, " ARRAY"); + break; + case JS_TYPE_OBJECT: + appendStringInfoString(context->buf, " OBJECT"); + break; + default: + break; + } + + if (pred->unique_keys) + appendStringInfoString(context->buf, " WITH UNIQUE KEYS"); + + if (!PRETTY_PAREN(context)) + appendStringInfoChar(context->buf, ')'); + } + break; + case T_List: { char *sep; |