IS JSON predicate
authorAndrew Dunstan <[email protected]>
Thu, 3 Mar 2022 18:02:53 +0000 (13:02 -0500)
committerAndrew Dunstan <[email protected]>
Mon, 28 Mar 2022 19:37:08 +0000 (15:37 -0400)
This patch intrdocuces 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 an IS and IS NOT variant. The tests are:

IS JSON [VALUE]
IS JSON ARRAY
IS JSON OBJECT
IS JSON SCALAR
IS JSON  WITH | WITHOUT UNIQUE KEYS

These are mostly self-explanatory, but note that IS JSON WITHOUT UNIQUE
KEYS is true whenever IS JSON is true, and IS JSON WITH UNIQUE KEYS is
true whenever IS JSON is true except it IS JSON OBJECT is true and there
are duplicate keys (which is never the case when applied to jsonb values).

Nikita Glukhov

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/cd0bb935-0158-78a7-08b5-904886deac4b@postgrespro.ru

25 files changed:
src/backend/executor/execExpr.c
src/backend/executor/execExprInterp.c
src/backend/jit/llvm/llvmjit_expr.c
src/backend/jit/llvm/llvmjit_types.c
src/backend/nodes/copyfuncs.c
src/backend/nodes/equalfuncs.c
src/backend/nodes/makefuncs.c
src/backend/nodes/nodeFuncs.c
src/backend/nodes/outfuncs.c
src/backend/nodes/readfuncs.c
src/backend/parser/gram.y
src/backend/parser/parse_expr.c
src/backend/utils/adt/json.c
src/backend/utils/adt/jsonfuncs.c
src/backend/utils/adt/ruleutils.c
src/backend/utils/misc/queryjumble.c
src/include/executor/execExpr.h
src/include/nodes/makefuncs.h
src/include/nodes/nodes.h
src/include/nodes/primnodes.h
src/include/parser/kwlist.h
src/include/utils/json.h
src/include/utils/jsonfuncs.h
src/test/regress/expected/sqljson.out
src/test/regress/sql/sqljson.sql

index a9547aaef1532cf02c50d835b692687a71300e64..acd3ea613440ab7a50a0c575a6d1ec1808c7a2dc 100644 (file)
@@ -2513,6 +2513,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;
+                       }
+
                default:
                        elog(ERROR, "unrecognized node type: %d",
                                 (int) nodeTag(node));
index f2a0821a7abf5925931ef4f47ae01a9770134856..c0bd9556209ded75ced9b61878556b44e7d5c78b 100644 (file)
@@ -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"
@@ -480,6 +481,7 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
                &&CASE_EEOP_WINDOW_FUNC,
                &&CASE_EEOP_SUBPLAN,
                &&CASE_EEOP_JSON_CONSTRUCTOR,
+               &&CASE_EEOP_IS_JSON,
                &&CASE_EEOP_AGG_STRICT_DESERIALIZE,
                &&CASE_EEOP_AGG_DESERIALIZE,
                &&CASE_EEOP_AGG_STRICT_INPUT_CHECK_ARGS,
@@ -1799,6 +1801,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_LAST)
                {
                        /* unreachable */
@@ -3909,6 +3919,91 @@ ExecEvalXmlExpr(ExprState *state, ExprEvalStep *op)
        }
 }
 
