Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit d45341c

Browse files
author
Nikita Glukhov
committed
Add IS JSON predicate transformation
1 parent 62141b4 commit d45341c

File tree

8 files changed

+598
-1
lines changed

8 files changed

+598
-1
lines changed

src/backend/nodes/nodeFuncs.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3755,6 +3755,8 @@ raw_expression_tree_walker(Node *node,
37553755
return true;
37563756
}
37573757
break;
3758+
case T_JsonIsPredicate:
3759+
return walker(((JsonIsPredicate *) node)->expr, context);
37583760
default:
37593761
elog(ERROR, "unrecognized node type: %d",
37603762
(int) nodeTag(node));

src/backend/parser/parse_expr.c

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,7 @@ static Node *transformJsonArrayQueryCtor(ParseState *pstate,
130130
JsonArrayQueryCtor *ctor);
131131
static Node *transformJsonObjectAgg(ParseState *pstate, JsonObjectAgg *agg);
132132
static Node *transformJsonArrayAgg(ParseState *pstate, JsonArrayAgg *agg);
133+
static Node *transformJsonIsPredicate(ParseState *pstate, JsonIsPredicate *p);
133134
static Node *make_row_comparison_op(ParseState *pstate, List *opname,
134135
List *largs, List *rargs, int location);
135136
static Node *make_row_distinct_op(ParseState *pstate, List *opname,
@@ -398,6 +399,10 @@ transformExprRecurse(ParseState *pstate, Node *expr)
398399
result = transformJsonArrayAgg(pstate, (JsonArrayAgg *) expr);
399400
break;
400401

402+
case T_JsonIsPredicate:
403+
result = transformJsonIsPredicate(pstate, (JsonIsPredicate *) expr);
404+
break;
405+
401406
default:
402407
/* should not reach here */
403408
elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
@@ -4124,3 +4129,100 @@ transformJsonArrayCtor(ParseState *pstate, JsonArrayCtor *ctor)
41244129
return coerceJsonFuncExpr(pstate, (Node *) fexpr, &ctor->output->returning,
41254130
true);
41264131
}
4132+
4133+
static const char *
4134+
JsonValueTypeStrings[] =
4135+
{
4136+
"any",
4137+
"object",
4138+
"array",
4139+
"scalar",
4140+
};
4141+
4142+
static Const *
4143+
makeJsonValueTypeConst(JsonValueType type)
4144+
{
4145+
return makeConst(TEXTOID, -1, InvalidOid, -1,
4146+
PointerGetDatum(cstring_to_text(
4147+
JsonValueTypeStrings[(int) type])),
4148+
false, false);
4149+
}
4150+
4151+
/*
4152+
* Transform IS JSON predicate into
4153+
* json[b]_is_valid(json, value_type [, check_key_uniqueness]) call.
4154+
*/
4155+
static Node *
4156+
transformJsonIsPredicate(ParseState *pstate, JsonIsPredicate *pred)
4157+
{
4158+
Node *expr = transformExprRecurse(pstate, pred->expr);
4159+
Oid exprtype = exprType(expr);
4160+
4161+
/* prepare input document */
4162+
if (exprtype == BYTEAOID)
4163+
{
4164+
expr = makeJsonByteaToTextConversion(expr, &pred->format,
4165+
exprLocation(expr));
4166+
exprtype = TEXTOID;
4167+
}
4168+
else
4169+
{
4170+
char typcategory;
4171+
bool typispreferred;
4172+
4173+
get_type_category_preferred(exprtype, &typcategory, &typispreferred);
4174+
4175+
if (exprtype == UNKNOWNOID || typcategory == TYPCATEGORY_STRING)
4176+
{
4177+
expr = coerce_to_target_type(pstate, (Node *) expr, exprtype,
4178+
TEXTOID, -1,
4179+
COERCION_IMPLICIT,
4180+
COERCE_IMPLICIT_CAST, -1);
4181+
exprtype = TEXTOID;
4182+
}
4183+
4184+
if (pred->format.encoding != JS_ENC_DEFAULT)
4185+
ereport(ERROR,
4186+
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
4187+
parser_errposition(pstate, pred->format.location),
4188+
errmsg("cannot use JSON FORMAT ENCODING clause for non-bytea input types")));
4189+
}
4190+
4191+
/* make resulting expression */
4192+
if (exprtype == TEXTOID || exprtype == JSONOID)
4193+
{
4194+
FuncExpr *fexpr = makeFuncExpr(F_JSON_IS_VALID, BOOLOID,
4195+
list_make3(expr,
4196+
makeJsonValueTypeConst(pred->vtype),
4197+
makeBoolConst(pred->unique_keys,
4198+
false)),
4199+
InvalidOid, InvalidOid,
4200+
COERCE_EXPLICIT_CALL);
4201+
4202+
fexpr->location = pred->location;
4203+
return (Node *) fexpr;
4204+
}
4205+
else if (exprtype == JSONBOID)
4206+
{
4207+
/* XXX the following expressions also can be used here:
4208+
* jsonb_type(jsonb) = 'type' (for object and array checks)
4209+
* CASE jsonb_type(jsonb) WHEN ... END (for scalars checks)
4210+
*/
4211+
FuncExpr *fexpr = makeFuncExpr(F_JSONB_IS_VALID, BOOLOID,
4212+
list_make2(expr,
4213+
makeJsonValueTypeConst(pred->vtype)),
4214+
InvalidOid, InvalidOid,
4215+
COERCE_EXPLICIT_CALL);
4216+
4217+
fexpr->location = pred->location;
4218+
return (Node *) fexpr;
4219+
}
4220+
else
4221+
{
4222+
ereport(ERROR,
4223+
(errcode(ERRCODE_DATATYPE_MISMATCH),
4224+
errmsg("cannot use type %s in IS JSON predicate",
4225+
format_type_be(exprtype))));
4226+
return NULL;
4227+
}
4228+
}

