From 042ae7d0cfdb8683e1a07f691a21e47d17ea2b39 Mon Sep 17 00:00:00 2001
From: Alexander Korotkov <akorotkov@postgresql.org>
Date: Mon, 5 Dec 2022 17:02:29 +0300
Subject: [PATCH] Teach jsonpath to handle missing variables

The current jsonpath code assumes that the referenced variable always exists.
It could only throw an error at the value valuation time.  At the same time
existence checking assumes variable is present without valuation, and error
suppression doesn't work for missing variables.

This commit makes jsonpath code to handle variables in the similar way to
key accessors.  The existence is now checked runtime, and error suppression is
also applied.

Backpatch to 12 where jsonpath was introduced.

Reported-by: David G. Johnston
Discussion: https://postgr.es/m/CAKFQuwbeytffJkVnEqDyLZ%3DrQsznoTh1OgDoOF3VmOMkxcTMjA%40mail.gmail.com
Author: Alexander Korotkov
Backpatch-through: 12
---
 src/backend/utils/adt/jsonpath_exec.c        | 47 +++++++++++++++-----
 src/test/regress/expected/jsonb_jsonpath.out |  5 ++-
 2 files changed, 40 insertions(+), 12 deletions(-)

diff --git a/src/backend/utils/adt/jsonpath_exec.c b/src/backend/utils/adt/jsonpath_exec.c
index 8d83b2edb35..e15ced19c4e 100644
--- a/src/backend/utils/adt/jsonpath_exec.c
+++ b/src/backend/utils/adt/jsonpath_exec.c
@@ -224,8 +224,9 @@ static JsonPathExecResult appendBoolResult(JsonPathExecContext *cxt,
 										   JsonPathItem *jsp, JsonValueList *found, JsonPathBool res);
 static void getJsonPathItem(JsonPathExecContext *cxt, JsonPathItem *item,
 							JsonbValue *value);
-static void getJsonPathVariable(JsonPathExecContext *cxt,
-								JsonPathItem *variable, Jsonb *vars, JsonbValue *value);
+static JsonPathExecResult getJsonPathVariable(JsonPathExecContext *cxt,
+											  JsonPathItem *variable,
+											  JsonbValue *value);
 static int	JsonbArraySize(JsonbValue *jb);
 static JsonPathBool executeComparison(JsonPathItem *cmp, JsonbValue *lv,
 									  JsonbValue *rv, void *p);
@@ -952,7 +953,6 @@ executeItemOptUnwrapTarget(JsonPathExecContext *cxt, JsonPathItem *jsp,
 		case jpiBool:
 		case jpiNumeric:
 		case jpiString:
-		case jpiVariable:
 			{
 				JsonbValue	vbuf;
 				JsonbValue *v;
@@ -975,6 +975,24 @@ executeItemOptUnwrapTarget(JsonPathExecContext *cxt, JsonPathItem *jsp,
 			}
 			break;
 
+		case jpiVariable:
+			{
+				JsonbValue	vbuf;
+				JsonbValue *v;
+				bool		hasNext = jspGetNext(jsp, &elem);
+
+				v = hasNext ? &vbuf : palloc(sizeof(*v));
+
+				baseObject = cxt->baseObject;
+				res = getJsonPathVariable(cxt, jsp, v);
+
+				if (res == jperOk)
+					res = executeNextItem(cxt, jsp, &elem,
+										  v, found, hasNext);
+				cxt->baseObject = baseObject;
+			}
+			break;
+
 		case jpiType:
 			{
 				JsonbValue *jbv = palloc(sizeof(*jbv));
@@ -2092,9 +2110,6 @@ getJsonPathItem(JsonPathExecContext *cxt, JsonPathItem *item,
 			value->val.string.val = jspGetString(item,
 												 &value->val.string.len);
 			break;
-		case jpiVariable:
-			getJsonPathVariable(cxt, item, cxt->vars, value);
-			return;
 		default:
 			elog(ERROR, "unexpected jsonpath item type");
 	}
@@ -2103,19 +2118,20 @@ getJsonPathItem(JsonPathExecContext *cxt, JsonPathItem *item,
 /*
  * Get the value of variable passed to jsonpath executor
  */
-static void
+static JsonPathExecResult
 getJsonPathVariable(JsonPathExecContext *cxt, JsonPathItem *variable,
-					Jsonb *vars, JsonbValue *value)
+					JsonbValue *value)
 {
 	char	   *varName;
 	int			varNameLength;
 	JsonbValue	tmp;
 	JsonbValue *v;
+	Jsonb	   *vars = cxt->vars;
+	JsonPathExecResult res;
 
 	if (!vars)
 	{
-		value->type = jbvNull;
-		return;
+		return jperNotFound;
 	}
 
 	Assert(variable->type == jpiVariable);
@@ -2130,17 +2146,26 @@ getJsonPathVariable(JsonPathExecContext *cxt, JsonPathItem *variable,
 	{
 		*value = *v;
 		pfree(v);
+		res = jperOk;
 	}
-	else
+	else if (!jspIgnoreStructuralErrors(cxt))
 	{
+		if (!jspThrowErrors(cxt))
+			res = jperError;
 		ereport(ERROR,
 				(errcode(ERRCODE_UNDEFINED_OBJECT),
 				 errmsg("could not find jsonpath variable \"%s\"",
 						pnstrdup(varName, varNameLength))));
 	}
+	else
+	{
+		res = jperNotFound;
+	}
 
 	JsonbInitBinary(&tmp, vars);
 	setBaseObject(cxt, &tmp, 1);
+
+	return res;
 }
 
 /**************** Support functions for JsonPath execution *****************/
diff --git a/src/test/regress/expected/jsonb_jsonpath.out b/src/test/regress/expected/jsonb_jsonpath.out
index 508ddd797ed..19c6bdc72c7 100644
--- a/src/test/regress/expected/jsonb_jsonpath.out
+++ b/src/test/regress/expected/jsonb_jsonpath.out
@@ -486,7 +486,10 @@ select * from jsonb_path_query('{"a": 10}', '$');
 (1 row)
 
 select * from jsonb_path_query('{"a": 10}', '$ ? (@.a < $value)');
-ERROR:  could not find jsonpath variable "value"
+ jsonb_path_query 
+------------------
+(0 rows)
+
 select * from jsonb_path_query('{"a": 10}', '$ ? (@.a < $value)', '1');
 ERROR:  "vars" argument is not an object
 DETAIL:  Jsonpath parameters should be encoded as key-value pairs of "vars" object.
-- 
2.24.3 (Apple Git-128)