+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->value_type == JS_TYPE_ANY)
+                       res = true;
+               else
+               {
+                       switch (json_get_first_token(json, false))
+                       {
+                               case JSON_TOKEN_OBJECT_START:
+                                       res = pred->value_type == JS_TYPE_OBJECT;
+                                       break;
+                               case JSON_TOKEN_ARRAY_START:
+                                       res = pred->value_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->value_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);
+       }
+       else if (exprtype == JSONBOID)
+       {
+               if (pred->value_type == JS_TYPE_ANY)
+                       res = true;
+               else
+               {
+                       Jsonb      *jb = DatumGetJsonbP(js);
+
+                       switch (pred->value_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
  *
index d0c26cf58b7a39f86904111dda53e5af03b9a54e..02511c6aecc499ce5b512e481762172269fc0e80 100644 (file)
@@ -2354,6 +2354,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_LAST:
                                Assert(false);
                                break;
index 53c75dd9d699dfeb17d803b4f90bfe7a8efc6a37..4d7029a27f10160876e7ebfccdd1a7e30656d947 100644 (file)
@@ -132,6 +132,7 @@ void           *referenced_functions[] =
        ExecEvalWholeRowVar,
        ExecEvalXmlExpr,
        ExecEvalJsonConstructor,
+       ExecEvalJsonIsPredicate,
        MakeExpandedObjectReadOnlyInternal,
        slot_getmissingattrs,
        slot_getsomeattrs_int,
index c09172164b9e3b643dc3244a6eed27fb8af92ee7..cb4b4d01f80025ea82ffb772adea21b312c5790e 100644 (file)
@@ -2491,6 +2491,23 @@ _copyJsonArrayQueryConstructor(const JsonArrayQueryConstructor *from)
        return newnode;
 }
 
+/*
+ * _copyJsonIsPredicate
+ */
+static JsonIsPredicate *
+_copyJsonIsPredicate(const JsonIsPredicate *from)
+{
+       JsonIsPredicate *newnode = makeNode(JsonIsPredicate);
+
+       COPY_NODE_FIELD(expr);
+       COPY_SCALAR_FIELD(format);
+       COPY_SCALAR_FIELD(value_type);
+       COPY_SCALAR_FIELD(unique_keys);
+       COPY_LOCATION_FIELD(location);
+
+       return newnode;
+}
+
 /* ****************************************************************
  *                                             pathnodes.h copy functions
  *
@@ -5625,6 +5642,9 @@ copyObjectImpl(const void *from)
                case T_JsonArrayAgg:
                        retval = _copyJsonArrayAgg(from);
                        break;
+               case T_JsonIsPredicate:
+                       retval = _copyJsonIsPredicate(from);
+                       break;
 
                        /*
                         * RELATION NODES
index 3fb423be47a0e67d6c37949c665d7c4a643f7af4..084d98b34cc836facd69e4205ce649df0a226851 100644 (file)
@@ -976,6 +976,18 @@ _equalJsonArrayQueryConstructor(const JsonArrayQueryConstructor *a,
        return true;
 }
 
+static bool
+_equalJsonIsPredicate(const JsonIsPredicate *a,
+                                         const JsonIsPredicate *b)
+{
+       COMPARE_NODE_FIELD(expr);
+       COMPARE_SCALAR_FIELD(value_type);
+       COMPARE_SCALAR_FIELD(unique_keys);
+       COMPARE_LOCATION_FIELD(location);
+
+       return true;
+}
+
 /*
  * Stuff from pathnodes.h
  */
@@ -3546,6 +3558,9 @@ equal(const void *a, const void *b)
                case T_JsonConstructorExpr:
                        retval = _equalJsonConstructorExpr(a, b);
                        break;
+               case T_JsonIsPredicate:
+                       retval = _equalJsonIsPredicate(a, b);
+                       break;
 
                        /*
                         * RELATION NODES
index 7b4f7972e6266258e98ca5c7cb8fb80993eb325f..b67e7c52973c01f5e7ee94696dbea2f81a6413fd 100644 (file)
@@ -887,3 +887,22 @@ makeJsonKeyValue(Node *key, Node *value)
 
        return (Node *) n;
 }
+
+/*
+ * makeJsonIsPredicate -
+ *       creates a JsonIsPredicate node
+ */
+Node *
+makeJsonIsPredicate(Node *expr, JsonFormat *format, JsonValueType value_type,
+                                       bool unique_keys, int location)
+{
+       JsonIsPredicate *n = makeNode(JsonIsPredicate);
+
+       n->expr = expr;
+       n->format = format;
+       n->value_type = value_type;
+       n->unique_keys = unique_keys;
+       n->location = location;
+
+       return (Node *) n;
+}
index 50898246f966e7b83d6830e628a8ab63b9bafd26..d697c7abd8c8bed833ef6d810a0ab678c9131f3c 100644 (file)
@@ -260,6 +260,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 */
@@ -985,6 +988,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 */
@@ -1211,6 +1217,9 @@ exprSetCollation(Node *expr, Oid collation)
                                        Assert(!OidIsValid(collation)); /* result is always a 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;
@@ -1663,6 +1672,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;
@@ -2429,6 +2441,8 @@ expression_tree_walker(Node *node,
                                        return true;
                        }
                        break;
+               case T_JsonIsPredicate:
+                       return walker(((JsonIsPredicate *) node)->expr, context);
                default:
                        elog(ERROR, "unrecognized node type: %d",
                                 (int) nodeTag(node));
@@ -3438,6 +3452,16 @@ expression_tree_mutator(Node *node,
                                MUTATE(newnode->coercion, jve->coercion, Expr *);
                                MUTATE(newnode->returning, jve->returning, JsonReturning *);
 
+                               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:
@@ -4290,6 +4314,8 @@ raw_expression_tree_walker(Node *node,
                                        return true;
                        }
                        break;
+               case T_JsonIsPredicate:
+                       return walker(((JsonIsPredicate *) node)->expr, context);
                default:
                        elog(ERROR, "unrecognized node type: %d",
                                 (int) nodeTag(node));
index 0c01f350867af37d2fd3790919424bd47b4d6ef8..278e87259dc503fa7e22949a06702d67be8a0d19 100644 (file)
@@ -1797,6 +1797,17 @@ _outJsonConstructorExpr(StringInfo str, const JsonConstructorExpr *node)
        WRITE_LOCATION_FIELD(location);
 }
 
+static void
+_outJsonIsPredicate(StringInfo str, const JsonIsPredicate *node)
+{
+       WRITE_NODE_TYPE("JSONISPREDICATE");
+
+       WRITE_NODE_FIELD(expr);
+       WRITE_ENUM_FIELD(value_type, JsonValueType);
+       WRITE_BOOL_FIELD(unique_keys);
+       WRITE_LOCATION_FIELD(location);
+}
+
 /*****************************************************************************
  *
  *     Stuff from pathnodes.h.
@@ -4630,6 +4641,9 @@ outNode(StringInfo str, const void *obj)
                        case T_JsonConstructorExpr:
                                _outJsonConstructorExpr(str, obj);
                                break;
+                       case T_JsonIsPredicate:
+                               _outJsonIsPredicate(str, obj);
+                               break;
 
                        default:
 
index 3ee8ba6f159eb19be02cae624b6b28d125478224..5b9e235e9ad7bb0da4a2220fc3b48e6d0276e2d7 100644 (file)
@@ -1492,6 +1492,22 @@ _readJsonConstructorExpr(void)
        READ_DONE();
 }
 
+/*
+ * _readJsonIsPredicate
+ */
+static JsonIsPredicate *
+_readJsonIsPredicate()
+{
+       READ_LOCALS(JsonIsPredicate);
+
+       READ_NODE_FIELD(expr);
+       READ_ENUM_FIELD(value_type, JsonValueType);
+       READ_BOOL_FIELD(unique_keys);
+       READ_LOCATION_FIELD(location);
+
+       READ_DONE();
+}
+
 /*
  *     Stuff from pathnodes.h.
  *
@@ -3090,6 +3106,8 @@ parseNodeString(void)
                return_value = _readJsonValueExpr();
        else if (MATCH("JSONCTOREXPR", 12))
                return_value = _readJsonConstructorExpr();
+       else if (MATCH("JSONISPREDICATE", 15))
+               return_value = _readJsonIsPredicate();
        else
        {
                elog(ERROR, "badly formatted node string \"%.32s\"...", token);
index 9399fff610f8df69e03c734f7728d0427ff6244a..b658bcc182c6cd0fe2843f2ddc6de33d0bc40f3a 100644 (file)
@@ -665,6 +665,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 
 %type <ival>           json_encoding
                                        json_encoding_clause_opt
+                                       json_predicate_type_constraint_opt
 
 %type <boolean>                json_key_uniqueness_constraint_opt
                                        json_object_constructor_null_clause_opt
@@ -734,7 +735,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 
        JOIN JSON JSON_ARRAY JSON_ARRAYAGG JSON_OBJECT JSON_OBJECTAGG
 
-       KEY KEYS
+       KEY KEYS KEEP
 
        LABEL LANGUAGE LARGE_P LAST_P LATERAL_P
        LEADING LEAKPROOF LEAST LEFT LEVEL LIKE LIMIT LISTEN LOAD LOCAL
@@ -763,9 +764,9 @@ 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
-       SERIALIZABLE SERVER SESSION SESSION_USER SET SETS SETOF SHARE SHOW
-       SIMILAR SIMPLE SKIP SMALLINT SNAPSHOT SOME SQL_P STABLE STANDALONE_P
+       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
        SUBSCRIPTION SUBSTRING SUPPORT SYMMETRIC SYSID SYSTEM_P
 
@@ -853,13 +854,14 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
  * Using the same precedence as IDENT seems right for the reasons given above.
  */
 %nonassoc      UNBOUNDED               /* ideally would have same precedence as IDENT */
-%nonassoc      ABSENT UNIQUE
+%nonassoc      ABSENT UNIQUE JSON
 %nonassoc      IDENT PARTITION RANGE ROWS GROUPS PRECEDING FOLLOWING CUBE ROLLUP
 %left          Op OPERATOR             /* multi-character ops and user-defined operators */
 %left          '+' '-'
 %left          '*' '/' '%'
 %left          '^'
 %left          KEYS                                            /* UNIQUE [ KEYS ] */
+%left          OBJECT_P SCALAR VALUE_P         /* JSON [ OBJECT | SCALAR | VALUE ] */
 /* Unary Operators */
 %left          AT                              /* sets precedence for AT TIME ZONE */
 %left          COLLATE
@@ -14141,6 +14143,46 @@ a_expr:                c_expr                                                                  { $$ = $1; }
                                                                                                                   @2),
                                                                         @2);
                                }
