public inbox for [email protected]
help / color / mirror / Atom feedFrom: SATYANARAYANA NARLAPURAM <[email protected]>
To: PostgreSQL Hackers <[email protected]>
Cc: [email protected]
Cc: [email protected]
Subject: BUG: jsonpath .split_part() bypasses lax-mode error suppression
Date: Sat, 18 Apr 2026 09:31:03 -0700
Message-ID: <CAHg+QDfdUG+6OJ5kSxmVim-UJkrtffWByLf4oftxwG7_ySYsDA@mail.gmail.com> (raw)
Hi hackers,
The jsonpath string method .split_part() bypasses lax-mode error
suppression.
This is because executeStringInternalMethod() uses
DirectFunctionCall1(numeric_int4, ...)
to convert the field number from numeric to int4. This throws
ereport(ERROR) directly,
bypassing the jspThrowErrors(cxt) / RETURN_ERROR mechanism that other
methods
like .double() use correctly.
Reproduction:
-- These should return NULL in lax mode, but throw hard ERRORs:
SELECT '"hello-world"'::jsonb @? '$.split_part("-", 99999999999)';
SELECT '"hello-world"'::jsonb @? '$.split_part("-", 0)';
ERROR: integer out of range
ERROR: field position must not be zero
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.
Thanks,
Satya
Attachments:
[application/octet-stream] 0001-Fix-jsonpath-.split_part-to-respect-error-suppressio.patch (6.2K, 3-0001-Fix-jsonpath-.split_part-to-respect-error-suppressio.patch)
download | inline diff:
From d1f001c81dd096cf2f994536579e995c4fceb3da Mon Sep 17 00:00:00 2001
From: satyanarayana narlapuram <[email protected]>
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
view thread (2+ messages) latest in thread
reply
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Reply to all the recipients using the --to and --cc options:
reply via email
To: [email protected]
Cc: [email protected], [email protected], [email protected], [email protected]
Subject: Re: BUG: jsonpath .split_part() bypasses lax-mode error suppression
In-Reply-To: <CAHg+QDfdUG+6OJ5kSxmVim-UJkrtffWByLf4oftxwG7_ySYsDA@mail.gmail.com>
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox