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

Commit 7320b4c

Browse files
author
Nikita Glukhov
committed
Add IS JSON predicate transformation
1 parent 980c6a8 commit 7320b4c

File tree

7 files changed

+645
-1
lines changed

7 files changed

+645
-1
lines changed

src/backend/parser/parse_expr.c

Lines changed: 101 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));
@@ -3981,3 +3986,99 @@ transformJsonArrayCtor(ParseState *pstate, JsonArrayCtor *ctor)
39813986

39823987
return coerceJsonFuncExpr(pstate, (Node *) fexpr, ctor->output);
39833988
}
3989+
3990+
static const char *
3991+
JsonValueTypeStrings[] =
3992+
{
3993+
"any",
3994+
"object",
3995+
"array",
3996+
"scalar",
3997+
};
3998+
3999+
static Const *
4000+
makeJsonValueTypeConst(JsonValueType type)
4001+
{
4002+
return makeConst(TEXTOID, -1, InvalidOid, -1,
4003+
PointerGetDatum(cstring_to_text(
4004+
JsonValueTypeStrings[(int) type])),
4005+
false, false);
4006+
}
4007+
4008+
static Node *
4009+
transformJsonIsPredicate(ParseState *pstate, JsonIsPredicate *pred)
4010+
{
4011+
Node *expr = transformExprRecurse(pstate, pred->expr);
4012+
Oid exprtype = exprType(expr);
4013+
4014+
if (exprtype == BYTEAOID)
4015+
{
4016+
if (pred->format.type != JS_FORMAT_JSONB)
4017+
{
4018+
expr = makeJsonByteaToTextConversion(expr, &pred->format,
4019+
exprLocation(expr));
4020+
exprtype = TEXTOID;
4021+
}
4022+
}
4023+
else
4024+
{
4025+
char typcategory;
4026+
bool typispreferred;
4027+
4028+
get_type_category_preferred(exprtype, &typcategory, &typispreferred);
4029+
4030+
if (exprtype == UNKNOWNOID || typcategory == TYPCATEGORY_STRING)
4031+
{
4032+
expr = coerce_to_target_type(pstate, (Node *) expr, exprtype,
4033+
TEXTOID, -1,
4034+
COERCION_IMPLICIT,
4035+
COERCE_IMPLICIT_CAST, -1);
4036+
exprtype = TEXTOID;
4037+
}
4038+
4039+
if (pred->format.type == JS_FORMAT_JSONB)
4040+
ereport(ERROR,
4041+
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
4042+
parser_errposition(pstate, pred->format.location),
4043+
errmsg("cannot use FORMAT JSONB for string input types")));
4044+
4045+
if (pred->format.encoding != JS_ENC_DEFAULT)
4046+
ereport(ERROR,
4047+
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
4048+
parser_errposition(pstate, pred->format.location),
4049+
errmsg("cannot use JSON FORMAT ENCODING clause for non-bytea input types")));
4050+
}
4051+
4052+
if (exprtype == TEXTOID || exprtype == JSONOID)
4053+
{
4054+
FuncExpr *fexpr = makeFuncExpr(F_JSON_IS_VALID, BOOLOID,
4055+
list_make3(expr,
4056+
makeJsonValueTypeConst(pred->vtype),
4057+
makeBoolConst(pred->unique_keys,
4058+
false)),
4059+
InvalidOid, InvalidOid,
4060+
COERCE_EXPLICIT_CALL);
4061+
4062+
fexpr->location = pred->location;
4063+
return (Node *) fexpr;
4064+
}
4065+
else if (exprtype == JSONBOID || exprtype == BYTEAOID)
4066+
{
4067+
FuncExpr *fexpr = makeFuncExpr(F_JSONB_IS_VALID, BOOLOID,
4068+
list_make2(expr,
4069+
makeJsonValueTypeConst(pred->vtype)),
4070+
InvalidOid, InvalidOid,
4071+
COERCE_EXPLICIT_CALL);
4072+
4073+
fexpr->location = pred->location;
4074+
return (Node *) fexpr;
4075+
}
4076+
else
4077+
{
4078+
ereport(ERROR,
4079+
(errcode(ERRCODE_DATATYPE_MISMATCH),
4080+
errmsg("cannot use type %s in IS JSON predicate",
4081+
format_type_be(exprtype))));
4082+
return NULL;
4083+
}
4084+
}