+                       | a_expr
+                               IS json_predicate_type_constraint_opt
+                                       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 standard, but it would conflict with expressions
+                        * like: 'str' || format(...)
+                       | a_expr
+                               FORMAT json_representation
+                               IS  json_predicate_type_constraint_opt
+                                       json_key_uniqueness_constraint_opt              %prec FORMAT
+                               {
+                                       $3.location = @2;
+                                       $$ = makeJsonIsPredicate($1, $3, $5, $6, @1);
+                               }
+                       */
+                       | a_expr
+                               IS NOT
+                                       json_predicate_type_constraint_opt
+                                       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 standard, but it would conflict with expressions
+                        * like: 'str' || format(...)
+                       | a_expr
+                               FORMAT json_representation
+                               IS NOT
+                                       json_predicate_type_constraint_opt
+                                       json_key_uniqueness_constraint_opt              %prec FORMAT
+                               {
+                                       $3.location = @2;
+                                       $$ = makeNotExpr(makeJsonIsPredicate($1, $3, $6, $7, @1), @1);
+                               }
+                       */
                        | DEFAULT
                                {
                                        /*
@@ -14223,6 +14265,14 @@ b_expr:                c_expr
                                }
                ;
 
+json_predicate_type_constraint_opt:
+                       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; }
+               ;
+
 json_key_uniqueness_constraint_opt:
                        WITH_LA_UNIQUE unique_keys                              { $$ = true; }
                        | WITHOUT unique_keys                                   { $$ = false; }
@@ -16412,6 +16462,7 @@ unreserved_keyword:
                        | ROWS
                        | RULE
                        | SAVEPOINT
+                       | SCALAR
                        | SCHEMA
                        | SCHEMAS
                        | SCROLL
@@ -16882,6 +16933,7 @@ bare_label_keyword:
                        | JSON_ARRAYAGG
                        | JSON_OBJECT
                        | JSON_OBJECTAGG
+                       | KEEP
                        | KEY
                        | KEYS
                        | LABEL
@@ -17011,6 +17063,7 @@ bare_label_keyword:
                        | ROWS
                        | RULE
                        | SAVEPOINT
+                       | SCALAR
                        | SCHEMA
                        | SCHEMAS
                        | SCROLL
index 84be354f714cd93b357db66b8bc3d995e02da33c..0b972ea6322c2963972ce5955683779b80e44f77 100644 (file)
@@ -85,6 +85,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,
@@ -332,6 +333,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));
@@ -3869,3 +3874,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 into
+ * json[b]_is_valid(json, value_type [, check_key_uniqueness]) call.
+ */
+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))));
+
+       return makeJsonIsPredicate(expr, NULL, pred->value_type,
+                                                          pred->unique_keys, pred->location);
+}
index d088fafc567fa4b234d3ff610f48f935dbf9714e..5edcb8bb60e1695c108fc16e4f31a630260b705d 100644 (file)
@@ -13,6 +13,7 @@
  */
 #include "postgres.h"
 
