v5-0005-jsonb-optimize-jsonpath-first-casts-to-scalar-typ.patch
application/octet-stream
Filename: v5-0005-jsonb-optimize-jsonpath-first-casts-to-scalar-typ.patch
Type: application/octet-stream
Part: 3
Patch
Same data as JSON:
GET /api/v1/attachments/:id/patch
the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes.
API reference →
Format: format-patch
Series: patch v5-0005
Subject: jsonb: optimize jsonpath-first casts to scalar types
| File | + | − |
|---|---|---|
| src/backend/utils/adt/jsonb.c | 98 | 4 |
| src/backend/utils/adt/jsonfuncs.c | 9 | 8 |
| src/backend/utils/adt/jsonpath_exec.c | 136 | 0 |
| src/include/catalog/pg_proc.dat | 73 | 0 |
| src/include/utils/jsonfuncs.h | 10 | 0 |
| src/test/regress/expected/jsonb.out | 612 | 234 |
| src/test/regress/sql/jsonb.sql | 176 | 53 |
From be43cda8d05767b4fee6a61f148c550cf8f78cd0 Mon Sep 17 00:00:00 2001
From: Haibo Yan <haibo.yan@apple.com>
Date: Fri, 10 Apr 2026 14:34:24 -0700
Subject: [PATCH v5 5/5] jsonb: optimize jsonpath-first casts to scalar types
Extend the existing support-function rewrite to jsonb_path_query_first
and jsonb_path_query_first_tz so casts are rewritten directly to
explicit typed extractor functions.
This patch supports numeric, bool, int2, int4, int8, float4, and
float8, and leaves jsonb_path_query out of scope.
---
src/backend/utils/adt/jsonb.c | 102 +++-
src/backend/utils/adt/jsonfuncs.c | 17 +-
src/backend/utils/adt/jsonpath_exec.c | 136 +++++
src/include/catalog/pg_proc.dat | 73 +++
src/include/utils/jsonfuncs.h | 10 +
src/test/regress/expected/jsonb.out | 846 +++++++++++++++++++-------
src/test/regress/sql/jsonb.sql | 229 +++++--
7 files changed, 1114 insertions(+), 299 deletions(-)
diff --git a/src/backend/utils/adt/jsonb.c b/src/backend/utils/adt/jsonb.c
index d5c0871a213..2985777f1c9 100644
--- a/src/backend/utils/adt/jsonb.c
+++ b/src/backend/utils/adt/jsonb.c
@@ -1836,6 +1836,8 @@ cannotCastJsonbValue(enum jbvType type, const char *sqltype, Node *escontext)
* - jsonb_array_element(j, idx) / j -> idx / j[idx]
* - jsonb_extract_path(j, ...) / j #> '{a,b}'
* - multi-subscript chains j['a']['b'], j['a'][0], etc.
+ * - jsonb_path_query_first(j, path [, vars [, silent]])
+ * - jsonb_path_query_first_tz(j, path [, vars [, silent]])
*/
Datum
jsonb_cast_support(PG_FUNCTION_ARGS)
@@ -2012,13 +2014,13 @@ jsonb_cast_support(PG_FUNCTION_ARGS)
/*
* Verify the inner extraction function and map the outer cast to the
* corresponding typed extractor. Each supported extraction family
- * has its own set of typed rewrite targets.
+ * has its own set of typed rewrite targets and expected arg count.
*/
- if (list_length(inner_args) != 2)
- PG_RETURN_POINTER(NULL);
-
if (inner_funcid == F_JSONB_OBJECT_FIELD)
{
+ if (list_length(inner_args) != 2)
+ PG_RETURN_POINTER(NULL);
+
if (fexpr->funcid == F_NUMERIC_JSONB)
{
replacement_funcid = F_JSONB_OBJECT_FIELD_NUMERIC;
@@ -2059,6 +2061,9 @@ jsonb_cast_support(PG_FUNCTION_ARGS)
}
else if (inner_funcid == F_JSONB_ARRAY_ELEMENT)
{
+ if (list_length(inner_args) != 2)
+ PG_RETURN_POINTER(NULL);
+
if (fexpr->funcid == F_NUMERIC_JSONB)
{
replacement_funcid = F_JSONB_ARRAY_ELEMENT_NUMERIC;
@@ -2099,6 +2104,9 @@ jsonb_cast_support(PG_FUNCTION_ARGS)
}
else if (inner_funcid == F_JSONB_EXTRACT_PATH)
{
+ if (list_length(inner_args) != 2)
+ PG_RETURN_POINTER(NULL);
+
if (fexpr->funcid == F_NUMERIC_JSONB)
{
replacement_funcid = F_JSONB_EXTRACT_PATH_NUMERIC;
@@ -2137,6 +2145,92 @@ jsonb_cast_support(PG_FUNCTION_ARGS)
else
PG_RETURN_POINTER(NULL);
}
+ else if (inner_funcid == F_JSONB_PATH_QUERY_FIRST)
+ {
+ if (list_length(inner_args) != 4)
+ PG_RETURN_POINTER(NULL);
+
+ if (fexpr->funcid == F_NUMERIC_JSONB)
+ {
+ replacement_funcid = F_JSONB_PATH_QUERY_FIRST_NUMERIC;
+ replacement_rettype = NUMERICOID;
+ }
+ else if (fexpr->funcid == F_BOOL_JSONB)
+ {
+ replacement_funcid = F_JSONB_PATH_QUERY_FIRST_BOOL;
+ replacement_rettype = BOOLOID;
+ }
+ else if (fexpr->funcid == F_INT4_JSONB)
+ {
+ replacement_funcid = F_JSONB_PATH_QUERY_FIRST_INT4;
+ replacement_rettype = INT4OID;
+ }
+ else if (fexpr->funcid == F_INT8_JSONB)
+ {
+ replacement_funcid = F_JSONB_PATH_QUERY_FIRST_INT8;
+ replacement_rettype = INT8OID;
+ }
+ else if (fexpr->funcid == F_FLOAT8_JSONB)
+ {
+ replacement_funcid = F_JSONB_PATH_QUERY_FIRST_FLOAT8;
+ replacement_rettype = FLOAT8OID;
+ }
+ else if (fexpr->funcid == F_INT2_JSONB)
+ {
+ replacement_funcid = F_JSONB_PATH_QUERY_FIRST_INT2;
+ replacement_rettype = INT2OID;
+ }
+ else if (fexpr->funcid == F_FLOAT4_JSONB)
+ {
+ replacement_funcid = F_JSONB_PATH_QUERY_FIRST_FLOAT4;
+ replacement_rettype = FLOAT4OID;
+ }
+ else
+ PG_RETURN_POINTER(NULL);
+ }
+ else if (inner_funcid == F_JSONB_PATH_QUERY_FIRST_TZ)
+ {
+ if (list_length(inner_args) != 4)
+ PG_RETURN_POINTER(NULL);
+
+ if (fexpr->funcid == F_NUMERIC_JSONB)
+ {
+ replacement_funcid = F_JSONB_PATH_QUERY_FIRST_TZ_NUMERIC;
+ replacement_rettype = NUMERICOID;
+ }
+ else if (fexpr->funcid == F_BOOL_JSONB)
+ {
+ replacement_funcid = F_JSONB_PATH_QUERY_FIRST_TZ_BOOL;
+ replacement_rettype = BOOLOID;
+ }
+ else if (fexpr->funcid == F_INT4_JSONB)
+ {
+ replacement_funcid = F_JSONB_PATH_QUERY_FIRST_TZ_INT4;
+ replacement_rettype = INT4OID;
+ }
+ else if (fexpr->funcid == F_INT8_JSONB)
+ {
+ replacement_funcid = F_JSONB_PATH_QUERY_FIRST_TZ_INT8;
+ replacement_rettype = INT8OID;
+ }
+ else if (fexpr->funcid == F_FLOAT8_JSONB)
+ {
+ replacement_funcid = F_JSONB_PATH_QUERY_FIRST_TZ_FLOAT8;
+ replacement_rettype = FLOAT8OID;
+ }
+ else if (fexpr->funcid == F_INT2_JSONB)
+ {
+ replacement_funcid = F_JSONB_PATH_QUERY_FIRST_TZ_INT2;
+ replacement_rettype = INT2OID;
+ }
+ else if (fexpr->funcid == F_FLOAT4_JSONB)
+ {
+ replacement_funcid = F_JSONB_PATH_QUERY_FIRST_TZ_FLOAT4;
+ replacement_rettype = FLOAT4OID;
+ }
+ else
+ PG_RETURN_POINTER(NULL);
+ }
else
PG_RETURN_POINTER(NULL);
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index b3fd1dfb631..32cdb5c61b8 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -969,7 +969,7 @@ jsonb_object_field_lookup(Jsonb *jb, text *key, JsonbValue *vbuf)
* The message wording matches cannotCastJsonbValue() in jsonb.c so that
* the optimized and unoptimized paths produce identical errors.
*/
-static void
+void
jsonb_field_cast_error(JsonbValue *v, const char *sqltype)
{
const char *jsontype;
@@ -1016,7 +1016,7 @@ jsonb_field_cast_error(JsonbValue *v, const char *sqltype)
* value as Datum. These are the only place where conversion semantics
* live; the wrapper macro below is intentionally kept thin.
*/
-static Datum
+Datum
jsonb_value_to_numeric_datum(JsonbValue *v)
{
if (v->type != jbvNumeric)
@@ -1025,7 +1025,7 @@ jsonb_value_to_numeric_datum(JsonbValue *v)
return NumericGetDatum(DatumGetNumericCopy(NumericGetDatum(v->val.numeric)));
}
-static Datum
+Datum
jsonb_value_to_bool_datum(JsonbValue *v)
{
if (v->type != jbvBool)
@@ -1034,7 +1034,7 @@ jsonb_value_to_bool_datum(JsonbValue *v)
return BoolGetDatum(v->val.boolean);
}
-static Datum
+Datum
jsonb_value_to_int4_datum(JsonbValue *v)
{
if (v->type != jbvNumeric)
@@ -1043,7 +1043,7 @@ jsonb_value_to_int4_datum(JsonbValue *v)
return DirectFunctionCall1(numeric_int4, NumericGetDatum(v->val.numeric));
}
-static Datum
+Datum
jsonb_value_to_int8_datum(JsonbValue *v)
{
if (v->type != jbvNumeric)
@@ -1052,7 +1052,7 @@ jsonb_value_to_int8_datum(JsonbValue *v)
return DirectFunctionCall1(numeric_int8, NumericGetDatum(v->val.numeric));
}
-static Datum
+Datum
jsonb_value_to_float8_datum(JsonbValue *v)
{
if (v->type != jbvNumeric)
@@ -1060,7 +1060,8 @@ jsonb_value_to_float8_datum(JsonbValue *v)
return DirectFunctionCall1(numeric_float8, NumericGetDatum(v->val.numeric));
}
-static Datum
+
+Datum
jsonb_value_to_int2_datum(JsonbValue *v)
{
if (v->type != jbvNumeric)
@@ -1069,7 +1070,7 @@ jsonb_value_to_int2_datum(JsonbValue *v)
return DirectFunctionCall1(numeric_int2, NumericGetDatum(v->val.numeric));
}
-static Datum
+Datum
jsonb_value_to_float4_datum(JsonbValue *v)
{
if (v->type != jbvNumeric)
diff --git a/src/backend/utils/adt/jsonpath_exec.c b/src/backend/utils/adt/jsonpath_exec.c
index 0ec9b4df2ef..2048696248b 100644
--- a/src/backend/utils/adt/jsonpath_exec.c
+++ b/src/backend/utils/adt/jsonpath_exec.c
@@ -72,6 +72,7 @@
#include "utils/float.h"
#include "utils/formatting.h"
#include "utils/json.h"
+#include "utils/jsonfuncs.h"
#include "utils/jsonpath.h"
#include "utils/memutils.h"
#include "utils/timestamp.h"
@@ -687,6 +688,141 @@ jsonb_path_query_first_tz(PG_FUNCTION_ARGS)
return jsonb_path_query_first_internal(fcinfo, true);
}
+/*
+ * Lookup helper for the jsonb_path_query_first typed extractor family.
+ *
+ * Invokes the jsonpath execution machinery and returns the first result as
+ * an in-memory JsonbValue, or NULL if there is no result or the result is
+ * JSON null. This avoids the JsonbValueToJsonb round-trip that the
+ * unoptimized cast(jsonb_path_query_first(...)) path would perform.
+ */
+static JsonbValue *
+jsonb_path_query_first_lookup(Jsonb *jb, JsonPath *jp, Jsonb *vars,
+ bool silent)
+{
+ JsonValueList found;
+ JsonbValue *v;
+
+ JsonValueListInit(&found);
+
+ (void) executeJsonPath(jp, vars, getJsonPathVariableFromJsonb,
+ countVariablesFromJsonb,
+ jb, !silent, &found, false);
+
+ if (JsonValueListIsEmpty(&found))
+ return NULL;
+
+ v = JsonValueListHead(&found);
+ if (v->type == jbvNull)
+ return NULL;
+
+ return v;
+}
+
+/*
+ * Thin-wrapper macro for the jsonb_path_query_first_<type> extractor family.
+ * Same pattern as the other typed extractor families in jsonfuncs.c.
+ */
+#define DEFINE_JSONB_PATH_QUERY_FIRST_TYPED(fname, convfn) \
+Datum \
+fname(PG_FUNCTION_ARGS) \
+{ \
+ Jsonb *jb = PG_GETARG_JSONB_P(0); \
+ JsonPath *jp = PG_GETARG_JSONPATH_P(1); \
+ Jsonb *vars = PG_GETARG_JSONB_P(2); \
+ bool silent = PG_GETARG_BOOL(3); \
+ JsonbValue *v; \
+ Datum result; \
+\
+ v = jsonb_path_query_first_lookup(jb, jp, vars, silent); \
+ if (v == NULL) \
+ { \
+ PG_FREE_IF_COPY(jb, 0); \
+ PG_FREE_IF_COPY(jp, 1); \
+ PG_FREE_IF_COPY(vars, 2); \
+ PG_RETURN_NULL(); \
+ } \
+\
+ result = convfn(v); \
+ PG_FREE_IF_COPY(jb, 0); \
+ PG_FREE_IF_COPY(jp, 1); \
+ PG_FREE_IF_COPY(vars, 2); \
+ return result; \
+}
+
+DEFINE_JSONB_PATH_QUERY_FIRST_TYPED(jsonb_path_query_first_numeric, jsonb_value_to_numeric_datum)
+DEFINE_JSONB_PATH_QUERY_FIRST_TYPED(jsonb_path_query_first_bool, jsonb_value_to_bool_datum)
+DEFINE_JSONB_PATH_QUERY_FIRST_TYPED(jsonb_path_query_first_int4, jsonb_value_to_int4_datum)
+DEFINE_JSONB_PATH_QUERY_FIRST_TYPED(jsonb_path_query_first_int8, jsonb_value_to_int8_datum)
+DEFINE_JSONB_PATH_QUERY_FIRST_TYPED(jsonb_path_query_first_float8, jsonb_value_to_float8_datum)
+DEFINE_JSONB_PATH_QUERY_FIRST_TYPED(jsonb_path_query_first_int2, jsonb_value_to_int2_datum)
+DEFINE_JSONB_PATH_QUERY_FIRST_TYPED(jsonb_path_query_first_float4, jsonb_value_to_float4_datum)
+
+/*
+ * Lookup helper for the jsonb_path_query_first_tz typed extractor family.
+ * Same as jsonb_path_query_first_lookup but with timezone-aware evaluation.
+ */
+static JsonbValue *
+jsonb_path_query_first_tz_lookup(Jsonb *jb, JsonPath *jp, Jsonb *vars,
+ bool silent)
+{
+ JsonValueList found;
+ JsonbValue *v;
+
+ JsonValueListInit(&found);
+
+ (void) executeJsonPath(jp, vars, getJsonPathVariableFromJsonb,
+ countVariablesFromJsonb,
+ jb, !silent, &found, true);
+
+ if (JsonValueListIsEmpty(&found))
+ return NULL;
+
+ v = JsonValueListHead(&found);
+ if (v->type == jbvNull)
+ return NULL;
+
+ return v;
+}
+
+/*
+ * Thin-wrapper macro for the jsonb_path_query_first_tz_<type> extractor family.
+ */
+#define DEFINE_JSONB_PATH_QUERY_FIRST_TZ_TYPED(fname, convfn) \
+Datum \
+fname(PG_FUNCTION_ARGS) \
+{ \
+ Jsonb *jb = PG_GETARG_JSONB_P(0); \
+ JsonPath *jp = PG_GETARG_JSONPATH_P(1); \
+ Jsonb *vars = PG_GETARG_JSONB_P(2); \
+ bool silent = PG_GETARG_BOOL(3); \
+ JsonbValue *v; \
+ Datum result; \
+\
+ v = jsonb_path_query_first_tz_lookup(jb, jp, vars, silent); \
+ if (v == NULL) \
+ { \
+ PG_FREE_IF_COPY(jb, 0); \
+ PG_FREE_IF_COPY(jp, 1); \
+ PG_FREE_IF_COPY(vars, 2); \
+ PG_RETURN_NULL(); \
+ } \
+\
+ result = convfn(v); \
+ PG_FREE_IF_COPY(jb, 0); \
+ PG_FREE_IF_COPY(jp, 1); \
+ PG_FREE_IF_COPY(vars, 2); \
+ return result; \
+}
+
+DEFINE_JSONB_PATH_QUERY_FIRST_TZ_TYPED(jsonb_path_query_first_tz_numeric, jsonb_value_to_numeric_datum)
+DEFINE_JSONB_PATH_QUERY_FIRST_TZ_TYPED(jsonb_path_query_first_tz_bool, jsonb_value_to_bool_datum)
+DEFINE_JSONB_PATH_QUERY_FIRST_TZ_TYPED(jsonb_path_query_first_tz_int4, jsonb_value_to_int4_datum)
+DEFINE_JSONB_PATH_QUERY_FIRST_TZ_TYPED(jsonb_path_query_first_tz_int8, jsonb_value_to_int8_datum)
+DEFINE_JSONB_PATH_QUERY_FIRST_TZ_TYPED(jsonb_path_query_first_tz_float8, jsonb_value_to_float8_datum)
+DEFINE_JSONB_PATH_QUERY_FIRST_TZ_TYPED(jsonb_path_query_first_tz_int2, jsonb_value_to_int2_datum)
+DEFINE_JSONB_PATH_QUERY_FIRST_TZ_TYPED(jsonb_path_query_first_tz_float4, jsonb_value_to_float4_datum)
+
/********************Execute functions for JsonPath**************************/
/*
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index d8ac9cbf053..921e278a045 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -12847,6 +12847,56 @@
proname => 'jsonb_extract_path_float8', prorettype => 'float8',
proargtypes => 'jsonb _text', proargnames => '{from_json,path_elems}',
prosrc => 'jsonb_extract_path_float8' },
+{ oid => '9970', descr => 'jsonpath query first item as numeric',
+ proname => 'jsonb_path_query_first_numeric', prorettype => 'numeric',
+ proargtypes => 'jsonb jsonpath jsonb bool',
+ proargnames => '{target,path,vars,silent}',
+ prosrc => 'jsonb_path_query_first_numeric' },
+{ oid => '9971', descr => 'jsonpath query first item as bool',
+ proname => 'jsonb_path_query_first_bool', prorettype => 'bool',
+ proargtypes => 'jsonb jsonpath jsonb bool',
+ proargnames => '{target,path,vars,silent}',
+ prosrc => 'jsonb_path_query_first_bool' },
+{ oid => '9972', descr => 'jsonpath query first item as int4',
+ proname => 'jsonb_path_query_first_int4', prorettype => 'int4',
+ proargtypes => 'jsonb jsonpath jsonb bool',
+ proargnames => '{target,path,vars,silent}',
+ prosrc => 'jsonb_path_query_first_int4' },
+{ oid => '9973', descr => 'jsonpath query first item as int8',
+ proname => 'jsonb_path_query_first_int8', prorettype => 'int8',
+ proargtypes => 'jsonb jsonpath jsonb bool',
+ proargnames => '{target,path,vars,silent}',
+ prosrc => 'jsonb_path_query_first_int8' },
+{ oid => '9974', descr => 'jsonpath query first item as float8',
+ proname => 'jsonb_path_query_first_float8', prorettype => 'float8',
+ proargtypes => 'jsonb jsonpath jsonb bool',
+ proargnames => '{target,path,vars,silent}',
+ prosrc => 'jsonb_path_query_first_float8' },
+{ oid => '9975', descr => 'jsonpath query first item as numeric with timezone',
+ proname => 'jsonb_path_query_first_tz_numeric', provolatile => 's',
+ prorettype => 'numeric', proargtypes => 'jsonb jsonpath jsonb bool',
+ proargnames => '{target,path,vars,silent}',
+ prosrc => 'jsonb_path_query_first_tz_numeric' },
+{ oid => '9976', descr => 'jsonpath query first item as bool with timezone',
+ proname => 'jsonb_path_query_first_tz_bool', provolatile => 's',
+ prorettype => 'bool', proargtypes => 'jsonb jsonpath jsonb bool',
+ proargnames => '{target,path,vars,silent}',
+ prosrc => 'jsonb_path_query_first_tz_bool' },
+{ oid => '9977', descr => 'jsonpath query first item as int4 with timezone',
+ proname => 'jsonb_path_query_first_tz_int4', provolatile => 's',
+ prorettype => 'int4', proargtypes => 'jsonb jsonpath jsonb bool',
+ proargnames => '{target,path,vars,silent}',
+ prosrc => 'jsonb_path_query_first_tz_int4' },
+{ oid => '9978', descr => 'jsonpath query first item as int8 with timezone',
+ proname => 'jsonb_path_query_first_tz_int8', provolatile => 's',
+ prorettype => 'int8', proargtypes => 'jsonb jsonpath jsonb bool',
+ proargnames => '{target,path,vars,silent}',
+ prosrc => 'jsonb_path_query_first_tz_int8' },
+{ oid => '9979', descr => 'jsonpath query first item as float8 with timezone',
+ proname => 'jsonb_path_query_first_tz_float8', provolatile => 's',
+ prorettype => 'float8', proargtypes => 'jsonb jsonpath jsonb bool',
+ proargnames => '{target,path,vars,silent}',
+ prosrc => 'jsonb_path_query_first_tz_float8' },
{ oid => '9982', descr => 'extract int2 from jsonb array by element index',
proname => 'jsonb_array_element_int2', prorettype => 'int2',
proargtypes => 'jsonb int4', proargnames => '{from_json,element_index}',
@@ -12865,4 +12915,27 @@
proargtypes => 'jsonb _text', proargnames => '{from_json,path_elems}',
prosrc => 'jsonb_extract_path_float4' },
+# jsonb_path_query_first family: int2 / float4
+{ oid => '9986', descr => 'jsonpath query first item as int2',
+ proname => 'jsonb_path_query_first_int2', prorettype => 'int2',
+ proargtypes => 'jsonb jsonpath jsonb bool',
+ proargnames => '{target,path,vars,silent}',
+ prosrc => 'jsonb_path_query_first_int2' },
+{ oid => '9987', descr => 'jsonpath query first item as float4',
+ proname => 'jsonb_path_query_first_float4', prorettype => 'float4',
+ proargtypes => 'jsonb jsonpath jsonb bool',
+ proargnames => '{target,path,vars,silent}',
+ prosrc => 'jsonb_path_query_first_float4' },
+
+# jsonb_path_query_first_tz family: int2 / float4
+{ oid => '9988', descr => 'jsonpath query first item as int2 with timezone',
+ proname => 'jsonb_path_query_first_tz_int2', provolatile => 's',
+ prorettype => 'int2', proargtypes => 'jsonb jsonpath jsonb bool',
+ proargnames => '{target,path,vars,silent}',
+ prosrc => 'jsonb_path_query_first_tz_int2' },
+{ oid => '9989', descr => 'jsonpath query first item as float4 with timezone',
+ proname => 'jsonb_path_query_first_tz_float4', provolatile => 's',
+ prorettype => 'float4', proargtypes => 'jsonb jsonpath jsonb bool',
+ proargnames => '{target,path,vars,silent}',
+ prosrc => 'jsonb_path_query_first_tz_float4' },
]
diff --git a/src/include/utils/jsonfuncs.h b/src/include/utils/jsonfuncs.h
index 27713be3aeb..ce55fe1b1ce 100644
--- a/src/include/utils/jsonfuncs.h
+++ b/src/include/utils/jsonfuncs.h
@@ -91,6 +91,16 @@ extern Datum datum_to_jsonb(Datum val, JsonTypeCategory tcategory,
Oid outfuncoid);
extern Datum jsonb_from_text(text *js, bool unique_keys);
+/* Typed field extraction helpers (shared by jsonfuncs.c and jsonpath_exec.c) */
+extern void jsonb_field_cast_error(JsonbValue *v, const char *sqltype);
+extern Datum jsonb_value_to_numeric_datum(JsonbValue *v);
+extern Datum jsonb_value_to_bool_datum(JsonbValue *v);
+extern Datum jsonb_value_to_int4_datum(JsonbValue *v);
+extern Datum jsonb_value_to_int8_datum(JsonbValue *v);
+extern Datum jsonb_value_to_float8_datum(JsonbValue *v);
+extern Datum jsonb_value_to_int2_datum(JsonbValue *v);
+extern Datum jsonb_value_to_float4_datum(JsonbValue *v);
+
extern Datum json_populate_type(Datum json_val, Oid json_type,
Oid typid, int32 typmod,
void **cache, MemoryContext mcxt,
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index 3198442352c..7de2bcea3f7 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -503,22 +503,6 @@ EXPLAIN (VERBOSE, COSTS OFF) SELECT (test_json -> 'field4')::float8 FROM test_js
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
@@ -560,7 +544,8 @@ EXPLAIN (VERBOSE, COSTS OFF) SELECT (test_json['field4'])::float8 FROM test_json
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';
+-- Verify that int2 and float4 are also rewritten through the object-field family.
+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
@@ -568,7 +553,7 @@ EXPLAIN (VERBOSE, COSTS OFF) SELECT (test_json['field4'])::int2 FROM test_jsonb
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';
+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
@@ -607,18 +592,6 @@ SELECT (test_json -> 'field4')::float8 FROM test_jsonb WHERE json_type = 'object
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
@@ -650,16 +623,17 @@ SELECT (test_json['field4'])::float8 FROM test_jsonb WHERE json_type = 'object';
4
(1 row)
-SELECT (test_json['field4'])::int2 FROM test_jsonb WHERE json_type = 'object';
- test_json
------------
- 4
+-- Unsupported targets still work correctly through the original path
+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';
- test_json
------------
- 4
+SELECT (test_json -> 'field4')::float4 FROM test_jsonb WHERE json_type = 'object';
+ float4
+--------
+ 4
(1 row)
-- Section 3: NULL semantics (missing key, JSON null, non-object input)
@@ -711,30 +685,6 @@ SELECT (test_json -> 'nonexistent')::float8 FROM test_jsonb WHERE json_type = 'o
(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
@@ -771,14 +721,6 @@ SELECT (test_json -> 'field5')::int4 FROM test_jsonb WHERE json_type = 'object';
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
@@ -827,30 +769,6 @@ SELECT jsonb_object_field_float8('{"a": 3.14}'::jsonb, 'a');
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
@@ -888,30 +806,6 @@ SELECT jsonb_object_field_float8('{"a": 1.0}'::jsonb, 'missing');
(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
@@ -925,20 +819,9 @@ 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
-- Optimized typed extraction: array-element family
-- The planner rewrites (j->idx)::type and (j[idx])::type into direct
-- typed extractor calls for the same target types as the object-field family.
@@ -981,20 +864,6 @@ EXPLAIN (VERBOSE, COSTS OFF) SELECT (test_arr -> 0)::int8 FROM test_jsonb_arr;
Output: jsonb_array_element_int8(test_arr, 0)
(2 rows)
-EXPLAIN (VERBOSE, COSTS OFF) SELECT (test_arr -> 0)::int2 FROM test_jsonb_arr;
- QUERY PLAN
--------------------------------------------------
- Seq Scan on pg_temp.test_jsonb_arr
- Output: jsonb_array_element_int2(test_arr, 0)
-(2 rows)
-
-EXPLAIN (VERBOSE, COSTS OFF) SELECT (test_arr -> 1)::float4 FROM test_jsonb_arr;
- QUERY PLAN
----------------------------------------------------
- Seq Scan on pg_temp.test_jsonb_arr
- Output: jsonb_array_element_float4(test_arr, 1)
-(2 rows)
-
-- Section A1b: planner rewrite verification (array subscripting form)
EXPLAIN (VERBOSE, COSTS OFF) SELECT (test_arr[0])::numeric FROM test_jsonb_arr;
QUERY PLAN
@@ -1031,13 +900,6 @@ EXPLAIN (VERBOSE, COSTS OFF) SELECT (test_arr[0])::int8 FROM test_jsonb_arr;
Output: jsonb_array_element_int8(test_arr, 0)
(2 rows)
-EXPLAIN (VERBOSE, COSTS OFF) SELECT (test_arr[0])::int2 FROM test_jsonb_arr;
- QUERY PLAN
--------------------------------------------------
- Seq Scan on pg_temp.test_jsonb_arr
- Output: jsonb_array_element_int2(test_arr, 0)
-(2 rows)
-
-- Section A1c: planner rewrite verification (direct function call form)
EXPLAIN (VERBOSE, COSTS OFF) SELECT (jsonb_array_element(test_arr, 0))::int4 FROM test_jsonb_arr;
QUERY PLAN
@@ -1077,18 +939,6 @@ SELECT (test_arr -> 2)::bool FROM test_jsonb_arr;
t
(1 row)
-SELECT (test_arr -> 0)::int2 FROM test_jsonb_arr;
- int2
-------
- 10
-(1 row)
-
-SELECT (test_arr -> 1)::float4 FROM test_jsonb_arr;
- float4
---------
- 2.5
-(1 row)
-
-- Section A2b: correct execution through subscripting
SELECT (test_arr[0])::int4 FROM test_jsonb_arr;
test_arr
@@ -1135,18 +985,6 @@ SELECT ('{"k":1}'::jsonb -> 0)::int4; -- non-array input
(1 row)
-SELECT (test_arr -> 99)::int2 FROM test_jsonb_arr; -- out of range, int2
- int2
-------
-
-(1 row)
-
-SELECT (test_arr -> 3)::float4 FROM test_jsonb_arr; -- JSON null element, float4
- float4
---------
-
-(1 row)
-
-- Section A3b: NULL through subscripting
SELECT (test_arr[99])::float8 FROM test_jsonb_arr; -- out of range
test_arr
@@ -1171,10 +1009,6 @@ SELECT (test_arr -> 6)::int8 FROM test_jsonb_arr; -- object container to int8
ERROR: cannot cast jsonb array or object to type bigint
SELECT (test_arr -> 2)::int4 FROM test_jsonb_arr; -- bool to int4
ERROR: cannot cast jsonb boolean to type integer
-SELECT (test_arr -> 4)::int2 FROM test_jsonb_arr; -- string to int2
-ERROR: cannot cast jsonb string to type smallint
-SELECT (test_arr -> 4)::float4 FROM test_jsonb_arr; -- string to float4
-ERROR: cannot cast jsonb string to type real
-- Section A4b: error through subscripting
SELECT (test_arr[4])::int8 FROM test_jsonb_arr; -- string to int8
ERROR: cannot cast jsonb string to type bigint
@@ -1235,18 +1069,6 @@ SELECT jsonb_array_element_int8('[true]'::jsonb, 0);
ERROR: cannot cast jsonb boolean to type bigint
SELECT jsonb_array_element_float8('[[1,2]]'::jsonb, 0); -- container to float8
ERROR: cannot cast jsonb array or object to type double precision
-SELECT jsonb_array_element_int2('[10, 20]'::jsonb, 0);
- jsonb_array_element_int2
---------------------------
- 10
-(1 row)
-
-SELECT jsonb_array_element_float4('[3.14, 2.5]'::jsonb, 1);
- jsonb_array_element_float4
-----------------------------
- 2.5
-(1 row)
-
-- Optimized typed extraction: extract-path family
-- The planner rewrites (j #> path)::type and (jsonb_extract_path(j, ...))::type
-- into direct typed extractor calls for the same target types.
@@ -1291,22 +1113,6 @@ EXPLAIN (VERBOSE, COSTS OFF) SELECT (test_json #> '{field7}')::bool FROM test_js
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_extract_path_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_extract_path_float4(test_json, '{field4}'::text[])
- Filter: (test_jsonb.json_type = 'object'::text)
-(3 rows)
-
-- Section P1b: planner rewrite verification (direct function-call form)
EXPLAIN (VERBOSE, COSTS OFF) SELECT (jsonb_extract_path(test_json, 'field4'))::float8 FROM test_jsonb WHERE json_type = 'object';
QUERY PLAN
@@ -1461,19 +1267,6 @@ SELECT jsonb_extract_path_int4('{"a":[10,20]}'::jsonb, ARRAY['a','1']);
20
(1 row)
--- direct calls: int2 and float4
-SELECT jsonb_extract_path_int2('{"a":{"b":7}}'::jsonb, ARRAY['a','b']);
- jsonb_extract_path_int2
--------------------------
- 7
-(1 row)
-
-SELECT jsonb_extract_path_float4('{"a":3.14}'::jsonb, ARRAY['a']);
- jsonb_extract_path_float4
----------------------------
- 3.14
-(1 row)
-
-- Optimized typed extraction: multi-subscript chains
-- The planner lowers j['a']['b'], j['a'][0], etc. to the extract-path typed
-- extractor family, reusing the same functions as the #> operator path.
@@ -1526,21 +1319,6 @@ EXPLAIN (VERBOSE, COSTS OFF) SELECT (test_json['field4'])::int4 FROM test_jsonb
Filter: (test_jsonb.json_type = 'object'::text)
(3 rows)
--- Section M1b: multi-subscript int2/float4
-EXPLAIN (VERBOSE, COSTS OFF) SELECT (test_json['field6']['f1'])::int2 FROM test_jsonb WHERE json_type = 'object';
- QUERY PLAN
----------------------------------------------------------------------------------
- Seq Scan on pg_temp.test_jsonb
- Output: jsonb_extract_path_int2(test_json, ARRAY['field6'::text, 'f1'::text])
- Filter: (test_jsonb.json_type = 'object'::text)
-(3 rows)
-
-SELECT (('{"a":{"b":7}}'::jsonb)['a']['b'])::int2;
- int2
-------
- 7
-(1 row)
-
-- Section M2: execution through multi-subscript rewrite
SELECT (('{"a":{"b":42}}'::jsonb)['a']['b'])::int4;
int4
@@ -1631,6 +1409,606 @@ SELECT (('{"a":[[10,20],[30,40]]}'::jsonb)['a'][i][0])::int4 FROM generate_serie
Function Call: generate_series(0, 1)
(3 rows)
+-- Optimized typed extraction: jsonb_path_query_first family
+-- The planner rewrites (jsonb_path_query_first(j, path))::type into a direct
+-- typed extractor call for the same target types as the other families.
+-- Section Q1: planner rewrite verification
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jsonb_path_query_first(test_json, '$.field4'))::int4 FROM test_jsonb WHERE json_type = 'object';
+ QUERY PLAN
+----------------------------------------------------------------------------------------------
+ Seq Scan on pg_temp.test_jsonb
+ Output: jsonb_path_query_first_int4(test_json, '$."field4"'::jsonpath, '{}'::jsonb, false)
+ Filter: (test_jsonb.json_type = 'object'::text)
+(3 rows)
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jsonb_path_query_first(test_json, '$.field7'))::bool FROM test_jsonb WHERE json_type = 'object';
+ QUERY PLAN
+----------------------------------------------------------------------------------------------
+ Seq Scan on pg_temp.test_jsonb
+ Output: jsonb_path_query_first_bool(test_json, '$."field7"'::jsonpath, '{}'::jsonb, false)
+ Filter: (test_jsonb.json_type = 'object'::text)
+(3 rows)
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jsonb_path_query_first(test_json, '$.field4'))::numeric FROM test_jsonb WHERE json_type = 'object';
+ QUERY PLAN
+-------------------------------------------------------------------------------------------------
+ Seq Scan on pg_temp.test_jsonb
+ Output: jsonb_path_query_first_numeric(test_json, '$."field4"'::jsonpath, '{}'::jsonb, false)
+ Filter: (test_jsonb.json_type = 'object'::text)
+(3 rows)
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jsonb_path_query_first(test_json, '$.field4'))::int8 FROM test_jsonb WHERE json_type = 'object';
+ QUERY PLAN
+----------------------------------------------------------------------------------------------
+ Seq Scan on pg_temp.test_jsonb
+ Output: jsonb_path_query_first_int8(test_json, '$."field4"'::jsonpath, '{}'::jsonb, false)
+ Filter: (test_jsonb.json_type = 'object'::text)
+(3 rows)
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jsonb_path_query_first(test_json, '$.field4'))::float8 FROM test_jsonb WHERE json_type = 'object';
+ QUERY PLAN
+------------------------------------------------------------------------------------------------
+ Seq Scan on pg_temp.test_jsonb
+ Output: jsonb_path_query_first_float8(test_json, '$."field4"'::jsonpath, '{}'::jsonb, false)
+ Filter: (test_jsonb.json_type = 'object'::text)
+(3 rows)
+
+-- 3-arg form (vars explicit)
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jsonb_path_query_first(test_json, '$.field4', '{}'))::int4 FROM test_jsonb WHERE json_type = 'object';
+ QUERY PLAN
+----------------------------------------------------------------------------------------------
+ Seq Scan on pg_temp.test_jsonb
+ Output: jsonb_path_query_first_int4(test_json, '$."field4"'::jsonpath, '{}'::jsonb, false)
+ Filter: (test_jsonb.json_type = 'object'::text)
+(3 rows)
+
+-- 4-arg form (vars + silent explicit)
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jsonb_path_query_first(test_json, '$.field4', '{}', true))::float8 FROM test_jsonb WHERE json_type = 'object';
+ QUERY PLAN
+-----------------------------------------------------------------------------------------------
+ Seq Scan on pg_temp.test_jsonb
+ Output: jsonb_path_query_first_float8(test_json, '$."field4"'::jsonpath, '{}'::jsonb, true)
+ Filter: (test_jsonb.json_type = 'object'::text)
+(3 rows)
+
+-- Section Q2: execution through rewrite
+SELECT (jsonb_path_query_first('{"a":42}'::jsonb, '$.a'))::int4;
+ jsonb_path_query_first
+------------------------
+ 42
+(1 row)
+
+SELECT (jsonb_path_query_first('{"a":3.14}'::jsonb, '$.a'))::float8;
+ jsonb_path_query_first
+------------------------
+ 3.14
+(1 row)
+
+SELECT (jsonb_path_query_first('{"a":true}'::jsonb, '$.a'))::bool;
+ jsonb_path_query_first
+------------------------
+ t
+(1 row)
+
+SELECT (jsonb_path_query_first('{"a":99}'::jsonb, '$.a'))::numeric;
+ jsonb_path_query_first
+------------------------
+ 99
+(1 row)
+
+SELECT (jsonb_path_query_first('{"a":99}'::jsonb, '$.a'))::int8;
+ jsonb_path_query_first
+------------------------
+ 99
+(1 row)
+
+-- nested object path
+SELECT (jsonb_path_query_first('{"a":{"b":42}}'::jsonb, '$.a.b'))::int4;
+ jsonb_path_query_first
+------------------------
+ 42
+(1 row)
+
+-- array access in jsonpath
+SELECT (jsonb_path_query_first('{"a":[10,20]}'::jsonb, '$.a[1]'))::int4;
+ jsonb_path_query_first
+------------------------
+ 20
+(1 row)
+
+-- Section Q3: NULL semantics
+SELECT (jsonb_path_query_first('{"a":1}'::jsonb, '$.b'))::int4; -- missing path
+ jsonb_path_query_first
+------------------------
+
+(1 row)
+
+SELECT (jsonb_path_query_first('{"a":null}'::jsonb, '$.a'))::int4; -- JSON null
+ jsonb_path_query_first
+------------------------
+
+(1 row)
+
+-- Section Q4: silent semantics
+-- silent=true suppresses jsonpath execution errors, NOT cast errors
+SELECT (jsonb_path_query_first('{"a":"text"}'::jsonb, '$.a', '{}', true))::int4; -- cast error not suppressed
+ERROR: cannot cast jsonb string to type integer
+-- silent=false (default): jsonpath strict error
+SELECT (jsonb_path_query_first('{"a":1}'::jsonb, 'strict $.b', '{}', false))::int4; -- strict missing key error
+ERROR: JSON object does not contain key "b"
+-- silent=true: jsonpath strict error suppressed
+SELECT (jsonb_path_query_first('{"a":1}'::jsonb, 'strict $.b', '{}', true))::int4; -- suppressed, returns NULL
+ jsonb_path_query_first
+------------------------
+
+(1 row)
+
+-- Section Q5: type-mismatch errors
+SELECT (jsonb_path_query_first('{"a":"hello"}'::jsonb, '$.a'))::int4; -- string to int4
+ERROR: cannot cast jsonb string to type integer
+SELECT (jsonb_path_query_first('{"a":[1,2]}'::jsonb, '$.a'))::int4; -- container to int4
+ERROR: cannot cast jsonb array to type integer
+-- Section Q6: direct calls to typed extractor builtins
+SELECT jsonb_path_query_first_int4('{"a":42}'::jsonb, '$.a'::jsonpath, '{}'::jsonb, false);
+ jsonb_path_query_first_int4
+-----------------------------
+ 42
+(1 row)
+
+SELECT jsonb_path_query_first_int8('{"a":99}'::jsonb, '$.a'::jsonpath, '{}'::jsonb, false);
+ jsonb_path_query_first_int8
+-----------------------------
+ 99
+(1 row)
+
+SELECT jsonb_path_query_first_float8('{"a":3.14}'::jsonb, '$.a'::jsonpath, '{}'::jsonb, false);
+ jsonb_path_query_first_float8
+-------------------------------
+ 3.14
+(1 row)
+
+SELECT jsonb_path_query_first_numeric('{"a":1.23}'::jsonb, '$.a'::jsonpath, '{}'::jsonb, false);
+ jsonb_path_query_first_numeric
+--------------------------------
+ 1.23
+(1 row)
+
+SELECT jsonb_path_query_first_bool('{"a":true}'::jsonb, '$.a'::jsonpath, '{}'::jsonb, false);
+ jsonb_path_query_first_bool
+-----------------------------
+ t
+(1 row)
+
+-- direct call: missing path
+SELECT jsonb_path_query_first_int4('{"a":1}'::jsonb, '$.b'::jsonpath, '{}'::jsonb, false);
+ jsonb_path_query_first_int4
+-----------------------------
+
+(1 row)
+
+-- direct call: with vars
+SELECT jsonb_path_query_first_int4('{"a":42}'::jsonb, '$.a ? (@ > $x)'::jsonpath, '{"x":10}'::jsonb, false);
+ jsonb_path_query_first_int4
+-----------------------------
+ 42
+(1 row)
+
+-- Optimized typed extraction: jsonb_path_query_first_tz family
+-- Same pattern as the non-tz family, but with timezone-aware evaluation.
+-- Section T1: planner rewrite verification
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jsonb_path_query_first_tz(test_json, '$.field4'))::int4 FROM test_jsonb WHERE json_type = 'object';
+ QUERY PLAN
+-------------------------------------------------------------------------------------------------
+ Seq Scan on pg_temp.test_jsonb
+ Output: jsonb_path_query_first_tz_int4(test_json, '$."field4"'::jsonpath, '{}'::jsonb, false)
+ Filter: (test_jsonb.json_type = 'object'::text)
+(3 rows)
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jsonb_path_query_first_tz(test_json, '$.field7'))::bool FROM test_jsonb WHERE json_type = 'object';
+ QUERY PLAN
+-------------------------------------------------------------------------------------------------
+ Seq Scan on pg_temp.test_jsonb
+ Output: jsonb_path_query_first_tz_bool(test_json, '$."field7"'::jsonpath, '{}'::jsonb, false)
+ Filter: (test_jsonb.json_type = 'object'::text)
+(3 rows)
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jsonb_path_query_first_tz(test_json, '$.field4'))::numeric FROM test_jsonb WHERE json_type = 'object';
+ QUERY PLAN
+----------------------------------------------------------------------------------------------------
+ Seq Scan on pg_temp.test_jsonb
+ Output: jsonb_path_query_first_tz_numeric(test_json, '$."field4"'::jsonpath, '{}'::jsonb, false)
+ Filter: (test_jsonb.json_type = 'object'::text)
+(3 rows)
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jsonb_path_query_first_tz(test_json, '$.field4'))::int8 FROM test_jsonb WHERE json_type = 'object';
+ QUERY PLAN
+-------------------------------------------------------------------------------------------------
+ Seq Scan on pg_temp.test_jsonb
+ Output: jsonb_path_query_first_tz_int8(test_json, '$."field4"'::jsonpath, '{}'::jsonb, false)
+ Filter: (test_jsonb.json_type = 'object'::text)
+(3 rows)
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jsonb_path_query_first_tz(test_json, '$.field4'))::float8 FROM test_jsonb WHERE json_type = 'object';
+ QUERY PLAN
+---------------------------------------------------------------------------------------------------
+ Seq Scan on pg_temp.test_jsonb
+ Output: jsonb_path_query_first_tz_float8(test_json, '$."field4"'::jsonpath, '{}'::jsonb, false)
+ Filter: (test_jsonb.json_type = 'object'::text)
+(3 rows)
+
+-- 3-arg form (vars explicit)
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jsonb_path_query_first_tz(test_json, '$.field4', '{}'))::int4 FROM test_jsonb WHERE json_type = 'object';
+ QUERY PLAN
+-------------------------------------------------------------------------------------------------
+ Seq Scan on pg_temp.test_jsonb
+ Output: jsonb_path_query_first_tz_int4(test_json, '$."field4"'::jsonpath, '{}'::jsonb, false)
+ Filter: (test_jsonb.json_type = 'object'::text)
+(3 rows)
+
+-- 4-arg form (vars + silent explicit)
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jsonb_path_query_first_tz(test_json, '$.field4', '{}', true))::float8 FROM test_jsonb WHERE json_type = 'object';
+ QUERY PLAN
+--------------------------------------------------------------------------------------------------
+ Seq Scan on pg_temp.test_jsonb
+ Output: jsonb_path_query_first_tz_float8(test_json, '$."field4"'::jsonpath, '{}'::jsonb, true)
+ Filter: (test_jsonb.json_type = 'object'::text)
+(3 rows)
+
+-- Section T2: execution through rewrite
+SELECT (jsonb_path_query_first_tz('{"a":42}'::jsonb, '$.a'))::int4;
+ jsonb_path_query_first_tz
+---------------------------
+ 42
+(1 row)
+
+SELECT (jsonb_path_query_first_tz('{"a":3.14}'::jsonb, '$.a'))::float8;
+ jsonb_path_query_first_tz
+---------------------------
+ 3.14
+(1 row)
+
+SELECT (jsonb_path_query_first_tz('{"a":true}'::jsonb, '$.a'))::bool;
+ jsonb_path_query_first_tz
+---------------------------
+ t
+(1 row)
+
+SELECT (jsonb_path_query_first_tz('{"a":99}'::jsonb, '$.a'))::numeric;
+ jsonb_path_query_first_tz
+---------------------------
+ 99
+(1 row)
+
+SELECT (jsonb_path_query_first_tz('{"a":99}'::jsonb, '$.a'))::int8;
+ jsonb_path_query_first_tz
+---------------------------
+ 99
+(1 row)
+
+-- nested path
+SELECT (jsonb_path_query_first_tz('{"a":{"b":42}}'::jsonb, '$.a.b'))::int4;
+ jsonb_path_query_first_tz
+---------------------------
+ 42
+(1 row)
+
+-- array access
+SELECT (jsonb_path_query_first_tz('{"a":[10,20]}'::jsonb, '$.a[1]'))::int4;
+ jsonb_path_query_first_tz
+---------------------------
+ 20
+(1 row)
+
+-- Section T3: NULL semantics
+SELECT (jsonb_path_query_first_tz('{"a":1}'::jsonb, '$.b'))::int4; -- missing path
+ jsonb_path_query_first_tz
+---------------------------
+
+(1 row)
+
+SELECT (jsonb_path_query_first_tz('{"a":null}'::jsonb, '$.a'))::int4; -- JSON null
+ jsonb_path_query_first_tz
+---------------------------
+
+(1 row)
+
+-- Section T4: silent semantics
+SELECT (jsonb_path_query_first_tz('{"a":"text"}'::jsonb, '$.a', '{}', true))::int4; -- cast error not suppressed
+ERROR: cannot cast jsonb string to type integer
+SELECT (jsonb_path_query_first_tz('{"a":1}'::jsonb, 'strict $.b', '{}', false))::int4; -- strict error
+ERROR: JSON object does not contain key "b"
+SELECT (jsonb_path_query_first_tz('{"a":1}'::jsonb, 'strict $.b', '{}', true))::int4; -- suppressed, NULL
+ jsonb_path_query_first_tz
+---------------------------
+
+(1 row)
+
+-- Section T5: type-mismatch errors
+SELECT (jsonb_path_query_first_tz('{"a":"hello"}'::jsonb, '$.a'))::int4;
+ERROR: cannot cast jsonb string to type integer
+-- Section T6: direct calls to typed extractor builtins
+SELECT jsonb_path_query_first_tz_int4('{"a":42}'::jsonb, '$.a'::jsonpath, '{}'::jsonb, false);
+ jsonb_path_query_first_tz_int4
+--------------------------------
+ 42
+(1 row)
+
+SELECT jsonb_path_query_first_tz_int8('{"a":99}'::jsonb, '$.a'::jsonpath, '{}'::jsonb, false);
+ jsonb_path_query_first_tz_int8
+--------------------------------
+ 99
+(1 row)
+
+SELECT jsonb_path_query_first_tz_float8('{"a":3.14}'::jsonb, '$.a'::jsonpath, '{}'::jsonb, false);
+ jsonb_path_query_first_tz_float8
+----------------------------------
+ 3.14
+(1 row)
+
+SELECT jsonb_path_query_first_tz_numeric('{"a":1.23}'::jsonb, '$.a'::jsonpath, '{}'::jsonb, false);
+ jsonb_path_query_first_tz_numeric
+-----------------------------------
+ 1.23
+(1 row)
+
+SELECT jsonb_path_query_first_tz_bool('{"a":true}'::jsonb, '$.a'::jsonpath, '{}'::jsonb, false);
+ jsonb_path_query_first_tz_bool
+--------------------------------
+ t
+(1 row)
+
+-- Optimized typed extraction: int2 / float4 matrix completion
+-- Extends the existing typed extractor families to cover int2 (smallint)
+-- and float4 (real) target types.
+-- Section I1: direct calls to int2 / float4 typed extractor builtins (object-field)
+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)
+
+-- NULL semantics
+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)
+
+-- type-mismatch errors
+SELECT jsonb_object_field_int2('{"a": "text"}'::jsonb, 'a');
+ERROR: cannot cast jsonb string to type smallint
+SELECT jsonb_object_field_float4('{"a": true}'::jsonb, 'a');
+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
+-- int2 overflow
+SELECT jsonb_object_field_int2('{"a": 99999}'::jsonb, 'a');
+ERROR: smallint out of range
+-- Section I2: planner rewrite verification — one per family (using int2)
+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_arr -> 0)::int2 FROM test_jsonb_arr;
+ QUERY PLAN
+-------------------------------------------------
+ Seq Scan on pg_temp.test_jsonb_arr
+ Output: jsonb_array_element_int2(test_arr, 0)
+(2 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_extract_path_int2(test_json, '{field4}'::text[])
+ Filter: (test_jsonb.json_type = 'object'::text)
+(3 rows)
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jsonb_path_query_first(test_json, '$.field4'))::int2 FROM test_jsonb WHERE json_type = 'object';
+ QUERY PLAN
+----------------------------------------------------------------------------------------------
+ Seq Scan on pg_temp.test_jsonb
+ Output: jsonb_path_query_first_int2(test_json, '$."field4"'::jsonpath, '{}'::jsonb, false)
+ Filter: (test_jsonb.json_type = 'object'::text)
+(3 rows)
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jsonb_path_query_first_tz(test_json, '$.field4'))::int2 FROM test_jsonb WHERE json_type = 'object';
+ QUERY PLAN
+-------------------------------------------------------------------------------------------------
+ Seq Scan on pg_temp.test_jsonb
+ Output: jsonb_path_query_first_tz_int2(test_json, '$."field4"'::jsonpath, '{}'::jsonb, false)
+ Filter: (test_jsonb.json_type = 'object'::text)
+(3 rows)
+
+-- float4 rewrite (one representative)
+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 I3: multi-subscript automatic coverage
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (test_json['field6']['f1'])::int2 FROM test_jsonb WHERE json_type = 'object';
+ QUERY PLAN
+---------------------------------------------------------------------------------
+ Seq Scan on pg_temp.test_jsonb
+ Output: jsonb_extract_path_int2(test_json, ARRAY['field6'::text, 'f1'::text])
+ Filter: (test_jsonb.json_type = 'object'::text)
+(3 rows)
+
+SELECT (('{"a":{"b":7}}'::jsonb)['a']['b'])::int2;
+ int2
+------
+ 7
+(1 row)
+
+-- Section I4: representative execution through rewrite (distributed across families)
+-- object-field: int2
+SELECT (test_json -> 'field4')::int2 FROM test_jsonb WHERE json_type = 'object';
+ int2
+------
+ 4
+(1 row)
+
+-- object-field: float4
+SELECT (test_json -> 'field4')::float4 FROM test_jsonb WHERE json_type = 'object';
+ float4
+--------
+ 4
+(1 row)
+
+-- array-element: int2
+SELECT (test_arr -> 0)::int2 FROM test_jsonb_arr;
+ int2
+------
+ 10
+(1 row)
+
+-- array-element: float4
+SELECT (test_arr -> 1)::float4 FROM test_jsonb_arr;
+ float4
+--------
+ 2.5
+(1 row)
+
+-- extract-path: float4
+SELECT (test_json #> '{field4}')::float4 FROM test_jsonb WHERE json_type = 'object';
+ float4
+--------
+ 4
+(1 row)
+
+-- jsonb_path_query_first: int2
+SELECT (jsonb_path_query_first('{"a":42}'::jsonb, '$.a'))::int2;
+ jsonb_path_query_first
+------------------------
+ 42
+(1 row)
+
+-- jsonb_path_query_first_tz: float4
+SELECT (jsonb_path_query_first_tz('{"a":3.14}'::jsonb, '$.a'))::float4;
+ jsonb_path_query_first_tz
+---------------------------
+ 3.14
+(1 row)
+
+-- Section I5: NULL through rewrite
+SELECT (test_json -> 'nonexistent')::int2 FROM test_jsonb WHERE json_type = 'object';
+ int2
+------
+
+(1 row)
+
+SELECT (test_arr -> 3)::float4 FROM test_jsonb_arr; -- JSON null element
+ float4
+--------
+
+(1 row)
+
+SELECT (test_arr -> 99)::int2 FROM test_jsonb_arr; -- out of range
+ int2
+------
+
+(1 row)
+
+-- Section I6: type-mismatch through rewrite
+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
+-- Section I7: direct calls to other family builtins (representative)
+SELECT jsonb_array_element_int2('[10, 20]'::jsonb, 0);
+ jsonb_array_element_int2
+--------------------------
+ 10
+(1 row)
+
+SELECT jsonb_array_element_float4('[3.14, 2.5]'::jsonb, 1);
+ jsonb_array_element_float4
+----------------------------
+ 2.5
+(1 row)
+
+SELECT jsonb_extract_path_int2('{"a":{"b":7}}'::jsonb, ARRAY['a','b']);
+ jsonb_extract_path_int2
+-------------------------
+ 7
+(1 row)
+
+SELECT jsonb_extract_path_float4('{"a":3.14}'::jsonb, ARRAY['a']);
+ jsonb_extract_path_float4
+---------------------------
+ 3.14
+(1 row)
+
+SELECT jsonb_path_query_first_int2('{"a":42}'::jsonb, '$.a'::jsonpath, '{}'::jsonb, false);
+ jsonb_path_query_first_int2
+-----------------------------
+ 42
+(1 row)
+
+SELECT jsonb_path_query_first_float4('{"a":3.14}'::jsonb, '$.a'::jsonpath, '{}'::jsonb, false);
+ jsonb_path_query_first_float4
+-------------------------------
+ 3.14
+(1 row)
+
+SELECT jsonb_path_query_first_tz_int2('{"a":42}'::jsonb, '$.a'::jsonpath, '{}'::jsonb, false);
+ jsonb_path_query_first_tz_int2
+--------------------------------
+ 42
+(1 row)
+
+SELECT jsonb_path_query_first_tz_float4('{"a":3.14}'::jsonb, '$.a'::jsonpath, '{}'::jsonb, false);
+ jsonb_path_query_first_tz_float4
+----------------------------------
+ 3.14
+(1 row)
+
SELECT test_json -> 'x' FROM test_jsonb WHERE json_type = 'scalar';
?column?
----------
diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql
index ed70c4d29ee..c3e1c7ae735 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -167,8 +167,6 @@ EXPLAIN (VERBOSE, COSTS OFF) SELECT (test_json -> 'field7')::bool FROM test_json
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';
@@ -176,8 +174,10 @@ EXPLAIN (VERBOSE, COSTS OFF) SELECT (test_json['field7'])::bool FROM test_jsonb
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';
+
+-- Verify that int2 and float4 are also rewritten through the object-field family.
+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';
@@ -185,8 +185,6 @@ 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';
@@ -194,8 +192,10 @@ 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';
+
+-- Unsupported targets still work correctly through the original path
+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
@@ -206,10 +206,6 @@ SELECT (test_json -> 'field3')::int4 FROM test_jsonb WHERE json_type = 'object';
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
@@ -225,10 +221,6 @@ SELECT (test_json -> 'field1')::int8 FROM test_jsonb WHERE json_type = 'object';
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
@@ -242,10 +234,6 @@ 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');
@@ -253,10 +241,6 @@ 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');
@@ -264,14 +248,8 @@ SELECT jsonb_object_field_numeric('{"a": {"x":1}}'::jsonb, 'a'); -- container t
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');
-- Optimized typed extraction: array-element family
-- The planner rewrites (j->idx)::type and (j[idx])::type into direct
@@ -287,8 +265,6 @@ EXPLAIN (VERBOSE, COSTS OFF) SELECT (test_arr -> 0)::int4 FROM test_jsonb_arr;
EXPLAIN (VERBOSE, COSTS OFF) SELECT (test_arr -> 1)::float8 FROM test_jsonb_arr;
EXPLAIN (VERBOSE, COSTS OFF) SELECT (test_arr -> 2)::bool FROM test_jsonb_arr;
EXPLAIN (VERBOSE, COSTS OFF) SELECT (test_arr -> 0)::int8 FROM test_jsonb_arr;
-EXPLAIN (VERBOSE, COSTS OFF) SELECT (test_arr -> 0)::int2 FROM test_jsonb_arr;
-EXPLAIN (VERBOSE, COSTS OFF) SELECT (test_arr -> 1)::float4 FROM test_jsonb_arr;
-- Section A1b: planner rewrite verification (array subscripting form)
EXPLAIN (VERBOSE, COSTS OFF) SELECT (test_arr[0])::numeric FROM test_jsonb_arr;
@@ -296,7 +272,6 @@ EXPLAIN (VERBOSE, COSTS OFF) SELECT (test_arr[0])::int4 FROM test_jsonb_arr;
EXPLAIN (VERBOSE, COSTS OFF) SELECT (test_arr[1])::float8 FROM test_jsonb_arr;
EXPLAIN (VERBOSE, COSTS OFF) SELECT (test_arr[2])::bool FROM test_jsonb_arr;
EXPLAIN (VERBOSE, COSTS OFF) SELECT (test_arr[0])::int8 FROM test_jsonb_arr;
-EXPLAIN (VERBOSE, COSTS OFF) SELECT (test_arr[0])::int2 FROM test_jsonb_arr;
-- Section A1c: planner rewrite verification (direct function call form)
EXPLAIN (VERBOSE, COSTS OFF) SELECT (jsonb_array_element(test_arr, 0))::int4 FROM test_jsonb_arr;
@@ -307,8 +282,6 @@ SELECT (test_arr -> 0)::int8 FROM test_jsonb_arr;
SELECT (test_arr -> 0)::numeric FROM test_jsonb_arr;
SELECT (test_arr -> 1)::float8 FROM test_jsonb_arr;
SELECT (test_arr -> 2)::bool FROM test_jsonb_arr;
-SELECT (test_arr -> 0)::int2 FROM test_jsonb_arr;
-SELECT (test_arr -> 1)::float4 FROM test_jsonb_arr;
-- Section A2b: correct execution through subscripting
SELECT (test_arr[0])::int4 FROM test_jsonb_arr;
@@ -321,8 +294,6 @@ SELECT (test_arr -> 3)::numeric FROM test_jsonb_arr; -- JSON null element
SELECT (test_arr -> -1)::int4 FROM test_jsonb_arr; -- negative: last element is object, wrong type would error; use -4 for null
SELECT (test_arr -> -4)::numeric FROM test_jsonb_arr; -- negative index pointing to null element
SELECT ('{"k":1}'::jsonb -> 0)::int4; -- non-array input
-SELECT (test_arr -> 99)::int2 FROM test_jsonb_arr; -- out of range, int2
-SELECT (test_arr -> 3)::float4 FROM test_jsonb_arr; -- JSON null element, float4
-- Section A3b: NULL through subscripting
SELECT (test_arr[99])::float8 FROM test_jsonb_arr; -- out of range
@@ -334,8 +305,6 @@ SELECT (test_arr -> 4)::float8 FROM test_jsonb_arr; -- string to float8
SELECT (test_arr -> 5)::numeric FROM test_jsonb_arr; -- array container to numeric
SELECT (test_arr -> 6)::int8 FROM test_jsonb_arr; -- object container to int8
SELECT (test_arr -> 2)::int4 FROM test_jsonb_arr; -- bool to int4
-SELECT (test_arr -> 4)::int2 FROM test_jsonb_arr; -- string to int2
-SELECT (test_arr -> 4)::float4 FROM test_jsonb_arr; -- string to float4
-- Section A4b: error through subscripting
SELECT (test_arr[4])::int8 FROM test_jsonb_arr; -- string to int8
@@ -354,9 +323,6 @@ SELECT jsonb_array_element_float8('{"a":1}'::jsonb, 0); -- non-array
SELECT jsonb_array_element_int4('["text"]'::jsonb, 0);
SELECT jsonb_array_element_int8('[true]'::jsonb, 0);
SELECT jsonb_array_element_float8('[[1,2]]'::jsonb, 0); -- container to float8
-SELECT jsonb_array_element_int2('[10, 20]'::jsonb, 0);
-SELECT jsonb_array_element_float4('[3.14, 2.5]'::jsonb, 1);
-
-- Optimized typed extraction: extract-path family
-- The planner rewrites (j #> path)::type and (jsonb_extract_path(j, ...))::type
@@ -369,8 +335,6 @@ EXPLAIN (VERBOSE, COSTS OFF) SELECT (test_json #> '{field4}')::float8 FROM test_
EXPLAIN (VERBOSE, COSTS OFF) SELECT (test_json #> '{field6,f1}')::int8 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}')::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 P1b: planner rewrite verification (direct function-call form)
EXPLAIN (VERBOSE, COSTS OFF) SELECT (jsonb_extract_path(test_json, 'field4'))::float8 FROM test_jsonb WHERE json_type = 'object';
EXPLAIN (VERBOSE, COSTS OFF) SELECT (jsonb_extract_path(test_json, 'field6', 'f1'))::int4 FROM test_jsonb WHERE json_type = 'object';
@@ -414,10 +378,6 @@ SELECT jsonb_extract_path_int4('{"a":1}'::jsonb, ARRAY['b']);
SELECT jsonb_extract_path_int4('{"a":"text"}'::jsonb, ARRAY['a']);
-- direct calls: array index in path
SELECT jsonb_extract_path_int4('{"a":[10,20]}'::jsonb, ARRAY['a','1']);
--- direct calls: int2 and float4
-SELECT jsonb_extract_path_int2('{"a":{"b":7}}'::jsonb, ARRAY['a','b']);
-SELECT jsonb_extract_path_float4('{"a":3.14}'::jsonb, ARRAY['a']);
-
-- Optimized typed extraction: multi-subscript chains
-- The planner lowers j['a']['b'], j['a'][0], etc. to the extract-path typed
@@ -433,10 +393,6 @@ EXPLAIN (VERBOSE, COSTS OFF) SELECT (test_json['field6']['f1'])::numeric FROM te
-- Verify single subscript still uses the existing object-field family, not extract-path
EXPLAIN (VERBOSE, COSTS OFF) SELECT (test_json['field4'])::int4 FROM test_jsonb WHERE json_type = 'object';
-
--- Section M1b: multi-subscript int2/float4
-EXPLAIN (VERBOSE, COSTS OFF) SELECT (test_json['field6']['f1'])::int2 FROM test_jsonb WHERE json_type = 'object';
-SELECT (('{"a":{"b":7}}'::jsonb)['a']['b'])::int2;
-- Section M2: execution through multi-subscript rewrite
SELECT (('{"a":{"b":42}}'::jsonb)['a']['b'])::int4;
SELECT (('{"a":[10,20,30]}'::jsonb)['a'][1])::int4;
@@ -462,6 +418,173 @@ SELECT (('{"a":{"b":[1,2]}}'::jsonb)['a']['b'])::int4; -- container to int4
EXPLAIN (VERBOSE, COSTS OFF)
SELECT (('{"a":[[10,20],[30,40]]}'::jsonb)['a'][i][0])::int4 FROM generate_series(0,1) AS i;
+-- Optimized typed extraction: jsonb_path_query_first family
+-- The planner rewrites (jsonb_path_query_first(j, path))::type into a direct
+-- typed extractor call for the same target types as the other families.
+
+-- Section Q1: planner rewrite verification
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jsonb_path_query_first(test_json, '$.field4'))::int4 FROM test_jsonb WHERE json_type = 'object';
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jsonb_path_query_first(test_json, '$.field7'))::bool FROM test_jsonb WHERE json_type = 'object';
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jsonb_path_query_first(test_json, '$.field4'))::numeric FROM test_jsonb WHERE json_type = 'object';
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jsonb_path_query_first(test_json, '$.field4'))::int8 FROM test_jsonb WHERE json_type = 'object';
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jsonb_path_query_first(test_json, '$.field4'))::float8 FROM test_jsonb WHERE json_type = 'object';
+-- 3-arg form (vars explicit)
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jsonb_path_query_first(test_json, '$.field4', '{}'))::int4 FROM test_jsonb WHERE json_type = 'object';
+-- 4-arg form (vars + silent explicit)
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jsonb_path_query_first(test_json, '$.field4', '{}', true))::float8 FROM test_jsonb WHERE json_type = 'object';
+
+-- Section Q2: execution through rewrite
+SELECT (jsonb_path_query_first('{"a":42}'::jsonb, '$.a'))::int4;
+SELECT (jsonb_path_query_first('{"a":3.14}'::jsonb, '$.a'))::float8;
+SELECT (jsonb_path_query_first('{"a":true}'::jsonb, '$.a'))::bool;
+SELECT (jsonb_path_query_first('{"a":99}'::jsonb, '$.a'))::numeric;
+SELECT (jsonb_path_query_first('{"a":99}'::jsonb, '$.a'))::int8;
+-- nested object path
+SELECT (jsonb_path_query_first('{"a":{"b":42}}'::jsonb, '$.a.b'))::int4;
+-- array access in jsonpath
+SELECT (jsonb_path_query_first('{"a":[10,20]}'::jsonb, '$.a[1]'))::int4;
+
+-- Section Q3: NULL semantics
+SELECT (jsonb_path_query_first('{"a":1}'::jsonb, '$.b'))::int4; -- missing path
+SELECT (jsonb_path_query_first('{"a":null}'::jsonb, '$.a'))::int4; -- JSON null
+
+-- Section Q4: silent semantics
+-- silent=true suppresses jsonpath execution errors, NOT cast errors
+SELECT (jsonb_path_query_first('{"a":"text"}'::jsonb, '$.a', '{}', true))::int4; -- cast error not suppressed
+-- silent=false (default): jsonpath strict error
+SELECT (jsonb_path_query_first('{"a":1}'::jsonb, 'strict $.b', '{}', false))::int4; -- strict missing key error
+-- silent=true: jsonpath strict error suppressed
+SELECT (jsonb_path_query_first('{"a":1}'::jsonb, 'strict $.b', '{}', true))::int4; -- suppressed, returns NULL
+
+-- Section Q5: type-mismatch errors
+SELECT (jsonb_path_query_first('{"a":"hello"}'::jsonb, '$.a'))::int4; -- string to int4
+SELECT (jsonb_path_query_first('{"a":[1,2]}'::jsonb, '$.a'))::int4; -- container to int4
+
+-- Section Q6: direct calls to typed extractor builtins
+SELECT jsonb_path_query_first_int4('{"a":42}'::jsonb, '$.a'::jsonpath, '{}'::jsonb, false);
+SELECT jsonb_path_query_first_int8('{"a":99}'::jsonb, '$.a'::jsonpath, '{}'::jsonb, false);
+SELECT jsonb_path_query_first_float8('{"a":3.14}'::jsonb, '$.a'::jsonpath, '{}'::jsonb, false);
+SELECT jsonb_path_query_first_numeric('{"a":1.23}'::jsonb, '$.a'::jsonpath, '{}'::jsonb, false);
+SELECT jsonb_path_query_first_bool('{"a":true}'::jsonb, '$.a'::jsonpath, '{}'::jsonb, false);
+-- direct call: missing path
+SELECT jsonb_path_query_first_int4('{"a":1}'::jsonb, '$.b'::jsonpath, '{}'::jsonb, false);
+-- direct call: with vars
+SELECT jsonb_path_query_first_int4('{"a":42}'::jsonb, '$.a ? (@ > $x)'::jsonpath, '{"x":10}'::jsonb, false);
+
+-- Optimized typed extraction: jsonb_path_query_first_tz family
+-- Same pattern as the non-tz family, but with timezone-aware evaluation.
+
+-- Section T1: planner rewrite verification
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jsonb_path_query_first_tz(test_json, '$.field4'))::int4 FROM test_jsonb WHERE json_type = 'object';
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jsonb_path_query_first_tz(test_json, '$.field7'))::bool FROM test_jsonb WHERE json_type = 'object';
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jsonb_path_query_first_tz(test_json, '$.field4'))::numeric FROM test_jsonb WHERE json_type = 'object';
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jsonb_path_query_first_tz(test_json, '$.field4'))::int8 FROM test_jsonb WHERE json_type = 'object';
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jsonb_path_query_first_tz(test_json, '$.field4'))::float8 FROM test_jsonb WHERE json_type = 'object';
+-- 3-arg form (vars explicit)
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jsonb_path_query_first_tz(test_json, '$.field4', '{}'))::int4 FROM test_jsonb WHERE json_type = 'object';
+-- 4-arg form (vars + silent explicit)
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jsonb_path_query_first_tz(test_json, '$.field4', '{}', true))::float8 FROM test_jsonb WHERE json_type = 'object';
+
+-- Section T2: execution through rewrite
+SELECT (jsonb_path_query_first_tz('{"a":42}'::jsonb, '$.a'))::int4;
+SELECT (jsonb_path_query_first_tz('{"a":3.14}'::jsonb, '$.a'))::float8;
+SELECT (jsonb_path_query_first_tz('{"a":true}'::jsonb, '$.a'))::bool;
+SELECT (jsonb_path_query_first_tz('{"a":99}'::jsonb, '$.a'))::numeric;
+SELECT (jsonb_path_query_first_tz('{"a":99}'::jsonb, '$.a'))::int8;
+-- nested path
+SELECT (jsonb_path_query_first_tz('{"a":{"b":42}}'::jsonb, '$.a.b'))::int4;
+-- array access
+SELECT (jsonb_path_query_first_tz('{"a":[10,20]}'::jsonb, '$.a[1]'))::int4;
+
+-- Section T3: NULL semantics
+SELECT (jsonb_path_query_first_tz('{"a":1}'::jsonb, '$.b'))::int4; -- missing path
+SELECT (jsonb_path_query_first_tz('{"a":null}'::jsonb, '$.a'))::int4; -- JSON null
+
+-- Section T4: silent semantics
+SELECT (jsonb_path_query_first_tz('{"a":"text"}'::jsonb, '$.a', '{}', true))::int4; -- cast error not suppressed
+SELECT (jsonb_path_query_first_tz('{"a":1}'::jsonb, 'strict $.b', '{}', false))::int4; -- strict error
+SELECT (jsonb_path_query_first_tz('{"a":1}'::jsonb, 'strict $.b', '{}', true))::int4; -- suppressed, NULL
+
+-- Section T5: type-mismatch errors
+SELECT (jsonb_path_query_first_tz('{"a":"hello"}'::jsonb, '$.a'))::int4;
+
+-- Section T6: direct calls to typed extractor builtins
+SELECT jsonb_path_query_first_tz_int4('{"a":42}'::jsonb, '$.a'::jsonpath, '{}'::jsonb, false);
+SELECT jsonb_path_query_first_tz_int8('{"a":99}'::jsonb, '$.a'::jsonpath, '{}'::jsonb, false);
+SELECT jsonb_path_query_first_tz_float8('{"a":3.14}'::jsonb, '$.a'::jsonpath, '{}'::jsonb, false);
+SELECT jsonb_path_query_first_tz_numeric('{"a":1.23}'::jsonb, '$.a'::jsonpath, '{}'::jsonb, false);
+SELECT jsonb_path_query_first_tz_bool('{"a":true}'::jsonb, '$.a'::jsonpath, '{}'::jsonb, false);
+
+-- Optimized typed extraction: int2 / float4 matrix completion
+-- Extends the existing typed extractor families to cover int2 (smallint)
+-- and float4 (real) target types.
+
+-- Section I1: direct calls to int2 / float4 typed extractor builtins (object-field)
+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');
+-- NULL semantics
+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');
+-- type-mismatch errors
+SELECT jsonb_object_field_int2('{"a": "text"}'::jsonb, 'a');
+SELECT jsonb_object_field_float4('{"a": true}'::jsonb, 'a');
+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
+-- int2 overflow
+SELECT jsonb_object_field_int2('{"a": 99999}'::jsonb, 'a');
+
+-- Section I2: planner rewrite verification — one per family (using int2)
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (test_json -> 'field4')::int2 FROM test_jsonb WHERE json_type = 'object';
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (test_arr -> 0)::int2 FROM test_jsonb_arr;
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (test_json #> '{field4}')::int2 FROM test_jsonb WHERE json_type = 'object';
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jsonb_path_query_first(test_json, '$.field4'))::int2 FROM test_jsonb WHERE json_type = 'object';
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jsonb_path_query_first_tz(test_json, '$.field4'))::int2 FROM test_jsonb WHERE json_type = 'object';
+-- float4 rewrite (one representative)
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (test_json -> 'field4')::float4 FROM test_jsonb WHERE json_type = 'object';
+
+-- Section I3: multi-subscript automatic coverage
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (test_json['field6']['f1'])::int2 FROM test_jsonb WHERE json_type = 'object';
+SELECT (('{"a":{"b":7}}'::jsonb)['a']['b'])::int2;
+
+-- Section I4: representative execution through rewrite (distributed across families)
+-- object-field: int2
+SELECT (test_json -> 'field4')::int2 FROM test_jsonb WHERE json_type = 'object';
+-- object-field: float4
+SELECT (test_json -> 'field4')::float4 FROM test_jsonb WHERE json_type = 'object';
+-- array-element: int2
+SELECT (test_arr -> 0)::int2 FROM test_jsonb_arr;
+-- array-element: float4
+SELECT (test_arr -> 1)::float4 FROM test_jsonb_arr;
+-- extract-path: float4
+SELECT (test_json #> '{field4}')::float4 FROM test_jsonb WHERE json_type = 'object';
+-- jsonb_path_query_first: int2
+SELECT (jsonb_path_query_first('{"a":42}'::jsonb, '$.a'))::int2;
+-- jsonb_path_query_first_tz: float4
+SELECT (jsonb_path_query_first_tz('{"a":3.14}'::jsonb, '$.a'))::float4;
+
+-- Section I5: NULL through rewrite
+SELECT (test_json -> 'nonexistent')::int2 FROM test_jsonb WHERE json_type = 'object';
+SELECT (test_arr -> 3)::float4 FROM test_jsonb_arr; -- JSON null element
+SELECT (test_arr -> 99)::int2 FROM test_jsonb_arr; -- out of range
+
+-- Section I6: type-mismatch through rewrite
+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
+
+-- Section I7: direct calls to other family builtins (representative)
+SELECT jsonb_array_element_int2('[10, 20]'::jsonb, 0);
+SELECT jsonb_array_element_float4('[3.14, 2.5]'::jsonb, 1);
+SELECT jsonb_extract_path_int2('{"a":{"b":7}}'::jsonb, ARRAY['a','b']);
+SELECT jsonb_extract_path_float4('{"a":3.14}'::jsonb, ARRAY['a']);
+SELECT jsonb_path_query_first_int2('{"a":42}'::jsonb, '$.a'::jsonpath, '{}'::jsonb, false);
+SELECT jsonb_path_query_first_float4('{"a":3.14}'::jsonb, '$.a'::jsonpath, '{}'::jsonb, false);
+SELECT jsonb_path_query_first_tz_int2('{"a":42}'::jsonb, '$.a'::jsonpath, '{}'::jsonb, false);
+SELECT jsonb_path_query_first_tz_float4('{"a":3.14}'::jsonb, '$.a'::jsonpath, '{}'::jsonb, false);
+
SELECT test_json -> 'x' FROM test_jsonb WHERE json_type = 'scalar';
SELECT test_json -> 'x' FROM test_jsonb WHERE json_type = 'array';
SELECT test_json -> 'x' FROM test_jsonb WHERE json_type = 'object';
@@ -1919,4 +2042,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;
\ No newline at end of file
+select ('{"text": "hello"}'::jsonb).text;
--
2.52.0