diff options
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; |