+#include "access/hash.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_type.h"
 #include "common/hashfn.h"
@@ -1655,6 +1656,94 @@ escape_json(StringInfo buf, const char *str)
        appendStringInfoCharMacro(buf, '"');
 }
 
+/* Semantic actions for key uniqueness check */
+static void
+json_unique_object_start(void *_state)
+{
+       JsonUniqueParsingState *state = _state;
+       JsonUniqueStackEntry *entry;
+
+       if (!state->unique)
+               return;
+
+       /* push object entry to stack */
+       entry = palloc(sizeof(*entry));
+       entry->object_id = state->id_counter++;
+       entry->parent = state->stack;
+       state->stack = entry;
+}
+
+static void
+json_unique_object_end(void *_state)
+{
+       JsonUniqueParsingState *state = _state;
+       JsonUniqueStackEntry *entry;
+
+       if (!state->unique)
+               return;
+
+       entry = state->stack;
+       state->stack = entry->parent;   /* pop object from stack */
+       pfree(entry);
+}
+
+static void
+json_unique_object_field_start(void *_state, char *field, bool isnull)
+{
+       JsonUniqueParsingState *state = _state;
+       JsonUniqueStackEntry *entry;
+
+       if (!state->unique)
+               return;
+
+       /* find key collision in the current object */
+       if (json_unique_check_key(&state->check, field, state->stack->object_id))
+               return;
+
+       state->unique = false;
+
+       /* pop all objects entries */
+       while ((entry = state->stack))
+       {
+               state->stack = entry->parent;
+               pfree(entry);
+       }
+}
+
+/* Validate JSON text and additionally check key uniqueness */
+bool
+json_validate(text *json, bool check_unique_keys)
+{
+       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)
+               return false;   /* invalid json */
+
+       if (check_unique_keys && !state.unique)
+               return false;   /* not unique keys */
+
+       return true;    /* ok */
+}
+
 /*
  * SQL function json_typeof(json) -> text
  *
@@ -1670,21 +1759,13 @@ 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);
        char       *type;
-       JsonParseErrorType result;
-
-       json = PG_GETARG_TEXT_PP(0);
-       lex = makeJsonLexContext(json, false);
+       JsonTokenType tok;
 
        /* Lex exactly one token from the input and check its type. */
-       result = json_lex(lex);
-       if (result != JSON_SUCCESS)
-               json_ereport_error(result, lex);
-       tok = lex->token_type;
+       tok = json_get_first_token(json, true);
+
        switch (tok)
        {
                case JSON_TOKEN_OBJECT_START:
index 29664aa6e40ebfad4a68d9e3e3ca9ae8b1bce346..a24d498b0608b29ff2825ee3bb87824b1298acd3 100644 (file)
@@ -5528,3 +5528,23 @@ transform_string_values_scalar(void *state, char *token, JsonTokenType tokentype
        else
                appendStringInfoString(_state->strval, token);
 }
+
+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_ereport_error(result, lex);
+
+       return JSON_TOKEN_INVALID;      /* invalid json */
+}
index 38da26951978b7757a7275133697fd004f832252..0ed774f6e66394e7ba2b3f131540ee813192a87f 100644 (file)
@@ -8230,6 +8230,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:
@@ -9635,6 +9636,40 @@ 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");
+
+                               switch (pred->value_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;
index d14b751058196e96426d188c9b020562b8569deb..831581279366e9286a6931a026e504161824bd02 100644 (file)
@@ -775,6 +775,16 @@ JumbleExpr(JumbleState *jstate, Node *node)
                                APP_JUMB(ctor->absent_on_null);
                        }
                        break;
+               case T_JsonIsPredicate:
+                       {
+                               JsonIsPredicate *pred = (JsonIsPredicate *) node;
+
+                               JumbleExpr(jstate, (Node *) pred->expr);
+                               JumbleExpr(jstate, (Node *) pred->format);
+                               APP_JUMB(pred->unique_keys);
+                               APP_JUMB(pred->value_type);
+                       }
+                       break;
                case T_List:
                        foreach(temp, (List *) node)
                        {
index c830fcf7262f5f53e0ed9c94ab9cebb7002757dc..a41722ae1e41024f8eeeabfdcfb723140804e643 100644 (file)
@@ -240,6 +240,7 @@ typedef enum ExprEvalOp
        EEOP_WINDOW_FUNC,
        EEOP_SUBPLAN,
        EEOP_JSON_CONSTRUCTOR,
+       EEOP_IS_JSON,
 
        /* aggregation related nodes */
        EEOP_AGG_STRICT_DESERIALIZE,
@@ -680,6 +681,12 @@ typedef struct ExprEvalStep
                        int                     nargs;
                }                       json_constructor;
 
+               /* for EEOP_IS_JSON */
+               struct
+               {
+                                       JsonIsPredicate *pred;  /* original expression node */
+               }                       is_json;
+
        }                       d;
 } ExprEvalStep;
 