src/backend/utils/adt/json.c

Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
*/
1414
#include "postgres.h"
1515

16+
#include "access/hash.h"
1617
#include "access/htup_details.h"
1718
#include "access/transam.h"
1819
#include "catalog/pg_type.h"
@@ -93,6 +94,20 @@ typedef struct JsonAggState
9394
JsonUniqueCheckContext unique_check;
9495
} JsonAggState;
9596

97+
/* Element of object stack for key uniqueness check */
98+
typedef struct JsonObjectFields
99+
{
100+
struct JsonObjectFields *parent;
101+
HTAB *fields;
102+
} JsonObjectFields;
103+
104+
/* State for key uniqueness check */
105+
typedef struct JsonUniqueState
106+
{
107+
JsonLexContext *lex;
108+
JsonObjectFields *stack;
109+
} JsonUniqueState;
110+
96111
static inline void json_lex(JsonLexContext *lex);
97112
static inline void json_lex_string(JsonLexContext *lex);
98113
static inline void json_lex_number(JsonLexContext *lex, char *s,
@@ -2710,6 +2725,178 @@ escape_json(StringInfo buf, const char *str)
27102725
appendStringInfoCharMacro(buf, '"');
27112726
}
27122727

2728+
/* Functions implementing hash table for key uniqueness check */
2729+
static int
2730+
json_unique_hash_match(const void *key1, const void *key2, Size keysize)
2731+
{
2732+
return strcmp(*(const char **) key1, *(const char **) key2);
2733+
}
2734+
2735+
static void *
2736+
json_unique_hash_keycopy(void *dest, const void *src, Size keysize)
2737+
{
2738+
*(const char **) dest = pstrdup(*(const char **) src);
2739+
2740+
return dest;
2741+
}
2742+
2743+
static uint32
2744+
json_unique_hash(const void *key, Size keysize)
2745+
{
2746+
const char *s = *(const char **) key;
2747+
2748+
return DatumGetUInt32(hash_any((const unsigned char *) s, (int) strlen(s)));
2749+
}
2750+
2751+
/* Semantic actions for key uniqueness check */
2752+
static void
2753+
json_unique_object_start(void *_state)
2754+
{
2755+
JsonUniqueState *state = _state;
2756+
JsonObjectFields *obj = palloc(sizeof(*obj));
2757+
HASHCTL ctl;
2758+
2759+
memset(&ctl, 0, sizeof(ctl));
2760+
ctl.keysize = sizeof(char *);
2761+
ctl.entrysize = sizeof(char *);
2762+
ctl.hcxt = CurrentMemoryContext;
2763+
ctl.hash = json_unique_hash;
2764+
ctl.keycopy = json_unique_hash_keycopy;
2765+
ctl.match = json_unique_hash_match;
2766+
obj->fields = hash_create("json object hashtable",
2767+
32,
2768+
&ctl,
2769+
HASH_ELEM | HASH_CONTEXT |
2770+
HASH_FUNCTION | HASH_COMPARE | HASH_KEYCOPY);
2771+
obj->parent = state->stack; /* push object to stack */
2772+
2773+
state->stack = obj;
2774+
}
2775+
2776+
static void
2777+
json_unique_object_end(void *_state)
2778+
{
2779+
JsonUniqueState *state = _state;
2780+
2781+
hash_destroy(state->stack->fields);
2782+
2783+
state->stack = state->stack->parent; /* pop object from stack */
2784+
}
2785+
2786+
static void
2787+
json_unique_object_field_start(void *_state, char *field, bool isnull)
2788+
{
2789+
JsonUniqueState *state = _state;
2790+
bool found;
2791+
2792+
/* find key collision in the current object */
2793+
(void) hash_search(state->stack->fields, &field, HASH_ENTER, &found);
2794+
2795+
if (found)
2796+
ereport(ERROR,
2797+
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
2798+
errmsg("duplicate JSON key \"%s\"", field),
2799+
report_json_context(state->lex)));
2800+
}
2801+
2802+
/*
2803+
* json_is_valid -- check json text validity, its value type and key uniqueness
2804+
*/
2805+
Datum
2806+
json_is_valid(PG_FUNCTION_ARGS)
2807+
{
2808+
text *json = PG_GETARG_TEXT_P(0);
2809+
text *type = PG_GETARG_TEXT_P(1);
2810+
bool unique = PG_GETARG_BOOL(2);
2811+
MemoryContext mcxt = CurrentMemoryContext;
2812+
2813+
if (PG_ARGISNULL(0))
2814+
PG_RETURN_NULL();
2815+
2816+
if (!PG_ARGISNULL(1) &&
2817+
strncmp("any", VARDATA(type), VARSIZE_ANY_EXHDR(type)))
2818+
{
2819+
JsonLexContext *lex;
2820+
JsonTokenType tok;
2821+
2822+
lex = makeJsonLexContext(json, false);
2823+
2824+
/* Lex exactly one token from the input and check its type. */
2825+
PG_TRY();
2826+
{
2827+
json_lex(lex);
2828+
}
2829+
PG_CATCH();
2830+
{
2831+
if (ERRCODE_TO_CATEGORY(geterrcode()) == ERRCODE_DATA_EXCEPTION)
2832+
{
2833+
FlushErrorState();
2834+
MemoryContextSwitchTo(mcxt);
2835+
PG_RETURN_BOOL(false); /* invalid json */
2836+
}
2837+
PG_RE_THROW();
2838+
}
2839+
PG_END_TRY();
2840+
2841+
tok = lex_peek(lex);
2842+
2843+
if (!strncmp("object", VARDATA(type), VARSIZE_ANY_EXHDR(type)))
2844+
{
2845+
if (tok != JSON_TOKEN_OBJECT_START)
2846+
PG_RETURN_BOOL(false); /* json is not a object */
2847+
}
2848+
else if (!strncmp("array", VARDATA(type), VARSIZE_ANY_EXHDR(type)))
2849+
{
2850+
if (tok != JSON_TOKEN_ARRAY_START)
2851+
PG_RETURN_BOOL(false); /* json is not an array */
2852+
}
2853+
else
2854+
{
2855+
if (tok == JSON_TOKEN_OBJECT_START ||
2856+
tok == JSON_TOKEN_ARRAY_START)
2857+
PG_RETURN_BOOL(false); /* json is not a scalar */
2858+
}
2859+
}
2860+
2861+
/* do full parsing pass only for uniqueness check or JSON text validation */
2862+
if (unique ||
2863+
get_fn_expr_argtype(fcinfo->flinfo, 0) != JSONOID)
2864+
{
2865+
JsonLexContext *lex = makeJsonLexContext(json, unique);
2866+
JsonSemAction uniqueSemAction = {0};
2867+
JsonUniqueState state;
2868+
2869+
if (unique)
2870+
{
2871+
state.lex = lex;
2872+
state.stack = NULL;
2873+
2874+
uniqueSemAction.semstate = &state;
2875+
uniqueSemAction.object_start = json_unique_object_start;
2876+
uniqueSemAction.object_field_start = json_unique_object_field_start;
2877+
uniqueSemAction.object_end = json_unique_object_end;
2878+
}
2879+
2880+
PG_TRY();
2881+
{
2882+
pg_parse_json(lex, unique ? &uniqueSemAction : &nullSemAction);
2883+
}
2884+
PG_CATCH();
2885+
{
2886+
if (ERRCODE_TO_CATEGORY(geterrcode()) == ERRCODE_DATA_EXCEPTION)
2887+
{
2888+
FlushErrorState();
2889+
MemoryContextSwitchTo(mcxt);
2890+
PG_RETURN_BOOL(false); /* invalid json or key collision found */
2891+
}
2892+
PG_RE_THROW();
2893+
}
2894+
PG_END_TRY();
2895+
}
2896+
2897+
PG_RETURN_BOOL(true); /* ok */
2898+
}
2899+
27132900
/*
27142901
* SQL function json_typeof(json) -> text
27152902
*

src/backend/utils/adt/jsonb.c

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2148,6 +2148,41 @@ jsonb_object_agg_finalfn(PG_FUNCTION_ARGS)
21482148
PG_RETURN_POINTER(out);
21492149
}
21502150

2151+
/*
2152+
* jsonb_is_valid -- check jsonb value type
2153+
*/
2154+
Datum
2155+
jsonb_is_valid(PG_FUNCTION_ARGS)
2156+
{
2157+
Jsonb *jb = PG_GETARG_JSONB_P(0);
2158+
text *type = PG_GETARG_TEXT_P(1);
2159+
2160+
if (PG_ARGISNULL(0))
2161+
PG_RETURN_NULL();
2162+
2163+
if (!PG_ARGISNULL(1) &&
2164+
strncmp("any", VARDATA(type), VARSIZE_ANY_EXHDR(type)))
2165+
{
2166+
if (!strncmp("object", VARDATA(type), VARSIZE_ANY_EXHDR(type)))
2167+
{
2168+
if (!JB_ROOT_IS_OBJECT(jb))
2169+
PG_RETURN_BOOL(false);
2170+
}
2171+
else if (!strncmp("array", VARDATA(type), VARSIZE_ANY_EXHDR(type)))
2172+
{
2173+
if (!JB_ROOT_IS_ARRAY(jb) || JB_ROOT_IS_SCALAR(jb))
2174+
PG_RETURN_BOOL(false);
2175+
}
2176+
else
2177+
{
2178+
if (!JB_ROOT_IS_ARRAY(jb) || !JB_ROOT_IS_SCALAR(jb))
2179+
PG_RETURN_BOOL(false);
2180+
}
2181+
}
2182+
2183+
PG_RETURN_BOOL(true);
2184+
}
2185+
21512186
JsonbValue *
21522187
JsonbExtractScalar(JsonbContainer *jbc, JsonbValue *res)
21532188
{

src/include/catalog/pg_proc.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4550,6 +4550,10 @@ DATA(insert OID = 3176 ( to_json PGNSP PGUID 12 1 0 0 0 f f f f t f s s 1 0
45504550
DESCR("map input to json");
45514551
DATA(insert OID = 3261 ( json_strip_nulls PGNSP PGUID 12 1 0 0 0 f f f f t f i s 1 0 114 "114" _null_ _null_ _null_ _null_ _null_ json_strip_nulls _null_ _null_ _null_ ));
45524552
DESCR("remove object fields with null values from json");
4553+
DATA(insert OID = 6060 ( json_is_valid PGNSP PGUID 12 1 0 0 0 f f f f t f i s 3 0 16 "114 25 16" _null_ _null_ _null_ _null_ _null_ json_is_valid _null_ _null_ _null_ ));
4554+
DESCR("check json value type and key uniqueness");
4555+
DATA(insert OID = 6061 ( json_is_valid PGNSP PGUID 12 1 0 0 0 f f f f t f i s 3 0 16 "25 25 16" _null_ _null_ _null_ _null_ _null_ json_is_valid _null_ _null_ _null_ ));
4556+
DESCR("check json text validity, value type and key uniqueness");
45534557

45544558
DATA(insert OID = 3947 ( json_object_field PGNSP PGUID 12 1 0 0 0 f f f f t f i s 2 0 114 "114 25" _null_ _null_ "{from_json, field_name}" _null_ _null_ json_object_field _null_ _null_ _null_ ));
45554559
DATA(insert OID = 3948 ( json_object_field_text PGNSP PGUID 12 1 0 0 0 f f f f t f i s 2 0 25 "114 25" _null_ _null_ "{from_json, field_name}" _null_ _null_ json_object_field_text _null_ _null_ _null_ ));
@@ -5019,6 +5023,9 @@ DATA(insert OID = 6067 ( jsonb_build_object_ext PGNSP PGUID 12 1 0 2276 0 f f f
50195023
DESCR("build a jsonb object from pairwise key/value inputs");
50205024
DATA(insert OID = 3262 ( jsonb_strip_nulls PGNSP PGUID 12 1 0 0 0 f f f f t f i s 1 0 3802 "3802" _null_ _null_ _null_ _null_ _null_ jsonb_strip_nulls _null_ _null_ _null_ ));
50215025
DESCR("remove object fields with null values from jsonb");
5026+
DATA(insert OID = 6062 ( jsonb_is_valid PGNSP PGUID 12 1 0 0 0 f f f f t f i s 2 0 16 "17 25" _null_ _null_ _null_ _null_ _null_ jsonb_is_valid _null_ _null_ _null_ ));
5027+
DESCR("check jsonb value type");
5028+
50225029

50235030
DATA(insert OID = 3478 ( jsonb_object_field PGNSP PGUID 12 1 0 0 0 f f f f t f i s 2 0 3802 "3802 25" _null_ _null_ "{from_json, field_name}" _null_ _null_ jsonb_object_field _null_ _null_ _null_ ));
50245031
DATA(insert OID = 3214 ( jsonb_object_field_text PGNSP PGUID 12 1 0 0 0 f f f f t f i s 2 0 25 "3802 25" _null_ _null_ "{from_json, field_name}" _null_ _null_ jsonb_object_field_text _null_ _null_ _null_ ));

0 commit comments

Comments
 (0)