From 8a06567bdc249ccf6bee91c51cd1fa734170f758 Mon Sep 17 00:00:00 2001 From: Haibo Yan Date: Thu, 2 Apr 2026 21:22:54 -0700 Subject: [PATCH v5 1/5] jsonb: optimize object-field casts to scalar types Extend the existing support-function rewrite for jsonb object-field extraction, including jsonb_object_field(), ->, and key subscripting. This keeps ordinary SQL syntax unchanged and rewrites supported casts directly to explicit typed extractor functions for numeric, bool, int2, int4, int8, float4, and float8. Co-authored-by: Andy Fan --- src/backend/utils/adt/jsonb.c | 154 ++++++++- src/backend/utils/adt/jsonfuncs.c | 195 ++++++++++- src/include/catalog/pg_proc.dat | 55 +++- src/test/regress/expected/jsonb.out | 485 +++++++++++++++++++++++++++- src/test/regress/sql/jsonb.sql | 120 ++++++- 5 files changed, 995 insertions(+), 14 deletions(-) diff --git a/src/backend/utils/adt/jsonb.c b/src/backend/utils/adt/jsonb.c index 864c5ac1c85..c85d4882f22 100644 --- a/src/backend/utils/adt/jsonb.c +++ b/src/backend/utils/adt/jsonb.c @@ -17,6 +17,9 @@ #include "funcapi.h" #include "libpq/pqformat.h" #include "miscadmin.h" +#include "nodes/makefuncs.h" +#include "nodes/nodeFuncs.h" +#include "nodes/supportnodes.h" #include "utils/builtins.h" #include "utils/fmgroids.h" #include "utils/json.h" @@ -1816,6 +1819,155 @@ cannotCastJsonbValue(enum jbvType type, const char *sqltype, Node *escontext) return (Datum) 0; } +/* + * jsonb_cast_support() + * + * Planner support function for jsonb-to-scalar cast functions, attached via + * prosupport on the jsonb_numeric, jsonb_bool, jsonb_int4, jsonb_int8, and + * jsonb_float8 catalog entries. + * + * When the sole argument to the cast is a jsonb_object_field() call (the -> + * operator), we replace the two-step cast(extract(...)) expression with a + * single typed extractor that reads the scalar directly from the in-memory + * JsonbValue, avoiding a round-trip through JsonbValueToJsonb. + * + * For example, (j -> 'a')::numeric is parsed as: + * jsonb_numeric(jsonb_object_field(j, 'a')) + * and is rewritten to: + * jsonb_object_field_numeric(j, 'a') + */ +Datum +jsonb_cast_support(PG_FUNCTION_ARGS) +{ + Node *rawreq = (Node *) PG_GETARG_POINTER(0); + Node *ret = NULL; + + if (IsA(rawreq, SupportRequestSimplify)) + { + SupportRequestSimplify *req = (SupportRequestSimplify *) rawreq; + FuncExpr *fexpr = req->fcall; + Node *arg; + Oid inner_funcid; + List *inner_args; + int location; + Oid replacement_funcid; + Oid replacement_rettype; + FuncExpr *newfexpr; + + /* The cast function must have exactly one argument */ + if (list_length(fexpr->args) != 1) + PG_RETURN_POINTER(NULL); + + arg = (Node *) linitial(fexpr->args); + + /* + * Identify the inner extraction expression. It may appear as a + * FuncExpr, an OpExpr, or a SubscriptingRef, depending on how the + * expression is represented at this point. Accept the supported + * forms. + */ + if (IsA(arg, FuncExpr)) + { + FuncExpr *inner = (FuncExpr *) arg; + + inner_funcid = inner->funcid; + inner_args = inner->args; + location = inner->location; + } + else if (IsA(arg, OpExpr)) + { + OpExpr *inner = (OpExpr *) arg; + + inner_funcid = inner->opfuncid; + inner_args = inner->args; + location = inner->location; + } + else if (IsA(arg, SubscriptingRef)) + { + SubscriptingRef *sbsref = (SubscriptingRef *) arg; + Node *subscript; + + /* + * Only handle the narrow case equivalent to object-field + * extraction: a single text-typed subscript on a jsonb + * container, with no slice and no assignment. + */ + if (sbsref->refcontainertype != JSONBOID) + PG_RETURN_POINTER(NULL); + if (list_length(sbsref->refupperindexpr) != 1) + PG_RETURN_POINTER(NULL); + if (sbsref->reflowerindexpr != NIL) + PG_RETURN_POINTER(NULL); + if (sbsref->refassgnexpr != NULL) + PG_RETURN_POINTER(NULL); + + subscript = (Node *) linitial(sbsref->refupperindexpr); + if (exprType(subscript) != TEXTOID) + PG_RETURN_POINTER(NULL); + + inner_funcid = F_JSONB_OBJECT_FIELD; + inner_args = list_make2(sbsref->refexpr, subscript); + location = exprLocation(arg); + } + else + PG_RETURN_POINTER(NULL); + + /* Only rewrite jsonb_object_field(jsonb, text); verify arity too */ + if (inner_funcid != F_JSONB_OBJECT_FIELD) + PG_RETURN_POINTER(NULL); + if (list_length(inner_args) != 2) + PG_RETURN_POINTER(NULL); + + /* Map the outer cast to the corresponding typed extractor */ + if (fexpr->funcid == F_NUMERIC_JSONB) + { + replacement_funcid = F_JSONB_OBJECT_FIELD_NUMERIC; + replacement_rettype = NUMERICOID; + } + else if (fexpr->funcid == F_BOOL_JSONB) + { + replacement_funcid = F_JSONB_OBJECT_FIELD_BOOL; + replacement_rettype = BOOLOID; + } + else if (fexpr->funcid == F_INT4_JSONB) + { + replacement_funcid = F_JSONB_OBJECT_FIELD_INT4; + replacement_rettype = INT4OID; + } + else if (fexpr->funcid == F_INT8_JSONB) + { + replacement_funcid = F_JSONB_OBJECT_FIELD_INT8; + replacement_rettype = INT8OID; + } + else if (fexpr->funcid == F_FLOAT8_JSONB) + { + replacement_funcid = F_JSONB_OBJECT_FIELD_FLOAT8; + replacement_rettype = FLOAT8OID; + } + else if (fexpr->funcid == F_INT2_JSONB) + { + replacement_funcid = F_JSONB_OBJECT_FIELD_INT2; + replacement_rettype = INT2OID; + } + else if (fexpr->funcid == F_FLOAT4_JSONB) + { + replacement_funcid = F_JSONB_OBJECT_FIELD_FLOAT4; + replacement_rettype = FLOAT4OID; + } + else + PG_RETURN_POINTER(NULL); + + /* Build the replacement function call */ + newfexpr = makeFuncExpr(replacement_funcid, replacement_rettype, + inner_args, InvalidOid, InvalidOid, + COERCE_EXPLICIT_CALL); + newfexpr->location = location; + ret = (Node *) newfexpr; + } + + PG_RETURN_POINTER(ret); +} + Datum jsonb_bool(PG_FUNCTION_ARGS) { @@ -2033,4 +2185,4 @@ JsonbUnquote(Jsonb *jb) } else return JsonbToCString(NULL, &jb->root, VARSIZE(jb)); -} +} \ No newline at end of file diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c index 97cc3d60340..c260577d895 100644 --- a/src/backend/utils/adt/jsonfuncs.c +++ b/src/backend/utils/adt/jsonfuncs.c @@ -921,6 +921,199 @@ jsonb_object_field_text(PG_FUNCTION_ARGS) PG_RETURN_NULL(); } +/* + * Typed scalar extraction from jsonb object fields. + * + * These functions extract a typed scalar directly from the in-memory + * JsonbValue found by key lookup, skipping the intermediate Jsonb + * serialization that occurs with the unoptimized cast-over-extraction path. + * + * They live here alongside jsonb_object_field() because they share the + * same key-lookup logic (getKeyJsonValueFromContainer). + * + * Registered in pg_proc as ordinary SQL-callable builtins; also serve as + * planner rewrite targets for jsonb_cast_support() in jsonb.c. + * + * NULL semantics match the existing cast path: a missing key or a JSON + * null value both produce SQL NULL; a type mismatch raises ERROR. + */ + +/* + * Look up a key in a jsonb object and return the JsonbValue, or NULL. + * Returns NULL (without error) when the input is not an object, the key + * is absent, or the value is JSON null. + */ +static JsonbValue * +jsonb_object_field_lookup(Jsonb *jb, text *key, JsonbValue *vbuf) +{ + JsonbValue *v; + + if (!JB_ROOT_IS_OBJECT(jb)) + return NULL; + + v = getKeyJsonValueFromContainer(&jb->root, + VARDATA_ANY(key), + VARSIZE_ANY_EXHDR(key), + vbuf); + + /* Missing key or JSON null both map to SQL NULL */ + if (v == NULL || v->type == jbvNull) + return NULL; + + return v; +} + +/* + * Raise a type-mismatch error for typed field extraction. + * + * The message wording matches cannotCastJsonbValue() in jsonb.c so that + * the optimized and unoptimized paths produce identical errors. + */ +static void +jsonb_field_cast_error(JsonbValue *v, const char *sqltype) +{ + const char *jsontype; + + switch (v->type) + { + case jbvNull: + jsontype = "null"; + break; + case jbvString: + jsontype = "string"; + break; + case jbvNumeric: + jsontype = "numeric"; + break; + case jbvBool: + jsontype = "boolean"; + break; + case jbvArray: + jsontype = "array"; + break; + case jbvObject: + jsontype = "object"; + break; + case jbvBinary: + jsontype = "array or object"; + break; + default: + elog(ERROR, "unknown jsonb type: %d", (int) v->type); + jsontype = NULL; /* keep compiler quiet */ + } + + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("cannot cast jsonb %s to type %s", + jsontype, sqltype))); +} + +/* + * Per-type conversion helpers for typed field extraction. + * + * Each validates the expected JsonbValue type, raises a type-mismatch + * error (via jsonb_field_cast_error) if wrong, and returns the converted + * value as Datum. These are the only place where conversion semantics + * live; the wrapper macro below is intentionally kept thin. + */ +static Datum +jsonb_value_to_numeric_datum(JsonbValue *v) +{ + if (v->type != jbvNumeric) + jsonb_field_cast_error(v, "numeric"); + + return NumericGetDatum(DatumGetNumericCopy(NumericGetDatum(v->val.numeric))); +} + +static Datum +jsonb_value_to_bool_datum(JsonbValue *v) +{ + if (v->type != jbvBool) + jsonb_field_cast_error(v, "boolean"); + + return BoolGetDatum(v->val.boolean); +} + +static Datum +jsonb_value_to_int4_datum(JsonbValue *v) +{ + if (v->type != jbvNumeric) + jsonb_field_cast_error(v, "integer"); + + return DirectFunctionCall1(numeric_int4, NumericGetDatum(v->val.numeric)); +} + +static Datum +jsonb_value_to_int8_datum(JsonbValue *v) +{ + if (v->type != jbvNumeric) + jsonb_field_cast_error(v, "bigint"); + + return DirectFunctionCall1(numeric_int8, NumericGetDatum(v->val.numeric)); +} + +static Datum +jsonb_value_to_float8_datum(JsonbValue *v) +{ + if (v->type != jbvNumeric) + jsonb_field_cast_error(v, "double precision"); + + return DirectFunctionCall1(numeric_float8, NumericGetDatum(v->val.numeric)); +} + +static Datum +jsonb_value_to_int2_datum(JsonbValue *v) +{ + if (v->type != jbvNumeric) + jsonb_field_cast_error(v, "smallint"); + + return DirectFunctionCall1(numeric_int2, NumericGetDatum(v->val.numeric)); +} + +static Datum +jsonb_value_to_float4_datum(JsonbValue *v) +{ + if (v->type != jbvNumeric) + jsonb_field_cast_error(v, "real"); + + return DirectFunctionCall1(numeric_float4, NumericGetDatum(v->val.numeric)); +} + +/* + * Thin-wrapper macro for the jsonb_object_field_ extractor family. + * Reduces repetition: each wrapper does lookup, NULL handling, and delegates + * to a type-specific conversion helper that holds the actual semantics. + */ +#define DEFINE_JSONB_OBJECT_FIELD_TYPED(fname, convfn) \ +Datum \ +fname(PG_FUNCTION_ARGS) \ +{ \ + Jsonb *jb = PG_GETARG_JSONB_P(0); \ + text *key = PG_GETARG_TEXT_PP(1); \ + JsonbValue vbuf; \ + JsonbValue *v; \ + Datum result; \ +\ + v = jsonb_object_field_lookup(jb, key, &vbuf); \ + if (v == NULL) \ + { \ + PG_FREE_IF_COPY(jb, 0); \ + PG_RETURN_NULL(); \ + } \ +\ + result = convfn(v); \ + PG_FREE_IF_COPY(jb, 0); \ + return result; \ +} + +DEFINE_JSONB_OBJECT_FIELD_TYPED(jsonb_object_field_numeric, jsonb_value_to_numeric_datum) +DEFINE_JSONB_OBJECT_FIELD_TYPED(jsonb_object_field_bool, jsonb_value_to_bool_datum) +DEFINE_JSONB_OBJECT_FIELD_TYPED(jsonb_object_field_int4, jsonb_value_to_int4_datum) +DEFINE_JSONB_OBJECT_FIELD_TYPED(jsonb_object_field_int8, jsonb_value_to_int8_datum) +DEFINE_JSONB_OBJECT_FIELD_TYPED(jsonb_object_field_float8, jsonb_value_to_float8_datum) +DEFINE_JSONB_OBJECT_FIELD_TYPED(jsonb_object_field_int2, jsonb_value_to_int2_datum) +DEFINE_JSONB_OBJECT_FIELD_TYPED(jsonb_object_field_float4, jsonb_value_to_float4_datum) + Datum json_array_element(PG_FUNCTION_ARGS) { @@ -6167,4 +6360,4 @@ json_check_mutability(Oid typoid, bool is_jsonb, bool *has_mutable) *has_mutable = true; break; } -} +} \ No newline at end of file diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..406e08dffdd 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -4798,25 +4798,30 @@ prosrc => 'numeric_pg_lsn' }, { oid => '3556', descr => 'convert jsonb to boolean', - proname => 'bool', prorettype => 'bool', proargtypes => 'jsonb', + proname => 'bool', prosupport => 'jsonb_cast_support', prorettype => 'bool', proargtypes => 'jsonb', prosrc => 'jsonb_bool' }, { oid => '3449', descr => 'convert jsonb to numeric', - proname => 'numeric', prorettype => 'numeric', proargtypes => 'jsonb', + proname => 'numeric', prosupport => 'jsonb_cast_support', prorettype => 'numeric', proargtypes => 'jsonb', prosrc => 'jsonb_numeric' }, { oid => '3450', descr => 'convert jsonb to int2', - proname => 'int2', prorettype => 'int2', proargtypes => 'jsonb', + proname => 'int2', prosupport => 'jsonb_cast_support', + prorettype => 'int2', proargtypes => 'jsonb', prosrc => 'jsonb_int2' }, { oid => '3451', descr => 'convert jsonb to int4', - proname => 'int4', prorettype => 'int4', proargtypes => 'jsonb', + proname => 'int4', prosupport => 'jsonb_cast_support', + prorettype => 'int4', proargtypes => 'jsonb', prosrc => 'jsonb_int4' }, { oid => '3452', descr => 'convert jsonb to int8', - proname => 'int8', prorettype => 'int8', proargtypes => 'jsonb', + proname => 'int8', prosupport => 'jsonb_cast_support', + prorettype => 'int8', proargtypes => 'jsonb', prosrc => 'jsonb_int8' }, { oid => '3453', descr => 'convert jsonb to float4', - proname => 'float4', prorettype => 'float4', proargtypes => 'jsonb', + proname => 'float4', prosupport => 'jsonb_cast_support', + prorettype => 'float4', proargtypes => 'jsonb', prosrc => 'jsonb_float4' }, { oid => '2580', descr => 'convert jsonb to float8', - proname => 'float8', prorettype => 'float8', proargtypes => 'jsonb', + proname => 'float8', prosupport => 'jsonb_cast_support', + prorettype => 'float8', proargtypes => 'jsonb', prosrc => 'jsonb_float8' }, # formatting @@ -12769,4 +12774,38 @@ proname => 'hashoid8extended', prorettype => 'int8', proargtypes => 'oid8 int8', prosrc => 'hashoid8extended' }, -] + +# jsonb cast optimization support functions +{ oid => '9950', descr => 'planner support for jsonb casts', + proname => 'jsonb_cast_support', prorettype => 'internal', + proargtypes => 'internal', prosrc => 'jsonb_cast_support' }, +{ oid => '9953', descr => 'extract numeric from jsonb object by field name', + proname => 'jsonb_object_field_numeric', prorettype => 'numeric', + proargtypes => 'jsonb text', proargnames => '{from_json,field_name}', + prosrc => 'jsonb_object_field_numeric' }, +{ oid => '9954', descr => 'extract boolean from jsonb object by field name', + proname => 'jsonb_object_field_bool', prorettype => 'bool', + proargtypes => 'jsonb text', proargnames => '{from_json,field_name}', + prosrc => 'jsonb_object_field_bool' }, +{ oid => '9955', descr => 'extract int4 from jsonb object by field name', + proname => 'jsonb_object_field_int4', prorettype => 'int4', + proargtypes => 'jsonb text', proargnames => '{from_json,field_name}', + prosrc => 'jsonb_object_field_int4' }, +{ oid => '9956', descr => 'extract int8 from jsonb object by field name', + proname => 'jsonb_object_field_int8', prorettype => 'int8', + proargtypes => 'jsonb text', proargnames => '{from_json,field_name}', + prosrc => 'jsonb_object_field_int8' }, +{ oid => '9957', descr => 'extract float8 from jsonb object by field name', + proname => 'jsonb_object_field_float8', prorettype => 'float8', + proargtypes => 'jsonb text', proargnames => '{from_json,field_name}', + prosrc => 'jsonb_object_field_float8' }, +{ oid => '9980', descr => 'extract int2 from jsonb object by field name', + proname => 'jsonb_object_field_int2', prorettype => 'int2', + proargtypes => 'jsonb text', proargnames => '{from_json,field_name}', + prosrc => 'jsonb_object_field_int2' }, +{ oid => '9981', descr => 'extract float4 from jsonb object by field name', + proname => 'jsonb_object_field_float4', prorettype => 'float4', + proargtypes => 'jsonb text', proargnames => '{from_json,field_name}', + prosrc => 'jsonb_object_field_float4' }, + +] \ No newline at end of file diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out index 4e2467852db..c31fb120dd8 100644 --- a/src/test/regress/expected/jsonb.out +++ b/src/test/regress/expected/jsonb.out @@ -458,7 +458,487 @@ CREATE TEMP TABLE test_jsonb ( INSERT INTO test_jsonb VALUES ('scalar','"a scalar"'), ('array','["zero", "one","two",null,"four","five", [1,2,3],{"f1":9}]'), -('object','{"field1":"val1","field2":"val2","field3":null, "field4": 4, "field5": [1,2,3], "field6": {"f1":9}}'); +('object','{"field1":"val1","field2":"val2","field3":null, "field4": 4, "field5": [1,2,3], "field6": {"f1":9}, "field7": true}'); +-- Optimized typed extraction: the planner rewrites (j->'key')::type into a +-- direct typed extractor call, currently for numeric, bool, int2, int4, int8, +-- float4, float8. +-- Section 1: planner rewrite verification (rewritten targets) +EXPLAIN (VERBOSE, COSTS OFF) SELECT (test_json -> 'field4')::numeric FROM test_jsonb WHERE json_type = 'object'; + QUERY PLAN +----------------------------------------------------------------- + Seq Scan on pg_temp.test_jsonb + Output: jsonb_object_field_numeric(test_json, 'field4'::text) + Filter: (test_jsonb.json_type = 'object'::text) +(3 rows) + +EXPLAIN (VERBOSE, COSTS OFF) SELECT (test_json -> 'field7')::bool FROM test_jsonb WHERE json_type = 'object'; + QUERY PLAN +-------------------------------------------------------------- + Seq Scan on pg_temp.test_jsonb + Output: jsonb_object_field_bool(test_json, 'field7'::text) + Filter: (test_jsonb.json_type = 'object'::text) +(3 rows) + +EXPLAIN (VERBOSE, COSTS OFF) SELECT (test_json -> 'field4')::int4 FROM test_jsonb WHERE json_type = 'object'; + QUERY PLAN +-------------------------------------------------------------- + Seq Scan on pg_temp.test_jsonb + Output: jsonb_object_field_int4(test_json, 'field4'::text) + Filter: (test_jsonb.json_type = 'object'::text) +(3 rows) + +EXPLAIN (VERBOSE, COSTS OFF) SELECT (test_json -> 'field4')::int8 FROM test_jsonb WHERE json_type = 'object'; + QUERY PLAN +-------------------------------------------------------------- + Seq Scan on pg_temp.test_jsonb + Output: jsonb_object_field_int8(test_json, 'field4'::text) + Filter: (test_jsonb.json_type = 'object'::text) +(3 rows) + +EXPLAIN (VERBOSE, COSTS OFF) SELECT (test_json -> 'field4')::float8 FROM test_jsonb WHERE json_type = 'object'; + QUERY PLAN +---------------------------------------------------------------- + Seq Scan on pg_temp.test_jsonb + Output: jsonb_object_field_float8(test_json, 'field4'::text) + Filter: (test_jsonb.json_type = 'object'::text) +(3 rows) + +EXPLAIN (VERBOSE, COSTS OFF) SELECT (test_json -> 'field4')::int2 FROM test_jsonb WHERE json_type = 'object'; + QUERY PLAN +-------------------------------------------------------------- + Seq Scan on pg_temp.test_jsonb + Output: jsonb_object_field_int2(test_json, 'field4'::text) + Filter: (test_jsonb.json_type = 'object'::text) +(3 rows) + +EXPLAIN (VERBOSE, COSTS OFF) SELECT (test_json -> 'field4')::float4 FROM test_jsonb WHERE json_type = 'object'; + QUERY PLAN +---------------------------------------------------------------- + Seq Scan on pg_temp.test_jsonb + Output: jsonb_object_field_float4(test_json, 'field4'::text) + Filter: (test_jsonb.json_type = 'object'::text) +(3 rows) + +-- Section 1b: planner rewrite verification for subscripting syntax +EXPLAIN (VERBOSE, COSTS OFF) SELECT (test_json['field4'])::numeric FROM test_jsonb WHERE json_type = 'object'; + QUERY PLAN +----------------------------------------------------------------- + Seq Scan on pg_temp.test_jsonb + Output: jsonb_object_field_numeric(test_json, 'field4'::text) + Filter: (test_jsonb.json_type = 'object'::text) +(3 rows) + +EXPLAIN (VERBOSE, COSTS OFF) SELECT (test_json['field7'])::bool FROM test_jsonb WHERE json_type = 'object'; + QUERY PLAN +-------------------------------------------------------------- + Seq Scan on pg_temp.test_jsonb + Output: jsonb_object_field_bool(test_json, 'field7'::text) + Filter: (test_jsonb.json_type = 'object'::text) +(3 rows) + +EXPLAIN (VERBOSE, COSTS OFF) SELECT (test_json['field4'])::int4 FROM test_jsonb WHERE json_type = 'object'; + QUERY PLAN +-------------------------------------------------------------- + Seq Scan on pg_temp.test_jsonb + Output: jsonb_object_field_int4(test_json, 'field4'::text) + Filter: (test_jsonb.json_type = 'object'::text) +(3 rows) + +EXPLAIN (VERBOSE, COSTS OFF) SELECT (test_json['field4'])::int8 FROM test_jsonb WHERE json_type = 'object'; + QUERY PLAN +-------------------------------------------------------------- + Seq Scan on pg_temp.test_jsonb + Output: jsonb_object_field_int8(test_json, 'field4'::text) + Filter: (test_jsonb.json_type = 'object'::text) +(3 rows) + +EXPLAIN (VERBOSE, COSTS OFF) SELECT (test_json['field4'])::float8 FROM test_jsonb WHERE json_type = 'object'; + QUERY PLAN +---------------------------------------------------------------- + Seq Scan on pg_temp.test_jsonb + Output: jsonb_object_field_float8(test_json, 'field4'::text) + Filter: (test_jsonb.json_type = 'object'::text) +(3 rows) + +EXPLAIN (VERBOSE, COSTS OFF) SELECT (test_json['field4'])::int2 FROM test_jsonb WHERE json_type = 'object'; + QUERY PLAN +-------------------------------------------------------------- + Seq Scan on pg_temp.test_jsonb + Output: jsonb_object_field_int2(test_json, 'field4'::text) + Filter: (test_jsonb.json_type = 'object'::text) +(3 rows) + +EXPLAIN (VERBOSE, COSTS OFF) SELECT (test_json['field4'])::float4 FROM test_jsonb WHERE json_type = 'object'; + QUERY PLAN +---------------------------------------------------------------- + Seq Scan on pg_temp.test_jsonb + Output: jsonb_object_field_float4(test_json, 'field4'::text) + Filter: (test_jsonb.json_type = 'object'::text) +(3 rows) + +-- Section 2: correct execution through the rewritten path +SELECT (test_json -> 'field4')::numeric FROM test_jsonb WHERE json_type = 'object'; + numeric +--------- + 4 +(1 row) + +SELECT (test_json -> 'field7')::bool FROM test_jsonb WHERE json_type = 'object'; + bool +------ + t +(1 row) + +SELECT (test_json -> 'field4')::int4 FROM test_jsonb WHERE json_type = 'object'; + int4 +------ + 4 +(1 row) + +SELECT (test_json -> 'field4')::int8 FROM test_jsonb WHERE json_type = 'object'; + int8 +------ + 4 +(1 row) + +SELECT (test_json -> 'field4')::float8 FROM test_jsonb WHERE json_type = 'object'; + float8 +-------- + 4 +(1 row) + +SELECT (test_json -> 'field4')::int2 FROM test_jsonb WHERE json_type = 'object'; + int2 +------ + 4 +(1 row) + +SELECT (test_json -> 'field4')::float4 FROM test_jsonb WHERE json_type = 'object'; + float4 +-------- + 4 +(1 row) + +-- Section 2b: correct execution through subscripting syntax +SELECT (test_json['field4'])::numeric FROM test_jsonb WHERE json_type = 'object'; + test_json +----------- + 4 +(1 row) + +SELECT (test_json['field7'])::bool FROM test_jsonb WHERE json_type = 'object'; + test_json +----------- + t +(1 row) + +SELECT (test_json['field4'])::int4 FROM test_jsonb WHERE json_type = 'object'; + test_json +----------- + 4 +(1 row) + +SELECT (test_json['field4'])::int8 FROM test_jsonb WHERE json_type = 'object'; + test_json +----------- + 4 +(1 row) + +SELECT (test_json['field4'])::float8 FROM test_jsonb WHERE json_type = 'object'; + test_json +----------- + 4 +(1 row) + +SELECT (test_json['field4'])::int2 FROM test_jsonb WHERE json_type = 'object'; + test_json +----------- + 4 +(1 row) + +SELECT (test_json['field4'])::float4 FROM test_jsonb WHERE json_type = 'object'; + test_json +----------- + 4 +(1 row) + +-- Section 3: NULL semantics (missing key, JSON null, non-object input) +SELECT (test_json -> 'field3')::numeric FROM test_jsonb WHERE json_type = 'object'; -- JSON null + numeric +--------- + +(1 row) + +SELECT (test_json -> 'nonexistent')::numeric FROM test_jsonb WHERE json_type = 'object'; -- missing key + numeric +--------- + +(1 row) + +SELECT (test_json -> 'x')::numeric FROM test_jsonb WHERE json_type = 'array'; -- non-object + numeric +--------- + +(1 row) + +SELECT (test_json -> 'field3')::bool FROM test_jsonb WHERE json_type = 'object'; -- JSON null, bool path + bool +------ + +(1 row) + +SELECT (test_json -> 'field3')::int4 FROM test_jsonb WHERE json_type = 'object'; -- JSON null, int4 path + int4 +------ + +(1 row) + +SELECT (test_json -> 'nonexistent')::int4 FROM test_jsonb WHERE json_type = 'object'; -- missing key, int4 + int4 +------ + +(1 row) + +SELECT (test_json -> 'field3')::float8 FROM test_jsonb WHERE json_type = 'object'; -- JSON null, float8 path + float8 +-------- + +(1 row) + +SELECT (test_json -> 'nonexistent')::float8 FROM test_jsonb WHERE json_type = 'object'; -- missing key, float8 + float8 +-------- + +(1 row) + +SELECT (test_json -> 'field3')::int2 FROM test_jsonb WHERE json_type = 'object'; -- JSON null, int2 path + int2 +------ + +(1 row) + +SELECT (test_json -> 'nonexistent')::int2 FROM test_jsonb WHERE json_type = 'object'; -- missing key, int2 + int2 +------ + +(1 row) + +SELECT (test_json -> 'field3')::float4 FROM test_jsonb WHERE json_type = 'object'; -- JSON null, float4 path + float4 +-------- + +(1 row) + +SELECT (test_json -> 'nonexistent')::float4 FROM test_jsonb WHERE json_type = 'object'; -- missing key, float4 + float4 +-------- + +(1 row) + +-- Section 3b: NULL semantics through subscripting syntax +SELECT (test_json['field3'])::numeric FROM test_jsonb WHERE json_type = 'object'; -- JSON null + test_json +----------- + +(1 row) + +SELECT (test_json['nonexistent'])::numeric FROM test_jsonb WHERE json_type = 'object'; -- missing key + test_json +----------- + +(1 row) + +SELECT (test_json['nonexistent'])::float8 FROM test_jsonb WHERE json_type = 'object'; -- missing key, float8 + test_json +----------- + +(1 row) + +-- Section 4: type-mismatch errors (scalar and container types) +SELECT (test_json -> 'field1')::numeric FROM test_jsonb WHERE json_type = 'object'; -- string to numeric +ERROR: cannot cast jsonb string to type numeric +SELECT (test_json -> 'field1')::bool FROM test_jsonb WHERE json_type = 'object'; -- string to bool +ERROR: cannot cast jsonb string to type boolean +SELECT (test_json -> 'field5')::numeric FROM test_jsonb WHERE json_type = 'object'; -- array to numeric +ERROR: cannot cast jsonb array or object to type numeric +SELECT (test_json -> 'field1')::int4 FROM test_jsonb WHERE json_type = 'object'; -- string to int4 +ERROR: cannot cast jsonb string to type integer +SELECT (test_json -> 'field1')::int8 FROM test_jsonb WHERE json_type = 'object'; -- string to int8 +ERROR: cannot cast jsonb string to type bigint +SELECT (test_json -> 'field1')::float8 FROM test_jsonb WHERE json_type = 'object'; -- string to float8 +ERROR: cannot cast jsonb string to type double precision +SELECT (test_json -> 'field5')::int4 FROM test_jsonb WHERE json_type = 'object'; -- array to int4 +ERROR: cannot cast jsonb array or object to type integer +SELECT (test_json -> 'field5')::float8 FROM test_jsonb WHERE json_type = 'object'; -- array to float8 +ERROR: cannot cast jsonb array or object to type double precision +SELECT (test_json -> 'field1')::int2 FROM test_jsonb WHERE json_type = 'object'; -- string to int2 +ERROR: cannot cast jsonb string to type smallint +SELECT (test_json -> 'field1')::float4 FROM test_jsonb WHERE json_type = 'object'; -- string to float4 +ERROR: cannot cast jsonb string to type real +SELECT (test_json -> 'field5')::int2 FROM test_jsonb WHERE json_type = 'object'; -- array to int2 +ERROR: cannot cast jsonb array or object to type smallint +SELECT (test_json -> 'field5')::float4 FROM test_jsonb WHERE json_type = 'object'; -- array to float4 +ERROR: cannot cast jsonb array or object to type real +-- Section 4b: type-mismatch error through subscripting syntax +SELECT (test_json['field1'])::numeric FROM test_jsonb WHERE json_type = 'object'; -- string to numeric +ERROR: cannot cast jsonb string to type numeric +SELECT (test_json['field1'])::int8 FROM test_jsonb WHERE json_type = 'object'; -- string to int8 +ERROR: cannot cast jsonb string to type bigint +-- Section 5: direct calls to typed extractor builtins +SELECT jsonb_object_field_numeric('{"a": 1}'::jsonb, 'a'); + jsonb_object_field_numeric +---------------------------- + 1 +(1 row) + +SELECT jsonb_object_field_numeric('{"a": 3.14}'::jsonb, 'a'); + jsonb_object_field_numeric +---------------------------- + 3.14 +(1 row) + +SELECT jsonb_object_field_bool('{"a": true}'::jsonb, 'a'); + jsonb_object_field_bool +------------------------- + t +(1 row) + +SELECT jsonb_object_field_bool('{"a": false}'::jsonb, 'a'); + jsonb_object_field_bool +------------------------- + f +(1 row) + +SELECT jsonb_object_field_int4('{"a": 42}'::jsonb, 'a'); + jsonb_object_field_int4 +------------------------- + 42 +(1 row) + +SELECT jsonb_object_field_int8('{"a": 9876543210}'::jsonb, 'a'); + jsonb_object_field_int8 +------------------------- + 9876543210 +(1 row) + +SELECT jsonb_object_field_float8('{"a": 3.14}'::jsonb, 'a'); + jsonb_object_field_float8 +--------------------------- + 3.14 +(1 row) + +SELECT jsonb_object_field_int2('{"a": 42}'::jsonb, 'a'); + jsonb_object_field_int2 +------------------------- + 42 +(1 row) + +SELECT jsonb_object_field_int2('{"a": 3}'::jsonb, 'a'); + jsonb_object_field_int2 +------------------------- + 3 +(1 row) + +SELECT jsonb_object_field_float4('{"a": 3.14}'::jsonb, 'a'); + jsonb_object_field_float4 +--------------------------- + 3.14 +(1 row) + +SELECT jsonb_object_field_float4('{"a": 1.5}'::jsonb, 'a'); + jsonb_object_field_float4 +--------------------------- + 1.5 +(1 row) + +-- direct calls: NULL semantics +SELECT jsonb_object_field_numeric('{"a": 1}'::jsonb, 'missing'); + jsonb_object_field_numeric +---------------------------- + +(1 row) + +SELECT jsonb_object_field_numeric('{"a": null}'::jsonb, 'a'); + jsonb_object_field_numeric +---------------------------- + +(1 row) + +SELECT jsonb_object_field_bool('{"a": true}'::jsonb, 'missing'); + jsonb_object_field_bool +------------------------- + +(1 row) + +SELECT jsonb_object_field_int4('{"a": 1}'::jsonb, 'missing'); + jsonb_object_field_int4 +------------------------- + +(1 row) + +SELECT jsonb_object_field_int4('{"a": null}'::jsonb, 'a'); + jsonb_object_field_int4 +------------------------- + +(1 row) + +SELECT jsonb_object_field_float8('{"a": 1.0}'::jsonb, 'missing'); + jsonb_object_field_float8 +--------------------------- + +(1 row) + +SELECT jsonb_object_field_int2('{"a": 1}'::jsonb, 'missing'); + jsonb_object_field_int2 +------------------------- + +(1 row) + +SELECT jsonb_object_field_int2('{"a": null}'::jsonb, 'a'); + jsonb_object_field_int2 +------------------------- + +(1 row) + +SELECT jsonb_object_field_float4('{"a": 1.0}'::jsonb, 'missing'); + jsonb_object_field_float4 +--------------------------- + +(1 row) + +SELECT jsonb_object_field_float4('{"a": null}'::jsonb, 'a'); + jsonb_object_field_float4 +--------------------------- + +(1 row) + +-- direct calls: type-mismatch errors +SELECT jsonb_object_field_numeric('{"a": "text"}'::jsonb, 'a'); +ERROR: cannot cast jsonb string to type numeric +SELECT jsonb_object_field_bool('{"a": 1}'::jsonb, 'a'); +ERROR: cannot cast jsonb numeric to type boolean +SELECT jsonb_object_field_numeric('{"a": {"x":1}}'::jsonb, 'a'); -- container to scalar +ERROR: cannot cast jsonb array or object to type numeric +SELECT jsonb_object_field_int4('{"a": "text"}'::jsonb, 'a'); +ERROR: cannot cast jsonb string to type integer +SELECT jsonb_object_field_int8('{"a": true}'::jsonb, 'a'); +ERROR: cannot cast jsonb boolean to type bigint +SELECT jsonb_object_field_float8('{"a": [1,2]}'::jsonb, 'a'); -- container to float8 +ERROR: cannot cast jsonb array or object to type double precision +SELECT jsonb_object_field_int2('{"a": "text"}'::jsonb, 'a'); -- string to int2 +ERROR: cannot cast jsonb string to type smallint +SELECT jsonb_object_field_float4('{"a": true}'::jsonb, 'a'); -- bool to float4 +ERROR: cannot cast jsonb boolean to type real +SELECT jsonb_object_field_int2('{"a": [1,2]}'::jsonb, 'a'); -- container to int2 +ERROR: cannot cast jsonb array or object to type smallint +SELECT jsonb_object_field_float4('{"a": {"x":1}}'::jsonb, 'a'); -- container to float4 +ERROR: cannot cast jsonb array or object to type real +-- direct calls: integer overflow +SELECT jsonb_object_field_int4('{"a": 9999999999}'::jsonb, 'a'); +ERROR: integer out of range +-- direct calls: int2 overflow +SELECT jsonb_object_field_int2('{"a": 99999}'::jsonb, 'a'); +ERROR: smallint out of range SELECT test_json -> 'x' FROM test_jsonb WHERE json_type = 'scalar'; ?column? ---------- @@ -586,7 +1066,8 @@ SELECT jsonb_object_keys(test_json) FROM test_jsonb WHERE json_type = 'object'; field4 field5 field6 -(6 rows) + field7 +(7 rows) -- nulls SELECT (test_json->'field3') IS NULL AS expect_false FROM test_jsonb WHERE json_type = 'object'; diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql index d28ed1c1e85..61c567110fc 100644 --- a/src/test/regress/sql/jsonb.sql +++ b/src/test/regress/sql/jsonb.sql @@ -155,7 +155,123 @@ CREATE TEMP TABLE test_jsonb ( INSERT INTO test_jsonb VALUES ('scalar','"a scalar"'), ('array','["zero", "one","two",null,"four","five", [1,2,3],{"f1":9}]'), -('object','{"field1":"val1","field2":"val2","field3":null, "field4": 4, "field5": [1,2,3], "field6": {"f1":9}}'); +('object','{"field1":"val1","field2":"val2","field3":null, "field4": 4, "field5": [1,2,3], "field6": {"f1":9}, "field7": true}'); + +-- Optimized typed extraction: the planner rewrites (j->'key')::type into a +-- direct typed extractor call, currently for numeric, bool, int2, int4, int8, +-- float4, float8. + +-- Section 1: planner rewrite verification (rewritten targets) +EXPLAIN (VERBOSE, COSTS OFF) SELECT (test_json -> 'field4')::numeric FROM test_jsonb WHERE json_type = 'object'; +EXPLAIN (VERBOSE, COSTS OFF) SELECT (test_json -> 'field7')::bool FROM test_jsonb WHERE json_type = 'object'; +EXPLAIN (VERBOSE, COSTS OFF) SELECT (test_json -> 'field4')::int4 FROM test_jsonb WHERE json_type = 'object'; +EXPLAIN (VERBOSE, COSTS OFF) SELECT (test_json -> 'field4')::int8 FROM test_jsonb WHERE json_type = 'object'; +EXPLAIN (VERBOSE, COSTS OFF) SELECT (test_json -> 'field4')::float8 FROM test_jsonb WHERE json_type = 'object'; +EXPLAIN (VERBOSE, COSTS OFF) SELECT (test_json -> 'field4')::int2 FROM test_jsonb WHERE json_type = 'object'; +EXPLAIN (VERBOSE, COSTS OFF) SELECT (test_json -> 'field4')::float4 FROM test_jsonb WHERE json_type = 'object'; + +-- Section 1b: planner rewrite verification for subscripting syntax +EXPLAIN (VERBOSE, COSTS OFF) SELECT (test_json['field4'])::numeric FROM test_jsonb WHERE json_type = 'object'; +EXPLAIN (VERBOSE, COSTS OFF) SELECT (test_json['field7'])::bool FROM test_jsonb WHERE json_type = 'object'; +EXPLAIN (VERBOSE, COSTS OFF) SELECT (test_json['field4'])::int4 FROM test_jsonb WHERE json_type = 'object'; +EXPLAIN (VERBOSE, COSTS OFF) SELECT (test_json['field4'])::int8 FROM test_jsonb WHERE json_type = 'object'; +EXPLAIN (VERBOSE, COSTS OFF) SELECT (test_json['field4'])::float8 FROM test_jsonb WHERE json_type = 'object'; +EXPLAIN (VERBOSE, COSTS OFF) SELECT (test_json['field4'])::int2 FROM test_jsonb WHERE json_type = 'object'; +EXPLAIN (VERBOSE, COSTS OFF) SELECT (test_json['field4'])::float4 FROM test_jsonb WHERE json_type = 'object'; + +-- Section 2: correct execution through the rewritten path +SELECT (test_json -> 'field4')::numeric FROM test_jsonb WHERE json_type = 'object'; +SELECT (test_json -> 'field7')::bool FROM test_jsonb WHERE json_type = 'object'; +SELECT (test_json -> 'field4')::int4 FROM test_jsonb WHERE json_type = 'object'; +SELECT (test_json -> 'field4')::int8 FROM test_jsonb WHERE json_type = 'object'; +SELECT (test_json -> 'field4')::float8 FROM test_jsonb WHERE json_type = 'object'; +SELECT (test_json -> 'field4')::int2 FROM test_jsonb WHERE json_type = 'object'; +SELECT (test_json -> 'field4')::float4 FROM test_jsonb WHERE json_type = 'object'; + +-- Section 2b: correct execution through subscripting syntax +SELECT (test_json['field4'])::numeric FROM test_jsonb WHERE json_type = 'object'; +SELECT (test_json['field7'])::bool FROM test_jsonb WHERE json_type = 'object'; +SELECT (test_json['field4'])::int4 FROM test_jsonb WHERE json_type = 'object'; +SELECT (test_json['field4'])::int8 FROM test_jsonb WHERE json_type = 'object'; +SELECT (test_json['field4'])::float8 FROM test_jsonb WHERE json_type = 'object'; +SELECT (test_json['field4'])::int2 FROM test_jsonb WHERE json_type = 'object'; +SELECT (test_json['field4'])::float4 FROM test_jsonb WHERE json_type = 'object'; + +-- Section 3: NULL semantics (missing key, JSON null, non-object input) +SELECT (test_json -> 'field3')::numeric FROM test_jsonb WHERE json_type = 'object'; -- JSON null +SELECT (test_json -> 'nonexistent')::numeric FROM test_jsonb WHERE json_type = 'object'; -- missing key +SELECT (test_json -> 'x')::numeric FROM test_jsonb WHERE json_type = 'array'; -- non-object +SELECT (test_json -> 'field3')::bool FROM test_jsonb WHERE json_type = 'object'; -- JSON null, bool path +SELECT (test_json -> 'field3')::int4 FROM test_jsonb WHERE json_type = 'object'; -- JSON null, int4 path +SELECT (test_json -> 'nonexistent')::int4 FROM test_jsonb WHERE json_type = 'object'; -- missing key, int4 +SELECT (test_json -> 'field3')::float8 FROM test_jsonb WHERE json_type = 'object'; -- JSON null, float8 path +SELECT (test_json -> 'nonexistent')::float8 FROM test_jsonb WHERE json_type = 'object'; -- missing key, float8 +SELECT (test_json -> 'field3')::int2 FROM test_jsonb WHERE json_type = 'object'; -- JSON null, int2 path +SELECT (test_json -> 'nonexistent')::int2 FROM test_jsonb WHERE json_type = 'object'; -- missing key, int2 +SELECT (test_json -> 'field3')::float4 FROM test_jsonb WHERE json_type = 'object'; -- JSON null, float4 path +SELECT (test_json -> 'nonexistent')::float4 FROM test_jsonb WHERE json_type = 'object'; -- missing key, float4 + +-- Section 3b: NULL semantics through subscripting syntax +SELECT (test_json['field3'])::numeric FROM test_jsonb WHERE json_type = 'object'; -- JSON null +SELECT (test_json['nonexistent'])::numeric FROM test_jsonb WHERE json_type = 'object'; -- missing key +SELECT (test_json['nonexistent'])::float8 FROM test_jsonb WHERE json_type = 'object'; -- missing key, float8 + +-- Section 4: type-mismatch errors (scalar and container types) +SELECT (test_json -> 'field1')::numeric FROM test_jsonb WHERE json_type = 'object'; -- string to numeric +SELECT (test_json -> 'field1')::bool FROM test_jsonb WHERE json_type = 'object'; -- string to bool +SELECT (test_json -> 'field5')::numeric FROM test_jsonb WHERE json_type = 'object'; -- array to numeric +SELECT (test_json -> 'field1')::int4 FROM test_jsonb WHERE json_type = 'object'; -- string to int4 +SELECT (test_json -> 'field1')::int8 FROM test_jsonb WHERE json_type = 'object'; -- string to int8 +SELECT (test_json -> 'field1')::float8 FROM test_jsonb WHERE json_type = 'object'; -- string to float8 +SELECT (test_json -> 'field5')::int4 FROM test_jsonb WHERE json_type = 'object'; -- array to int4 +SELECT (test_json -> 'field5')::float8 FROM test_jsonb WHERE json_type = 'object'; -- array to float8 +SELECT (test_json -> 'field1')::int2 FROM test_jsonb WHERE json_type = 'object'; -- string to int2 +SELECT (test_json -> 'field1')::float4 FROM test_jsonb WHERE json_type = 'object'; -- string to float4 +SELECT (test_json -> 'field5')::int2 FROM test_jsonb WHERE json_type = 'object'; -- array to int2 +SELECT (test_json -> 'field5')::float4 FROM test_jsonb WHERE json_type = 'object'; -- array to float4 + +-- Section 4b: type-mismatch error through subscripting syntax +SELECT (test_json['field1'])::numeric FROM test_jsonb WHERE json_type = 'object'; -- string to numeric +SELECT (test_json['field1'])::int8 FROM test_jsonb WHERE json_type = 'object'; -- string to int8 + +-- Section 5: direct calls to typed extractor builtins +SELECT jsonb_object_field_numeric('{"a": 1}'::jsonb, 'a'); +SELECT jsonb_object_field_numeric('{"a": 3.14}'::jsonb, 'a'); +SELECT jsonb_object_field_bool('{"a": true}'::jsonb, 'a'); +SELECT jsonb_object_field_bool('{"a": false}'::jsonb, 'a'); +SELECT jsonb_object_field_int4('{"a": 42}'::jsonb, 'a'); +SELECT jsonb_object_field_int8('{"a": 9876543210}'::jsonb, 'a'); +SELECT jsonb_object_field_float8('{"a": 3.14}'::jsonb, 'a'); +SELECT jsonb_object_field_int2('{"a": 42}'::jsonb, 'a'); +SELECT jsonb_object_field_int2('{"a": 3}'::jsonb, 'a'); +SELECT jsonb_object_field_float4('{"a": 3.14}'::jsonb, 'a'); +SELECT jsonb_object_field_float4('{"a": 1.5}'::jsonb, 'a'); +-- direct calls: NULL semantics +SELECT jsonb_object_field_numeric('{"a": 1}'::jsonb, 'missing'); +SELECT jsonb_object_field_numeric('{"a": null}'::jsonb, 'a'); +SELECT jsonb_object_field_bool('{"a": true}'::jsonb, 'missing'); +SELECT jsonb_object_field_int4('{"a": 1}'::jsonb, 'missing'); +SELECT jsonb_object_field_int4('{"a": null}'::jsonb, 'a'); +SELECT jsonb_object_field_float8('{"a": 1.0}'::jsonb, 'missing'); +SELECT jsonb_object_field_int2('{"a": 1}'::jsonb, 'missing'); +SELECT jsonb_object_field_int2('{"a": null}'::jsonb, 'a'); +SELECT jsonb_object_field_float4('{"a": 1.0}'::jsonb, 'missing'); +SELECT jsonb_object_field_float4('{"a": null}'::jsonb, 'a'); +-- direct calls: type-mismatch errors +SELECT jsonb_object_field_numeric('{"a": "text"}'::jsonb, 'a'); +SELECT jsonb_object_field_bool('{"a": 1}'::jsonb, 'a'); +SELECT jsonb_object_field_numeric('{"a": {"x":1}}'::jsonb, 'a'); -- container to scalar +SELECT jsonb_object_field_int4('{"a": "text"}'::jsonb, 'a'); +SELECT jsonb_object_field_int8('{"a": true}'::jsonb, 'a'); +SELECT jsonb_object_field_float8('{"a": [1,2]}'::jsonb, 'a'); -- container to float8 +SELECT jsonb_object_field_int2('{"a": "text"}'::jsonb, 'a'); -- string to int2 +SELECT jsonb_object_field_float4('{"a": true}'::jsonb, 'a'); -- bool to float4 +SELECT jsonb_object_field_int2('{"a": [1,2]}'::jsonb, 'a'); -- container to int2 +SELECT jsonb_object_field_float4('{"a": {"x":1}}'::jsonb, 'a'); -- container to float4 +-- direct calls: integer overflow +SELECT jsonb_object_field_int4('{"a": 9999999999}'::jsonb, 'a'); +-- direct calls: int2 overflow +SELECT jsonb_object_field_int2('{"a": 99999}'::jsonb, 'a'); SELECT test_json -> 'x' FROM test_jsonb WHERE json_type = 'scalar'; SELECT test_json -> 'x' FROM test_jsonb WHERE json_type = 'array'; @@ -1614,4 +1730,4 @@ select ('[{"name": "alice"}, {"name": "bob"}]'::jsonb).name; select ('true'::jsonb)::bool; select ('true'::jsonb).bool; select ('{"text": "hello"}'::jsonb)::text; -select ('{"text": "hello"}'::jsonb).text; +select ('{"text": "hello"}'::jsonb).text; \ No newline at end of file -- 2.52.0