From d1f001c81dd096cf2f994536579e995c4fceb3da Mon Sep 17 00:00:00 2001
From: satyanarayana narlapuram <satyanarlapuram@gmail.com>
Date: Sat, 18 Apr 2026 15:42:18 +0000
Subject: [PATCH] Fix jsonpath .split_part() to respect error suppression in
 lax mode

The .split_part() jsonpath string method, introduced in bd4f879a, used
bare DirectFunctionCall1(numeric_int4, ...) to convert the field number
argument from numeric to int4.  This throws an uncatchable ERROR when the
field number overflows int32 range or is zero, bypassing the lax-mode
error suppression mechanism that other jsonpath methods use.

For example, these expressions should return NULL / false in lax mode,
but instead throw hard ERRORs:

    '"hello"'::jsonb @? '$.split_part("-", 99999999999)'
    -- ERROR: integer out of range (expected: NULL)

    '"hello"'::jsonb @? '$.split_part("-", 0)'
    -- ERROR: field position must not be zero (expected: NULL)

Fix by replacing the bare DirectFunctionCall with numeric_int4_safe()
using ErrorSaveContext to catch overflow, and adding an explicit field==0
check, both gated behind RETURN_ERROR so errors are properly suppressed
in lax/silent mode while still raised in strict/non-silent mode.

This is consistent with how .double() and .integer() handle conversion
errors using ErrorSaveContext and RETURN_ERROR.
---
 src/backend/utils/adt/jsonpath_exec.c        | 17 ++++++++-
 src/test/regress/expected/jsonb_jsonpath.out | 40 ++++++++++++++++++++
 src/test/regress/sql/jsonb_jsonpath.sql      | 11 ++++++
 3 files changed, 67 insertions(+), 1 deletion(-)

diff --git a/src/backend/utils/adt/jsonpath_exec.c b/src/backend/utils/adt/jsonpath_exec.c
index 0ec9b4df..42ed3aea 100644
--- a/src/backend/utils/adt/jsonpath_exec.c
+++ b/src/backend/utils/adt/jsonpath_exec.c
@@ -74,6 +74,7 @@
 #include "utils/json.h"
 #include "utils/jsonpath.h"
 #include "utils/memutils.h"
+#include "utils/numeric.h"
 #include "utils/timestamp.h"
 
 /*
@@ -3018,6 +3019,8 @@ executeStringInternalMethod(JsonPathExecContext *cxt, JsonPathItem *jsp,
 			{
 				char	   *from_str;
 				Numeric		n;
+				int32		field;
+				ErrorSaveContext escontext = {T_ErrorSaveContext};
 
 				jspGetLeftArg(jsp, &elem);
 				if (elem.type != jpiString)
@@ -3031,11 +3034,23 @@ executeStringInternalMethod(JsonPathExecContext *cxt, JsonPathItem *jsp,
 
 				n = jspGetNumeric(&elem);
 
+				field = numeric_int4_safe(n, (Node *) &escontext);
+				if (escontext.error_occurred)
+					RETURN_ERROR(ereport(ERROR,
+										 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+										  errmsg("jsonpath item method .%s() field number is out of range for type integer",
+												 jspOperationName(jsp->type)))));
+
+				if (field == 0)
+					RETURN_ERROR(ereport(ERROR,
+										 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+										  errmsg("field position must not be zero"))));
+
 				resStr = TextDatumGetCString(DirectFunctionCall3Coll(split_part,
 																	 DEFAULT_COLLATION_OID,
 																	 str,
 																	 CStringGetTextDatum(from_str),
-																	 DirectFunctionCall1(numeric_int4, NumericGetDatum(n))));
+																	 Int32GetDatum(field)));
 				break;
 			}
 		default:
diff --git a/src/test/regress/expected/jsonb_jsonpath.out b/src/test/regress/expected/jsonb_jsonpath.out
index afa6c4cb..14c13592 100644
--- a/src/test/regress/expected/jsonb_jsonpath.out
+++ b/src/test/regress/expected/jsonb_jsonpath.out
@@ -3073,6 +3073,46 @@ select jsonb_path_query('"abc,def,ghi,jkl"', '$.split_part(",", -2)');
  "ghi"
 (1 row)
 
+-- Test .split_part() error handling in lax mode (should suppress errors)
+select jsonb '"hello-world"' @? '$.split_part("-", 0)';
+ ?column? 
+----------
+ 
+(1 row)
+
+select jsonb '"hello-world"' @? '$.split_part("-", 99999999999)';
+ ?column? 
+----------
+ 
+(1 row)
+
+select jsonb_path_exists('"hello-world"', '$.split_part("-", 0)', silent => true);
+ jsonb_path_exists 
+-------------------
+ 
+(1 row)
+
+select jsonb_path_exists('"hello-world"', '$.split_part("-", 99999999999)', silent => true);
+ jsonb_path_exists 
+-------------------
+ 
+(1 row)
+
+select jsonb_path_query('"hello-world"', '$.split_part("-", 0)', silent => true);
+ jsonb_path_query 
+------------------
+(0 rows)
+
+select jsonb_path_query('"hello-world"', '$.split_part("-", 99999999999)', silent => true);
+ jsonb_path_query 
+------------------
+(0 rows)
+
+-- These should still error in strict/non-silent mode
+select jsonb_path_query('"hello-world"', '$.split_part("-", 0)');
+ERROR:  field position must not be zero
+select jsonb_path_query('"hello-world"', '$.split_part("-", 99999999999)');
+ERROR:  jsonpath item method .split_part() field number is out of range for type integer
 -- Test string methods play nicely together
 select jsonb_path_query('"hello world"', '$.replace("hello","bye").upper()');
  jsonb_path_query 
diff --git a/src/test/regress/sql/jsonb_jsonpath.sql b/src/test/regress/sql/jsonb_jsonpath.sql
index d3a38c57..b9e189a0 100644
--- a/src/test/regress/sql/jsonb_jsonpath.sql
+++ b/src/test/regress/sql/jsonb_jsonpath.sql
@@ -722,6 +722,17 @@ select jsonb_path_query('"hello world"', '$.replace("hello","bye") starts with "
 select jsonb_path_query('"abc~@~def~@~ghi"', '$.split_part("~@~", 2)');
 select jsonb_path_query('"abc,def,ghi,jkl"', '$.split_part(",", -2)');
 
+-- Test .split_part() error handling in lax mode (should suppress errors)
+select jsonb '"hello-world"' @? '$.split_part("-", 0)';
+select jsonb '"hello-world"' @? '$.split_part("-", 99999999999)';
+select jsonb_path_exists('"hello-world"', '$.split_part("-", 0)', silent => true);
+select jsonb_path_exists('"hello-world"', '$.split_part("-", 99999999999)', silent => true);
+select jsonb_path_query('"hello-world"', '$.split_part("-", 0)', silent => true);
+select jsonb_path_query('"hello-world"', '$.split_part("-", 99999999999)', silent => true);
+-- These should still error in strict/non-silent mode
+select jsonb_path_query('"hello-world"', '$.split_part("-", 0)');
+select jsonb_path_query('"hello-world"', '$.split_part("-", 99999999999)');
+
 -- Test string methods play nicely together
 select jsonb_path_query('"hello world"', '$.replace("hello","bye").upper()');
 select jsonb_path_query('"hElLo WorlD"', '$.lower().upper().lower().replace("hello","bye")');
-- 
2.43.0

