Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'src/backend')
-rw-r--r--src/backend/executor/execExpr.c13
-rw-r--r--src/backend/executor/execExprInterp.c99
-rw-r--r--src/backend/jit/llvm/llvmjit_expr.c6
-rw-r--r--src/backend/jit/llvm/llvmjit_types.c1
-rw-r--r--src/backend/nodes/makefuncs.c19
-rw-r--r--src/backend/nodes/nodeFuncs.c26
-rw-r--r--src/backend/parser/gram.y68
-rw-r--r--src/backend/parser/parse_expr.c76
-rw-r--r--src/backend/utils/adt/json.c132
-rw-r--r--src/backend/utils/adt/jsonfuncs.c20
-rw-r--r--src/backend/utils/adt/ruleutils.c37
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;