From: Nikita Glukhov <n.gluhov@postgrespro.ru>
To: Andres Freund <andres@anarazel.de>
To: Jonathan S. Katz <jkatz@postgresql.org>
Cc: PostgreSQL Hackers <pgsql-hackers@lists.postgresql.org>
Cc: Andrew Dunstan <andrew@dunslane.net>
Cc: Tom Lane <tgl@sss.pgh.pa.us>
Cc: Michael Paquier <michael@paquier.xyz>
Cc: John Naylor <john.naylor@enterprisedb.com>
Cc: Amit Langote <amitlangote09@gmail.com>
Subject: Re: SQL/JSON features for v15
Date: Tue, 16 Aug 2022 04:02:17 +0300
Message-ID: <225d341b-f310-eed0-ceff-b8ec61669d9f@postgrespro.ru> (raw)
In-Reply-To: <20220815223853.itrlpqgpqheg6r6a@awork3.anarazel.de>
References: <abd9b83b-aa66-f230-3d6d-734817f0995d@postgresql.org>
<20220815223853.itrlpqgpqheg6r6a@awork3.anarazel.de>
Hi,
On 16.08.2022 01:38, Andres Freund wrote:
> Continuation from the thread at
> https://postgr.es/m/20220811171740.m5b4h7x63g4lzgrk%40awork3.anarazel.de
>
>
> I started hacking on this Friday. I think there's some relatively easy
> improvements that make the code substantially more understandable, at least
> for me, without even addressing the structural stuff.
I also started hacking Friday, hacked all weekend, and now have a new
version of the patch.
I received your message when I finished writing of mine, so I will
try answer your new questions only in next message. But in short, I
can say that some things like ExecEvalJsonExprSubtrans() were fixed.
I took Amit's patch and tried to simplify execution further.
Explanation of the patches is at the very end of message.
Next, I try to answer some of previous questions.
On Aug 2, 2022 at 9:39 AM Andres Freund<andres@anarazel.de> wrote:
> The whole coercion stuff just seems incredibly clunky (in a
> slightly different shape before this patch).
> ExecEvalJsonExprItemCoercion() calls ExecPrepareJsonItemCoercion(),
> which gets a pointer to one of the per-type elements in
> JsonItemCoercionsState, dispatching on the type of the json
> object. Then we later call ExecGetJsonItemCoercion() (via a
> convoluted path), which again will dispatch on the type
> (extracting the json object again afaics!), to then somehow
> eventually get the coerced value. I think it might be possible
> to make this a bit simpler, by not leaving anything
> coercion-related in ExecEvalJsonExpr().
On 2022-08-02 12:05:55 +0900, Amit Langote wrote:
> I left some pieces there, because I thought the error of not finding an
> appropriate coercion must be thrown right away as the code in
> ExecEvalJsonExpr() does after calling ExecGetJsonItemCoercion().
> ExecPrepareJsonItemCoercion() is called later when it's time to
> actually evaluate the coercion. If we move the error path to
> ExecPrepareJsonItemCoercion(), both ExecGetJsonItemCoercion() and the
> error path code in ExecEvalJsonExpr() will be unnecessary. I will
> give that a try.
The first dispatch is done only for throwing error about missing cast
without starting subtransaction in which second dispatch is executed.
I agree, this is bad that result of first dispatch is not used later,
and I have removed second dispatch.
> I don't understand the design of what needs to have error handling,
> and what not.
> I don't think subtransactions per-se are a fundamental problem.
> I think the error handling implementation is ridiculously complicated,
> and while I started to hack on improving it, I stopped when I really
> couldn't understand what errors it actually needs to handle when and
> why.
Here is the diagram that may help to understand error handling in
SQL/JSON functions (I hope it will be displayed correctly):
JSON path -------
expression \
->+-----------+ SQL/JSON +----------+ Result
PASSING args ------->| JSON path |--> item or --->| Output |-> SQL
->| executor | JSONB .->| Coercion | value
/ +-----------+ datum | +----------+
JSON + - - - -+ | | | |
Context ->: FORMAT : v v | v
item : JSON : error? empty? | error?
+ - - - -+ | | | |
| | +----------+ | /
v | | ON EMPTY |--> SQL --' /
error? | +----------+ value /
| | | /
\ | v /
\ \ error? /
\ \ | /
\______ \ | _____________/
\ \ | /
v v v v +----------+
+----------+ | Output | Result
| ON ERROR |--->| Coercion |--> SQL
+----------+ +----------+ value
| |
V V
EXCEPTION EXCEPTION
The first dashed box "FORMAT JSON" used for parsing JSON is absent in
our implementation, because we support only jsonb type which is
pre-parsed. This could be used in queries like that:
JSON_VALUE('invalid json', '$' DEFAULT 'error' ON ERROR) => 'error'
JSON path executor already has error handling and does not need
subtransactions. We had to add functions like numeric_add_opt_error()
which return error flag instead of throwing exceptions.
On Aug 10, 2022 at 3:57 AM Andres Freund<andres@anarazel.de> wrote:
>>> One way this code could be drastically simplified is to force all
>>> type-coercions to go through the "io coercion" path, which could be
>>> implemented as a single execution step (which thus could trivially
>>> start/finish a subtransaction) and would remove a lot of the
>>> complicated code around coercions.
>> Could you please clarify how you think we might do the io coercion
>> wrapped with a subtransaction all as a single execution step? I
>> would've thought that we couldn't do the sub-transaction without
>> leaving ExecInterpExpr() anyway, so maybe you meant the io coercion
>> itself was done using some code outside ExecInterpExpr()?
>> The current JsonExpr code does it by recursively calling
>> ExecInterpExpr() using the nested ExprState expressly for the
>> coercion.
> The basic idea is to rip out all the type-dependent stuff out and
> replace it with a single JSON_IOCERCE step, which has a parameter
> about whether to wrap things in a subtransaction or not. That step
> would always perform the coercion by calling the text output function
> of the input and the text input function of the output.
On Aug 3, 2022 at 12:00 AM Andres Freund<andres@anarazel.de> wrote:
> But we don't need to wrap arbitrary evaluation in a subtransaction -
> afaics the coercion calls a single function, not an arbitrary
> expression?
SQL standard says that scalar SQL/JSON items are converted to SQL type
through CAST(corresponding_SQL_type_for_item AS returning_type).
Our JSON_VALUE implementation supports arbitrary output types that can
have specific CASTs from numeric, bool, datetime, which we can't
emulate with simple I/O coercion. But supporting of arbitrary types
may be dangerous, because SQL standard denotes only a limited set of
types:
The <data type> contained in the explicit or implicit
<JSON returning clause> shall be a <predefined type> that identifies
a character string data type, numeric data type, boolean data type,
or datetime data type.
I/O coercion will not even work in the following simple case:
JSON_VALUE('1.23', '$' RETURNING int)
It is expected to return 1::int, like ordinary cast 1.23::numeric::int.
Exceptions may come not only from coercions. Expressions in DEFAULT ON
EMPTY can also throw exceptions, which also must be handled.
Here is excerpt from ISO/IEC 19075-6:2021(E) "Part 6: Support for JSON",
which explains SQL standard features in human-readable manner:
6.4.3 JSON_VALUE:
<JSON value error behavior> specifies what to do if there is an
unhandled error. Unhandled errors can arise if there is an input
conversion error (for example, if the context item cannot be parsed),
an error returned by the SQL/JSON path engine, or an output
conversion error. The choices are the same as for
<JSON value empty behavior>.
When using DEFAULT <value expression> for either the empty or error
behavior, what happens if the <value expression> raises an exception?
The answer is that an error during empty behavior "falls through"
to the error behavior. If the error behavior itself has an error,
there is no further recourse but to raise the exception.
So, we need to support execution of arbitrary expressions inside a
subtransaction, and do not try to somehow simplify coercions.
In Amit's fix, wrapping DEFAULT ON EMPTY into subtransactions was
lost, mainly because there were no tests for this case. The following
test should not fall on the second row:
SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING int
DEFAULT 1 / x ON EMPTY
DEFAULT 2 ON ERROR)
FROM (VALUES (1::int), (0)) x(x);
json_value
------------
1
2
I have added this test in 0003.
On Aug 3, 2022 at 12:00 AM Andres Freund<andres@anarazel.de> wrote:
> On 2022-08-02 12:05:55 +0900, Amit Langote wrote:
>> I am not really sure if different coercions may be used
>> in the same query over multiple evaluations of the same JSON path
>> expression, but maybe that's also possible.
> Even if the type can change, I don't think that means we need to have
> space for multiple types at the same time - there can't be multiple
> coercions happening at the same time, otherwise there could be two
> coercions of the same type as well. So we don't need memory for
> every coercion type.
Only the one item coercion is used in execution of JSON_VALUE().
Multiple coercions could be executed, if we supported quite useful SRF
JSON_QUERY() using "RETURNING SETOF type" (I had this idea for a long
time, but I didn't dare to implement it).
I don't understand what "memory" you mean. If we will not emit all
possible expressions statically, we will need to generate them
dynamically at run-time, and this could be hardly acceptable. In the
last version of the fix there is only 4 bytes (int jump) of additional
state space per coercion.
On Aug 6, 2022 at 5:37 Andres Freund<andres@anarazel.de> wrote:
> There's one layer of subtransactions in one of the paths in
> ExecEvalJsonExpr(), another in ExecEvalJson(). Some paths of
> ExecEvalJsonExpr() go through subtransactions, others don't.
Really, there is only one level of subtransactions. Outer subtransaction
may be used for FORMAT JSON handling which always requires
subtransaction at the beginning of expression execution.
Inner subtransactions are conditional, they are started only and when
there is no outer subtransaction.
Now, outer subtransactions are not used at all,
ExecEvalJsonNeedsSubtransaction(NULL) always returns false. (AFAIR,
FORMAT JSON was present in older version of SQL/JSON patches, then it
was removed, but outer subtransactions were not). In the last version
of the fix I have removed them completely and moved inner
subtransactions into a separate executor step (see below).
The description of the patches:
0001 - Fix returning of json[b] domains in JSON_VALUE()
(This may require a separate thread.)
I found a bug in returning json[b] domains in JSON_VALUE(). json[b]
has special processing in JSON_VALUE, bypassing oridinary
SQL/JSON item type => SQL type coercions. But json[b] domains miss
this processing:
CREATE DOMAIN jsonb_not_null AS jsonb NOT NULL;
SELECT JSON_VALUE('"123"', '$' RETURNING jsonb);
"123"
SELECT JSON_VALUE( '123', '$' RETURNING jsonb);
123
SELECT JSON_VALUE('"123"', '$' RETURNING jsonb_not_null);
123
SELECT JSON_VALUE( '123', '$' RETURNING jsonb_not_null ERROR ON ERROR);
ERROR: SQL/JSON item cannot be cast to target type
Fixed by examinating output base type in parse_expr.c and skipping
allocation of item coercions, what later will be a signal for special
processing in ExecEvalJsonExpr().
0002 - Add EEOP_SUBTRANS executor step
On 2022-08-02 12:05:55 +0900, Amit Langote wrote:
> So, the problem with inlining coercion evaluation into the main parent
> JsonExpr's is that it needs to be wrapped in a sub-transaction to
> catch any errors and return NULL instead. I don't know a way to wrap
> ExprEvalStep evaluation in a sub-transaction to achieve that effect.
I also don't know way to run subtransactions without recursion in
executor, but I still managed to elimiate subsidary ExprStates.
I have introduced new EEOP_SUBTRANS step which executes its subsequent
steps in a subtransaction. It recursively calls a new variant of
ExecInterpExpr() in which starting stepno is passed. The return from
subtransaction is done with EEOP_DONE step that emitted after
subexpression. This step can be reused for other future expressions,
that's why it has no JSON prefix in its name (you could see recent
message in the thread about casts with default values, which are
missing in PostgreSQL).
But for JIT I still had to construct additional ExprState with a
function compiled from subexpression steps.
0003 - Simplify JsonExpr execution:
- New EEOP_SUBTRANS was used to wrap individual coercion expressions:
after execution it jumps to "done" or "onerror" step
- JSONEXPR_ITEM_COERCE step was removed
- JSONEXPR_COERCE split into JSONEXPR_IOCOERCE and JSONEXPR_POPULATE
- Removed all JsonExprPostEvalState
- JSONEXPR step simply returns jump address to one of its possible
continuations: done, onempty, onerror, coercion, coercion_subtrans,
io_coercion or one of item_coercions
- Fixed JsonExprNeedsSubTransaction(): considired more cases
- Eliminated transactions on Const expressions
--
Nikita Glukhov
Postgres Professional:http://www.postgrespro.com
The Russian Postgres Company
Attachments:download all (3 files, .tar.gz)v6-0001-Fix-returning-of-json-b-domains-in-JSON_VALUE.patch (text/x-patch, 22.7K) download
From e843bb377d0ff7132a1ccc43c07ba892d16628a9 Mon Sep 17 00:00:00 2001
From: Nikita Glukhov <n.gluhov@postgrespro.ru>
Date: Mon, 15 Aug 2022 20:18:11 +0300
Subject: [PATCH v6 1/3] Fix returning of json[b] domains in JSON_VALUE()
---
src/backend/executor/execExpr.c | 4 +-
src/backend/executor/execExprInterp.c | 17 +-
src/backend/optimizer/util/clauses.c | 2 +-
src/backend/parser/parse_expr.c | 65 +++++--
src/include/executor/execExpr.h | 14 +-
src/test/regress/expected/jsonb_sqljson.out | 181 +++++++++++++++++++-
src/test/regress/sql/jsonb_sqljson.sql | 63 ++++++-
7 files changed, 318 insertions(+), 28 deletions(-)
diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.cindex d0a57c7aaee..96196d50876 100644--- a/src/backend/executor/execExpr.c+++ b/src/backend/executor/execExpr.c@@ -2645,13 +2645,15 @@ ExecInitExprRec(Expr *node, ExprState *state,
Datum *caseval;
bool *casenull;
+ jsestate->coercions =+ palloc(sizeof(*jsestate->coercions));
jsestate->coercion_expr =
palloc(sizeof(*jsestate->coercion_expr));
caseval = &jsestate->coercion_expr->value;
casenull = &jsestate->coercion_expr->isnull;
- for (cstate = &jsestate->coercions.null,+ for (cstate = &jsestate->coercions->null,
coercion = &jexpr->coercions->null;
coercion <= &jexpr->coercions->composite;
coercion++, cstate++)
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.cindex 636794ca6f1..353346d5528 100644--- a/src/backend/executor/execExprInterp.c+++ b/src/backend/executor/execExprInterp.c@@ -5053,10 +5053,13 @@ ExecEvalJsonExpr(ExprEvalStep *op, ExprContext *econtext,
*resnull = false;
/* coerce scalar item to the output type */
- if (jexpr->returning->typid == JSONOID ||- jexpr->returning->typid == JSONBOID)+ if (!jsestate->coercions)
{
- /* Use result coercion from json[b] to the output type */+ /*+ * Use result coercion from json[b] to the output+ * type when casting to json[b] types or their+ * domains.+ */
res = JsonbPGetDatum(JsonbValueToJsonb(jbv));
break;
}
@@ -5064,7 +5067,7 @@ ExecEvalJsonExpr(ExprEvalStep *op, ExprContext *econtext,
/* Use coercion from SQL/JSON item type to the output type */
res = ExecPrepareJsonItemCoercion(jbv,
jsestate->jsexpr->returning,
- &jsestate->coercions,+ jsestate->coercions,
&jcstate);
if (jcstate->coercion &&
@@ -5161,7 +5164,7 @@ ExecEvalJsonExpr(ExprEvalStep *op, ExprContext *econtext,
bool
ExecEvalJsonNeedsSubTransaction(JsonExpr *jsexpr,
- struct JsonCoercionsState *coercions)+ bool consider_conditional_coercions)
{
if (jsexpr->on_error->btype == JSON_BEHAVIOR_ERROR)
return false;
@@ -5169,7 +5172,7 @@ ExecEvalJsonNeedsSubTransaction(JsonExpr *jsexpr,
if (jsexpr->op == JSON_EXISTS_OP && !jsexpr->result_coercion)
return false;
- if (!coercions)+ if (consider_conditional_coercions)
return true;
return false;
@@ -5218,7 +5221,7 @@ ExecEvalJson(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
var->evaluated = false;
}
- needSubtrans = ExecEvalJsonNeedsSubTransaction(jexpr, &jsestate->coercions);+ needSubtrans = ExecEvalJsonNeedsSubTransaction(jexpr, false);
cxt.path = path;
cxt.error = throwErrors ? NULL : &error;
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.cindex 533df86ff77..a1d868bb87b 100644--- a/src/backend/optimizer/util/clauses.c+++ b/src/backend/optimizer/util/clauses.c@@ -901,7 +901,7 @@ max_parallel_hazard_walker(Node *node, max_parallel_hazard_context *context)
{
JsonExpr *jsexpr = (JsonExpr *) node;
- if (ExecEvalJsonNeedsSubTransaction(jsexpr, NULL))+ if (ExecEvalJsonNeedsSubTransaction(jsexpr, true))
{
context->max_hazard = PROPARALLEL_UNSAFE;
return true;
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.cindex fabb5f72076..31fb5555659 100644--- a/src/backend/parser/parse_expr.c+++ b/src/backend/parser/parse_expr.c@@ -4203,12 +4203,29 @@ coerceJsonExpr(ParseState *pstate, Node *expr, const JsonReturning *returning)
return coercion;
}
+/* Is it a json/jsonb type or its domain? */+static bool+isJsonType(Oid typid)+{+ Oid basetypid;++ if (typid == JSONOID || typid == JSONBOID)+ return true;++ basetypid = getBaseType(typid);++ if (basetypid == JSONOID || basetypid == JSONBOID)+ return true;++ return false;+}+
/*
* Transform a JSON output clause of JSON_VALUE and JSON_QUERY.
*/
static void
transformJsonFuncExprOutput(ParseState *pstate, JsonFuncExpr *func,
- JsonExpr *jsexpr)+ JsonExpr *jsexpr, bool *returning_json)
{
Node *expr = jsexpr->formatted_expr;
@@ -4225,13 +4242,20 @@ transformJsonFuncExprOutput(ParseState *pstate, JsonFuncExpr *func,
{
JsonReturning ret;
- if (func->op == JSON_VALUE_OP &&- jsexpr->returning->typid != JSONOID &&- jsexpr->returning->typid != JSONBOID)+ *returning_json = isJsonType(jsexpr->returning->typid);++ if (func->op == JSON_VALUE_OP && !*returning_json)
{
- /* Forced coercion via I/O for JSON_VALUE for non-JSON types */+ /*+ * In JSON_VALUE result_coercion can only be used when+ * returning JSON types and for NULL values (resulting+ * item is JSON null or NULL behavior is used).+ * So there is no need to find a cast from json[b] to+ * output type when it is a non-JSON type, we only need to+ * check domains for NOT NULL, what can be done using I/O+ * coercion.+ */
jsexpr->result_coercion = makeNode(JsonCoercion);
- jsexpr->result_coercion->expr = NULL;
jsexpr->result_coercion->via_io = true;
return;
}
@@ -4251,8 +4275,11 @@ transformJsonFuncExprOutput(ParseState *pstate, JsonFuncExpr *func,
}
}
else
+ {
assignDefaultJsonReturningType(jsexpr->formatted_expr, jsexpr->format,
jsexpr->returning);
+ *returning_json = true;+ }
}
/*
@@ -4321,10 +4348,11 @@ initJsonItemCoercion(ParseState *pstate, Oid typid,
return coerceJsonExpr(pstate, expr, returning);
}
-static void-initJsonItemCoercions(ParseState *pstate, JsonItemCoercions *coercions,- const JsonReturning *returning, Oid contextItemTypeId)+static JsonItemCoercions *+initJsonItemCoercions(ParseState *pstate, const JsonReturning *returning,+ Oid contextItemTypeId)
{
+ JsonItemCoercions *coercions = makeNode(JsonItemCoercions);
struct
{
JsonCoercion **coercion;
@@ -4347,6 +4375,8 @@ initJsonItemCoercions(ParseState *pstate, JsonItemCoercions *coercions,
for (p = coercionTypids; p->coercion; p++)
*p->coercion = initJsonItemCoercion(pstate, p->typid, returning);
++ return coercions;
}
/*
@@ -4358,13 +4388,14 @@ transformJsonFuncExpr(ParseState *pstate, JsonFuncExpr *func)
JsonExpr *jsexpr = transformJsonExprCommon(pstate, func);
const char *func_name = NULL;
Node *contextItemExpr = jsexpr->formatted_expr;
+ bool returning_json;
switch (func->op)
{
case JSON_VALUE_OP:
func_name = "JSON_VALUE";
- transformJsonFuncExprOutput(pstate, func, jsexpr);+ transformJsonFuncExprOutput(pstate, func, jsexpr, &returning_json);
jsexpr->returning->format->format_type = JS_FORMAT_DEFAULT;
jsexpr->returning->format->encoding = JS_ENC_DEFAULT;
@@ -4377,16 +4408,22 @@ transformJsonFuncExpr(ParseState *pstate, JsonFuncExpr *func)
coerceDefaultJsonExpr(pstate, jsexpr,
jsexpr->on_error->default_expr);
- jsexpr->coercions = makeNode(JsonItemCoercions);- initJsonItemCoercions(pstate, jsexpr->coercions, jsexpr->returning,- exprType(contextItemExpr));+ /*+ * Don't initialize item coercions if returning one of JSON+ * types or their domains. Coercion will be done using+ * "result_coercion".+ */+ if (!returning_json)+ jsexpr->coercions =+ initJsonItemCoercions(pstate, jsexpr->returning,+ exprType(contextItemExpr));
break;
case JSON_QUERY_OP:
func_name = "JSON_QUERY";
- transformJsonFuncExprOutput(pstate, func, jsexpr);+ transformJsonFuncExprOutput(pstate, func, jsexpr, &returning_json);
jsexpr->on_empty->default_expr =
coerceDefaultJsonExpr(pstate, jsexpr,
diff --git a/src/include/executor/execExpr.h b/src/include/executor/execExpr.hindex c8ef917ffe0..de9bbd2abcd 100644--- a/src/include/executor/execExpr.h+++ b/src/include/executor/execExpr.h@@ -781,6 +781,15 @@ typedef struct JsonExprState
void *cache; /* cache for json_populate_type() */
+ /*+ * This is used only in JSON_VALUE.+ *+ * States for coercion from SQL/JSON item types directly to the+ * output type.+ *+ * "coercions == NULL" means output type is json[b] or its domain+ * and we use "result_expr" for coercion.+ */
struct JsonCoercionsState
{
struct JsonCoercionState
@@ -797,8 +806,7 @@ typedef struct JsonExprState
timestamp,
timestamptz,
composite;
- } coercions; /* states for coercion from SQL/JSON item- * types directly to the output type */+ } *coercions;
} JsonExprState;
/* functions in execExpr.c */
@@ -867,7 +875,7 @@ extern Datum ExecPrepareJsonItemCoercion(struct JsonbValue *item,
struct JsonCoercionsState *coercions,
struct JsonCoercionState **pjcstate);
extern bool ExecEvalJsonNeedsSubTransaction(JsonExpr *jsexpr,
- struct JsonCoercionsState *);+ bool consider_conditional_coercions);
extern Datum ExecEvalExprPassingCaseValue(ExprState *estate,
ExprContext *econtext, bool *isnull,
Datum caseval_datum,
diff --git a/src/test/regress/expected/jsonb_sqljson.out b/src/test/regress/expected/jsonb_sqljson.outindex ef496110af3..ee5e597909c 100644--- a/src/test/regress/expected/jsonb_sqljson.out+++ b/src/test/regress/expected/jsonb_sqljson.out@@ -349,14 +349,193 @@ SELECT JSON_VALUE(jsonb '"2017-02-20"', '$' RETURNING date) + 9;
03-01-2017
(1 row)
--- Test NULL checks execution in domain types+-- Test for domain types
CREATE DOMAIN sqljsonb_int_not_null AS int NOT NULL;
+CREATE DOMAIN sqljsonb_json_not_null AS json NOT NULL CHECK (VALUE::text <> '0');+CREATE DOMAIN sqljsonb_jsonb_not_null AS jsonb NOT NULL CHECK (VALUE <> '0');+-- Test casting to json[b] domains (items casted as is, strings are not unquoted)+SELECT JSON_VALUE(jsonb '"1"', '$' RETURNING sqljsonb_json_not_null);+ json_value +------------+ "1"+(1 row)++SELECT JSON_VALUE(jsonb '"1"', '$' RETURNING sqljsonb_jsonb_not_null);+ json_value +------------+ "1"+(1 row)++SELECT JSON_VALUE(jsonb '1', '$' RETURNING sqljsonb_json_not_null);+ json_value +------------+ 1+(1 row)++SELECT JSON_VALUE(jsonb '1', '$' RETURNING sqljsonb_jsonb_not_null);+ json_value +------------+ 1+(1 row)++SELECT JSON_VALUE(jsonb 'null', '$' RETURNING sqljsonb_json_not_null);+ERROR: domain sqljsonb_json_not_null does not allow null values+SELECT JSON_VALUE(jsonb 'null', '$' RETURNING sqljsonb_jsonb_not_null);+ERROR: domain sqljsonb_jsonb_not_null does not allow null values+SELECT JSON_VALUE(jsonb 'true', '$' RETURNING sqljsonb_json_not_null);+ json_value +------------+ true+(1 row)++SELECT JSON_VALUE(jsonb 'true', '$' RETURNING sqljsonb_jsonb_not_null);+ json_value +------------+ true+(1 row)++-- Test NULL checks execution in domain types
SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_int_not_null);
ERROR: domain sqljsonb_int_not_null does not allow null values
+SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_int_not_null DEFAULT 2 ON EMPTY);+ json_value +------------+ 2+(1 row)++SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_int_not_null DEFAULT NULL ON EMPTY);+ERROR: domain sqljsonb_int_not_null does not allow null values+SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_int_not_null DEFAULT 2 ON ERROR);+ json_value +------------+ 2+(1 row)++SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_int_not_null DEFAULT NULL ON EMPTY DEFAULT 2 ON ERROR);+ json_value +------------+ 2+(1 row)+
SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_int_not_null NULL ON ERROR);
ERROR: domain sqljsonb_int_not_null does not allow null values
SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_int_not_null DEFAULT NULL ON ERROR);
ERROR: domain sqljsonb_int_not_null does not allow null values
+SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_json_not_null);+ERROR: domain sqljsonb_json_not_null does not allow null values+SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_json_not_null DEFAULT '2' ON EMPTY);+ json_value +------------+ 2+(1 row)++SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_json_not_null DEFAULT NULL ON EMPTY);+ERROR: domain sqljsonb_json_not_null does not allow null values+SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_json_not_null DEFAULT '2' ON ERROR);+ json_value +------------+ 2+(1 row)++SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_json_not_null DEFAULT NULL ON EMPTY DEFAULT '2' ON ERROR);+ json_value +------------+ 2+(1 row)++SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_json_not_null NULL ON ERROR);+ERROR: domain sqljsonb_json_not_null does not allow null values+SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_json_not_null DEFAULT NULL ON ERROR);+ERROR: domain sqljsonb_json_not_null does not allow null values+SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_jsonb_not_null);+ERROR: domain sqljsonb_jsonb_not_null does not allow null values+SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_jsonb_not_null DEFAULT '2' ON EMPTY);+ json_value +------------+ 2+(1 row)++SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_jsonb_not_null DEFAULT NULL ON EMPTY);+ERROR: domain sqljsonb_jsonb_not_null does not allow null values+SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_jsonb_not_null DEFAULT '2' ON ERROR);+ json_value +------------+ 2+(1 row)++SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_jsonb_not_null DEFAULT NULL ON EMPTY DEFAULT '2' ON ERROR);+ json_value +------------+ 2+(1 row)++SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_jsonb_not_null NULL ON ERROR);+ERROR: domain sqljsonb_jsonb_not_null does not allow null values+SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_jsonb_not_null DEFAULT NULL ON ERROR);+ERROR: domain sqljsonb_jsonb_not_null does not allow null values+SELECT JSON_VALUE(jsonb 'null', '$' RETURNING sqljsonb_int_not_null);+ERROR: domain sqljsonb_int_not_null does not allow null values+SELECT JSON_VALUE(jsonb 'null', '$' RETURNING sqljsonb_int_not_null DEFAULT 2 ON EMPTY);+ERROR: domain sqljsonb_int_not_null does not allow null values+SELECT JSON_VALUE(jsonb 'null', '$' RETURNING sqljsonb_int_not_null DEFAULT 2 ON ERROR);+ json_value +------------+ 2+(1 row)++SELECT JSON_VALUE(jsonb 'null', '$' RETURNING sqljsonb_int_not_null NULL ON ERROR);+ERROR: domain sqljsonb_int_not_null does not allow null values+SELECT JSON_VALUE(jsonb 'null', '$' RETURNING sqljsonb_int_not_null DEFAULT NULL ON ERROR);+ERROR: domain sqljsonb_int_not_null does not allow null values+SELECT JSON_VALUE(jsonb 'null', '$' RETURNING sqljsonb_json_not_null);+ERROR: domain sqljsonb_json_not_null does not allow null values+SELECT JSON_VALUE(jsonb 'null', '$' RETURNING sqljsonb_json_not_null DEFAULT '2' ON EMPTY);+ERROR: domain sqljsonb_json_not_null does not allow null values+SELECT JSON_VALUE(jsonb 'null', '$' RETURNING sqljsonb_json_not_null DEFAULT '2' ON ERROR);+ json_value +------------+ 2+(1 row)++SELECT JSON_VALUE(jsonb 'null', '$' RETURNING sqljsonb_json_not_null NULL ON ERROR);+ERROR: domain sqljsonb_json_not_null does not allow null values+SELECT JSON_VALUE(jsonb 'null', '$' RETURNING sqljsonb_json_not_null DEFAULT NULL ON ERROR);+ERROR: domain sqljsonb_json_not_null does not allow null values+SELECT JSON_VALUE(jsonb 'null', '$' RETURNING sqljsonb_jsonb_not_null);+ERROR: domain sqljsonb_jsonb_not_null does not allow null values+SELECT JSON_VALUE(jsonb 'null', '$' RETURNING sqljsonb_jsonb_not_null DEFAULT '2' ON EMPTY);+ERROR: domain sqljsonb_jsonb_not_null does not allow null values+SELECT JSON_VALUE(jsonb 'null', '$' RETURNING sqljsonb_jsonb_not_null DEFAULT '2' ON ERROR);+ json_value +------------+ 2+(1 row)++SELECT JSON_VALUE(jsonb 'null', '$' RETURNING sqljsonb_jsonb_not_null NULL ON ERROR);+ERROR: domain sqljsonb_jsonb_not_null does not allow null values+SELECT JSON_VALUE(jsonb 'null', '$' RETURNING sqljsonb_jsonb_not_null DEFAULT NULL ON ERROR);+ERROR: domain sqljsonb_jsonb_not_null does not allow null values+SELECT JSON_VALUE(jsonb '0', '$' RETURNING sqljsonb_json_not_null);+ERROR: domain sqljsonb_json_not_null does not allow null values+SELECT JSON_VALUE(jsonb '0', '$' RETURNING sqljsonb_json_not_null ERROR ON ERROR);+ERROR: value for domain sqljsonb_json_not_null violates check constraint "sqljsonb_json_not_null_check"+SELECT JSON_VALUE(jsonb '0', '$' RETURNING sqljsonb_json_not_null DEFAULT '2' ON ERROR);+ json_value +------------+ 2+(1 row)++SELECT JSON_VALUE(jsonb '0', '$' RETURNING sqljsonb_jsonb_not_null);+ERROR: domain sqljsonb_jsonb_not_null does not allow null values+SELECT JSON_VALUE(jsonb '0', '$' RETURNING sqljsonb_jsonb_not_null ERROR ON ERROR);+ERROR: value for domain sqljsonb_jsonb_not_null violates check constraint "sqljsonb_jsonb_not_null_check"+SELECT JSON_VALUE(jsonb '0', '$' RETURNING sqljsonb_jsonb_not_null DEFAULT '2' ON ERROR);+ json_value +------------+ 2+(1 row)++-- Test returning of non-scalar items
SELECT JSON_VALUE(jsonb '[]', '$');
json_value
------------
diff --git a/src/test/regress/sql/jsonb_sqljson.sql b/src/test/regress/sql/jsonb_sqljson.sqlindex fff25374808..d70104b70ef 100644--- a/src/test/regress/sql/jsonb_sqljson.sql+++ b/src/test/regress/sql/jsonb_sqljson.sql@@ -82,12 +82,73 @@ SELECT JSON_VALUE(jsonb '"123"', '$' RETURNING int) + 234;
SELECT JSON_VALUE(jsonb '"2017-02-20"', '$' RETURNING date) + 9;
--- Test NULL checks execution in domain types+-- Test for domain types
CREATE DOMAIN sqljsonb_int_not_null AS int NOT NULL;
+CREATE DOMAIN sqljsonb_json_not_null AS json NOT NULL CHECK (VALUE::text <> '0');+CREATE DOMAIN sqljsonb_jsonb_not_null AS jsonb NOT NULL CHECK (VALUE <> '0');++-- Test casting to json[b] domains (items casted as is, strings are not unquoted)+SELECT JSON_VALUE(jsonb '"1"', '$' RETURNING sqljsonb_json_not_null);+SELECT JSON_VALUE(jsonb '"1"', '$' RETURNING sqljsonb_jsonb_not_null);+SELECT JSON_VALUE(jsonb '1', '$' RETURNING sqljsonb_json_not_null);+SELECT JSON_VALUE(jsonb '1', '$' RETURNING sqljsonb_jsonb_not_null);+SELECT JSON_VALUE(jsonb 'null', '$' RETURNING sqljsonb_json_not_null);+SELECT JSON_VALUE(jsonb 'null', '$' RETURNING sqljsonb_jsonb_not_null);+SELECT JSON_VALUE(jsonb 'true', '$' RETURNING sqljsonb_json_not_null);+SELECT JSON_VALUE(jsonb 'true', '$' RETURNING sqljsonb_jsonb_not_null);++-- Test NULL checks execution in domain types
SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_int_not_null);
+SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_int_not_null DEFAULT 2 ON EMPTY);+SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_int_not_null DEFAULT NULL ON EMPTY);+SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_int_not_null DEFAULT 2 ON ERROR);+SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_int_not_null DEFAULT NULL ON EMPTY DEFAULT 2 ON ERROR);
SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_int_not_null NULL ON ERROR);
SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_int_not_null DEFAULT NULL ON ERROR);
+SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_json_not_null);+SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_json_not_null DEFAULT '2' ON EMPTY);+SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_json_not_null DEFAULT NULL ON EMPTY);+SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_json_not_null DEFAULT '2' ON ERROR);+SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_json_not_null DEFAULT NULL ON EMPTY DEFAULT '2' ON ERROR);+SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_json_not_null NULL ON ERROR);+SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_json_not_null DEFAULT NULL ON ERROR);++SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_jsonb_not_null);+SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_jsonb_not_null DEFAULT '2' ON EMPTY);+SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_jsonb_not_null DEFAULT NULL ON EMPTY);+SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_jsonb_not_null DEFAULT '2' ON ERROR);+SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_jsonb_not_null DEFAULT NULL ON EMPTY DEFAULT '2' ON ERROR);+SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_jsonb_not_null NULL ON ERROR);+SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_jsonb_not_null DEFAULT NULL ON ERROR);++SELECT JSON_VALUE(jsonb 'null', '$' RETURNING sqljsonb_int_not_null);+SELECT JSON_VALUE(jsonb 'null', '$' RETURNING sqljsonb_int_not_null DEFAULT 2 ON EMPTY);+SELECT JSON_VALUE(jsonb 'null', '$' RETURNING sqljsonb_int_not_null DEFAULT 2 ON ERROR);+SELECT JSON_VALUE(jsonb 'null', '$' RETURNING sqljsonb_int_not_null NULL ON ERROR);+SELECT JSON_VALUE(jsonb 'null', '$' RETURNING sqljsonb_int_not_null DEFAULT NULL ON ERROR);++SELECT JSON_VALUE(jsonb 'null', '$' RETURNING sqljsonb_json_not_null);+SELECT JSON_VALUE(jsonb 'null', '$' RETURNING sqljsonb_json_not_null DEFAULT '2' ON EMPTY);+SELECT JSON_VALUE(jsonb 'null', '$' RETURNING sqljsonb_json_not_null DEFAULT '2' ON ERROR);+SELECT JSON_VALUE(jsonb 'null', '$' RETURNING sqljsonb_json_not_null NULL ON ERROR);+SELECT JSON_VALUE(jsonb 'null', '$' RETURNING sqljsonb_json_not_null DEFAULT NULL ON ERROR);++SELECT JSON_VALUE(jsonb 'null', '$' RETURNING sqljsonb_jsonb_not_null);+SELECT JSON_VALUE(jsonb 'null', '$' RETURNING sqljsonb_jsonb_not_null DEFAULT '2' ON EMPTY);+SELECT JSON_VALUE(jsonb 'null', '$' RETURNING sqljsonb_jsonb_not_null DEFAULT '2' ON ERROR);+SELECT JSON_VALUE(jsonb 'null', '$' RETURNING sqljsonb_jsonb_not_null NULL ON ERROR);+SELECT JSON_VALUE(jsonb 'null', '$' RETURNING sqljsonb_jsonb_not_null DEFAULT NULL ON ERROR);++SELECT JSON_VALUE(jsonb '0', '$' RETURNING sqljsonb_json_not_null);+SELECT JSON_VALUE(jsonb '0', '$' RETURNING sqljsonb_json_not_null ERROR ON ERROR);+SELECT JSON_VALUE(jsonb '0', '$' RETURNING sqljsonb_json_not_null DEFAULT '2' ON ERROR);++SELECT JSON_VALUE(jsonb '0', '$' RETURNING sqljsonb_jsonb_not_null);+SELECT JSON_VALUE(jsonb '0', '$' RETURNING sqljsonb_jsonb_not_null ERROR ON ERROR);+SELECT JSON_VALUE(jsonb '0', '$' RETURNING sqljsonb_jsonb_not_null DEFAULT '2' ON ERROR);++-- Test returning of non-scalar items
SELECT JSON_VALUE(jsonb '[]', '$');
SELECT JSON_VALUE(jsonb '[]', '$' ERROR ON ERROR);
SELECT JSON_VALUE(jsonb '{}', '$');
--
2.25.1
v6-0002-Add-EEOP_SUBTRANS-executor-step.patch (text/x-patch, 11.0K) download
From 45d938995ef8bab8049638a0ee63efbc9595ab45 Mon Sep 17 00:00:00 2001
From: Nikita Glukhov <n.gluhov@postgrespro.ru>
Date: Mon, 15 Aug 2022 15:04:17 +0300
Subject: [PATCH v6 2/3] Add EEOP_SUBTRANS executor step
---
src/backend/executor/execExprInterp.c | 102 +++++++++++++++++++++++++-
src/backend/jit/llvm/llvmjit_expr.c | 90 +++++++++++++++++++++--
src/backend/jit/llvm/llvmjit_types.c | 1 +
src/include/executor/execExpr.h | 13 ++++
4 files changed, 198 insertions(+), 8 deletions(-)
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.cindex 353346d5528..662d47f8cbb 100644--- a/src/backend/executor/execExprInterp.c+++ b/src/backend/executor/execExprInterp.c@@ -158,6 +158,8 @@ static TupleDesc get_cached_rowtype(Oid type_id, int32 typmod,
bool *changed);
static void ExecEvalRowNullInt(ExprState *state, ExprEvalStep *op,
ExprContext *econtext, bool checkisnull);
+static void ExecEvalSubtransInterp(ExprState *state, ExprEvalStep *op,+ ExprContext *econtext);
/* fast-path evaluation functions */
static Datum ExecJustInnerVar(ExprState *state, ExprContext *econtext, bool *isnull);
@@ -398,7 +400,8 @@ ExecReadyInterpretedExpr(ExprState *state)
* (Only applies when EEO_USE_COMPUTED_GOTO is defined.)
*/
static Datum
-ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)+ExecInterpExprStep(ExprState *state, int stepno,+ ExprContext *econtext, bool *isnull)
{
ExprEvalStep *op;
TupleTableSlot *resultslot;
@@ -488,6 +491,7 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
&&CASE_EEOP_GROUPING_FUNC,
&&CASE_EEOP_WINDOW_FUNC,
&&CASE_EEOP_SUBPLAN,
+ &&CASE_EEOP_SUBTRANS,
&&CASE_EEOP_JSON_CONSTRUCTOR,
&&CASE_EEOP_IS_JSON,
&&CASE_EEOP_JSONEXPR,
@@ -519,7 +523,7 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
#endif /* EEO_USE_COMPUTED_GOTO */
/* setup state */
- op = state->steps;+ op = &state->steps[stepno];
resultslot = state->resultslot;
innerslot = econtext->ecxt_innertuple;
outerslot = econtext->ecxt_outertuple;
@@ -1827,6 +1831,16 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
EEO_NEXT();
}
+ EEO_CASE(EEOP_SUBTRANS)+ {+ /* too complex for an inline implementation */+ if (ExecEvalSubtrans(state, op, econtext,+ ExecEvalSubtransInterp))+ EEO_JUMP(op->d.subtrans.jump_done);+ else+ EEO_JUMP(op->d.subtrans.jump_error);+ }+
EEO_CASE(EEOP_JSON_CONSTRUCTOR)
{
/* too complex for an inline implementation */
@@ -1861,6 +1875,12 @@ out:
return state->resvalue;
}
+static Datum+ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)+{+ return ExecInterpExprStep(state, 0, econtext, isnull);+}+
/*
* Expression evaluation callback that performs extra checks before executing
* the expression. Declared extern so other methods of execution can use it
@@ -4620,6 +4640,84 @@ ExecAggPlainTransByRef(AggState *aggstate, AggStatePerTrans pertrans,
MemoryContextSwitchTo(oldContext);
}
+/*+ * Execute subexpression steps using recursive interpreter call.+ * "op" points EEOP_SUBTRANS step.+ */+static void+ExecEvalSubtransInterp(ExprState *state, ExprEvalStep *op,+ ExprContext *econtext)+{+ /* Recursively call interpreter for next step ignoring result */+ bool isnull;++ (void) ExecInterpExprStep(state, op - state->steps + 1, econtext,+ &isnull);+}++/*+ * Execute EEOP_SUBTRANS, calling "eval" function inside a+ * subtransaction. This function executes sequence of subexpression+ * steps that begins right after current step and ends with EEOP_DONE.+ */+bool+ExecEvalSubtrans(ExprState *state, ExprEvalStep *op,+ ExprContext *econtext, ExecEvalSubroutine eval)+{+ MemoryContext oldcontext = CurrentMemoryContext;+ ResourceOwner oldowner = CurrentResourceOwner;+ bool error;++ /*+ * We should catch exceptions of category ERRCODE_DATA_EXCEPTION and+ * execute the corresponding ON ERROR behavior then.+ */+ BeginInternalSubTransaction(NULL);+ /* Want to execute expressions inside function's memory context */+ MemoryContextSwitchTo(oldcontext);++ PG_TRY();+ {+ eval(state, op, econtext);++ /* Commit the inner transaction, return to outer xact context */+ ReleaseCurrentSubTransaction();+ MemoryContextSwitchTo(oldcontext);+ CurrentResourceOwner = oldowner;++ error = false;+ }+ PG_CATCH();+ {+ ErrorData *edata;+ int ecategory;++ /* Save error info in oldcontext */+ MemoryContextSwitchTo(oldcontext);+ edata = CopyErrorData();+ FlushErrorState();++ /* Abort the inner transaction */+ RollbackAndReleaseCurrentSubTransaction();+ MemoryContextSwitchTo(oldcontext);+ CurrentResourceOwner = oldowner;++ ecategory = ERRCODE_TO_CATEGORY(edata->sqlerrcode);++ if (ecategory != ERRCODE_DATA_EXCEPTION && /* jsonpath and other data errors */+ ecategory != ERRCODE_INTEGRITY_CONSTRAINT_VIOLATION) /* domain errors */+ ReThrowError(edata);++ *op->resvalue = (Datum) 0;+ *op->resnull = true;++ error = true;+ }+ PG_END_TRY();++ return !error;+}+
/*
* Evaluate a JSON constructor expression.
*/
diff --git a/src/backend/jit/llvm/llvmjit_expr.c b/src/backend/jit/llvm/llvmjit_expr.cindex bd3965143da..614ed58f1cb 100644--- a/src/backend/jit/llvm/llvmjit_expr.c+++ b/src/backend/jit/llvm/llvmjit_expr.c@@ -53,6 +53,8 @@ typedef struct CompiledExprState
static Datum ExecRunCompiledExpr(ExprState *state, ExprContext *econtext, bool *isNull);
+static void ExecEvalSubtransCompiled(ExprState *state, ExprEvalStep *op,+ ExprContext *econtext);
static LLVMValueRef BuildV1Call(LLVMJitContext *context, LLVMBuilderRef b,
LLVMModuleRef mod, FunctionCallInfo fcinfo,
@@ -72,10 +74,10 @@ static LLVMValueRef create_LifetimeEnd(LLVMModuleRef mod);
/*
- * JIT compile expression.+ * JIT compile expression staring from the specified step.
*/
-bool-llvm_compile_expr(ExprState *state)+static bool+llvm_compile_expr_step(ExprState *state, int start_opno)
{
PlanState *parent = state->parent;
char *funcname;
@@ -224,13 +226,13 @@ llvm_compile_expr(ExprState *state)
/* allocate blocks for each op upfront, so we can do jumps easily */
opblocks = palloc(sizeof(LLVMBasicBlockRef) * state->steps_len);
- for (int opno = 0; opno < state->steps_len; opno++)+ for (int opno = start_opno; opno < state->steps_len; opno++)
opblocks[opno] = l_bb_append_v(eval_fn, "b.op.%d.start", opno);
/* jump from entry to first block */
- LLVMBuildBr(b, opblocks[0]);+ LLVMBuildBr(b, opblocks[start_opno]);- for (int opno = 0; opno < state->steps_len; opno++)+ for (int opno = start_opno; opno < state->steps_len; opno++)
{
ExprEvalStep *op;
ExprEvalOp opcode;
@@ -2395,6 +2397,62 @@ llvm_compile_expr(ExprState *state)
LLVMBuildBr(b, opblocks[opno + 1]);
break;
+ case EEOP_SUBTRANS:+ {+ ExprState *substate;+ LLVMValueRef v_ret;+ LLVMTypeRef v_functype;++ /* Create subexpression ExprState */+ substate = makeNode(ExprState);+ substate->parent = state->parent;+ substate->steps = state->steps;+ substate->steps_len = op->d.subtrans.jump_next;++ /*+ * Compile it starting from the first subexpression+ * step, which is the next step.+ */+ llvm_compile_expr_step(substate, opno + 1);++ /*+ * Save subexpression state in the private area of+ * the step.+ */+ op->d.subtrans.private_data = substate;++ /*+ * Call ExecEvalJsonExprSubtrans() passing+ * ExecEvalSubtransCompiled(), which will execute+ * subexpression getting ExprState from the private+ * area.+ */+ v_functype = llvm_pg_var_func_type("TypeExecEvalSubroutine");++ v_ret = build_EvalXFunc(b, mod, "ExecEvalSubtrans",+ v_state, op, v_econtext,+ l_ptr_const(ExecEvalSubtransCompiled,+ LLVMPointerType(v_functype, 0)));+ v_ret = LLVMBuildZExt(b, v_ret, TypeStorageBool, "");++ /*+ * Jump to "error" or "done" steps depending of+ * returned boolean value (TRUE means "done").+ */+ LLVMBuildCondBr(b,+ LLVMBuildICmp(b,+ LLVMIntEQ,+ v_ret,+ l_sbool_const(0),+ ""),+ opblocks[op->d.subtrans.jump_error],+ opblocks[op->d.subtrans.jump_done]);++ /* Skip subexpression steps */+ opno = op->d.subtrans.jump_next - 1;+ break;+ }+
case EEOP_JSON_CONSTRUCTOR:
build_EvalXFunc(b, mod, "ExecEvalJsonConstructor",
v_state, op, v_econtext);
@@ -2447,6 +2505,15 @@ llvm_compile_expr(ExprState *state)
return true;
}
+/*+ * JIT compile expression staring from the first step.+ */+bool+llvm_compile_expr(ExprState *state)+{+ return llvm_compile_expr_step(state, 0);+}+
/*
* Run compiled expression.
*
@@ -2475,6 +2542,17 @@ ExecRunCompiledExpr(ExprState *state, ExprContext *econtext, bool *isNull)
return func(state, econtext, isNull);
}
+/* Execute subexpression steps using compiled ExprState */+static void+ExecEvalSubtransCompiled(ExprState *state, ExprEvalStep *op,+ ExprContext *econtext)+{+ ExprState *substate = op->d.subtrans.private_data;+ bool isnull;++ (void) ExecEvalExpr(substate, econtext, &isnull);+}+
static LLVMValueRef
BuildV1Call(LLVMJitContext *context, LLVMBuilderRef b,
LLVMModuleRef mod, FunctionCallInfo fcinfo,
diff --git a/src/backend/jit/llvm/llvmjit_types.c b/src/backend/jit/llvm/llvmjit_types.cindex 373471ad27f..a9ddba6e4cd 100644--- a/src/backend/jit/llvm/llvmjit_types.c+++ b/src/backend/jit/llvm/llvmjit_types.c@@ -133,6 +133,7 @@ void *referenced_functions[] =
ExecEvalSysVar,
ExecEvalWholeRowVar,
ExecEvalXmlExpr,
+ ExecEvalSubtrans,
ExecEvalJsonConstructor,
ExecEvalJsonIsPredicate,
ExecEvalJson,
diff --git a/src/include/executor/execExpr.h b/src/include/executor/execExpr.hindex de9bbd2abcd..14064dd9521 100644--- a/src/include/executor/execExpr.h+++ b/src/include/executor/execExpr.h@@ -242,6 +242,7 @@ typedef enum ExprEvalOp
EEOP_GROUPING_FUNC,
EEOP_WINDOW_FUNC,
EEOP_SUBPLAN,
+ EEOP_SUBTRANS,
EEOP_JSON_CONSTRUCTOR,
EEOP_IS_JSON,
EEOP_JSONEXPR,
@@ -680,6 +681,15 @@ typedef struct ExprEvalStep
int setoff;
} agg_trans;
+ /* for EEOP_SUBTRANS */+ struct+ {+ void *private_data;+ int jump_done;+ int jump_error;+ int jump_next;+ } subtrans;+
/* for EEOP_JSON_CONSTRUCTOR */
struct
{
@@ -866,6 +876,9 @@ extern void ExecEvalWholeRowVar(ExprState *state, ExprEvalStep *op,
ExprContext *econtext);
extern void ExecEvalSysVar(ExprState *state, ExprEvalStep *op,
ExprContext *econtext, TupleTableSlot *slot);
+extern bool ExecEvalSubtrans(ExprState *state, ExprEvalStep *op,+ ExprContext *econtext,+ ExecEvalSubroutine func);
extern void ExecEvalJsonConstructor(ExprState *state, ExprEvalStep *op,
ExprContext *econtext);
extern void ExecEvalJson(ExprState *state, ExprEvalStep *op,
--
2.25.1
v6-0003-Simplify-JsonExpr-execution.patch (text/x-patch, 61.1K) download
From 842c002642c06d2dd371e195544236ae9ad2e76e Mon Sep 17 00:00:00 2001
From: Nikita Glukhov <n.gluhov@postgrespro.ru>
Date: Tue, 16 Aug 2022 03:17:16 +0300
Subject: [PATCH v6 3/3] Simplify JsonExpr execution
---
src/backend/executor/execExpr.c | 675 +++++++++++++++++---
src/backend/executor/execExprInterp.c | 605 ++++++------------
src/backend/jit/llvm/llvmjit_expr.c | 137 +++-
src/backend/jit/llvm/llvmjit_types.c | 3 +
src/backend/optimizer/util/clauses.c | 2 +-
src/backend/parser/parse_expr.c | 18 +-
src/backend/utils/adt/jsonpath_exec.c | 68 ++
src/include/executor/execExpr.h | 150 +++--
src/include/nodes/primnodes.h | 9 +-
src/include/utils/jsonb.h | 2 +
src/include/utils/jsonpath.h | 3 -
src/test/regress/expected/jsonb_sqljson.out | 17 +
src/test/regress/sql/jsonb_sqljson.sql | 7 +
13 files changed, 1110 insertions(+), 586 deletions(-)
diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.cindex 96196d50876..9370727d902 100644--- a/src/backend/executor/execExpr.c+++ b/src/backend/executor/execExpr.c@@ -86,7 +86,8 @@ static void ExecBuildAggTransCall(ExprState *state, AggState *aggstate,
FunctionCallInfo fcinfo, AggStatePerTrans pertrans,
int transno, int setno, int setoff, bool ishash,
bool nullcheck);
-+static void ExecInitJsonExpr(ExprEvalStep *scratch, JsonExpr *jexpr, ExprState *state,+ Datum *resv, bool *resnull);
static ExprState *
ExecInitExprInternal(Expr *node, PlanState *parent, ParamListInfo ext_params,
@@ -178,7 +179,7 @@ ExecInitExprWithParams(Expr *node, ParamListInfo ext_params)
* ExecInitExprWithCaseValue: prepare an expression tree for execution
*
* This is the same as ExecInitExpr, except that a pointer to the value for
- * CasTestExpr is passed here.+ * CaseTestExpr is passed here.
*/
ExprState *
ExecInitExprWithCaseValue(Expr *node, PlanState *parent,
@@ -2561,112 +2562,20 @@ ExecInitExprRec(Expr *node, ExprState *state,
case T_JsonExpr:
{
JsonExpr *jexpr = castNode(JsonExpr, node);
- JsonExprState *jsestate = palloc0(sizeof(JsonExprState));- ListCell *argexprlc;- ListCell *argnamelc;-- scratch.opcode = EEOP_JSONEXPR;- scratch.d.jsonexpr.jsestate = jsestate;-- jsestate->jsexpr = jexpr;-- jsestate->formatted_expr =- palloc(sizeof(*jsestate->formatted_expr));-- ExecInitExprRec((Expr *) jexpr->formatted_expr, state,- &jsestate->formatted_expr->value,- &jsestate->formatted_expr->isnull);-- jsestate->pathspec =- palloc(sizeof(*jsestate->pathspec));-- ExecInitExprRec((Expr *) jexpr->path_spec, state,- &jsestate->pathspec->value,- &jsestate->pathspec->isnull);-- jsestate->res_expr =- palloc(sizeof(*jsestate->res_expr));-- jsestate->result_expr = jexpr->result_coercion- ? ExecInitExprWithCaseValue((Expr *) jexpr->result_coercion->expr,- state->parent,- &jsestate->res_expr->value,- &jsestate->res_expr->isnull)- : NULL;-- jsestate->default_on_empty = !jexpr->on_empty ? NULL :- ExecInitExpr((Expr *) jexpr->on_empty->default_expr,- state->parent);-- jsestate->default_on_error =- ExecInitExpr((Expr *) jexpr->on_error->default_expr,- state->parent);-- if (jexpr->omit_quotes ||- (jexpr->result_coercion && jexpr->result_coercion->via_io))- {- Oid typinput;-- /* lookup the result type's input function */- getTypeInputInfo(jexpr->returning->typid, &typinput,- &jsestate->input.typioparam);- fmgr_info(typinput, &jsestate->input.func);- }- jsestate->args = NIL;-- forboth(argexprlc, jexpr->passing_values,- argnamelc, jexpr->passing_names)- {- Expr *argexpr = (Expr *) lfirst(argexprlc);- String *argname = lfirst_node(String, argnamelc);- JsonPathVariableEvalContext *var = palloc(sizeof(*var));-- var->name = pstrdup(argname->sval);- var->typid = exprType((Node *) argexpr);- var->typmod = exprTypmod((Node *) argexpr);- var->estate = ExecInitExpr(argexpr, state->parent);- var->econtext = NULL;- var->mcxt = NULL;- var->evaluated = false;- var->value = (Datum) 0;- var->isnull = true;-- jsestate->args =- lappend(jsestate->args, var);- }-- jsestate->cache = NULL;+ ExecInitJsonExpr(&scratch, jexpr, state, resv, resnull);+ break;+ }- if (jexpr->coercions)- {- JsonCoercion **coercion;- struct JsonCoercionState *cstate;- Datum *caseval;- bool *casenull;-- jsestate->coercions =- palloc(sizeof(*jsestate->coercions));- jsestate->coercion_expr =- palloc(sizeof(*jsestate->coercion_expr));-- caseval = &jsestate->coercion_expr->value;- casenull = &jsestate->coercion_expr->isnull;-- for (cstate = &jsestate->coercions->null,- coercion = &jexpr->coercions->null;- coercion <= &jexpr->coercions->composite;- coercion++, cstate++)- {- cstate->coercion = *coercion;- cstate->estate = *coercion ?- ExecInitExprWithCaseValue((Expr *) (*coercion)->expr,- state->parent,- caseval, casenull) : NULL;- }- }+ case T_JsonItemCoercions:+ {+ elog(ERROR, "JsonItemCoercions must be initialized manually");+ break;+ }- ExprEvalPushStep(state, &scratch);+ case T_JsonCoercion:+ {+ elog(ERROR, "JsonCoercion must be initialized manually");
break;
}
@@ -4259,3 +4168,559 @@ ExecBuildParamSetEqual(TupleDesc desc,
return state;
}
++static void+ExecInitJsonCoercionExpr(ExprState *state, Expr *coercion,+ Datum *resv, bool *resnull)+{+ Datum *save_innermost_caseval;+ bool *save_innermost_isnull;++ /* Push step(s) to compute expr using CaseTestValue. */+ save_innermost_caseval = state->innermost_caseval;+ save_innermost_isnull = state->innermost_casenull;++ state->innermost_caseval = resv;+ state->innermost_casenull = resnull;++ ExecInitExprRec(coercion, state, resv, resnull);++ state->innermost_caseval = save_innermost_caseval;+ state->innermost_casenull = save_innermost_isnull;+}++/* Context for passing common fields used for JsonExpr initialization */+typedef struct ExecInitJsonContext+{+ ExprState *state;+ ExprEvalStep *scratch;+ JsonExprState *jsestate;+ Datum *resv;+ bool *resnull;+ List *adjust_jumps; /* list of step numbers of jump_done steps */+ List *adjust_subtrans; /* list of step numbers of subtrans steps */+} ExecInitJsonContext;++static bool+ExecExprNeedsSubTransaction(Node *expr)+{+ /* No need for sub-tranactions for Const expressions */+ if (IsA(expr, Const))+ return false;++ /* TODO check other expression types */+ return true;+}++bool+ExecJsonExprNeedsSubTransaction(JsonExpr *jsexpr)+{+ if (jsexpr->on_error->btype == JSON_BEHAVIOR_ERROR)+ return false; /* no error handling */++ if (jsexpr->result_coercion &&+ (jsexpr->result_coercion->expr ||+ jsexpr->result_coercion->via_io ||+ jsexpr->result_coercion->via_populate))+ return true;++ if (jsexpr->on_empty &&+ jsexpr->on_empty->btype == JSON_BEHAVIOR_DEFAULT &&+ ExecExprNeedsSubTransaction(jsexpr->on_empty->default_expr))+ return true;++ if (jsexpr->op == JSON_EXISTS_OP)+ return false;++ if (jsexpr->op == JSON_VALUE_OP && jsexpr->coercions)+ return true; /* may call item coercions */++ if (jsexpr->op == JSON_QUERY_OP && jsexpr->omit_quotes)+ return true; /* may call coercion via I/O */++ return false;+}++static int+ExecInitJsonCoercion(ExecInitJsonContext *cxt, Node *expr,+ bool need_subtrans)+{+ ExprState *state = cxt->state;+ ExprEvalStep *scratch = cxt->scratch;+ int step_off = state->steps_len;++ if (IsA(expr, JsonCoercion))+ {+ JsonCoercion *coercion = (JsonCoercion *) expr;++ if (!coercion->expr &&+ !coercion->via_io &&+ !coercion->via_populate)+ return -1; /* no coercion is needed */+ }++ /* Check whether expression really needs sub-transactions */+ if (need_subtrans)+ need_subtrans = ExecExprNeedsSubTransaction(expr);++ /* Emit EEOP_JSONEXPR_SUBTRANS step */+ if (need_subtrans)+ {+ cxt->adjust_subtrans =+ lappend_int(cxt->adjust_subtrans, state->steps_len);++ scratch->opcode = EEOP_SUBTRANS;+ scratch->d.subtrans.jump_next = -1; /* computed later */+ scratch->d.subtrans.jump_done = -1; /* computed later */+ scratch->d.subtrans.jump_error = -1; /* computed later */+ ExprEvalPushStep(state, scratch);+ }++ /* Emit expression step, if needed. */+ if (IsA(expr, JsonCoercion))+ {+ JsonCoercion *coercion = (JsonCoercion *) expr;++ if (coercion->expr)+ /* Push step(s) to compute coercion->expr */+ ExecInitJsonCoercionExpr(state, (Expr *) coercion->expr,+ cxt->resv, cxt->resnull);+ else if (coercion->via_populate)+ {+ /* Push step for coercion using json_populate_type() */+ scratch->opcode = EEOP_JSONEXPR_POPULATE;+ scratch->d.jsonexpr_populate.jsexpr = cxt->jsestate->jsexpr;+ scratch->d.jsonexpr_populate.cache = NULL;+ ExprEvalPushStep(state, scratch);+ }+ else if (coercion->via_io)+ {+ /* Push step for coercion via I/O */+ FmgrInfo *finfo;+ Oid typinput;+ Oid typioparam;+ JsonReturning *returning = cxt->jsestate->jsexpr->returning;++ /* lookup the result type's input function */+ getTypeInputInfo(returning->typid, &typinput,+ &typioparam);+ finfo = palloc0(sizeof(FmgrInfo));+ fmgr_info(typinput, finfo);++ scratch->opcode = EEOP_JSONEXPR_IOCOERCE;+ scratch->d.jsonexpr_iocoerce.finfo = finfo;+ scratch->d.jsonexpr_iocoerce.typioparam = typioparam;+ scratch->d.jsonexpr_iocoerce.typmod = returning->typmod;+ ExprEvalPushStep(state, scratch);+ }+ else+ {+ Assert(0); /* no coercion case must be checked at the beginning */+ }+ }+ else+ /* Push step(s) to compute expr */+ ExecInitExprRec((Expr *) expr, state, cxt->resv, cxt->resnull);++ if (need_subtrans)+ {+ /* Step for returning back to EEOP_JSONEXPR_SUBTRANS step. */+ scratch->opcode = EEOP_DONE;+ ExprEvalPushStep(state, scratch);++ state->steps[step_off].d.subtrans.jump_next = state->steps_len;+ }+ else+ {+ /*+ * Emit JUMP step to jump to end of JsonExpr code, because+ * evaluating the coercion or default expression gives the+ * final result and there's nothing more to do.+ */+ scratch->opcode = EEOP_JUMP;+ scratch->d.jump.jumpdone = -1; /* computed later */+ ExprEvalPushStep(state, scratch);++ /*+ * Don't know address for that jump yet, compute once the whole+ * JsonExpr is built. So remember JUMP step address to set+ * the actual jump target addreess.+ */+ cxt->adjust_jumps =+ lappend_int(cxt->adjust_jumps, state->steps_len - 1);+ }++ return step_off;+}++/*+ * Push steps to evaluate a JsonItemCoercions, which contains the state+ * for evaluating all possible coercions that may need to be applied to+ * a JSON item coming from evaluating the parent JsonExpr.+ */+static JsonItemCoercionsSteps *+ExecInitJsonItemCoercions(ExecInitJsonContext *cxt,+ JsonItemCoercions *coercions,+ bool need_subtrans)+{+ JsonItemCoercionsSteps *jcstate = palloc0(sizeof(*jcstate));+ JsonCoercion **coercion;+ int *step;++ /* Push the steps of individual coercion's expression, if needed. */+ for (step = &jcstate->null,+ coercion = &coercions->null;+ coercion <= &coercions->composite;+ coercion++, step++)+ {+ if (*coercion)+ {+ Assert(!(*coercion)->via_io && !(*coercion)->via_populate);++ if ((*coercion)->expr)+ *step = ExecInitJsonCoercion(cxt, (Node *) *coercion,+ need_subtrans);+ else+ *step = 0; /* No coercion is needed */+ }+ else+ *step = -1; /* This means there is no cast to output type */+ }++ return jcstate;+}++/*+ * Evaluate a JSON error/empty behavior result.+ */+static Datum+ExecJsonBehavior(JsonBehavior *behavior, bool *is_null)+{+ *is_null = false;++ switch (behavior->btype)+ {+ case JSON_BEHAVIOR_EMPTY_ARRAY:+ return JsonbPGetDatum(JsonbMakeEmptyArray());++ case JSON_BEHAVIOR_EMPTY_OBJECT:+ return JsonbPGetDatum(JsonbMakeEmptyObject());++ case JSON_BEHAVIOR_TRUE:+ return BoolGetDatum(true);++ case JSON_BEHAVIOR_FALSE:+ return BoolGetDatum(false);++ case JSON_BEHAVIOR_NULL:+ case JSON_BEHAVIOR_UNKNOWN:+ case JSON_BEHAVIOR_EMPTY:+ *is_null = true;+ return (Datum) 0;++ case JSON_BEHAVIOR_DEFAULT:+ /* Always handled in the caller. */+ Assert(false);+ return (Datum) 0;++ default:+ elog(ERROR, "unrecognized SQL/JSON behavior %d", behavior->btype);+ return (Datum) 0;+ }+}++/*+ * Push steps to evaluate ON ERROR/EMPTY behavior: it is either DEFAULT+ * expression or constant with optional coercion.+ *+ * Returned first step number, or -1 if no steps.+ */+static int+ExecInitJsonBehavior(ExecInitJsonContext *cxt, JsonBehavior *behavior,+ bool need_subtrans, int *jump_coercion_step_off)+{+ if (!behavior || behavior->btype == JSON_BEHAVIOR_ERROR)+ return -1;++ if (behavior->default_expr)+ return ExecInitJsonCoercion(cxt, behavior->default_expr,+ need_subtrans);+ else+ {+ ExprState *state = cxt->state;+ ExprEvalStep *scratch = cxt->scratch;+ bool isnull;+ Datum val = ExecJsonBehavior(behavior, &isnull);+ int step_off = state->steps_len;++ /* Step to emit resulting constant */+ scratch->opcode = EEOP_CONST;+ scratch->d.constval.value = val;+ scratch->d.constval.isnull = isnull;+ ExprEvalPushStep(state, scratch);++ /* Step to the coercion or the end of expression */+ scratch->opcode = EEOP_JUMP;+ scratch->d.jump.jumpdone = -1; /* computed later */+ ExprEvalPushStep(state, scratch);++ /*+ * If it is a jump to coercion, return it through special output+ * parameter, else append it to jump_done list.+ */+ if (cxt->jsestate->jsexpr->result_coercion)+ *jump_coercion_step_off = state->steps_len - 1;+ else+ cxt->adjust_jumps =+ lappend_int(cxt->adjust_jumps, state->steps_len - 1);++ return step_off;+ }+}++/*+ * Push steps to evaluate a JsonExpr and its various subsidiary+ * expressions.+ */+static void+ExecInitJsonExpr(ExprEvalStep *scratch, JsonExpr *jexpr, ExprState *state,+ Datum *resv, bool *resnull)+{+ ExecInitJsonContext cxt;+ JsonExprState *jsestate = palloc0(sizeof(JsonExprState));+ JsonExprPreEvalState *pre_eval = &jsestate->pre_eval;+ ExprEvalStep *as;+ ListCell *argexprlc;+ ListCell *argnamelc;+ ListCell *lc;+ int skip_step_off;+ int execpath_step_off;+ int onempty_step_off;+ int onerror_step_off;+ int onempty_jump_coercion_step_off = -1;+ int onerror_jump_coercion_step_off = -1;+ int coercion_step_off = -1;+ int coercion_subtrans_step_off = -1;+ int io_coercion_step_off = -1;+ int done_off;+ bool need_subtrans;++ cxt.state = state;+ cxt.scratch = scratch;+ cxt.jsestate = jsestate;+ cxt.resv = resv;+ cxt.resnull = resnull;+ cxt.adjust_jumps = NIL;+ cxt.adjust_subtrans = NIL;++ jsestate->jsexpr = jexpr;++ /*+ * Set if coercion or DEFAULT ON EMPTY expression, which runs+ * separately from path evaluation and whose errors must be caught+ * and handled per ON ERROR behavior, must use a sub-transaction.+ */+ need_subtrans = jexpr->on_error->btype != JSON_BEHAVIOR_ERROR;++ /*+ * Add steps to compute formatted_expr, pathspec, and PASSING arg+ * expressions as things that must be evaluated *before* the actual+ * JSON path expression.+ */+ ExecInitExprRec((Expr *) jexpr->formatted_expr, state,+ &pre_eval->formatted_expr.value,+ &pre_eval->formatted_expr.isnull);+ ExecInitExprRec((Expr *) jexpr->path_spec, state,+ &pre_eval->pathspec.value,+ &pre_eval->pathspec.isnull);++ /*+ * Before pushing steps for PASSING args, push a step to decide+ * whether to skip evaluating the args and the JSON path expression+ * depending on whether either of formatted_expr and pathspec is+ * NULL.+ */+ scratch->opcode = EEOP_JSONEXPR_SKIP;+ scratch->d.jsonexpr_skip.jsestate = jsestate;+ skip_step_off = state->steps_len;+ ExprEvalPushStep(state, scratch);++ /* PASSING args. */+ jsestate->pre_eval.args = NIL;+ forboth(argexprlc, jexpr->passing_values,+ argnamelc, jexpr->passing_names)+ {+ Expr *argexpr = (Expr *) lfirst(argexprlc);+ String *argname = lfirst_node(String, argnamelc);+ JsonPathVariableEvalContext *var = palloc(sizeof(*var));++ var->name = pstrdup(argname->sval);+ var->typid = exprType((Node *) argexpr);+ var->typmod = exprTypmod((Node *) argexpr);++ /*+ * A separate ExprState is not necessary for these expressions+ * when being evaluated for a JsonExpr, like in this case,+ * because they will evaluated as the steps of the JsonExpr.+ */+ var->estate = NULL;+ var->econtext = NULL;+ var->mcxt = NULL;++ /*+ * Mark these as always evaluated because they must have been+ * evaluated before JSON path evaluation begins, because we+ * haven't pushed the step for the latter yet.+ */+ var->evaluated = true;++ ExecInitExprRec((Expr *) argexpr, state, &var->value, &var->isnull);++ pre_eval->args = lappend(pre_eval->args, var);+ }++ /* Step for the actual JSON path evaluation. */+ scratch->opcode = EEOP_JSONEXPR_PATH;+ scratch->d.jsonexpr.jsestate = jsestate;+ execpath_step_off = state->steps_len;+ ExprEvalPushStep(state, scratch);++ /*+ * Push steps to control the evaluation of expressions based+ * on the result of JSON path evaluation.+ */++ /* Step(s) to evaluate ON EMPTY behavior */+ onempty_step_off = ExecInitJsonBehavior(&cxt, jexpr->on_empty,+ need_subtrans,+ &onempty_jump_coercion_step_off);++ /* Step(s) to evaluate ON ERROR behavior */+ onerror_step_off = ExecInitJsonBehavior(&cxt, jexpr->on_error,+ false, /* sub-transactions are not needed here */+ &onerror_jump_coercion_step_off);++ Assert(!need_subtrans || onerror_step_off >= 0);++ /*+ * Initialize coercion expression.+ */+ if (jexpr->result_coercion)+ {+ /*+ * Don't use a sub-transaction for coercing the ON ERROR+ * expression.+ */+ coercion_step_off =+ ExecInitJsonCoercion(&cxt, (Node *) jexpr->result_coercion, false);++ /*+ * Generate also coercion expression wrapped into a+ * sub-transaction, if needed.+ */+ if (need_subtrans)+ coercion_subtrans_step_off =+ ExecInitJsonCoercion(&cxt, (Node *) jexpr->result_coercion, true);+ }++ /*+ * Emit I/O coercion if OMIT QUOTES is used and it was not already+ * emitted.+ */+ if (jexpr->omit_quotes &&+ (!jexpr->result_coercion || !jexpr->result_coercion->via_io))+ {+ JsonCoercion io_coercion = {0};++ io_coercion.type = T_JsonCoercion;+ io_coercion.via_io = true;++ io_coercion_step_off =+ ExecInitJsonCoercion(&cxt, (Node *) &io_coercion, need_subtrans);+ }++ /*+ * Initialize "SQL/JSON item type => SQL type" coercion expressions.+ */+ if (jexpr->coercions)+ jsestate->item_coercions =+ ExecInitJsonItemCoercions(&cxt, jexpr->coercions, need_subtrans);++ done_off = state->steps_len;++ /* Redirect missing steps to the end step */+ if (onerror_step_off < 0)+ onerror_step_off = done_off;++ if (onempty_step_off < 0)+ onempty_step_off = done_off;++ if (coercion_step_off < 0)+ coercion_step_off = done_off;++ if (coercion_subtrans_step_off < 0)+ coercion_subtrans_step_off = coercion_step_off;++ if (io_coercion_step_off < 0)+ io_coercion_step_off = done_off;++ /*+ * Adjust jump target addresses in various post-eval steps now that+ * we have all the steps in place.+ */++ /* EEOP_JSONEXPR_PATH */+ as = &state->steps[execpath_step_off];+ as->d.jsonexpr.jump_done = done_off;+ as->d.jsonexpr.jump_onerror = onerror_step_off;+ as->d.jsonexpr.jump_onempty = onempty_step_off;+ as->d.jsonexpr.jump_coercion = coercion_step_off;+ as->d.jsonexpr.jump_coercion_subtrans = coercion_subtrans_step_off;+ as->d.jsonexpr.jump_coercion_via_io = io_coercion_step_off;++ /* EEOP_JSONEXPR_SKIP */+ as = &state->steps[skip_step_off];+ as->d.jsonexpr_skip.jump_coercion = coercion_step_off;++ /* Adjust jump to ON EMPTY coercion, if any */+ if (onempty_jump_coercion_step_off >= 0)+ {+ as = &state->steps[onempty_jump_coercion_step_off];+ as->d.jump.jumpdone = coercion_subtrans_step_off;+ }++ /* Adjust jump to ON ERROR coercion, if any */+ if (onerror_jump_coercion_step_off >= 0)+ {+ as = &state->steps[onerror_jump_coercion_step_off];+ as->d.jump.jumpdone = coercion_step_off;+ }++ /*+ * EEOP_JUMP steps added after default expressions and coercions+ * should jump to the expression end.+ */+ foreach(lc, cxt.adjust_jumps)+ {+ as = &state->steps[lfirst_int(lc)];+ as->d.jump.jumpdone = done_off;+ }++ /*+ * Adjust EEOP_SUBTRANS jumps to point to the expression end and to+ * ON ERROR handler.+ */+ foreach(lc, cxt.adjust_subtrans)+ {+ as = &state->steps[lfirst_int(lc)];+ as->d.subtrans.jump_done = done_off;+ as->d.subtrans.jump_error = onerror_step_off;+ }++ /*+ * Verify that no subtransaction nodes were generated if+ * ExecJsonExprNeedsSubTransaction() returns false.+ */+ Assert(cxt.adjust_subtrans == NIL ||+ ExecJsonExprNeedsSubTransaction(jexpr));+}diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.cindex 662d47f8cbb..15278b296cd 100644--- a/src/backend/executor/execExprInterp.c+++ b/src/backend/executor/execExprInterp.c@@ -494,7 +494,10 @@ ExecInterpExprStep(ExprState *state, int stepno,
&&CASE_EEOP_SUBTRANS,
&&CASE_EEOP_JSON_CONSTRUCTOR,
&&CASE_EEOP_IS_JSON,
- &&CASE_EEOP_JSONEXPR,+ &&CASE_EEOP_JSONEXPR_SKIP,+ &&CASE_EEOP_JSONEXPR_PATH,+ &&CASE_EEOP_JSONEXPR_IOCOERCE,+ &&CASE_EEOP_JSONEXPR_POPULATE,
&&CASE_EEOP_AGG_STRICT_DESERIALIZE,
&&CASE_EEOP_AGG_DESERIALIZE,
&&CASE_EEOP_AGG_STRICT_INPUT_CHECK_ARGS,
@@ -1855,10 +1858,32 @@ ExecInterpExprStep(ExprState *state, int stepno,
EEO_NEXT();
}
- EEO_CASE(EEOP_JSONEXPR)+ EEO_CASE(EEOP_JSONEXPR_PATH)
{
/* too complex for an inline implementation */
- ExecEvalJson(state, op, econtext);+ EEO_JUMP(ExecEvalJson(state, op));+ }++ EEO_CASE(EEOP_JSONEXPR_SKIP)+ {+ /* too complex for an inline implementation */+ if (ExecEvalJsonExprSkip(state, op))+ EEO_JUMP(op->d.jsonexpr_skip.jump_coercion);+ else+ EEO_NEXT();+ }++ EEO_CASE(EEOP_JSONEXPR_IOCOERCE)+ {+ /* too complex for an inline implementation */+ ExecEvalJsonExprIOCoerce(state, op);+ EEO_NEXT();+ }++ EEO_CASE(EEOP_JSONEXPR_POPULATE)+ {+ /* too complex for an inline implementation */+ ExecEvalJsonExprPopulate(state, op, econtext);
EEO_NEXT();
}
@@ -4799,550 +4824,300 @@ ExecEvalJsonConstructor(ExprState *state, ExprEvalStep *op,
}
/*
- * Evaluate a JSON error/empty behavior result.+ * Prepare SQL/JSON item coercion to the output type. Returned a datum+ * of the corresponding SQL type and pointers to the coercion and its+ * state.
*/
static Datum
-ExecEvalJsonBehavior(ExprContext *econtext, JsonBehavior *behavior,- ExprState *default_estate, bool *is_null)+ExecPrepareJsonItemCoercion(JsonbValue *item, ExprEvalStep *op,+ JsonItemCoercionsSteps *steps, int *jump)
{
- *is_null = false;-- switch (behavior->btype)- {- case JSON_BEHAVIOR_EMPTY_ARRAY:- return JsonbPGetDatum(JsonbMakeEmptyArray());-- case JSON_BEHAVIOR_EMPTY_OBJECT:- return JsonbPGetDatum(JsonbMakeEmptyObject());-- case JSON_BEHAVIOR_TRUE:- return BoolGetDatum(true);-- case JSON_BEHAVIOR_FALSE:- return BoolGetDatum(false);-- case JSON_BEHAVIOR_NULL:- case JSON_BEHAVIOR_UNKNOWN:- case JSON_BEHAVIOR_EMPTY:- *is_null = true;- return (Datum) 0;-- case JSON_BEHAVIOR_DEFAULT:- return ExecEvalExpr(default_estate, econtext, is_null);-- default:- elog(ERROR, "unrecognized SQL/JSON behavior %d", behavior->btype);- return (Datum) 0;- }-}--/*- * Evaluate a coercion of a JSON item to the target type.- */-static Datum-ExecEvalJsonExprCoercion(ExprEvalStep *op, ExprContext *econtext,- Datum res, bool *isNull, void *p, bool *error)-{- ExprState *estate = p;- JsonExprState *jsestate;-- if (estate) /* coerce using specified expression */- return ExecEvalExpr(estate, econtext, isNull);-- jsestate = op->d.jsonexpr.jsestate;-- if (jsestate->jsexpr->op != JSON_EXISTS_OP)- {- JsonCoercion *coercion = jsestate->jsexpr->result_coercion;- JsonExpr *jexpr = jsestate->jsexpr;- Jsonb *jb = *isNull ? NULL : DatumGetJsonbP(res);-- if ((coercion && coercion->via_io) ||- (jexpr->omit_quotes && !*isNull &&- JB_ROOT_IS_SCALAR(jb)))- {- /* strip quotes and call typinput function */- char *str = *isNull ? NULL : JsonbUnquote(jb);-- return InputFunctionCall(&jsestate->input.func, str,- jsestate->input.typioparam,- jexpr->returning->typmod);- }- else if (coercion && coercion->via_populate)- return json_populate_type(res, JSONBOID,- jexpr->returning->typid,- jexpr->returning->typmod,- &jsestate->cache,- econtext->ecxt_per_query_memory,- isNull);- }-- if (jsestate->result_expr)- {- jsestate->res_expr->value = res;- jsestate->res_expr->isnull = *isNull;-- res = ExecEvalExpr(jsestate->result_expr, econtext, isNull);- }-- return res;-}--/*- * Evaluate a JSON path variable caching computed value.- */-int-EvalJsonPathVar(void *cxt, char *varName, int varNameLen,- JsonbValue *val, JsonbValue *baseObject)-{- JsonPathVariableEvalContext *var = NULL;- List *vars = cxt;- ListCell *lc;- int id = 1;-- if (!varName)- return list_length(vars);-- foreach(lc, vars)- {- var = lfirst(lc);+ JsonbValue buf;- if (!strncmp(var->name, varName, varNameLen))- break;-- var = NULL;- id++;- }-- if (!var)- return -1;-- if (!var->evaluated)+ /*+ * Special case for json and jsonb types for which we don't have+ * casts like numeric::jsonb etc.+ */+ if (!steps)
{
- MemoryContext oldcxt = var->mcxt ?- MemoryContextSwitchTo(var->mcxt) : NULL;-- var->value = ExecEvalExpr(var->estate, var->econtext, &var->isnull);- var->evaluated = true;+ Jsonb *jb = JsonbValueToJsonb(item);- if (oldcxt)- MemoryContextSwitchTo(oldcxt);- }+ /*+ * Use result coercion from json[b] to the output+ * type when casting to json[b] types or their+ * domains.+ */- if (var->isnull)- {- val->type = jbvNull;- return 0;+ *jump = op->d.jsonexpr.jump_coercion_subtrans;+ return JsonbPGetDatum(jb);;
}
- JsonItemFromDatum(var->value, var->typid, var->typmod, val);-- *baseObject = *val;- return id;-}--/*- * Prepare SQL/JSON item coercion to the output type. Returned a datum of the- * corresponding SQL type and a pointer to the coercion state.- */-Datum-ExecPrepareJsonItemCoercion(JsonbValue *item,- JsonReturning *returning,- struct JsonCoercionsState *coercions,- struct JsonCoercionState **pcoercion)-{- struct JsonCoercionState *coercion;- Datum res;- JsonbValue buf;-+ /* Extract scalar from binary scalar presudo-array */
if (item->type == jbvBinary &&
JsonContainerIsScalar(item->val.binary.data))
{
- bool res PG_USED_FOR_ASSERTS_ONLY;+ bool is_scalar PG_USED_FOR_ASSERTS_ONLY;++ is_scalar = JsonbExtractScalar(item->val.binary.data, &buf);+ Assert(is_scalar);- res = JsonbExtractScalar(item->val.binary.data, &buf);
item = &buf;
- Assert(res);
}
/* get coercion state reference and datum of the corresponding SQL type */
switch (item->type)
{
case jbvNull:
- coercion = &coercions->null;- res = (Datum) 0;- break;+ *jump = steps->null;+ return (Datum) 0;
case jbvString:
- coercion = &coercions->string;- res = PointerGetDatum(cstring_to_text_with_len(item->val.string.val,- item->val.string.len));- break;+ *jump = steps->string;+ return *jump < 0 ? (Datum) 0 :+ PointerGetDatum(cstring_to_text_with_len(item->val.string.val,+ item->val.string.len));
case jbvNumeric:
- coercion = &coercions->numeric;- res = NumericGetDatum(item->val.numeric);- break;+ *jump = steps->numeric;+ return NumericGetDatum(item->val.numeric);
case jbvBool:
- coercion = &coercions->boolean;- res = BoolGetDatum(item->val.boolean);- break;+ *jump = steps->boolean;+ return BoolGetDatum(item->val.boolean);
case jbvDatetime:
- res = item->val.datetime.value;
switch (item->val.datetime.typid)
{
case DATEOID:
- coercion = &coercions->date;+ *jump = steps->date;
break;
case TIMEOID:
- coercion = &coercions->time;+ *jump = steps->time;
break;
case TIMETZOID:
- coercion = &coercions->timetz;+ *jump = steps->timetz;
break;
case TIMESTAMPOID:
- coercion = &coercions->timestamp;+ *jump = steps->timestamp;
break;
case TIMESTAMPTZOID:
- coercion = &coercions->timestamptz;+ *jump = steps->timestamptz;
break;
default:
elog(ERROR, "unexpected jsonb datetime type oid %u",
item->val.datetime.typid);
return (Datum) 0;
}
- break;++ return item->val.datetime.value;
case jbvArray:
case jbvObject:
case jbvBinary:
- coercion = &coercions->composite;- res = JsonbPGetDatum(JsonbValueToJsonb(item));- break;+ *jump = steps->composite;+ return *jump < 0 ? (Datum) 0 :+ JsonbPGetDatum(JsonbValueToJsonb(item));
default:
elog(ERROR, "unexpected jsonb value type %d", item->type);
return (Datum) 0;
}
-- *pcoercion = coercion;-- return res;-}--typedef Datum (*JsonFunc) (ExprEvalStep *op, ExprContext *econtext,- Datum item, bool *resnull, void *p, bool *error);--static Datum-ExecEvalJsonExprSubtrans(JsonFunc func, ExprEvalStep *op,- ExprContext *econtext,- Datum res, bool *resnull,- void *p, bool *error, bool subtrans)-{- MemoryContext oldcontext;- ResourceOwner oldowner;-- if (!subtrans)- /* No need to use subtransactions. */- return func(op, econtext, res, resnull, p, error);-- /*- * We should catch exceptions of category ERRCODE_DATA_EXCEPTION and- * execute the corresponding ON ERROR behavior then.- */- oldcontext = CurrentMemoryContext;- oldowner = CurrentResourceOwner;-- Assert(error);-- BeginInternalSubTransaction(NULL);- /* Want to execute expressions inside function's memory context */- MemoryContextSwitchTo(oldcontext);-- PG_TRY();- {- res = func(op, econtext, res, resnull, p, error);-- /* Commit the inner transaction, return to outer xact context */- ReleaseCurrentSubTransaction();- MemoryContextSwitchTo(oldcontext);- CurrentResourceOwner = oldowner;- }- PG_CATCH();- {- ErrorData *edata;- int ecategory;-- /* Save error info in oldcontext */- MemoryContextSwitchTo(oldcontext);- edata = CopyErrorData();- FlushErrorState();-- /* Abort the inner transaction */- RollbackAndReleaseCurrentSubTransaction();- MemoryContextSwitchTo(oldcontext);- CurrentResourceOwner = oldowner;-- ecategory = ERRCODE_TO_CATEGORY(edata->sqlerrcode);-- if (ecategory != ERRCODE_DATA_EXCEPTION && /* jsonpath and other data- * errors */- ecategory != ERRCODE_INTEGRITY_CONSTRAINT_VIOLATION) /* domain errors */- ReThrowError(edata);-- res = (Datum) 0;- *error = true;- }- PG_END_TRY();-- return res;
}
--typedef struct-{- JsonPath *path;- bool *error;- bool coercionInSubtrans;-} ExecEvalJsonExprContext;--static Datum-ExecEvalJsonExpr(ExprEvalStep *op, ExprContext *econtext,- Datum item, bool *resnull, void *pcxt,- bool *error)+/*+ * Evaluate a SQL/JSON function.+ */+int+ExecEvalJson(ExprState *state, ExprEvalStep *op)
{
- ExecEvalJsonExprContext *cxt = pcxt;- JsonPath *path = cxt->path;
JsonExprState *jsestate = op->d.jsonexpr.jsestate;
+ JsonExprPreEvalState *pre_eval = &jsestate->pre_eval;
JsonExpr *jexpr = jsestate->jsexpr;
- ExprState *estate = NULL;- bool empty = false;+ Datum item = pre_eval->formatted_expr.value;+ JsonPath *path = DatumGetJsonPathP(pre_eval->pathspec.value);+ bool catch_errors =+ jexpr->on_error->btype != JSON_BEHAVIOR_ERROR;
Datum res = (Datum) 0;
+ bool empty = false;+ bool error = false;+ bool *p_error = catch_errors ? &error : NULL;++ *op->resvalue = (Datum) 0;+ *op->resnull = true; /* until we get a result */
switch (jexpr->op)
{
case JSON_QUERY_OP:
- res = JsonPathQuery(item, path, jexpr->wrapper, &empty, error,- jsestate->args);- if (error && *error)- {- *resnull = true;- return (Datum) 0;- }- *resnull = !DatumGetPointer(res);+ res = JsonPathQuery(item, path, jexpr->wrapper, &empty,+ p_error, pre_eval->args);++ if (error)+ return op->d.jsonexpr.jump_onerror;++ *op->resnull = !DatumGetPointer(res);+ *op->resvalue = res;++ /* Force coercion via I/O if OMIT QUOTES is used */+ if (jexpr->omit_quotes && !empty && !*op->resnull &&+ JB_ROOT_IS_SCALAR(DatumGetJsonbP(res)))+ return op->d.jsonexpr.jump_coercion_via_io;+
break;
case JSON_VALUE_OP:
{
- struct JsonCoercionState *jcstate;- JsonbValue *jbv = JsonPathValue(item, path, &empty, error,- jsestate->args);+ int jump_item_coercion;+ JsonbValue *jbv = JsonPathValue(item, path, &empty,+ p_error, pre_eval->args);- if (error && *error)- return (Datum) 0;+ if (error)+ return op->d.jsonexpr.jump_onerror;
if (!jbv) /* NULL or empty */
break;
Assert(!empty);
+ Assert(jbv->type != jbvNull);- *resnull = false;+ /*+ * Prepare coercion from SQL/JSON item type to the+ * output SQL type.+ */+ res = ExecPrepareJsonItemCoercion(jbv, op,+ jsestate->item_coercions,+ &jump_item_coercion);- /* coerce scalar item to the output type */- if (!jsestate->coercions)- {- /*- * Use result coercion from json[b] to the output- * type when casting to json[b] types or their- * domains.- */- res = JsonbPGetDatum(JsonbValueToJsonb(jbv));- break;- }+ *op->resvalue = res;+ *op->resnull = false;- /* Use coercion from SQL/JSON item type to the output type */- res = ExecPrepareJsonItemCoercion(jbv,- jsestate->jsexpr->returning,- jsestate->coercions,- &jcstate);+ /* Jump to zero here means jump to the end. */+ if (jump_item_coercion == 0)+ return op->d.jsonexpr.jump_done;- if (jcstate->coercion &&- (jcstate->coercion->via_io ||- jcstate->coercion->via_populate))+ /*+ * Error out if no cast exists to coerce SQL/JSON item+ * to the the output type.+ */+ if (jump_item_coercion < 0)
{
- if (error)- {- *error = true;- return (Datum) 0;- }+ *op->resnull = true;++ if (catch_errors)+ return op->d.jsonexpr.jump_onerror;- /*- * Coercion via I/O means here that the cast to the target- * type simply does not exist.- */
ereport(ERROR,
(errcode(ERRCODE_SQL_JSON_ITEM_CANNOT_BE_CAST_TO_TARGET_TYPE),
errmsg("SQL/JSON item cannot be cast to target type")));
}
- else if (!jcstate->estate)- return res; /* no coercion */- /* coerce using specific expression */- estate = jcstate->estate;- jsestate->coercion_expr->value = res;- jsestate->coercion_expr->isnull = *resnull;- break;+ /* Coerce using a specific coercion expression */+ return jump_item_coercion;
}
case JSON_EXISTS_OP:
{
bool exists = JsonPathExists(item, path,
- jsestate->args,- error);+ pre_eval->args,+ p_error);- *resnull = error && *error;- res = BoolGetDatum(exists);+ *op->resnull = error;+ *op->resvalue = BoolGetDatum(exists);- if (!jsestate->result_expr)- return res;+ if (error)+ return op->d.jsonexpr.jump_onerror;- /* coerce using result expression */- estate = jsestate->result_expr;- jsestate->res_expr->value = res;- jsestate->res_expr->isnull = *resnull;
break;
}
case JSON_TABLE_OP:
- *resnull = false;- return item;+ *op->resvalue = item;+ *op->resnull = false;+ return op->d.jsonexpr.jump_done;
default:
elog(ERROR, "unrecognized SQL/JSON expression op %d", jexpr->op);
- return (Datum) 0;+ return op->d.jsonexpr.jump_done;
}
+ /* Execute ON EMPTY behavior if no SQL/JSON items were found */
if (empty)
{
Assert(jexpr->on_empty); /* it is not JSON_EXISTS */
if (jexpr->on_empty->btype == JSON_BEHAVIOR_ERROR)
{
- if (error)- {- *error = true;- return (Datum) 0;- }+ /* Throw an error or jump to ON ERROR handler */+ if (catch_errors)+ return op->d.jsonexpr.jump_onerror;
ereport(ERROR,
(errcode(ERRCODE_NO_SQL_JSON_ITEM),
errmsg("no SQL/JSON item")));
}
- if (jexpr->on_empty->btype == JSON_BEHAVIOR_DEFAULT)-- /*- * Execute DEFAULT expression as a coercion expression, because- * its result is already coerced to the target type.- */- estate = jsestate->default_on_empty;- else- /* Execute ON EMPTY behavior */- res = ExecEvalJsonBehavior(econtext, jexpr->on_empty,- jsestate->default_on_empty,- resnull);+ /* Evaluate ON EMPTY behavior */+ return op->d.jsonexpr.jump_onempty;
}
- return ExecEvalJsonExprSubtrans(ExecEvalJsonExprCoercion, op, econtext,- res, resnull, estate, error,- cxt->coercionInSubtrans);+ /* Evaluate coercion expression inside subtransaction */+ return op->d.jsonexpr.jump_coercion_subtrans;
}
+/* Skip calling ExecEvalJson() on a JsonExpr? */
bool
-ExecEvalJsonNeedsSubTransaction(JsonExpr *jsexpr,- bool consider_conditional_coercions)+ExecEvalJsonExprSkip(ExprState *state, ExprEvalStep *op)
{
- if (jsexpr->on_error->btype == JSON_BEHAVIOR_ERROR)- return false;-- if (jsexpr->op == JSON_EXISTS_OP && !jsexpr->result_coercion)- return false;+ JsonExprState *jsestate = op->d.jsonexpr_skip.jsestate;- if (consider_conditional_coercions)+ /*+ * Skip if either of the input expressions has turned out to be+ * NULL, though do execute domain checks for NULLs, which are+ * handled by the coercion step.+ */+ if (jsestate->pre_eval.formatted_expr.isnull ||+ jsestate->pre_eval.pathspec.isnull)+ {+ *op->resvalue = (Datum) 0;+ *op->resnull = true;
return true;
+ }+ /*+ * Go evaluate the PASSING args if any and subsequently JSON path+ * itself.+ */
return false;
}
-/* ----------------------------------------------------------------- * ExecEvalJson- * ----------------------------------------------------------------- */+/* Apply I/O coercion to a JSON item */
void
-ExecEvalJson(ExprState *state, ExprEvalStep *op, ExprContext *econtext)+ExecEvalJsonExprIOCoerce(ExprState *state, ExprEvalStep *op)
{
- ExecEvalJsonExprContext cxt;- JsonExprState *jsestate = op->d.jsonexpr.jsestate;- JsonExpr *jexpr = jsestate->jsexpr;- Datum item;- Datum res = (Datum) 0;- JsonPath *path;- ListCell *lc;- bool error = false;- bool needSubtrans;- bool throwErrors = jexpr->on_error->btype == JSON_BEHAVIOR_ERROR;-- *op->resnull = true; /* until we get a result */- *op->resvalue = (Datum) 0;-- if (jsestate->formatted_expr->isnull || jsestate->pathspec->isnull)- {- /* execute domain checks for NULLs */- (void) ExecEvalJsonExprCoercion(op, econtext, res, op->resnull,- NULL, NULL);+ /* Strip quotes and call typinput function */+ char *str = NULL;- Assert(*op->resnull);- return;- }-- item = jsestate->formatted_expr->value;- path = DatumGetJsonPathP(jsestate->pathspec->value);-- /* reset JSON path variable contexts */- foreach(lc, jsestate->args)+ if (!*op->resnull)
{
- JsonPathVariableEvalContext *var = lfirst(lc);+ Jsonb *jb = DatumGetJsonbP(*op->resvalue);- var->econtext = econtext;- var->evaluated = false;+ str = JsonbUnquote(jb);
}
- needSubtrans = ExecEvalJsonNeedsSubTransaction(jexpr, false);-- cxt.path = path;- cxt.error = throwErrors ? NULL : &error;- cxt.coercionInSubtrans = !needSubtrans && !throwErrors;- Assert(!needSubtrans || cxt.error);-- res = ExecEvalJsonExprSubtrans(ExecEvalJsonExpr, op, econtext, item,- op->resnull, &cxt, cxt.error,- needSubtrans);-- if (error)- {- /* Execute ON ERROR behavior */- res = ExecEvalJsonBehavior(econtext, jexpr->on_error,- jsestate->default_on_error,- op->resnull);-- /* result is already coerced in DEFAULT behavior case */- if (jexpr->on_error->btype != JSON_BEHAVIOR_DEFAULT)- res = ExecEvalJsonExprCoercion(op, econtext, res,- op->resnull,- NULL, NULL);- }+ *op->resvalue = InputFunctionCall(op->d.jsonexpr_iocoerce.finfo, str,+ op->d.jsonexpr_iocoerce.typioparam,+ op->d.jsonexpr_iocoerce.typmod);+}- *op->resvalue = res;+/* Apply json_populate_type() to a JSON item */+void+ExecEvalJsonExprPopulate(ExprState *state, ExprEvalStep *op,+ ExprContext *econtext)+{+ *op->resvalue =+ json_populate_type(*op->resvalue, JSONBOID,+ op->d.jsonexpr_populate.jsexpr->returning->typid,+ op->d.jsonexpr_populate.jsexpr->returning->typmod,+ &op->d.jsonexpr_populate.cache,+ econtext->ecxt_per_query_memory,+ op->resnull);
}
diff --git a/src/backend/jit/llvm/llvmjit_expr.c b/src/backend/jit/llvm/llvmjit_expr.cindex 614ed58f1cb..5245fe765c5 100644--- a/src/backend/jit/llvm/llvmjit_expr.c+++ b/src/backend/jit/llvm/llvmjit_expr.c@@ -2465,8 +2465,141 @@ llvm_compile_expr_step(ExprState *state, int start_opno)
LLVMBuildBr(b, opblocks[opno + 1]);
break;
- case EEOP_JSONEXPR:- build_EvalXFunc(b, mod, "ExecEvalJson",+ case EEOP_JSONEXPR_PATH:+ {+ LLVMValueRef v_ret;+ LLVMBasicBlockRef *b_coercions;+ JsonItemCoercionsSteps *jcstate =+ op->d.jsonexpr.jsestate->item_coercions;+ int n_jumps = 6;+ int n_coercions = !jcstate ? 0 :+ (int) (&jcstate->composite - &jcstate->null) + 1;+ int *jumps;++ /*+ * ExecEvalJson() returns number of the step we+ * need to jump.+ */+ v_ret = build_EvalXFunc(b, mod, "ExecEvalJson",+ v_state, op);++ /*+ * Will create a block for each possible jump to+ * check whether to evaluate the coercion's+ * expression if there's one or to skip to the end+ * if not.+ */+ b_coercions = palloc((n_jumps + n_coercions) * sizeof(*b_coercions));+ jumps = palloc((n_jumps + n_coercions) * sizeof(*jumps));++ jumps[0] = op->d.jsonexpr.jump_done;+ jumps[1] = op->d.jsonexpr.jump_coercion;+ jumps[2] = op->d.jsonexpr.jump_coercion_subtrans;+ jumps[3] = op->d.jsonexpr.jump_coercion_via_io;+ jumps[4] = op->d.jsonexpr.jump_onempty;+ jumps[5] = op->d.jsonexpr.jump_onerror;++ b_coercions[0] =+ l_bb_before_v(opblocks[opno + 1],+ "op.%d.jsonexpr_jump_coercion", opno);+ b_coercions[1] =+ l_bb_before_v(opblocks[opno + 1],+ "op.%d.jsonexpr_jump_coercion_subtrans", opno);+ b_coercions[2] =+ l_bb_before_v(opblocks[opno + 1],+ "op.%d.jsonexpr_jump_coercion_io", opno);+ b_coercions[3] =+ l_bb_before_v(opblocks[opno + 1],+ "op.%d.jsonexpr_jump_onempty", opno);+ b_coercions[4] =+ l_bb_before_v(opblocks[opno + 1],+ "op.%d.jsonexpr_jump_onerror", opno);++ for (int i = 0; i < n_coercions; i++)+ jumps[i + n_jumps] = (&jcstate->null)[i];++ for (int i = 0; i < n_coercions + 1; i++)+ b_coercions[i + n_jumps - 1] =+ l_bb_before_v(opblocks[opno + 1],+ "op.%d.jsonexpr_jump_item.%d",+ opno, i);++ /*+ * Add conditional branches for individual+ * coercion's expressions.+ *+ * Jump to coercion step if the returned address is+ * the same as jumps[i], else jump to the next+ * block, that checks next jump address.+ */+ for (int i = 0; i < n_jumps + n_coercions; i++)+ {+ if (i > 0)+ LLVMPositionBuilderAtEnd(b, b_coercions[i - 1]);++ LLVMBuildCondBr(b,+ LLVMBuildICmp(b,+ LLVMIntEQ,+ v_ret,+ l_int32_const(jumps[i]),+ ""),+ opblocks[jumps[i] >= 0 ? jumps[i] : jumps[0]],+ b_coercions[i]);+ }++ /*+ * A placeholder block that the last coercion's+ * block might jump to, which unconditionally jumps+ * to end of coercions.+ */+ LLVMPositionBuilderAtEnd(b, b_coercions[n_jumps + n_coercions - 1]);+ LLVMBuildBr(b, opblocks[op->d.jsonexpr.jump_done]);++ pfree(b_coercions);+ pfree(jumps);+ break;+ }+ break;++ case EEOP_JSONEXPR_SKIP:+ {+ LLVMValueRef v_ret;++ /*+ * Call ExecEvalJsonExprSkip() to decide if JSON+ * path evaluation can be skipped. This returns+ * boolean "skip" flag.+ */+ v_ret = build_EvalXFunc(b, mod, "ExecEvalJsonExprSkip",+ v_state, op);+ v_ret = LLVMBuildZExt(b, v_ret, TypeStorageBool, "");++ /*+ * Jump to coercion step if true was returned,+ * which signifies skipping of JSON path evaluation,+ * else to the next step which must point to the+ * steps to evaluate PASSING args, if any, or to+ * the JSON path evaluation.+ */+ LLVMBuildCondBr(b,+ LLVMBuildICmp(b,+ LLVMIntEQ,+ v_ret,+ l_sbool_const(0),+ ""),+ opblocks[opno + 1],+ opblocks[op->d.jsonexpr_skip.jump_coercion]);+ break;+ }++ case EEOP_JSONEXPR_IOCOERCE:+ build_EvalXFunc(b, mod, "ExecEvalJsonExprIOCoerce",+ v_state, op);+ LLVMBuildBr(b, opblocks[opno + 1]);+ break;++ case EEOP_JSONEXPR_POPULATE:+ build_EvalXFunc(b, mod, "ExecEvalJsonExprPopulate",
v_state, op, v_econtext);
LLVMBuildBr(b, opblocks[opno + 1]);
break;
diff --git a/src/backend/jit/llvm/llvmjit_types.c b/src/backend/jit/llvm/llvmjit_types.cindex a9ddba6e4cd..d47d065dc59 100644--- a/src/backend/jit/llvm/llvmjit_types.c+++ b/src/backend/jit/llvm/llvmjit_types.c@@ -136,6 +136,9 @@ void *referenced_functions[] =
ExecEvalSubtrans,
ExecEvalJsonConstructor,
ExecEvalJsonIsPredicate,
+ ExecEvalJsonExprSkip,+ ExecEvalJsonExprIOCoerce,+ ExecEvalJsonExprPopulate,
ExecEvalJson,
MakeExpandedObjectReadOnlyInternal,
slot_getmissingattrs,
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.cindex a1d868bb87b..e9eee8c1a0c 100644--- a/src/backend/optimizer/util/clauses.c+++ b/src/backend/optimizer/util/clauses.c@@ -901,7 +901,7 @@ max_parallel_hazard_walker(Node *node, max_parallel_hazard_context *context)
{
JsonExpr *jsexpr = (JsonExpr *) node;
- if (ExecEvalJsonNeedsSubTransaction(jsexpr, true))+ if (ExecJsonExprNeedsSubTransaction(jsexpr))
{
context->max_hazard = PROPARALLEL_UNSAFE;
return true;
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.cindex 31fb5555659..41f4acbc262 100644--- a/src/backend/parser/parse_expr.c+++ b/src/backend/parser/parse_expr.c@@ -4329,6 +4329,7 @@ initJsonItemCoercion(ParseState *pstate, Oid typid,
const JsonReturning *returning)
{
Node *expr;
+ JsonCoercion *coercion;
if (typid == UNKNOWNOID)
{
@@ -4345,7 +4346,16 @@ initJsonItemCoercion(ParseState *pstate, Oid typid,
expr = (Node *) placeholder;
}
- return coerceJsonExpr(pstate, expr, returning);+ coercion = coerceJsonExpr(pstate, expr, returning);++ /*+ * Coercion via I/O means here that the cast to the target type+ * simply does not exist.+ */+ if (coercion->via_io || coercion->via_populate)+ return NULL;++ return coercion;
}
static JsonItemCoercions *
@@ -4373,6 +4383,11 @@ initJsonItemCoercions(ParseState *pstate, const JsonReturning *returning,
{NULL, InvalidOid}
};
+ /* When returning JSON types, no need to initialize coercions */+ /* XXX domain types on json/jsonb */+ if (returning->typid == JSONBOID || returning->typid == JSONOID)+ return NULL;+
for (p = coercionTypids; p->coercion; p++)
*p->coercion = initJsonItemCoercion(pstate, p->typid, returning);
@@ -4453,6 +4468,7 @@ transformJsonFuncExpr(ParseState *pstate, JsonFuncExpr *func)
}
else if (jsexpr->returning->typid != BOOLOID)
{
+ /* We need to handle RETURNING int etc. */
CaseTestExpr *placeholder = makeNode(CaseTestExpr);
int location = exprLocation((Node *) jsexpr);
diff --git a/src/backend/utils/adt/jsonpath_exec.c b/src/backend/utils/adt/jsonpath_exec.cindex 10c7e64aab3..c3fc77cf515 100644--- a/src/backend/utils/adt/jsonpath_exec.c+++ b/src/backend/utils/adt/jsonpath_exec.c@@ -286,6 +286,8 @@ static JsonPathExecResult appendBoolResult(JsonPathExecContext *cxt,
JsonPathItem *jsp, JsonValueList *found, JsonPathBool res);
static void getJsonPathItem(JsonPathExecContext *cxt, JsonPathItem *item,
JsonbValue *value);
+static int EvalJsonPathVar(void *vars, char *varName, int varNameLen,+ JsonbValue *val, JsonbValue *baseObject);
static void getJsonPathVariable(JsonPathExecContext *cxt,
JsonPathItem *variable, JsonbValue *value);
static int getJsonPathVariableFromJsonb(void *varsJsonb, char *varName,
@@ -2172,6 +2174,72 @@ getJsonPathItem(JsonPathExecContext *cxt, JsonPathItem *item,
}
}
+/*+ * Evaluate a JSON path variable caching computed value.+ */+int+EvalJsonPathVar(void *cxt, char *varName, int varNameLen,+ JsonbValue *val, JsonbValue *baseObject)+{+ JsonPathVariableEvalContext *var = NULL;+ List *vars = cxt;+ ListCell *lc;+ int id = 1;++ if (!varName)+ return list_length(vars);++ foreach(lc, vars)+ {+ var = lfirst(lc);++ if (!strncmp(var->name, varName, varNameLen))+ break;++ var = NULL;+ id++;+ }++ if (!var)+ return -1;++ /*+ * When belonging to a JsonExpr, path variables are computed with the+ * JsonExpr's ExprState (var->estate is NULL), so don't need to be computed+ * here. In some other cases, such as when the path variables belonging+ * to a JsonTable instead, those variables must be evaluated on their own,+ * without the enclosing JsonExpr itself needing to be evaluated, so must+ * be handled here.+ */+ if (var->estate && !var->evaluated)+ {+ MemoryContext oldcxt = var->mcxt ?+ MemoryContextSwitchTo(var->mcxt) : NULL;++ Assert(var->econtext != NULL);+ var->value = ExecEvalExpr(var->estate, var->econtext, &var->isnull);+ var->evaluated = true;++ if (oldcxt)+ MemoryContextSwitchTo(oldcxt);+ }+ else+ {+ Assert(var->evaluated);+ }++ if (var->isnull)+ {+ val->type = jbvNull;+ return 0;+ }++ JsonItemFromDatum(var->value, var->typid, var->typmod, val);++ *baseObject = *val;+ return id;+}+
/*
* Get the value of variable passed to jsonpath executor
*/
diff --git a/src/include/executor/execExpr.h b/src/include/executor/execExpr.hindex 14064dd9521..432876fb01f 100644--- a/src/include/executor/execExpr.h+++ b/src/include/executor/execExpr.h@@ -245,7 +245,10 @@ typedef enum ExprEvalOp
EEOP_SUBTRANS,
EEOP_JSON_CONSTRUCTOR,
EEOP_IS_JSON,
- EEOP_JSONEXPR,+ EEOP_JSONEXPR_SKIP,+ EEOP_JSONEXPR_PATH,+ EEOP_JSONEXPR_IOCOERCE,+ EEOP_JSONEXPR_POPULATE,
/* aggregation related nodes */
EEOP_AGG_STRICT_DESERIALIZE,
@@ -702,12 +705,48 @@ typedef struct ExprEvalStep
JsonIsPredicate *pred; /* original expression node */
} is_json;
- /* for EEOP_JSONEXPR */+ /* for EEOP_JSONEXPR_PATH */
struct
{
struct JsonExprState *jsestate;
++ /* See ExecEvalJsonExpr() */+ int jump_done;+ int jump_onerror;+ int jump_onempty;+ int jump_coercion;+ int jump_coercion_subtrans;+ int jump_coercion_via_io;
} jsonexpr;
+ /* for EEOP_JSONEXPR_SKIP */+ struct+ {+ /* Same as jsonexpr.jsestate */+ struct JsonExprState *jsestate;++ /* See ExecEvalJsonExprSkip() */+ int jump_coercion;+ } jsonexpr_skip;++ /* for EEOP_JSONEXPR_IOCOERCE */+ struct+ {+ /* I/O info for output type */+ FmgrInfo *finfo; /* typinput function for output type */+ Oid typioparam;+ int32 typmod;+ } jsonexpr_iocoerce;++ /* for EEOP_JSONEXPR_POPULATE */+ struct+ {+ struct JsonExpr *jsexpr;++ /* Cache for json_populate_type() */+ void *cache;+ } jsonexpr_populate;+
} d;
} ExprEvalStep;
@@ -767,60 +806,67 @@ typedef struct JsonConstructorExprState
int nargs;
} JsonConstructorExprState;
+/*+ * Information computed before evaluating a JsonExpr expression.+ */+typedef struct JsonExprPreEvalState+{+ /* value/isnull for JsonExpr.formatted_expr */+ NullableDatum formatted_expr;++ /* value/isnull for JsonExpr.pathspec */+ NullableDatum pathspec;++ /* JsonPathVariableEvalContext entries for JsonExpr.passing_values */+ List *args;+} JsonExprPreEvalState;++/*+ * State for evaluating the coercion for a given JSON item using one of+ * the following coercions.+ *+ * Note that while ExecInitExprRec() for JsonItemCoercions will+ * initialize ExprEvalSteps for all of the members that need it, only+ * one will get run during a given evaluation of the enclosing JsonExpr+ * depending on the type of the result JSON item.+ */+typedef struct JsonItemCoercionsSteps+{+ /* Number of ExprEvalStep to compute this coercion's expression */+ int null;+ int string;+ int numeric;+ int boolean;+ int date;+ int time;+ int timetz;+ int timestamp;+ int timestamptz;+ int composite;+} JsonItemCoercionsSteps;+
/* EEOP_JSONEXPR state, too big to inline */
typedef struct JsonExprState
{
JsonExpr *jsexpr; /* original expression node */
- struct- {- FmgrInfo func; /* typinput function for output type */- Oid typioparam;- } input; /* I/O info for output type */-- NullableDatum- *formatted_expr, /* formatted context item value */- *res_expr, /* result item */- *coercion_expr, /* input for JSON item coercion */- *pathspec; /* path specification value */+ JsonExprPreEvalState pre_eval;- ExprState *result_expr; /* coerced to output type */- ExprState *default_on_empty; /* ON EMPTY DEFAULT expression */- ExprState *default_on_error; /* ON ERROR DEFAULT expression */- List *args; /* passing arguments */-- void *cache; /* cache for json_populate_type() */+ /*+ * Either of the following two is used by ExecEvalJsonCoercion()+ * to apply coercion to the final result if needed.+ */
/*
- * This is used only in JSON_VALUE.- *- * States for coercion from SQL/JSON item types directly to the- * output type.- *- * "coercions == NULL" means output type is json[b] or its domain- * and we use "result_expr" for coercion.+ * In JSON_VALUE() "item_coercions == NULL" means that output type+ * is json[b] or its domain and we use "result_coercion".
*/
- struct JsonCoercionsState- {- struct JsonCoercionState- {- JsonCoercion *coercion; /* coercion expression */- ExprState *estate; /* coercion expression state */- } null,- string,- numeric ,- boolean,- date,- time,- timetz,- timestamp,- timestamptz,- composite;- } *coercions;+ JsonItemCoercionsSteps *item_coercions;
} JsonExprState;
/* functions in execExpr.c */
extern void ExprEvalPushStep(ExprState *es, const ExprEvalStep *s);
+extern bool ExecJsonExprNeedsSubTransaction(JsonExpr *jsexpr);
/* functions in execExprInterp.c */
extern void ExecReadyInterpretedExpr(ExprState *state);
@@ -881,19 +927,11 @@ extern bool ExecEvalSubtrans(ExprState *state, ExprEvalStep *op,
ExecEvalSubroutine func);
extern void ExecEvalJsonConstructor(ExprState *state, ExprEvalStep *op,
ExprContext *econtext);
-extern void ExecEvalJson(ExprState *state, ExprEvalStep *op,- ExprContext *econtext);-extern Datum ExecPrepareJsonItemCoercion(struct JsonbValue *item,- JsonReturning *returning,- struct JsonCoercionsState *coercions,- struct JsonCoercionState **pjcstate);-extern bool ExecEvalJsonNeedsSubTransaction(JsonExpr *jsexpr,- bool consider_conditional_coercions);-extern Datum ExecEvalExprPassingCaseValue(ExprState *estate,- ExprContext *econtext, bool *isnull,- Datum caseval_datum,- bool caseval_isnull);-+extern bool ExecEvalJsonExprSkip(ExprState *state, ExprEvalStep *op);+extern void ExecEvalJsonExprIOCoerce(ExprState *state, ExprEvalStep *op);+extern void ExecEvalJsonExprPopulate(ExprState *state, ExprEvalStep *op,+ ExprContext *econtext);+extern int ExecEvalJson(ExprState *state, ExprEvalStep *op);
extern void ExecAggInitGroup(AggState *aggstate, AggStatePerTrans pertrans, AggStatePerGroup pergroup,
ExprContext *aggcontext);
extern Datum ExecAggTransReparent(AggState *aggstate, AggStatePerTrans pertrans,
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.hindex 3aa96bb6855..6afb4228730 100644--- a/src/include/nodes/primnodes.h+++ b/src/include/nodes/primnodes.h@@ -1566,8 +1566,11 @@ typedef struct JsonCoercion
/*
* JsonItemCoercions -
- * expressions for coercion from SQL/JSON item types directly to the- * output SQL type+ * expressions for coercion from SQL/JSON item types directly to+ * the output SQL type.+ *+ * "JsonCoercion == NULL" means no cast is available.+ * "JsonCoercion.expr == NULL" means no coercion is needed.
*/
typedef struct JsonItemCoercions
{
@@ -1581,7 +1584,7 @@ typedef struct JsonItemCoercions
JsonCoercion *timetz;
JsonCoercion *timestamp;
JsonCoercion *timestamptz;
- JsonCoercion *composite; /* arrays and objects */+ JsonCoercion *composite;
} JsonItemCoercions;
/*
diff --git a/src/include/utils/jsonb.h b/src/include/utils/jsonb.hindex bae466b5234..6bdd9f51219 100644--- a/src/include/utils/jsonb.h+++ b/src/include/utils/jsonb.h@@ -69,8 +69,10 @@ typedef enum
/* Convenience macros */
#define DatumGetJsonbP(d) ((Jsonb *) PG_DETOAST_DATUM(d))
+#define DatumGetJsonbValueP(d) ((JsonbValue *) DatumGetPointer(d))
#define DatumGetJsonbPCopy(d) ((Jsonb *) PG_DETOAST_DATUM_COPY(d))
#define JsonbPGetDatum(p) PointerGetDatum(p)
+#define JsonbValuePGetDatum(p) PointerGetDatum(p)
#define PG_GETARG_JSONB_P(x) DatumGetJsonbP(PG_GETARG_DATUM(x))
#define PG_GETARG_JSONB_P_COPY(x) DatumGetJsonbPCopy(PG_GETARG_DATUM(x))
#define PG_RETURN_JSONB_P(x) PG_RETURN_POINTER(x)
diff --git a/src/include/utils/jsonpath.h b/src/include/utils/jsonpath.hindex 8e79b8dc9f0..fa1d3eae6cf 100644--- a/src/include/utils/jsonpath.h+++ b/src/include/utils/jsonpath.h@@ -280,9 +280,6 @@ extern Datum JsonPathQuery(Datum jb, JsonPath *jp, JsonWrapper wrapper,
extern JsonbValue *JsonPathValue(Datum jb, JsonPath *jp, bool *empty,
bool *error, List *vars);
-extern int EvalJsonPathVar(void *vars, char *varName, int varNameLen,- JsonbValue *val, JsonbValue *baseObject);-
extern PGDLLIMPORT const TableFuncRoutine JsonbTableRoutine;
#endif
diff --git a/src/test/regress/expected/jsonb_sqljson.out b/src/test/regress/expected/jsonb_sqljson.outindex ee5e597909c..c84fb03df37 100644--- a/src/test/regress/expected/jsonb_sqljson.out+++ b/src/test/regress/expected/jsonb_sqljson.out@@ -693,6 +693,17 @@ SELECT JSON_VALUE(jsonb 'null', '$ts' PASSING timestamptz '2018-02-21 12:34:56 +
"2018-02-21T02:34:56+00:00"
(1 row)
+-- test errors in DEFAULT ON EMPTY expressions+SELECT JSON_VALUE('1'::jsonb, '$.x' RETURNING int+ DEFAULT 1 / x ON EMPTY+ DEFAULT 2 ON ERROR)+FROM (VALUES (1::int), (0)) x(x);+ json_value +------------+ 1+ 2+(2 rows)+
-- JSON_QUERY
SELECT
JSON_QUERY(js, '$'),
@@ -794,6 +805,12 @@ SELECT JSON_QUERY(jsonb '"aaa"', '$' RETURNING bytea FORMAT JSON OMIT QUOTES ERR
\x616161
(1 row)
+SELECT JSON_QUERY(jsonb '"aaa"', '$' RETURNING json OMIT QUOTES EMPTY ARRAY ON ERROR);+ json_query +------------+ []+(1 row)+
-- QUOTES behavior should not be specified when WITH WRAPPER used:
-- Should fail
SELECT JSON_QUERY(jsonb '[1]', '$' WITH WRAPPER OMIT QUOTES);
diff --git a/src/test/regress/sql/jsonb_sqljson.sql b/src/test/regress/sql/jsonb_sqljson.sqlindex d70104b70ef..eed163e7089 100644--- a/src/test/regress/sql/jsonb_sqljson.sql+++ b/src/test/regress/sql/jsonb_sqljson.sql@@ -194,6 +194,12 @@ SELECT JSON_VALUE(jsonb 'null', '$ts' PASSING timestamptz '2018-02-21 12:34:56 +
SELECT JSON_VALUE(jsonb 'null', '$ts' PASSING timestamptz '2018-02-21 12:34:56 +10' AS ts RETURNING json);
SELECT JSON_VALUE(jsonb 'null', '$ts' PASSING timestamptz '2018-02-21 12:34:56 +10' AS ts RETURNING jsonb);
+-- test errors in DEFAULT ON EMPTY expressions+SELECT JSON_VALUE('1'::jsonb, '$.x' RETURNING int+ DEFAULT 1 / x ON EMPTY+ DEFAULT 2 ON ERROR)+FROM (VALUES (1::int), (0)) x(x);+
-- JSON_QUERY
SELECT
@@ -239,6 +245,7 @@ SELECT JSON_QUERY(jsonb '"aaa"', '$' RETURNING text OMIT QUOTES ON SCALAR STRING
SELECT JSON_QUERY(jsonb '"aaa"', '$' OMIT QUOTES ERROR ON ERROR);
SELECT JSON_QUERY(jsonb '"aaa"', '$' RETURNING json OMIT QUOTES ERROR ON ERROR);
SELECT JSON_QUERY(jsonb '"aaa"', '$' RETURNING bytea FORMAT JSON OMIT QUOTES ERROR ON ERROR);
+SELECT JSON_QUERY(jsonb '"aaa"', '$' RETURNING json OMIT QUOTES EMPTY ARRAY ON ERROR);
-- QUOTES behavior should not be specified when WITH WRAPPER used:
-- Should fail
--
2.25.1
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: pgsql-hackers@postgresql.org
Cc: n.gluhov@postgrespro.ru, andres@anarazel.de, jkatz@postgresql.org, pgsql-hackers@lists.postgresql.org, andrew@dunslane.net, tgl@sss.pgh.pa.us, michael@paquier.xyz, john.naylor@enterprisedb.com, amitlangote09@gmail.com
Subject: Re: SQL/JSON features for v15
In-Reply-To: <225d341b-f310-eed0-ceff-b8ec61669d9f@postgrespro.ru>
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
This inbox is served by DDX for PostgreSQL; see mirroring instructions
for how to clone and mirror all data and code used for this inbox