1
0
mirror of https://github.com/postgres/postgres.git synced 2025-11-24 00:23:06 +03:00

SQL JSON functions

This Patch introduces three SQL standard JSON functions:

JSON() (incorrectly mentioned in my commit message for f4fb45d15c)
JSON_SCALAR()
JSON_SERIALIZE()

JSON() produces json values from text, bytea, json or jsonb values, and
has facilitites for handling duplicate keys.
JSON_SCALAR() produces a json value from any scalar sql value, including
json and jsonb.
JSON_SERIALIZE() produces text or bytea from input which containis or
represents json or jsonb;

For the most part these functions don't add any significant new
capabilities, but they will be of use to users wanting standard
compliant JSON handling.

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
This commit is contained in:
Andrew Dunstan
2022-03-03 13:15:13 -05:00
parent 8e053dc6df
commit 606948b058
22 changed files with 880 additions and 82 deletions

View File

@@ -294,6 +294,10 @@ format_type_extended(Oid type_oid, int32 typemod, bits16 flags)
else
buf = pstrdup("character varying");
break;
case JSONOID:
buf = pstrdup("json");
break;
}
if (buf == NULL)

View File

@@ -30,21 +30,6 @@
#include "utils/lsyscache.h"
#include "utils/typcache.h"
typedef enum /* type categories for datum_to_json */
{
JSONTYPE_NULL, /* null, so we didn't bother to identify */
JSONTYPE_BOOL, /* boolean (built-in types only) */
JSONTYPE_NUMERIC, /* numeric (ditto) */
JSONTYPE_DATE, /* we use special formatting for datetimes */
JSONTYPE_TIMESTAMP,
JSONTYPE_TIMESTAMPTZ,
JSONTYPE_JSON, /* JSON itself (and JSONB) */
JSONTYPE_ARRAY, /* array */
JSONTYPE_COMPOSITE, /* composite */
JSONTYPE_CAST, /* something with an explicit cast to JSON */
JSONTYPE_OTHER /* all else */
} JsonTypeCategory;
/* Common context for key uniqueness check */
typedef struct HTAB *JsonUniqueCheckState; /* hash table for key names */
@@ -99,9 +84,6 @@ static void array_dim_to_json(StringInfo result, int dim, int ndims, int *dims,
bool use_line_feeds);
static void array_to_json_internal(Datum array, StringInfo result,
bool use_line_feeds);
static void json_categorize_type(Oid typoid,
JsonTypeCategory *tcategory,
Oid *outfuncoid);
static void datum_to_json(Datum val, bool is_null, StringInfo result,
JsonTypeCategory tcategory, Oid outfuncoid,
bool key_scalar);
@@ -180,7 +162,7 @@ json_recv(PG_FUNCTION_ARGS)
* output function OID. If the returned category is JSONTYPE_CAST, we
* return the OID of the type->JSON cast function instead.
*/
static void
void
json_categorize_type(Oid typoid,
JsonTypeCategory *tcategory,
Oid *outfuncoid)
@@ -762,6 +744,16 @@ row_to_json_pretty(PG_FUNCTION_ARGS)
PG_RETURN_TEXT_P(cstring_to_text_with_len(result->data, result->len));
}
Datum
to_json_worker(Datum val, JsonTypeCategory tcategory, Oid outfuncoid)
{
StringInfo result = makeStringInfo();
datum_to_json(val, false, result, tcategory, outfuncoid, false);
return PointerGetDatum(cstring_to_text_with_len(result->data, result->len));
}
bool
to_json_is_immutable(Oid typoid)
{
@@ -802,7 +794,6 @@ to_json(PG_FUNCTION_ARGS)
{
Datum val = PG_GETARG_DATUM(0);
Oid val_type = get_fn_expr_argtype(fcinfo->flinfo, 0);
StringInfo result;
JsonTypeCategory tcategory;
Oid outfuncoid;
@@ -814,11 +805,7 @@ to_json(PG_FUNCTION_ARGS)
json_categorize_type(val_type,
&tcategory, &outfuncoid);
result = makeStringInfo();
datum_to_json(val, false, result, tcategory, outfuncoid, false);
PG_RETURN_TEXT_P(cstring_to_text_with_len(result->data, result->len));
PG_RETURN_DATUM(to_json_worker(val, tcategory, outfuncoid));
}
/*
@@ -1712,7 +1699,7 @@ json_unique_object_field_start(void *_state, char *field, bool isnull)
/* Validate JSON text and additionally check key uniqueness */
bool
json_validate(text *json, bool check_unique_keys)
json_validate(text *json, bool check_unique_keys, bool throw_error)
{
JsonLexContext *lex = makeJsonLexContext(json, check_unique_keys);
JsonSemAction uniqueSemAction = {0};
@@ -1736,10 +1723,22 @@ json_validate(text *json, bool check_unique_keys)
result = pg_parse_json(lex, check_unique_keys ? &uniqueSemAction : &nullSemAction);
if (result != JSON_SUCCESS)
{
if (throw_error)
json_ereport_error(result, lex);
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 */
}

View File

@@ -34,25 +34,9 @@ typedef struct JsonbInState
{
JsonbParseState *parseState;
JsonbValue *res;
bool unique_keys;
} JsonbInState;
/* unlike with json categories, we need to treat json and jsonb differently */
typedef enum /* type categories for datum_to_jsonb */
{
JSONBTYPE_NULL, /* null, so we didn't bother to identify */
JSONBTYPE_BOOL, /* boolean (built-in types only) */
JSONBTYPE_NUMERIC, /* numeric (ditto) */
JSONBTYPE_DATE, /* we use special formatting for datetimes */
JSONBTYPE_TIMESTAMP, /* we use special formatting for timestamp */
JSONBTYPE_TIMESTAMPTZ, /* ... and timestamptz */
JSONBTYPE_JSON, /* JSON */
JSONBTYPE_JSONB, /* JSONB */
JSONBTYPE_ARRAY, /* array */
JSONBTYPE_COMPOSITE, /* composite */
JSONBTYPE_JSONCAST, /* something with an explicit cast to JSON */
JSONBTYPE_OTHER /* all else */
} JsonbTypeCategory;
typedef struct JsonbAggState
{
JsonbInState *res;
@@ -62,7 +46,7 @@ typedef struct JsonbAggState
Oid val_output_func;
} JsonbAggState;
static inline Datum jsonb_from_cstring(char *json, int len);
static inline Datum jsonb_from_cstring(char *json, int len, bool unique_keys);
static size_t checkStringLen(size_t len);
static void jsonb_in_object_start(void *pstate);
static void jsonb_in_object_end(void *pstate);
@@ -71,17 +55,11 @@ static void jsonb_in_array_end(void *pstate);
static void jsonb_in_object_field_start(void *pstate, char *fname, bool isnull);
static void jsonb_put_escaped_value(StringInfo out, JsonbValue *scalarVal);
static void jsonb_in_scalar(void *pstate, char *token, JsonTokenType tokentype);
static void jsonb_categorize_type(Oid typoid,
JsonbTypeCategory *tcategory,
Oid *outfuncoid);
static void composite_to_jsonb(Datum composite, JsonbInState *result);
static void array_dim_to_jsonb(JsonbInState *result, int dim, int ndims, int *dims,
Datum *vals, bool *nulls, int *valcount,
JsonbTypeCategory tcategory, Oid outfuncoid);
static void array_to_jsonb_internal(Datum array, JsonbInState *result);
static void jsonb_categorize_type(Oid typoid,
JsonbTypeCategory *tcategory,
Oid *outfuncoid);
static void datum_to_jsonb(Datum val, bool is_null, JsonbInState *result,
JsonbTypeCategory tcategory, Oid outfuncoid,
bool key_scalar);
@@ -99,7 +77,7 @@ jsonb_in(PG_FUNCTION_ARGS)
{
char *json = PG_GETARG_CSTRING(0);
return jsonb_from_cstring(json, strlen(json));
return jsonb_from_cstring(json, strlen(json), false);
}
/*
@@ -123,7 +101,7 @@ jsonb_recv(PG_FUNCTION_ARGS)
else
elog(ERROR, "unsupported jsonb version number %d", version);
return jsonb_from_cstring(str, nbytes);
return jsonb_from_cstring(str, nbytes, false);
}
/*
@@ -164,6 +142,14 @@ jsonb_send(PG_FUNCTION_ARGS)
PG_RETURN_BYTEA_P(pq_endtypsend(&buf));
}
Datum
jsonb_from_text(text *js, bool unique_keys)
{
return jsonb_from_cstring(VARDATA_ANY(js),
VARSIZE_ANY_EXHDR(js),
unique_keys);
}
/*
* Get the type name of a jsonb container.
*/
@@ -254,7 +240,7 @@ jsonb_typeof(PG_FUNCTION_ARGS)
* Uses the json parser (with hooks) to construct a jsonb.
*/
static inline Datum
jsonb_from_cstring(char *json, int len)
jsonb_from_cstring(char *json, int len, bool unique_keys)
{
JsonLexContext *lex;
JsonbInState state;
@@ -264,6 +250,8 @@ jsonb_from_cstring(char *json, int len)
memset(&sem, 0, sizeof(sem));
lex = makeJsonLexContextCstringLen(json, len, GetDatabaseEncoding(), true);
state.unique_keys = unique_keys;
sem.semstate = (void *) &state;
sem.object_start = jsonb_in_object_start;
@@ -298,6 +286,7 @@ jsonb_in_object_start(void *pstate)
JsonbInState *_state = (JsonbInState *) pstate;
_state->res = pushJsonbValue(&_state->parseState, WJB_BEGIN_OBJECT, NULL);
_state->parseState->unique_keys = _state->unique_keys;
}
static void
@@ -620,7 +609,7 @@ add_indent(StringInfo out, bool indent, int level)
* output function OID. If the returned category is JSONBTYPE_JSONCAST,
* we return the OID of the relevant cast function instead.
*/
static void
void
jsonb_categorize_type(Oid typoid,
JsonbTypeCategory *tcategory,
Oid *outfuncoid)
@@ -1127,6 +1116,18 @@ add_jsonb(Datum val, bool is_null, JsonbInState *result,
datum_to_jsonb(val, is_null, result, tcategory, outfuncoid, key_scalar);
}
Datum
to_jsonb_worker(Datum val, JsonbTypeCategory tcategory, Oid outfuncoid)
{
JsonbInState result;
memset(&result, 0, sizeof(JsonbInState));
datum_to_jsonb(val, false, &result, tcategory, outfuncoid, false);
return JsonbPGetDatum(JsonbValueToJsonb(result.res));
}
bool
to_jsonb_is_immutable(Oid typoid)
{
@@ -1168,7 +1169,6 @@ to_jsonb(PG_FUNCTION_ARGS)
{
Datum val = PG_GETARG_DATUM(0);
Oid val_type = get_fn_expr_argtype(fcinfo->flinfo, 0);
JsonbInState result;
JsonbTypeCategory tcategory;
Oid outfuncoid;
@@ -1180,11 +1180,7 @@ to_jsonb(PG_FUNCTION_ARGS)
jsonb_categorize_type(val_type,
&tcategory, &outfuncoid);
memset(&result, 0, sizeof(JsonbInState));
datum_to_jsonb(val, false, &result, tcategory, outfuncoid, false);
PG_RETURN_POINTER(JsonbValueToJsonb(result.res));
PG_RETURN_DATUM(to_jsonb_worker(val, tcategory, outfuncoid));
}
Datum

View File

@@ -10092,7 +10092,9 @@ get_json_constructor_options(JsonConstructorExpr *ctor, StringInfo buf)
if (ctor->unique)
appendStringInfoString(buf, " WITH UNIQUE KEYS");
get_json_returning(ctor->returning, buf, true);
if (ctor->type != JSCTOR_JSON_PARSE &&
ctor->type != JSCTOR_JSON_SCALAR)
get_json_returning(ctor->returning, buf, true);
}
static void
@@ -10106,6 +10108,15 @@ get_json_constructor(JsonConstructorExpr *ctor, deparse_context *context,
switch (ctor->type)
{
case JSCTOR_JSON_PARSE:
funcname = "JSON";
break;
case JSCTOR_JSON_SCALAR:
funcname = "JSON_SCALAR";
break;
case JSCTOR_JSON_SERIALIZE:
funcname = "JSON_SERIALIZE";
break;
case JSCTOR_JSON_OBJECT:
funcname = "JSON_OBJECT";
break;