@@ -774,6 +781,7 @@ extern void ExecEvalHashedScalarArrayOp(ExprState *state, ExprEvalStep *op,
 extern void ExecEvalConstraintNotNull(ExprState *state, ExprEvalStep *op);
 extern void ExecEvalConstraintCheck(ExprState *state, ExprEvalStep *op);
 extern void ExecEvalXmlExpr(ExprState *state, ExprEvalStep *op);
+extern void ExecEvalJsonIsPredicate(ExprState *state, ExprEvalStep *op);
 extern void ExecEvalGroupingFunc(ExprState *state, ExprEvalStep *op);
 extern void ExecEvalSubPlan(ExprState *state, ExprEvalStep *op,
                                                        ExprContext *econtext);
index e50b933288d949afb3f0d3049af84a4d6119a821..380940968bdb257c3fe7599a6300c9ae261b4288 100644 (file)
@@ -110,6 +110,9 @@ extern JsonFormat *makeJsonFormat(JsonFormatType type, JsonEncoding encoding,
                                                                  int location);
 extern JsonValueExpr *makeJsonValueExpr(Expr *expr, JsonFormat *format);
 extern Node *makeJsonKeyValue(Node *key, Node *value);
+extern Node *makeJsonIsPredicate(Node *expr, JsonFormat *format,
+                                                                JsonValueType vtype, bool unique_keys,
+                                                                int location);
 extern JsonEncoding makeJsonEncoding(char *name);
 
 #endif                                                 /* MAKEFUNC_H */
index ca440eb75ffd618dd09030ee3049acc24ffe5689..e8f30367a48a6fe8917cda28b1ff47e942d94891 100644 (file)
@@ -505,6 +505,7 @@ typedef enum NodeTag
        T_JsonAggConstructor,
        T_JsonObjectAgg,
        T_JsonArrayAgg,
+       T_JsonIsPredicate,
        T_JsonKeyValue,
        T_JsonOutput,
 
index c48527e998a9661d2ec426261a8cc9e2a89f7032..f4a39653ac67d83926d5d16982498468180af392 100644 (file)
@@ -1317,6 +1317,32 @@ typedef struct JsonConstructorExpr
        int                     location;
 } JsonConstructorExpr;
 
+/*
+ * JsonValueType -
+ *             representation of JSON item type in IS JSON predicate
+ */
+typedef enum JsonValueType
+{
+       JS_TYPE_ANY,                            /* IS JSON [VALUE] */
+       JS_TYPE_OBJECT,                         /* IS JSON OBJECT */
+       JS_TYPE_ARRAY,                          /* IS JSON ARRAY*/
+       JS_TYPE_SCALAR                          /* IS JSON SCALAR */
+} JsonValueType;
+
+/*
+ * JsonIsPredicate -
+ *             untransformed representation of IS JSON predicate
+ */
+typedef struct JsonIsPredicate
+{
+       NodeTag         type;
+       Node       *expr;                       /* untransformed expression */
+       JsonFormat *format;                     /* FORMAT clause, if specified */
+       JsonValueType value_type;       /* JSON item type */
+       bool            unique_keys;    /* check key uniqueness? */
+       int                     location;               /* token location, or -1 if unknown */
+} JsonIsPredicate;
+
 /* ----------------
  * NullTest
  *
index 10829941e0dd21a9b3704bf71023b2ca31113838..c0ffa516d0638c9818139ad7df4a9c2f9ee0eaf5 100644 (file)
@@ -374,6 +374,7 @@ PG_KEYWORD("row", ROW, COL_NAME_KEYWORD, BARE_LABEL)
 PG_KEYWORD("rows", ROWS, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("rule", RULE, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("savepoint", SAVEPOINT, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("scalar", SCALAR, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("schema", SCHEMA, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("schemas", SCHEMAS, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("scroll", SCROLL, UNRESERVED_KEYWORD, BARE_LABEL)
index 63d83b815fb9efe931d44c96cd5e30a31c869e40..bfe5b215912ec654e8d66b7314ab1e957982a0f4 100644 (file)
@@ -26,5 +26,6 @@ extern Datum json_build_object_worker(int nargs, Datum *args, bool *nulls,
                                                                          bool unique_keys);
 extern Datum json_build_array_worker(int nargs, Datum *args, bool *nulls,
                                                                         Oid *types, bool absent_on_null);
+extern bool json_validate(text *json, bool check_unique_keys);
 
 #endif                                                 /* JSON_H */
index 865b2ff7c11b99d33fc44dd35d83f30a210caafe..cd16b6c0c846e7bed00d3e53fde2361af1467012 100644 (file)
@@ -45,6 +45,9 @@ extern void pg_parse_json_or_ereport(JsonLexContext *lex, JsonSemAction *sem);
 /* report an error during json lexing or parsing */
 extern void json_ereport_error(JsonParseErrorType error, JsonLexContext *lex);
 
+/* get first JSON token */
+extern JsonTokenType json_get_first_token(text *json, bool throw_error);
+
 extern uint32 parse_jsonb_index_flags(Jsonb *jb);
 extern void iterate_jsonb_values(Jsonb *jb, uint32 flags, void *state,
                                                                 JsonIterateStringValuesAction action);