src/backend/utils/adt/json.c

Lines changed: 175 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"
@@ -2732,6 +2733,180 @@ escape_json(StringInfo buf, const char *str)
27322733
appendStringInfoCharMacro(buf, '"');
27332734
}
27342735

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

src/backend/utils/adt/jsonb.c

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2190,6 +2190,45 @@ jsonb_object_agg_finalfn(PG_FUNCTION_ARGS)
21902190
PG_RETURN_POINTER(out);
21912191
}
21922192

2193+
/*
2194+
* jsonb_is_valid -- check bytea jsonb validity and its value type
2195+
*/
2196+
Datum
2197+
jsonb_is_valid(PG_FUNCTION_ARGS)
2198+
{
2199+
bytea *ba = PG_GETARG_BYTEA_P(0);
2200+
text *type = PG_GETARG_TEXT_P(1);
2201+
Jsonb *jb = (Jsonb *) ba;
2202+
2203+
if (PG_ARGISNULL(0))
2204+
PG_RETURN_BOOL(false);
2205+
2206+
if (!JsonbValidate(VARDATA(jb), VARSIZE(jb) - VARHDRSZ))
2207+
PG_RETURN_BOOL(false);
2208+
2209+
if (!PG_ARGISNULL(1) &&
2210+
strncmp("any", VARDATA(type), VARSIZE_ANY_EXHDR(type)))
2211+
{
2212+
if (!strncmp("object", VARDATA(type), VARSIZE_ANY_EXHDR(type)))
2213+
{
2214+
if (!JB_ROOT_IS_OBJECT(jb))
2215+
PG_RETURN_BOOL(false);
2216+
}
2217+
else if (!strncmp("array", VARDATA(type), VARSIZE_ANY_EXHDR(type)))
2218+
{
2219+
if (!JB_ROOT_IS_ARRAY(jb) || JB_ROOT_IS_SCALAR(jb))
2220+
PG_RETURN_BOOL(false);
2221+
}
2222+
else
2223+
{
2224+
if (!JB_ROOT_IS_ARRAY(jb) || !JB_ROOT_IS_SCALAR(jb))
2225+
PG_RETURN_BOOL(false);
2226+
}
2227+
}
2228+
2229+
PG_RETURN_BOOL(true);
2230+
}
2231+
21932232
JsonbValue *
21942233
JsonbExtractScalar(JsonbContainer *jbc, JsonbValue *res)
21952234
{

src/include/catalog/pg_proc.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4505,6 +4505,10 @@ DATA(insert OID = 3176 ( to_json PGNSP PGUID 12 1 0 0 0 f f f f t f s s 1 0
45054505
DESCR("map input to json");
45064506
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_ ));
45074507
DESCR("remove object fields with null values from json");
4508+
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_ ));
4509+
DESCR("check json value type and key uniqueness");
4510+
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_ ));
4511+
DESCR("check json text validity, value type and key uniqueness");
45084512

45094513
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_ ));
45104514
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_ ));
@@ -4972,6 +4976,9 @@ DATA(insert OID = 6067 ( jsonb_build_object_ext PGNSP PGUID 12 1 0 2276 0 f f f
49724976
DESCR("build a jsonb object from pairwise key/value inputs");
49734977
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_ ));
49744978
DESCR("remove object fields with null values from jsonb");
4979+
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_ ));
4980+
DESCR("check jsonb bytea validity and value type");
4981+
49754982

49764983
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_ ));
49774984
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_ ));

src/test/regress/expected/opr_sanity.out

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -205,11 +205,12 @@ WHERE p1.oid != p2.oid AND
205205
ORDER BY 1, 2;
206206
proargtypes | proargtypes
207207
-------------+-------------
208+
25 | 114
208209
25 | 1042
209210
25 | 1043
210211
1114 | 1184
211212
1560 | 1562
212-
(4 rows)
213+
(5 rows)
213214

214215
SELECT DISTINCT p1.proargtypes[1], p2.proargtypes[1]
215216
FROM pg_proc AS p1, pg_proc AS p2

0 commit comments

Comments
 (0)