index 7dca5a8a30f408ebbde215b37ef511c0823222f1..27dca7815a72c284e4d2c438781d3b3e58c7d033 100644 (file)
@@ -744,3 +744,201 @@ CREATE OR REPLACE VIEW public.json_array_subquery_view AS
            FROM ( SELECT foo.i
                    FROM ( VALUES (1), (2), (NULL::integer), (4)) foo(i)) q(a)) AS "json_array"
 DROP VIEW json_array_subquery_view;
+-- IS JSON predicate
+SELECT NULL IS JSON;
+ ?column? 
+----------
+(1 row)
+
+SELECT NULL IS NOT JSON;
+ ?column? 
+----------
+(1 row)
+
+SELECT NULL::json IS JSON;
+ ?column? 
+----------
+(1 row)
+
+SELECT NULL::jsonb IS JSON;
+ ?column? 
+----------
+(1 row)
+
+SELECT NULL::text IS JSON;
+ ?column? 
+----------
+(1 row)
+
+SELECT NULL::bytea IS JSON;
+ ?column? 
+----------
+(1 row)
+
+SELECT NULL::int IS JSON;
+ERROR:  cannot use type integer in IS JSON predicate
+SELECT '' IS JSON;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT bytea '\x00' IS JSON;
+ERROR:  invalid byte sequence for encoding "UTF8": 0x00
+CREATE TABLE test_is_json (js text);
+INSERT INTO test_is_json VALUES
+ (NULL),
+ (''),
+ ('123'),
+ ('"aaa "'),
+ ('true'),
+ ('null'),
+ ('[]'),
+ ('[1, "2", {}]'),
+ ('{}'),
+ ('{ "a": 1, "b": null }'),
+ ('{ "a": 1, "a": null }'),
+ ('{ "a": 1, "b": [{ "a": 1 }, { "a": 2 }] }'),
+ ('{ "a": 1, "b": [{ "a": 1, "b": 0, "a": 2 }] }'),
+ ('aaa'),
+ ('{a:1}'),
+ ('["a",]');
+SELECT
+       js,
+       js IS JSON "IS JSON",
+       js IS NOT JSON "IS NOT JSON",
+       js IS JSON VALUE "IS VALUE",
+       js IS JSON OBJECT "IS OBJECT",
+       js IS JSON ARRAY "IS ARRAY",
+       js IS JSON SCALAR "IS SCALAR",
+       js IS JSON WITHOUT UNIQUE KEYS "WITHOUT UNIQUE",
+       js IS JSON WITH UNIQUE KEYS "WITH UNIQUE"
+FROM
+       test_is_json;
+                      js                       | IS JSON | IS NOT JSON | IS VALUE | IS OBJECT | IS ARRAY | IS SCALAR | WITHOUT UNIQUE | WITH UNIQUE 
+-----------------------------------------------+---------+-------------+----------+-----------+----------+-----------+----------------+-------------
+                                               |         |             |          |           |          |           |                | 
+                                               | f       | t           | f        | f         | f        | f         | f              | f
+ 123                                           | t       | f           | t        | f         | f        | t         | t              | t
+ "aaa "                                        | t       | f           | t        | f         | f        | t         | t              | t
+ true                                          | t       | f           | t        | f         | f        | t         | t              | t
+ null                                          | t       | f           | t        | f         | f        | t         | t              | t
+ []                                            | t       | f           | t        | f         | t        | f         | t              | t
+ [1, "2", {}]                                  | t       | f           | t        | f         | t        | f         | t              | t
+ {}                                            | t       | f           | t        | t         | f        | f         | t              | t
+ { "a": 1, "b": null }                         | t       | f           | t        | t         | f        | f         | t              | t
+ { "a": 1, "a": null }                         | t       | f           | t        | t         | f        | f         | t              | f
+ { "a": 1, "b": [{ "a": 1 }, { "a": 2 }] }     | t       | f           | t        | t         | f        | f         | t              | t
+ { "a": 1, "b": [{ "a": 1, "b": 0, "a": 2 }] } | t       | f           | t        | t         | f        | f         | t              | f
+ aaa                                           | f       | t           | f        | f         | f        | f         | f              | f
+ {a:1}                                         | f       | t           | f        | f         | f        | f         | f              | f
+ ["a",]                                        | f       | t           | f        | f         | f        | f         | f              | f
+(16 rows)
+
+SELECT
+       js,
+       js IS JSON "IS JSON",
+       js IS NOT JSON "IS NOT JSON",
+       js IS JSON VALUE "IS VALUE",
+       js IS JSON OBJECT "IS OBJECT",
+       js IS JSON ARRAY "IS ARRAY",
+       js IS JSON SCALAR "IS SCALAR",
+       js IS JSON WITHOUT UNIQUE KEYS "WITHOUT UNIQUE",
+       js IS JSON WITH UNIQUE KEYS "WITH UNIQUE"
+FROM
+       (SELECT js::json FROM test_is_json WHERE js IS JSON) foo(js);
+                      js                       | IS JSON | IS NOT JSON | IS VALUE | IS OBJECT | IS ARRAY | IS SCALAR | WITHOUT UNIQUE | WITH UNIQUE 
+-----------------------------------------------+---------+-------------+----------+-----------+----------+-----------+----------------+-------------
+ 123                                           | t       | f           | t        | f         | f        | t         | t              | t
+ "aaa "                                        | t       | f           | t        | f         | f        | t         | t              | t
+ true                                          | t       | f           | t        | f         | f        | t         | t              | t
+ null                                          | t       | f           | t        | f         | f        | t         | t              | t
+ []                                            | t       | f           | t        | f         | t        | f         | t              | t
+ [1, "2", {}]                                  | t       | f           | t        | f         | t        | f         | t              | t
+ {}                                            | t       | f           | t        | t         | f        | f         | t              | t
+ { "a": 1, "b": null }                         | t       | f           | t        | t         | f        | f         | t              | t
+ { "a": 1, "a": null }                         | t       | f           | t        | t         | f        | f         | t              | f
+ { "a": 1, "b": [{ "a": 1 }, { "a": 2 }] }     | t       | f           | t        | t         | f        | f         | t              | t
+ { "a": 1, "b": [{ "a": 1, "b": 0, "a": 2 }] } | t       | f           | t        | t         | f        | f         | t              | f
+(11 rows)
+
+SELECT
+       js0,
+       js IS JSON "IS JSON",
+       js IS NOT JSON "IS NOT JSON",
+       js IS JSON VALUE "IS VALUE",
+       js IS JSON OBJECT "IS OBJECT",
+       js IS JSON ARRAY "IS ARRAY",
+       js IS JSON SCALAR "IS SCALAR",
+       js IS JSON WITHOUT UNIQUE KEYS "WITHOUT UNIQUE",
+       js IS JSON WITH UNIQUE KEYS "WITH UNIQUE"
+FROM
+       (SELECT js, js::bytea FROM test_is_json WHERE js IS JSON) foo(js0, js);
+                      js0                      | IS JSON | IS NOT JSON | IS VALUE | IS OBJECT | IS ARRAY | IS SCALAR | WITHOUT UNIQUE | WITH UNIQUE 
+-----------------------------------------------+---------+-------------+----------+-----------+----------+-----------+----------------+-------------
+ 123                                           | t       | f           | t        | f         | f        | t         | t              | t
+ "aaa "                                        | t       | f           | t        | f         | f        | t         | t              | t
+ true                                          | t       | f           | t        | f         | f        | t         | t              | t
+ null                                          | t       | f           | t        | f         | f        | t         | t              | t
+ []                                            | t       | f           | t        | f         | t        | f         | t              | t
+ [1, "2", {}]                                  | t       | f           | t        | f         | t        | f         | t              | t
+ {}                                            | t       | f           | t        | t         | f        | f         | t              | t
+ { "a": 1, "b": null }                         | t       | f           | t        | t         | f        | f         | t              | t
+ { "a": 1, "a": null }                         | t       | f           | t        | t         | f        | f         | t              | f
+ { "a": 1, "b": [{ "a": 1 }, { "a": 2 }] }     | t       | f           | t        | t         | f        | f         | t              | t
+ { "a": 1, "b": [{ "a": 1, "b": 0, "a": 2 }] } | t       | f           | t        | t         | f        | f         | t              | f
+(11 rows)
+
+SELECT
+       js,
+       js IS JSON "IS JSON",
+       js IS NOT JSON "IS NOT JSON",
+       js IS JSON VALUE "IS VALUE",
+       js IS JSON OBJECT "IS OBJECT",
+       js IS JSON ARRAY "IS ARRAY",
+       js IS JSON SCALAR "IS SCALAR",
+       js IS JSON WITHOUT UNIQUE KEYS "WITHOUT UNIQUE",
+       js IS JSON WITH UNIQUE KEYS "WITH UNIQUE"
+FROM
+       (SELECT js::jsonb FROM test_is_json WHERE js IS JSON) foo(js);
+                 js                  | IS JSON | IS NOT JSON | IS VALUE | IS OBJECT | IS ARRAY | IS SCALAR | WITHOUT UNIQUE | WITH UNIQUE 
+-------------------------------------+---------+-------------+----------+-----------+----------+-----------+----------------+-------------
+ 123                                 | t       | f           | t        | f         | f        | t         | t              | t
+ "aaa "                              | t       | f           | t        | f         | f        | t         | t              | t
+ true                                | t       | f           | t        | f         | f        | t         | t              | t
+ null                                | t       | f           | t        | f         | f        | t         | t              | t
+ []                                  | t       | f           | t        | f         | t        | f         | t              | t
+ [1, "2", {}]                        | t       | f           | t        | f         | t        | f         | t              | t
+ {}                                  | t       | f           | t        | t         | f        | f         | t              | t
+ {"a": 1, "b": null}                 | t       | f           | t        | t         | f        | f         | t              | t
+ {"a": null}                         | t       | f           | t        | t         | f        | f         | t              | t
+ {"a": 1, "b": [{"a": 1}, {"a": 2}]} | t       | f           | t        | t         | f        | f         | t              | t
+ {"a": 1, "b": [{"a": 2, "b": 0}]}   | t       | f           | t        | t         | f        | f         | t              | t
+(11 rows)
+
+-- Test IS JSON deparsing
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT '1' IS JSON AS "any", ('1' || i) IS JSON SCALAR AS "scalar", '[]' IS NOT JSON ARRAY AS "array", '{}' IS JSON OBJECT WITH UNIQUE AS "object" FROM generate_series(1, 3) i;
+                                                                        QUERY PLAN                                                                        
+----------------------------------------------------------------------------------------------------------------------------------------------------------
+ Function Scan on pg_catalog.generate_series i
+   Output: ('1'::text IS JSON), (('1'::text || (i)::text) IS JSON SCALAR), (NOT ('[]'::text IS JSON ARRAY)), ('{}'::text IS JSON OBJECT WITH UNIQUE KEYS)
+   Function Call: generate_series(1, 3)
+(3 rows)
+
+CREATE VIEW is_json_view AS
+SELECT '1' IS JSON AS "any", ('1' || i) IS JSON SCALAR AS "scalar", '[]' IS NOT JSON ARRAY AS "array", '{}' IS JSON OBJECT WITH UNIQUE AS "object" FROM generate_series(1, 3) i;
+\sv is_json_view
+CREATE OR REPLACE VIEW public.is_json_view AS
+ SELECT '1'::text IS JSON AS "any",
+    ('1'::text || i.i) IS JSON SCALAR AS scalar,
+    NOT '[]'::text IS JSON ARRAY AS "array",
+    '{}'::text IS JSON OBJECT WITH UNIQUE KEYS AS object
+   FROM generate_series(1, 3) i(i)
+DROP VIEW is_json_view;
index aaef2d8aab0cdb541cae3e7b2a1f91abcf62e27d..4f3c06dcb3dbe327308f8c75d26e3341e66aa32e 100644 (file)
@@ -280,3 +280,99 @@ SELECT JSON_ARRAY(SELECT i FROM (VALUES (1), (2), (NULL), (4)) foo(i) RETURNING
 \sv json_array_subquery_view
 
 DROP VIEW json_array_subquery_view;
+
+-- IS JSON predicate
+SELECT NULL IS JSON;
+SELECT NULL IS NOT JSON;
+SELECT NULL::json IS JSON;
+SELECT NULL::jsonb IS JSON;
+SELECT NULL::text IS JSON;
+SELECT NULL::bytea IS JSON;
+SELECT NULL::int IS JSON;
+
+SELECT '' IS JSON;
+
+SELECT bytea '\x00' IS JSON;
+
+CREATE TABLE test_is_json (js text);
+
+INSERT INTO test_is_json VALUES
+ (NULL),
+ (''),
+ ('123'),
+ ('"aaa "'),
+ ('true'),
+ ('null'),
+ ('[]'),
+ ('[1, "2", {}]'),
+ ('{}'),
+ ('{ "a": 1, "b": null }'),
+ ('{ "a": 1, "a": null }'),
+ ('{ "a": 1, "b": [{ "a": 1 }, { "a": 2 }] }'),
+ ('{ "a": 1, "b": [{ "a": 1, "b": 0, "a": 2 }] }'),
+ ('aaa'),
+ ('{a:1}'),
+ ('["a",]');
+
+SELECT
+       js,
+       js IS JSON "IS JSON",
+       js IS NOT JSON "IS NOT JSON",
+       js IS JSON VALUE "IS VALUE",
+       js IS JSON OBJECT "IS OBJECT",
+       js IS JSON ARRAY "IS ARRAY",
+       js IS JSON SCALAR "IS SCALAR",
+       js IS JSON WITHOUT UNIQUE KEYS "WITHOUT UNIQUE",
+       js IS JSON WITH UNIQUE KEYS "WITH UNIQUE"
+FROM
+       test_is_json;
+
+SELECT
+       js,
+       js IS JSON "IS JSON",
+       js IS NOT JSON "IS NOT JSON",
+       js IS JSON VALUE "IS VALUE",
+       js IS JSON OBJECT "IS OBJECT",
+       js IS JSON ARRAY "IS ARRAY",
+       js IS JSON SCALAR "IS SCALAR",
+       js IS JSON WITHOUT UNIQUE KEYS "WITHOUT UNIQUE",
+       js IS JSON WITH UNIQUE KEYS "WITH UNIQUE"
+FROM
+       (SELECT js::json FROM test_is_json WHERE js IS JSON) foo(js);
+
+SELECT
+       js0,
+       js IS JSON "IS JSON",
+       js IS NOT JSON "IS NOT JSON",
+       js IS JSON VALUE "IS VALUE",
+       js IS JSON OBJECT "IS OBJECT",
+       js IS JSON ARRAY "IS ARRAY",
+       js IS JSON SCALAR "IS SCALAR",
+       js IS JSON WITHOUT UNIQUE KEYS "WITHOUT UNIQUE",
+       js IS JSON WITH UNIQUE KEYS "WITH UNIQUE"
+FROM
+       (SELECT js, js::bytea FROM test_is_json WHERE js IS JSON) foo(js0, js);
+
+SELECT
+       js,
+       js IS JSON "IS JSON",
+       js IS NOT JSON "IS NOT JSON",
+       js IS JSON VALUE "IS VALUE",
+       js IS JSON OBJECT "IS OBJECT",
+       js IS JSON ARRAY "IS ARRAY",
+       js IS JSON SCALAR "IS SCALAR",
+       js IS JSON WITHOUT UNIQUE KEYS "WITHOUT UNIQUE",
+       js IS JSON WITH UNIQUE KEYS "WITH UNIQUE"
+FROM
+       (SELECT js::jsonb FROM test_is_json WHERE js IS JSON) foo(js);
+
+-- Test IS JSON deparsing
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT '1' IS JSON AS "any", ('1' || i) IS JSON SCALAR AS "scalar", '[]' IS NOT JSON ARRAY AS "array", '{}' IS JSON OBJECT WITH UNIQUE AS "object" FROM generate_series(1, 3) i;
+
+CREATE VIEW is_json_view AS
+SELECT '1' IS JSON AS "any", ('1' || i) IS JSON SCALAR AS "scalar", '[]' IS NOT JSON ARRAY AS "array", '{}' IS JSON OBJECT WITH UNIQUE AS "object" FROM generate_series(1, 3) i;
+
+\sv is_json_view
+
+DROP VIEW is_json_view;