SQL/JSON features for v15
Hi,
(Personal hat, not RMT hat unless otherwise noted).
This thread[1]/messages/by-id/20220616233130.rparivafipt6doj3@alap3.anarazel.de raised some concerns around the implementation of the
SQL/JSON features that are slated for v15, which includes an outstanding
open item[2]https://wiki.postgresql.org/wiki/PostgreSQL_15_Open_Items. Given the current state of the discussion, when the RMT
met on Aug 8, they several options, readable here[3]/messages/by-id/787cef45-15de-8f1d-ed58-a1c1435bfc0e@postgresql.org. Given we are now
into the later part of the release cycle, we need to make some decisions
on how to proceed with this feature given the concerns raised.
Per additional discussion on the thread, the group wanted to provide
more visibility into the discussion to get opinions on how to proceed
for the v15 release.
Without rehashing the thread, the options presented were:
1. Fixing the concerns addressed in the thread around the v15 SQL/JSON
features implementation, noting that this would likely entail at least
one more beta release and would push the GA date past our normal timeframe.
2. Try to commit a subset of the features that caused less debate. This
was ruled out.
3. Revert the v15 SQL/JSON features work.
<RMT hat>
Based on the current release timing and the open issues presented on the
thread, and the RMT had recommended reverting, but preferred to drive
consensus on next steps.
</RMT hat>
From a release advocacy standpoint, I need about 6 weeks lead time to
put together the GA launch. We're at the point where I typically deliver
a draft release announcement. From this, given this involves a high
visibility feature, I would want some clarity on what option we would
like to pursue. Once the announcement translation process has begun (and
this is when we have consensus on the release announcement), it becomes
more challenging to change things out.
From a personal standpoint (restating from[3]/messages/by-id/787cef45-15de-8f1d-ed58-a1c1435bfc0e@postgresql.org), I would like to see
what we could do to include support for this batch of the SQL/JSON
features in v15. What is included looks like it closes most of the gap
on what we've been missing syntactically since the standard was adopted,
and the JSON_TABLE work is very convenient for converting JSON data into
a relational format. I believe having this feature set is important for
maintaining standards compliance, interoperability, tooling support, and
general usability. Plus, JSON still seems to be pretty popular.
We're looking for additional input on what makes sense as a best course
of action, given what is presented in[3]/messages/by-id/787cef45-15de-8f1d-ed58-a1c1435bfc0e@postgresql.org.
Thanks,
Jonathan
[1]: /messages/by-id/20220616233130.rparivafipt6doj3@alap3.anarazel.de
/messages/by-id/20220616233130.rparivafipt6doj3@alap3.anarazel.de
[2]: https://wiki.postgresql.org/wiki/PostgreSQL_15_Open_Items
[3]: /messages/by-id/787cef45-15de-8f1d-ed58-a1c1435bfc0e@postgresql.org
/messages/by-id/787cef45-15de-8f1d-ed58-a1c1435bfc0e@postgresql.org
On 8/9/22 4:58 PM, Jonathan S. Katz wrote:
We're looking for additional input on what makes sense as a best course
of action, given what is presented in[3].
Missed adding Amit on the CC.
Jonathan
On 2022-08-09 Tu 16:58, Jonathan S. Katz wrote:
Hi,
(Personal hat, not RMT hat unless otherwise noted).
This thread[1] raised some concerns around the implementation of the
SQL/JSON features that are slated for v15, which includes an
outstanding open item[2]. Given the current state of the discussion,
when the RMT met on Aug 8, they several options, readable here[3].
Given we are now into the later part of the release cycle, we need to
make some decisions on how to proceed with this feature given the
concerns raised.Per additional discussion on the thread, the group wanted to provide
more visibility into the discussion to get opinions on how to proceed
for the v15 release.Without rehashing the thread, the options presented were:
1. Fixing the concerns addressed in the thread around the v15 SQL/JSON
features implementation, noting that this would likely entail at least
one more beta release and would push the GA date past our normal
timeframe.2. Try to commit a subset of the features that caused less debate.
This was ruled out.3. Revert the v15 SQL/JSON features work.
<RMT hat>
Based on the current release timing and the open issues presented on
the thread, and the RMT had recommended reverting, but preferred to
drive consensus on next steps.
</RMT hat>From a release advocacy standpoint, I need about 6 weeks lead time to
put together the GA launch. We're at the point where I typically
deliver a draft release announcement. From this, given this involves a
high visibility feature, I would want some clarity on what option we
would like to pursue. Once the announcement translation process has
begun (and this is when we have consensus on the release
announcement), it becomes more challenging to change things out.From a personal standpoint (restating from[3]), I would like to see
what we could do to include support for this batch of the SQL/JSON
features in v15. What is included looks like it closes most of the gap
on what we've been missing syntactically since the standard was
adopted, and the JSON_TABLE work is very convenient for converting
JSON data into a relational format. I believe having this feature set
is important for maintaining standards compliance, interoperability,
tooling support, and general usability. Plus, JSON still seems to be
pretty popular.We're looking for additional input on what makes sense as a best
course of action, given what is presented in[3].Thanks,
Jonathan
[1]
/messages/by-id/20220616233130.rparivafipt6doj3@alap3.anarazel.de
[2] https://wiki.postgresql.org/wiki/PostgreSQL_15_Open_Items
[3]
/messages/by-id/787cef45-15de-8f1d-ed58-a1c1435bfc0e@postgresql.org
To preserve options I will start preparing reversion patches. Given
there are I think more than 20 commits all told that could be fun, and
will probably take me a little while. The sad part is that to the best
of my knowledge this code is producing correct results, and not
disturbing the stability or performance of anything else. There was a
performance issue but it's been dealt with AIUI.
cheers
andrew
--
Andrew Dunstan
EDB: https://www.enterprisedb.com
On 8/10/22 11:50 AM, Andrew Dunstan wrote:
On 2022-08-09 Tu 16:58, Jonathan S. Katz wrote:
Hi,
(Personal hat, not RMT hat unless otherwise noted).
This thread[1] raised some concerns around the implementation of the
SQL/JSON features that are slated for v15, which includes an
outstanding open item[2]. Given the current state of the discussion,
when the RMT met on Aug 8, they several options, readable here[3].
Given we are now into the later part of the release cycle, we need to
make some decisions on how to proceed with this feature given the
concerns raised.Per additional discussion on the thread, the group wanted to provide
more visibility into the discussion to get opinions on how to proceed
for the v15 release.Without rehashing the thread, the options presented were:
1. Fixing the concerns addressed in the thread around the v15 SQL/JSON
features implementation, noting that this would likely entail at least
one more beta release and would push the GA date past our normal
timeframe.2. Try to commit a subset of the features that caused less debate.
This was ruled out.3. Revert the v15 SQL/JSON features work.
<RMT hat>
Based on the current release timing and the open issues presented on
the thread, and the RMT had recommended reverting, but preferred to
drive consensus on next steps.
</RMT hat>From a release advocacy standpoint, I need about 6 weeks lead time to
put together the GA launch. We're at the point where I typically
deliver a draft release announcement. From this, given this involves a
high visibility feature, I would want some clarity on what option we
would like to pursue. Once the announcement translation process has
begun (and this is when we have consensus on the release
announcement), it becomes more challenging to change things out.From a personal standpoint (restating from[3]), I would like to see
what we could do to include support for this batch of the SQL/JSON
features in v15. What is included looks like it closes most of the gap
on what we've been missing syntactically since the standard was
adopted, and the JSON_TABLE work is very convenient for converting
JSON data into a relational format. I believe having this feature set
is important for maintaining standards compliance, interoperability,
tooling support, and general usability. Plus, JSON still seems to be
pretty popular.We're looking for additional input on what makes sense as a best
course of action, given what is presented in[3].
To preserve options I will start preparing reversion patches. Given
there are I think more than 20 commits all told that could be fun, and
will probably take me a little while. The sad part is that to the best
of my knowledge this code is producing correct results, and not
disturbing the stability or performance of anything else. There was a
performance issue but it's been dealt with AIUI.
Personally, I hope we don't need to revert. If everything from the open
item standpoint is addressed, I want to ensure we capture and complete
the remaining issues that were raised on the other thread, i.e.
* adding design docs
* simplifying the type-coercion code
* another other design concerns that were presented
We switched this discussion out to a different thread to get some more
visibility on the issue and see if other folks would weigh in. Thus far,
there has not been much additional say either way. It would be good if
other folks chimed in.
Thanks,
Jonathan
Hi,
Continuation from the thread at
/messages/by-id/20220811171740.m5b4h7x63g4lzgrk@awork3.anarazel.de
On 2022-08-11 10:17:40 -0700, Andres Freund wrote:
On 2022-08-11 13:08:27 -0400, Jonathan S. Katz wrote:
With RMT hat on, Andres do you have any thoughts on this?
I think I need to prototype how it'd look like to give a more detailed
answer. I have a bunch of meetings over the next few hours, but after that I
can give it a shot.
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.
One thing I could use help understanding is the logic behind
ExecEvalJsonNeedsSubTransaction() - there's no useful comments, so it's hard
to follow.
bool
ExecEvalJsonNeedsSubTransaction(JsonExpr *jsexpr,
struct JsonCoercionsState *coercions)
{
/* want error to be raised, so clearly no subtrans needed */
if (jsexpr->on_error->btype == JSON_BEHAVIOR_ERROR)
return false;
if (jsexpr->op == JSON_EXISTS_OP && !jsexpr->result_coercion)
return false;
if (!coercions)
return true;
return false;
}
I guess the !coercions bit is just about the planner, where we want to be
pessimistic about when subtransactions are used, for the purpose of
parallelism? Because that's the only place that passes in NULL.
What really baffles me is that last 'return false' - it seems to indicate that
there's no paths during query execution where
ExecEvalJsonNeedsSubTransaction() returns true. And indeed, tests pass with an
Assert(!needSubtrans) added to ExecEvalJson() (and then unsurprisingly also
after removing the ExecEvalJsonExprSubtrans() indirection).
What's going on here?
We, somewhat confusingly, still rely on subtransactions, heavily
so. Responsible for that is this hunk of code:
bool throwErrors = jexpr->on_error->btype == JSON_BEHAVIOR_ERROR;
[...]
cxt.error = throwErrors ? NULL : &error;
cxt.coercionInSubtrans = !needSubtrans && !throwErrors;
Assert(!needSubtrans || cxt.error);
So basically we start a subtransaction inside ExecEvalJsonExpr(), to coerce
the result type, whenever !needSubtrans (which is always!), unless ERROR ON
ERROR is used.
Which then also explains the theory behind the EXISTS_OP check in
ExecEvalJsonNeedsSubTransaction(). In that case ExecEvalJsonExpr() returns
early, before doing a return value coercion, thus not starting a
subtransaction.
I don't think it's sane from a performance view to start a subtransaction for
every coercion, particularly because most coercion paths will never trigger an
error, leaving things like out-of-memory or interrupts aside. And those are
re-thrown by ExecEvalJsonExprSubtrans(). A quick and dirty benchmark shows
ERROR ON ERROR nearly 2xing speed. I'm worried about the system impact of
using subtransactions this heavily, it's not exactly the best performing
system - the only reason it's kind of ok here is that it's going to be very
rare to allocate a subxid, I think.
Next question:
/*
* 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;
Two points:
1) I suspect it's not safe to switch to oldcontext before calling func().
On error we'll have leaked memory into oldcontext and we'll just continue
on. It might not be very consequential here, because the calling context
presumably isn't very long lived, but that's probably not something we should
rely on.
Also, are we sure that the context will be in a clean state when it's used
within an erroring subtransaction?
I think the right thing here would be to stay in the subtransaction context
and then copy the datum out to the surrounding context in the success case.
2) If there was an out-of-memory error, it'll have been in oldcontext. So
switching back to it before calling CopyErrorData() doesn't seem good - we'll
just hit OOM issues again.
I realize that both of these issues are present in plenty other code (see
e.g. plperl_spi_exec()). So I'm curious why they are ok?
Greetings,
Andres Freund
Hi,
On 16.08.2022 01:38, Andres Freund wrote:
Continuation from the thread at
/messages/by-id/20220811171740.m5b4h7x63g4lzgrk@awork3.anarazel.deI 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:
v6-0001-Fix-returning-of-json-b-domains-in-JSON_VALUE.patchtext/x-patch; charset=UTF-8; name=v6-0001-Fix-returning-of-json-b-domains-in-JSON_VALUE.patchDownload
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.c
index 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.c
index 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.c
index 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.c
index 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.h
index 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.out
index 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.sql
index 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.patchtext/x-patch; charset=UTF-8; name=v6-0002-Add-EEOP_SUBTRANS-executor-step.patchDownload
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.c
index 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.c
index 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.c
index 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.h
index 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.patchtext/x-patch; charset=UTF-8; name=v6-0003-Simplify-JsonExpr-execution.patchDownload
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.c
index 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.c
index 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.c
index 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.c
index 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.c
index 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.c
index 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.c
index 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.h
index 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.h
index 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.h
index 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.h
index 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.out
index 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.sql
index 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
Hi,
On 2022-08-15 15:38:53 -0700, Andres Freund wrote:
Next question:
/*
* 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;Two points:
1) I suspect it's not safe to switch to oldcontext before calling func().
On error we'll have leaked memory into oldcontext and we'll just continue
on. It might not be very consequential here, because the calling context
presumably isn't very long lived, but that's probably not something we should
rely on.Also, are we sure that the context will be in a clean state when it's used
within an erroring subtransaction?I think the right thing here would be to stay in the subtransaction context
and then copy the datum out to the surrounding context in the success case.2) If there was an out-of-memory error, it'll have been in oldcontext. So
switching back to it before calling CopyErrorData() doesn't seem good - we'll
just hit OOM issues again.I realize that both of these issues are present in plenty other code (see
e.g. plperl_spi_exec()). So I'm curious why they are ok?
Certainly seems to be missing a FreeErrorData() for the happy path?
It'd be nicer if we didn't copy the error. In the case we rethrow we don't
need it, because we can just PG_RE_THROW(). And in the other path we just want
to get the error code. It just risks additional errors to CopyErrorData(). But
it's not entirely obvious that geterrcode() is intended for this:
* This is only intended for use in error callback subroutines, since there
* is no other place outside elog.c where the concept is meaningful.
*/
a PG_CATCH() block isn't really an error callback subroutine. But it should be
fine.
Greetings,
Andres Freund
Hi,
On 2022-08-16 04:02:17 +0300, Nikita Glukhov wrote:
Hi,
On 16.08.2022 01:38, Andres Freund wrote:
Continuation from the thread at
/messages/by-id/20220811171740.m5b4h7x63g4lzgrk@awork3.anarazel.deI 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.
Cool.
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):
I think that is helpful.
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 EXCEPTIONThe 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'
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.
Whether it's just IO coercions or also coercions through function calls
doesn't matter terribly, as long as both can be wrapped as a single
interpretation step. You can have a EEOP_JSON_COERCE_IO,
EEOP_JSON_COERCE_FUNC that respectively call input/output function and the
transformation routine within a subtransaction. On error they can jump to some
on_error execution step.
The difficulty is likely just dealing with the intermediary nodes like
RelabelType.
Exceptions may come not only from coercions. Expressions in DEFAULT ON
EMPTY can also throw exceptions, which also must be handled.
Are there other cases?
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.
I'm not entirely sure what I meant at that time either. Understanding this
code involves a lot of guessing since there's practically no explanatory
comments.
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.
I'm not convinced that that's true. We spend a fair amount of memory
generating expression paths for the per-type elements in JsonItemCoercions,
most of which will never be used. Even trivial stuff ends up with ~2kB.
Then there's of course the executor side, where the various ExprStates really
add up:
MemoryContextStats(CurrentMemoryContext) in ExecInitExprRec(), just before
if (jext->coercions)
ExecutorState: 8192 total in 1 blocks; 4464 free (0 chunks); 3728 used
ExprContext: 8192 total in 1 blocks; 7928 free (0 chunks); 264 used
Grand total: 16384 bytes in 2 blocks; 12392 free (0 chunks); 3992 used
just after:
ExecutorState: 32768 total in 3 blocks; 15032 free (2 chunks); 17736 used
ExprContext: 8192 total in 1 blocks; 7928 free (0 chunks); 264 used
Grand total: 40960 bytes in 4 blocks; 22960 free (2 chunks); 18000 used
for SELECT JSON_VALUE(NULL::jsonb, '$');
In the last version of the fix there is only 4 bytes (int jump) of
additional state space per coercion.
That's certainly a *lot* better.
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.
Yea, I realized that by now as well. But the code doesn't make that
understandable. E.g.:
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).
is very misleading.
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).
I've wondered about this as well, but I think it'd require quite careful work
to be safe. And certainly isn't something we can do at this point in the cycle
- it'll potentially impact every query, not just ones with json in, if we
screw up something (or introduce overhead).
But for JIT I still had to construct additional ExprState with a
function compiled from subexpression steps.
JIT is one of the reason *not* want to construct subsidiary ExprState's, since
they will trigger separate code generation (and thus overhead).
Why did you have to do this?
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
I pushed a few cleanups to https://github.com/anarazel/postgres/commits/json
while I was hacking on this (ignore that it's based on the meson tree, that's
just faster for me). Some of them might not be applicable anymore, but it
might still make sense for you to look at.
Greetings,
Andres Freund
On Mon, Aug 15, 2022 at 6:39 PM Andres Freund <andres@anarazel.de> wrote:
I don't think it's sane from a performance view to start a subtransaction for
every coercion, particularly because most coercion paths will never trigger an
error, leaving things like out-of-memory or interrupts aside. And those are
re-thrown by ExecEvalJsonExprSubtrans(). A quick and dirty benchmark shows
ERROR ON ERROR nearly 2xing speed. I'm worried about the system impact of
using subtransactions this heavily, it's not exactly the best performing
system - the only reason it's kind of ok here is that it's going to be very
rare to allocate a subxid, I think.
I agree. It kinda surprises me that we thought it was OK to commit
something that uses that many subtransactions. I feel like that's
going to cause people to hose themselves in ways that we can't really
do anything about. Like they'll test it out, it will work, and then
when they put it into production, they'll have constant wraparound
issues for which the only real solution is to not use the feature they
relied on to build the application.
--
Robert Haas
EDB: http://www.enterprisedb.com
Hi,
On 8/15/22 10:14 PM, Andres Freund wrote:
I pushed a few cleanups to https://github.com/anarazel/postgres/commits/json
while I was hacking on this (ignore that it's based on the meson tree, that's
just faster for me). Some of them might not be applicable anymore, but it
might still make sense for you to look at.
With RMT hat on, this appears to be making progress. A few questions /
comments for the group:
1. Nikita: Did you have a chance to review Andres's changes as well?
2. There seems to be some, though limited, progress on design docs.
Andres keeps making a point on adding additional comments to the code to
make it easier to follow. Please do not lose sight of this.
3. Robert raised a point about the use of subtransactions and the
increased risk of wraparound on busy systems using the SQL/JSON
features. Do these patches help reduce this risk? I read some clarity on
the use of subtransactions within the patchset, but want to better
understand if the risks pointed out are a concern.
Thanks everyone for your work on this so far!
Jonathan
Hi,
On 17.08.2022 04:45, Jonathan S. Katz wrote:
On 8/15/22 10:14 PM, Andres Freund wrote:
I pushed a few cleanups to
https://github.com/anarazel/postgres/commits/json
while I was hacking on this (ignore that it's based on the meson
tree, that's
just faster for me). Some of them might not be applicable anymore,
but it
might still make sense for you to look at.With RMT hat on, this appears to be making progress. A few questions /
comments for the group:1. Nikita: Did you have a chance to review Andres's changes as well?
Yes, I have reviewed Andres's changes, they all are ok.
Then I started to do on the top of it other fixes that help to avoid
subtransactions when they are not needed. And it ended in the new
refactoring of coercion code. Also I moved here from v6-0003 fix of
ExecEvalJsonNeedSubtransaction() which considers more cases.
On 16.08.2022 05:14, Andres Freund wrote:
But for JIT I still had to construct additional ExprState with a
function compiled from subexpression steps.
Why did you have to do this?
I simply did not dare to implement compilation of recursively-callable
function with additional parameter stepno. In the v8 patch I did it
by adding a switch with all possible jump addresses of EEOP_SUBTRANS
steps in the beginning of the function. And it really seems to work
faster, but needs more exploration. See patch 0003, where both
variants preserved using #ifdef.
The desciprion of the v7 patches:
0001 Simplify JsonExpr execution
Andres's changes + mine:
- Added JsonCoercionType enum, fields like via_io replaced with it
- Emit only context item steps in JSON_TABLE_OP case
- Skip coercion of NULLs to non-domain types (is it correct?)
0002 Fix returning of json[b] domains in JSON_VALUE:
simply rebase of v6 onto 0001
0003 Add EEOP_SUBTRANS executor step
v6 + new recursive JIT
0004 Split JsonExpr execution into steps
simply rebase of v6 + used LLMBuildSwitch() in EEOP_JSONEXPR
--
Nikita Glukhov
Postgres Professional:http://www.postgrespro.com
The Russian Postgres Company
Attachments:
v7-0001-Simplify-JsonExpr-execution.patchtext/x-patch; charset=UTF-8; name=v7-0001-Simplify-JsonExpr-execution.patchDownload
From 99cf2d1c6cea1889120da5e845b17ccf5db10332 Mon Sep 17 00:00:00 2001
From: Andres Freund <andres@anarazel.de>
Date: Fri, 12 Aug 2022 11:07:06 -0700
Subject: [PATCH v7 1/4] Simplify JsonExpr execution
json: rename ExecEvalJson() to ExecEvalJsonExpr()
It was confusing that EEOP_JSONEXPR was evaluated by ExecEvalJson() but that a
ExecEvalJsonExpr() also existed. The latter is now renamed to
ExecEvalJsonExprInternal(), but something better might be possible.
json: make ExecPrepareJsonItemCoercion static
json: Remove unused 'returning' argument to ExecPrepareJsonItemCoercion()'s
json: wip: catching errors via macro, cleanup singatures.
wip: no need for subtrans in ExecEvalJsonExpr()???
Remove JSE_OPT_SUBTRANS's parameter coercionInSubtrans
Refactor JsonExpr coercions
More correctly implement ExecEvalJsonNeedsSubTransaction()
Emit only context item for JSON_TABLE
Skip coercion of NULLs for non-domain types
---
src/backend/executor/execExpr.c | 109 +++--
src/backend/executor/execExprInterp.c | 559 ++++++++++++++------------
src/backend/jit/llvm/llvmjit_expr.c | 2 +-
src/backend/jit/llvm/llvmjit_types.c | 2 +-
src/backend/nodes/nodeFuncs.c | 10 +-
src/backend/optimizer/util/clauses.c | 2 +-
src/backend/parser/parse_expr.c | 62 ++-
src/include/executor/execExpr.h | 55 ++-
src/include/nodes/primnodes.h | 18 +-
9 files changed, 472 insertions(+), 347 deletions(-)
diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
index d0a57c7aaee..1c9782de15f 100644
--- a/src/backend/executor/execExpr.c
+++ b/src/backend/executor/execExpr.c
@@ -2561,49 +2561,72 @@ ExecInitExprRec(Expr *node, ExprState *state,
case T_JsonExpr:
{
JsonExpr *jexpr = castNode(JsonExpr, node);
- JsonExprState *jsestate = palloc0(sizeof(JsonExprState));
+ JsonExprState *jsestate;
+ JsonCoercion *result_coercion = jexpr->result_coercion;
ListCell *argexprlc;
ListCell *argnamelc;
- scratch.opcode = EEOP_JSONEXPR;
- scratch.d.jsonexpr.jsestate = jsestate;
+ /* JSON_TABLE preudo-function returns context item as a result */
+ if (jexpr->op == JSON_TABLE_OP)
+ {
+ ExecInitExprRec((Expr *) jexpr->formatted_expr, state,
+ resv, resnull);
+ break;
+ }
+ jsestate = palloc0(sizeof(JsonExprState));
jsestate->jsexpr = jexpr;
- jsestate->formatted_expr =
- palloc(sizeof(*jsestate->formatted_expr));
+ scratch.opcode = EEOP_JSONEXPR;
+ scratch.d.jsonexpr.jsestate = jsestate;
ExecInitExprRec((Expr *) jexpr->formatted_expr, state,
- &jsestate->formatted_expr->value,
- &jsestate->formatted_expr->isnull);
-
- jsestate->pathspec =
- palloc(sizeof(*jsestate->pathspec));
+ &jsestate->formatted_expr.value,
+ &jsestate->formatted_expr.isnull);
ExecInitExprRec((Expr *) jexpr->path_spec, state,
- &jsestate->pathspec->value,
- &jsestate->pathspec->isnull);
+ &jsestate->pathspec.value,
+ &jsestate->pathspec.isnull);
- jsestate->res_expr =
- palloc(sizeof(*jsestate->res_expr));
+ if (result_coercion)
+ {
+ jsestate->result_coercion.type = result_coercion->ctype;
- jsestate->result_expr = jexpr->result_coercion
- ? ExecInitExprWithCaseValue((Expr *) jexpr->result_coercion->expr,
- state->parent,
- &jsestate->res_expr->value,
- &jsestate->res_expr->isnull)
- : NULL;
+ if (result_coercion->expr)
+ jsestate->result_coercion.estate =
+ ExecInitExprWithCaseValue((Expr *) result_coercion->expr,
+ state->parent,
+ &jsestate->coercion_expr.value,
+ &jsestate->coercion_expr.isnull);
- jsestate->default_on_empty = !jexpr->on_empty ? NULL :
- ExecInitExpr((Expr *) jexpr->on_empty->default_expr,
- state->parent);
+ /* Skip coercion of NULLs for non-domain types */
+ jsestate->skip_null_coercion =
+ getBaseType(jexpr->returning->typid) == jexpr->returning->typid;
+ }
+ else
+ jsestate->result_coercion.type = JSON_COERCION_NONE;
+
+ /* Additional coercion of JSON_QUERY(OMIT QUOTES) */
+ jsestate->io_coercion.type = JSON_COERCION_VIA_IO;
+
+ if (jexpr->on_empty)
+ {
+ jsestate->default_on_empty =
+ ExecInitExpr((Expr *) jexpr->on_empty->default_expr,
+ state->parent);
+
+ jsestate->default_on_empty_can_throw =
+ ExecEvalExprCanThrowErrors(jexpr->on_empty->default_expr);
+ }
jsestate->default_on_error =
ExecInitExpr((Expr *) jexpr->on_error->default_expr,
state->parent);
+
+ /* Initialize type input info if I/O coercion can be used */
if (jexpr->omit_quotes ||
- (jexpr->result_coercion && jexpr->result_coercion->via_io))
+ jsestate->result_coercion.type == JSON_COERCION_VIA_IO)
{
Oid typinput;
@@ -2641,30 +2664,46 @@ ExecInitExprRec(Expr *node, ExprState *state,
if (jexpr->coercions)
{
JsonCoercion **coercion;
- struct JsonCoercionState *cstate;
+ JsonCoercionState *cstate;
Datum *caseval;
bool *casenull;
- jsestate->coercion_expr =
- palloc(sizeof(*jsestate->coercion_expr));
+ jsestate->item_coercions = palloc0(sizeof(*jsestate->item_coercions));
- caseval = &jsestate->coercion_expr->value;
- casenull = &jsestate->coercion_expr->isnull;
+ caseval = &jsestate->coercion_expr.value;
+ casenull = &jsestate->coercion_expr.isnull;
- for (cstate = &jsestate->coercions.null,
+ for (cstate = &jsestate->item_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;
+ if (!*coercion)
+ /* Missing coercion here means missing cast */
+ cstate->type = JSON_COERCION_ERROR;
+ else if ((*coercion)->ctype == JSON_COERCION_VIA_EXPR)
+ {
+ Assert((*coercion)->expr);
+ cstate->estate =
+ ExecInitExprWithCaseValue((Expr *) (*coercion)->expr,
+ state->parent,
+ caseval,
+ casenull);
+ cstate->type = JSON_COERCION_VIA_EXPR;
+ }
+ else if ((*coercion)->ctype == JSON_COERCION_NONE)
+ cstate->type = JSON_COERCION_NONE;
+ else
+ {
+ /* There should not be other coercion types */
+ Assert(0);
+ cstate->type = JSON_COERCION_ERROR;
+ }
}
}
ExprEvalPushStep(state, &scratch);
+
break;
}
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index 636794ca6f1..bf141a66066 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -1844,7 +1844,7 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
EEO_CASE(EEOP_JSONEXPR)
{
/* too complex for an inline implementation */
- ExecEvalJson(state, op, econtext);
+ ExecEvalJsonExpr(state, op, econtext);
EEO_NEXT();
}
@@ -4738,56 +4738,159 @@ ExecEvalJsonBehavior(ExprContext *econtext, JsonBehavior *behavior,
}
}
+
+typedef struct EvalJsonSubtransState
+{
+ MemoryContext oldcontext;
+ ResourceOwner oldowner;
+} EvalJsonSubtransState;
+
+static void
+ExecEvalJsonStartSubtrans(EvalJsonSubtransState *state)
+{
+ /*
+ * We should catch exceptions of category ERRCODE_DATA_EXCEPTION and
+ * execute the corresponding ON ERROR behavior then.
+ */
+ state->oldcontext = CurrentMemoryContext;
+ state->oldowner = CurrentResourceOwner;
+
+ BeginInternalSubTransaction(NULL);
+
+ /* Want to execute expressions inside function's memory context */
+ /*
+ * AFIXME: I don't think that's OK, there might be lots of leaked memory
+ * etc.
+ */
+ MemoryContextSwitchTo(state->oldcontext);
+}
+
+static void
+ExecEvalJsonEndSubtrans(EvalJsonSubtransState *state)
+{
+ /* Commit the inner transaction, return to outer xact context */
+ ReleaseCurrentSubTransaction();
+ MemoryContextSwitchTo(state->oldcontext);
+ CurrentResourceOwner = state->oldowner;
+}
+
+static void
+ExecEvalJsonCatchError(EvalJsonSubtransState *state)
+{
+ ErrorData *edata;
+ int ecategory;
+
+ /* Save error info in oldcontext */
+ MemoryContextSwitchTo(state->oldcontext);
+ edata = CopyErrorData();
+ FlushErrorState();
+
+ /* Abort the inner transaction */
+ RollbackAndReleaseCurrentSubTransaction();
+ MemoryContextSwitchTo(state->oldcontext);
+ CurrentResourceOwner = state->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);
+ }
+}
+
/*
- * Evaluate a coercion of a JSON item to the target type.
+ * If `error` is not NULL (we need to catch errors), execute the
+ * statement passed in the third argument inside a subtransaction,
+ * otherwise do it outside. In the subtrans case an error classified as
+ * being catchable by ExecEvalJsonCatchError() will set `error` to true.
*/
+#define JSE_OPT_SUBTRANS(error, jexpr, res, ...) \
+ do { \
+ bool *error_ = (error); \
+ \
+ if (!error_) \
+ { \
+ res = __VA_ARGS__; \
+ } \
+ else \
+ { \
+ EvalJsonSubtransState substate = {0}; \
+ \
+ /* Check correctness of ExecEvalJsonNeedsSubTransaction() */ \
+ AssertMacro(ExecEvalJsonNeedsSubTransaction(jexpr)); \
+ \
+ ExecEvalJsonStartSubtrans(&substate); \
+ \
+ PG_TRY(); \
+ { \
+ res = __VA_ARGS__; \
+ ExecEvalJsonEndSubtrans(&substate); \
+ } \
+ PG_CATCH(); \
+ { \
+ ExecEvalJsonCatchError(&substate); \
+ \
+ res = (Datum) 0; \
+ *error_ = true; \
+ } \
+ PG_END_TRY(); \
+ } \
+ } while (0)
+
+/* Execute one of possible coercions or report an error if there is no cast */
static Datum
-ExecEvalJsonExprCoercion(ExprEvalStep *op, ExprContext *econtext,
- Datum res, bool *isNull, void *p, bool *error)
+ExecEvalJsonExprCoercion(JsonExprState *jsestate, ExprContext *econtext,
+ JsonCoercionState *coercion,
+ Datum res, bool *resnull, bool *error)
{
- ExprState *estate = p;
- JsonExprState *jsestate;
+ switch (coercion->type)
+ {
+ case JSON_COERCION_NONE:
+ return (Datum) res;
- if (estate) /* coerce using specified expression */
- return ExecEvalExpr(estate, econtext, isNull);
+ case JSON_COERCION_VIA_EXPR:
+ jsestate->coercion_expr.value = res;
+ jsestate->coercion_expr.isnull = *resnull;
+ return ExecEvalExpr(coercion->estate, econtext, resnull);
- jsestate = op->d.jsonexpr.jsestate;
+ case JSON_COERCION_VIA_IO:
+ {
+ /* Strip quotes from jsonb strings and call typinput() */
+ char *str;
- if (jsestate->jsexpr->op != JSON_EXISTS_OP)
- {
- JsonCoercion *coercion = jsestate->jsexpr->result_coercion;
- JsonExpr *jexpr = jsestate->jsexpr;
- Jsonb *jb = *isNull ? NULL : DatumGetJsonbP(res);
+ if (*resnull)
+ str = NULL;
+ else
+ str = JsonbUnquote(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,
+ jsestate->jsexpr->returning->typmod);
+ }
- return InputFunctionCall(&jsestate->input.func, str,
- jsestate->input.typioparam,
- jexpr->returning->typmod);
- }
- else if (coercion && coercion->via_populate)
+ case JSON_COERCION_VIA_POPULATE:
return json_populate_type(res, JSONBOID,
- jexpr->returning->typid,
- jexpr->returning->typmod,
+ jsestate->jsexpr->returning->typid,
+ jsestate->jsexpr->returning->typmod,
&jsestate->cache,
econtext->ecxt_per_query_memory,
- isNull);
- }
+ resnull);
- if (jsestate->result_expr)
- {
- jsestate->res_expr->value = res;
- jsestate->res_expr->isnull = *isNull;
+ default:
+ Assert(coercion->type == JSON_COERCION_ERROR);
- res = ExecEvalExpr(jsestate->result_expr, econtext, isNull);
- }
+ /* Report an error if there is no cast to the output type */
+ if (!error)
+ ereport(ERROR,
+ (errcode(ERRCODE_SQL_JSON_ITEM_CANNOT_BE_CAST_TO_TARGET_TYPE),
+ errmsg("SQL/JSON item cannot be cast to target type")));
- return res;
+ *error = true;
+ *resnull = true;
+ return (Datum) 0;
+ }
}
/*
@@ -4844,19 +4947,48 @@ EvalJsonPathVar(void *cxt, char *varName, int varNameLen,
}
/*
- * Prepare SQL/JSON item coercion to the output type. Returned a datum of the
- * corresponding SQL type and a pointer to the coercion state.
+ * Check whether we need to override default coercion in
+ * JSON_QUERY(OMIT QUOTES) case.
*/
-Datum
-ExecPrepareJsonItemCoercion(JsonbValue *item,
- JsonReturning *returning,
- struct JsonCoercionsState *coercions,
- struct JsonCoercionState **pcoercion)
+static bool
+ExecJsonQueryNeedsIOCoercion(JsonExpr *jsexpr, Datum res, bool isnull)
{
- struct JsonCoercionState *coercion;
- Datum res;
+ if (jsexpr->omit_quotes && !isnull)
+ {
+ Jsonb *jb = DatumGetJsonbP(res);
+
+ /* Coerce non-null scalar items via I/O in OMIT QUOTES case */
+ if (JB_ROOT_IS_SCALAR(jb))
+ return true;
+ }
+
+ return false;
+}
+
+/*
+ * Prepare SQL/JSON item coercion to the JSON_VALUE output type.
+ * Returned a coercion status, a datum of the corresponding SQL type
+ * and a pointer to selected coercion state.
+ */
+static Datum
+ExecPrepareJsonValueCoercion(JsonExprState *jsestate, JsonbValue *item,
+ JsonCoercionState **coercion, bool *resnull)
+{
+ struct JsonItemsCoercionStates *coercions = jsestate->item_coercions;
JsonbValue buf;
+ *resnull = false;
+
+ if (!coercions)
+ {
+ /*
+ * Special case for json[b] output types. Simply use
+ * default coercion from jsonb to the output type.
+ */
+ return JsonbPGetDatum(JsonbValueToJsonb(item));
+ }
+
+ /* Use coercion from SQL/JSON item type to the output type */
if (item->type == jbvBinary &&
JsonContainerIsScalar(item->val.binary.data))
{
@@ -4871,174 +5003,96 @@ ExecPrepareJsonItemCoercion(JsonbValue *item,
switch (item->type)
{
case jbvNull:
- coercion = &coercions->null;
- res = (Datum) 0;
- break;
+ Assert(0); /* must be handled by the caller */
+ *coercion = &coercions->null;
+ *resnull = true;
+ return (Datum) 0;
case jbvString:
- coercion = &coercions->string;
- res = PointerGetDatum(cstring_to_text_with_len(item->val.string.val,
- item->val.string.len));
- break;
+ *coercion = &coercions->string;
+ return 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;
+ *coercion = &coercions->numeric;
+ return NumericGetDatum(item->val.numeric);
case jbvBool:
- coercion = &coercions->boolean;
- res = BoolGetDatum(item->val.boolean);
- break;
+ *coercion = &coercions->boolean;
+ return BoolGetDatum(item->val.boolean);
case jbvDatetime:
- res = item->val.datetime.value;
switch (item->val.datetime.typid)
{
case DATEOID:
- coercion = &coercions->date;
+ *coercion = &coercions->date;
break;
case TIMEOID:
- coercion = &coercions->time;
+ *coercion = &coercions->time;
break;
case TIMETZOID:
- coercion = &coercions->timetz;
+ *coercion = &coercions->timetz;
break;
case TIMESTAMPOID:
- coercion = &coercions->timestamp;
+ *coercion = &coercions->timestamp;
break;
case TIMESTAMPTZOID:
- coercion = &coercions->timestamptz;
+ *coercion = &coercions->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;
+ Assert(0); /* non-scalars must be rejected by JsonPathValue() */
+ *coercion = &coercions->composite;
+ return 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)
+ExecEvalJsonExprInternal(JsonExprState *jsestate, ExprContext *econtext,
+ JsonPath *path, Datum item, bool *resnull,
+ bool *error)
{
- 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)
-{
- ExecEvalJsonExprContext *cxt = pcxt;
- JsonPath *path = cxt->path;
- JsonExprState *jsestate = op->d.jsonexpr.jsestate;
JsonExpr *jexpr = jsestate->jsexpr;
- ExprState *estate = NULL;
+ JsonCoercionState *coercion = &jsestate->result_coercion;
bool empty = false;
Datum res = (Datum) 0;
+ *resnull = true;
+
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;
- }
+
+ if (empty)
+ break;
+
*resnull = !DatumGetPointer(res);
+
+ /* Override default coercion in OMIT QUOTES case */
+ if (ExecJsonQueryNeedsIOCoercion(jexpr, res, *resnull))
+ coercion = &jsestate->io_coercion;
+
break;
case JSON_VALUE_OP:
{
- struct JsonCoercionState *jcstate;
JsonbValue *jbv = JsonPathValue(item, path, &empty, error,
jsestate->args);
@@ -5049,49 +5103,10 @@ ExecEvalJsonExpr(ExprEvalStep *op, ExprContext *econtext,
break;
Assert(!empty);
+ Assert(jbv->type != jbvNull);
- *resnull = false;
-
- /* coerce scalar item to the output type */
- if (jexpr->returning->typid == JSONOID ||
- jexpr->returning->typid == JSONBOID)
- {
- /* Use result coercion from json[b] to the output type */
- res = JsonbPGetDatum(JsonbValueToJsonb(jbv));
- break;
- }
-
- /* Use coercion from SQL/JSON item type to the output type */
- res = ExecPrepareJsonItemCoercion(jbv,
- jsestate->jsexpr->returning,
- &jsestate->coercions,
- &jcstate);
-
- if (jcstate->coercion &&
- (jcstate->coercion->via_io ||
- jcstate->coercion->via_populate))
- {
- if (error)
- {
- *error = true;
- return (Datum) 0;
- }
-
- /*
- * 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;
+ res = ExecPrepareJsonValueCoercion(jsestate, jbv,
+ &coercion, resnull);
break;
}
@@ -5103,21 +5118,9 @@ ExecEvalJsonExpr(ExprEvalStep *op, ExprContext *econtext,
*resnull = error && *error;
res = BoolGetDatum(exists);
-
- if (!jsestate->result_expr)
- return res;
-
- /* coerce using result expression */
- estate = jsestate->result_expr;
- jsestate->res_expr->value = res;
- jsestate->res_expr->isnull = *resnull;
- break;
+ break; /* always use result coercion */
}
- case JSON_TABLE_OP:
- *resnull = false;
- return item;
-
default:
elog(ERROR, "unrecognized SQL/JSON expression op %d", jexpr->op);
return (Datum) 0;
@@ -5141,73 +5144,115 @@ ExecEvalJsonExpr(ExprEvalStep *op, ExprContext *econtext,
}
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.
+ * Execute DEFAULT expression in a subtransation if needed.
+ * Coercion is not needed here, because expression is
+ * already coerced to the target type by the parser.
*/
- estate = jsestate->default_on_empty;
- else
- /* Execute ON EMPTY behavior */
- res = ExecEvalJsonBehavior(econtext, jexpr->on_empty,
- jsestate->default_on_empty,
- resnull);
+ JSE_OPT_SUBTRANS(jsestate->default_on_empty_can_throw ? error : NULL,
+ jexpr, res,
+ ExecEvalExpr(jsestate->default_on_empty,
+ econtext, resnull));
+ return res;
+ }
+
+ /* Execute ON EMPTY behavior, coercion is default */
+ res = ExecEvalJsonBehavior(econtext, jexpr->on_empty,
+ jsestate->default_on_empty,
+ resnull);
}
- return ExecEvalJsonExprSubtrans(ExecEvalJsonExprCoercion, op, econtext,
- res, resnull, estate, error,
- cxt->coercionInSubtrans);
+ /*
+ * Execute resulting coercion in a subtransaction if `error` != NULL.
+ * In case of cast errors, there is no need to use subtransaction.
+ */
+ if (coercion->type != JSON_COERCION_NONE)
+ {
+ /* Skip coercion of NULLs for non-domain types */
+ if (*resnull && jsestate->skip_null_coercion)
+ return (Datum) 0;
+
+ JSE_OPT_SUBTRANS(coercion->type != JSON_COERCION_ERROR ? error : NULL,
+ jexpr, res,
+ ExecEvalJsonExprCoercion(jsestate, econtext,
+ coercion, res,
+ resnull, error));
+ }
+
+ return res;
}
bool
-ExecEvalJsonNeedsSubTransaction(JsonExpr *jsexpr,
- struct JsonCoercionsState *coercions)
+ExecEvalExprCanThrowErrors(Node *expr)
{
- if (jsexpr->on_error->btype == JSON_BEHAVIOR_ERROR)
+ if (!expr)
return false;
- if (jsexpr->op == JSON_EXISTS_OP && !jsexpr->result_coercion)
+ if (IsA(expr, Const))
return false;
- if (!coercions)
- return true;
+ /* TODO consider more cases */
+ return true;
+}
- return false;
+/* Can SQL/JSON expression start subtransactions for error handling? */
+bool
+ExecEvalJsonNeedsSubTransaction(JsonExpr *jsexpr)
+{
+ if (jsexpr->on_error->btype == JSON_BEHAVIOR_ERROR)
+ return false; /* no error handling needed */
+
+ if (jsexpr->result_coercion &&
+ jsexpr->result_coercion->ctype != JSON_COERCION_NONE)
+ return true; /* may execute non-empty result coercion */
+
+ if (jsexpr->on_empty &&
+ jsexpr->on_empty->btype == JSON_BEHAVIOR_DEFAULT &&
+ ExecEvalExprCanThrowErrors(jsexpr->on_empty->default_expr))
+ return true; /* may execute arbitrary unsafe expression */
+
+ if (jsexpr->coercions)
+ return true; /* JSON_VALUE may execute item coercions */
+
+ if (jsexpr->omit_quotes)
+ return true; /* JSON_QUERY may execute coercion via I/O */
+
+ return false; /* subtransactios should not be used */
}
/* ----------------------------------------------------------------
- * ExecEvalJson
+ * ExecEvalJsonExpr
* ----------------------------------------------------------------
*/
void
-ExecEvalJson(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
+ExecEvalJsonExpr(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
{
- ExecEvalJsonExprContext cxt;
JsonExprState *jsestate = op->d.jsonexpr.jsestate;
JsonExpr *jexpr = jsestate->jsexpr;
Datum item;
- Datum res = (Datum) 0;
+ Datum res;
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)
+ if (jsestate->formatted_expr.isnull || jsestate->pathspec.isnull)
{
+ *op->resnull = true;
+ *op->resvalue = (Datum) 0;
+
/* execute domain checks for NULLs */
- (void) ExecEvalJsonExprCoercion(op, econtext, res, op->resnull,
- NULL, NULL);
+ (void) ExecEvalJsonExprCoercion(jsestate, econtext,
+ &jsestate->result_coercion,
+ *op->resvalue, op->resnull, NULL);
Assert(*op->resnull);
return;
}
- item = jsestate->formatted_expr->value;
- path = DatumGetJsonPathP(jsestate->pathspec->value);
+ item = jsestate->formatted_expr.value;
+ path = DatumGetJsonPathP(jsestate->pathspec.value);
/* reset JSON path variable contexts */
foreach(lc, jsestate->args)
@@ -5218,19 +5263,13 @@ ExecEvalJson(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
var->evaluated = false;
}
- needSubtrans = ExecEvalJsonNeedsSubTransaction(jexpr, &jsestate->coercions);
-
- 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);
-
+ res = ExecEvalJsonExprInternal(jsestate, econtext,
+ path, item, op->resnull,
+ throwErrors ? NULL : &error);
if (error)
{
+ Assert(jexpr->on_error->btype != JSON_BEHAVIOR_ERROR);
+
/* Execute ON ERROR behavior */
res = ExecEvalJsonBehavior(econtext, jexpr->on_error,
jsestate->default_on_error,
@@ -5238,9 +5277,9 @@ ExecEvalJson(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
/* 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);
+ res = ExecEvalJsonExprCoercion(jsestate, econtext,
+ &jsestate->result_coercion,
+ res, op->resnull, NULL);
}
*op->resvalue = res;
diff --git a/src/backend/jit/llvm/llvmjit_expr.c b/src/backend/jit/llvm/llvmjit_expr.c
index bd3965143da..fd72630f5e6 100644
--- a/src/backend/jit/llvm/llvmjit_expr.c
+++ b/src/backend/jit/llvm/llvmjit_expr.c
@@ -2408,7 +2408,7 @@ llvm_compile_expr(ExprState *state)
break;
case EEOP_JSONEXPR:
- build_EvalXFunc(b, mod, "ExecEvalJson",
+ build_EvalXFunc(b, mod, "ExecEvalJsonExpr",
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.c
index 373471ad27f..37fe64654b6 100644
--- a/src/backend/jit/llvm/llvmjit_types.c
+++ b/src/backend/jit/llvm/llvmjit_types.c
@@ -135,7 +135,7 @@ void *referenced_functions[] =
ExecEvalXmlExpr,
ExecEvalJsonConstructor,
ExecEvalJsonIsPredicate,
- ExecEvalJson,
+ ExecEvalJsonExpr,
MakeExpandedObjectReadOnlyInternal,
slot_getmissingattrs,
slot_getsomeattrs_int,
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index c334daae392..b7fdd0af9d8 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -1014,9 +1014,10 @@ exprCollation(const Node *expr)
if (!coercion)
coll = InvalidOid;
- else if (coercion->expr)
+ else if (coercion->ctype == JSON_COERCION_VIA_EXPR)
coll = exprCollation(coercion->expr);
- else if (coercion->via_io || coercion->via_populate)
+ else if (coercion->ctype == JSON_COERCION_VIA_IO ||
+ coercion->ctype == JSON_COERCION_VIA_POPULATE)
coll = coercion->collation;
else
coll = InvalidOid;
@@ -1259,9 +1260,10 @@ exprSetCollation(Node *expr, Oid collation)
if (!coercion)
Assert(!OidIsValid(collation));
- else if (coercion->expr)
+ else if (coercion->ctype == JSON_COERCION_VIA_EXPR)
exprSetCollation(coercion->expr, collation);
- else if (coercion->via_io || coercion->via_populate)
+ else if (coercion->ctype == JSON_COERCION_VIA_IO ||
+ coercion->ctype == JSON_COERCION_VIA_POPULATE)
coercion->collation = collation;
else
Assert(!OidIsValid(collation));
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 533df86ff77..980c34cf49d 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))
{
context->max_hazard = PROPARALLEL_UNSAFE;
return true;
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index fabb5f72076..3b82c0f3328 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -4172,10 +4172,11 @@ assignDefaultJsonReturningType(Node *context_item, JsonFormat *context_format,
/*
* Try to coerce expression to the output type or
* use json_populate_type() for composite, array and domain types or
- * use coercion via I/O.
+ * use coercion via I/O, if allowed.
*/
static JsonCoercion *
-coerceJsonExpr(ParseState *pstate, Node *expr, const JsonReturning *returning)
+coerceJsonExpr(ParseState *pstate, Node *expr,
+ const JsonReturning *returning, bool allow_io_coercion)
{
char typtype;
JsonCoercion *coercion = makeNode(JsonCoercion);
@@ -4185,20 +4186,28 @@ coerceJsonExpr(ParseState *pstate, Node *expr, const JsonReturning *returning)
if (coercion->expr)
{
if (coercion->expr == expr)
+ {
coercion->expr = NULL;
+ coercion->ctype = JSON_COERCION_NONE;
+ }
+ else
+ coercion->ctype = JSON_COERCION_VIA_EXPR;
return coercion;
}
+ if (!allow_io_coercion)
+ return NULL;
+
typtype = get_typtype(returning->typid);
if (returning->typid == RECORDOID ||
typtype == TYPTYPE_COMPOSITE ||
typtype == TYPTYPE_DOMAIN ||
type_is_array(returning->typid))
- coercion->via_populate = true;
+ coercion->ctype = JSON_COERCION_VIA_POPULATE;
else
- coercion->via_io = true;
+ coercion->ctype = JSON_COERCION_VIA_IO;
return coercion;
}
@@ -4231,8 +4240,7 @@ transformJsonFuncExprOutput(ParseState *pstate, JsonFuncExpr *func,
{
/* Forced coercion via I/O for JSON_VALUE for non-JSON types */
jsexpr->result_coercion = makeNode(JsonCoercion);
- jsexpr->result_coercion->expr = NULL;
- jsexpr->result_coercion->via_io = true;
+ jsexpr->result_coercion->ctype = JSON_COERCION_VIA_IO;
return;
}
@@ -4247,7 +4255,8 @@ transformJsonFuncExprOutput(ParseState *pstate, JsonFuncExpr *func,
Assert(((CaseTestExpr *) placeholder)->typeMod == ret.typmod);
jsexpr->result_coercion = coerceJsonExpr(pstate, placeholder,
- jsexpr->returning);
+ jsexpr->returning,
+ true);
}
}
else
@@ -4318,13 +4327,15 @@ initJsonItemCoercion(ParseState *pstate, Oid typid,
expr = (Node *) placeholder;
}
- return coerceJsonExpr(pstate, expr, returning);
+ /* Allow only ordinary casts here */
+ return coerceJsonExpr(pstate, expr, returning, false);
}
-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 +4358,8 @@ initJsonItemCoercions(ParseState *pstate, JsonItemCoercions *coercions,
for (p = coercionTypids; p->coercion; p++)
*p->coercion = initJsonItemCoercion(pstate, p->typid, returning);
+
+ return coercions;
}
/*
@@ -4377,9 +4390,12 @@ 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));
+ if (jsexpr->returning->typid != JSONOID &&
+ jsexpr->returning->typid != JSONBOID)
+ jsexpr->coercions =
+ initJsonItemCoercions(pstate, jsexpr->returning,
+ exprType(contextItemExpr));
+
break;
@@ -4409,6 +4425,7 @@ transformJsonFuncExpr(ParseState *pstate, JsonFuncExpr *func)
jsexpr->returning->format->format_type = JS_FORMAT_DEFAULT;
jsexpr->returning->format->encoding = JS_ENC_DEFAULT;
+ /* Coerce intermediate boolean result to the output type if needed */
if (!OidIsValid(jsexpr->returning->typid))
{
jsexpr->returning->typid = BOOLOID;
@@ -4418,21 +4435,22 @@ transformJsonFuncExpr(ParseState *pstate, JsonFuncExpr *func)
{
CaseTestExpr *placeholder = makeNode(CaseTestExpr);
int location = exprLocation((Node *) jsexpr);
+ Node *coercion_expr;
placeholder->typeId = BOOLOID;
placeholder->typeMod = -1;
placeholder->collation = InvalidOid;
- jsexpr->result_coercion = makeNode(JsonCoercion);
- jsexpr->result_coercion->expr =
- coerce_to_target_type(pstate, (Node *) placeholder, BOOLOID,
+ coercion_expr =
+ coerce_to_target_type(pstate, (Node *) placeholder,
+ BOOLOID,
jsexpr->returning->typid,
jsexpr->returning->typmod,
COERCION_EXPLICIT,
COERCE_IMPLICIT_CAST,
location);
- if (!jsexpr->result_coercion->expr)
+ if (!coercion_expr)
ereport(ERROR,
(errcode(ERRCODE_CANNOT_COERCE),
errmsg("cannot cast type %s to %s",
@@ -4440,8 +4458,12 @@ transformJsonFuncExpr(ParseState *pstate, JsonFuncExpr *func)
format_type_be(jsexpr->returning->typid)),
parser_coercion_errposition(pstate, location, (Node *) jsexpr)));
- if (jsexpr->result_coercion->expr == (Node *) placeholder)
- jsexpr->result_coercion->expr = NULL;
+ if (coercion_expr != (Node *) placeholder)
+ {
+ jsexpr->result_coercion = makeNode(JsonCoercion);
+ jsexpr->result_coercion->expr = coercion_expr;
+ jsexpr->result_coercion->ctype = JSON_COERCION_VIA_EXPR;
+ }
}
break;
diff --git a/src/include/executor/execExpr.h b/src/include/executor/execExpr.h
index c8ef917ffe0..7dd96b8d5ba 100644
--- a/src/include/executor/execExpr.h
+++ b/src/include/executor/execExpr.h
@@ -757,6 +757,12 @@ typedef struct JsonConstructorExprState
int nargs;
} JsonConstructorExprState;
+typedef struct JsonCoercionState
+{
+ ExprState *estate; /* coercion expression state, if any */
+ JsonCoercionType type;
+} JsonCoercionState;
+
/* EEOP_JSONEXPR state, too big to inline */
typedef struct JsonExprState
{
@@ -769,10 +775,9 @@ typedef struct JsonExprState
} 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 */
+ formatted_expr, /* formatted context item value */
+ coercion_expr, /* input for JSON item coercion */
+ pathspec; /* path specification value */
ExprState *result_expr; /* coerced to output type */
ExprState *default_on_empty; /* ON EMPTY DEFAULT expression */
@@ -781,15 +786,16 @@ typedef struct JsonExprState
void *cache; /* cache for json_populate_type() */
- struct JsonCoercionsState
+ /*
+ * States for coercion of SQL/JSON items produced in JSON_VALUE
+ * directly to the output type.
+ */
+ struct JsonItemsCoercionStates
{
- struct JsonCoercionState
- {
- JsonCoercion *coercion; /* coercion expression */
- ExprState *estate; /* coercion expression state */
- } null,
+ JsonCoercionState
+ null,
string,
- numeric ,
+ numeric,
boolean,
date,
time,
@@ -797,8 +803,19 @@ typedef struct JsonExprState
timestamp,
timestamptz,
composite;
- } coercions; /* states for coercion from SQL/JSON item
- * types directly to the output type */
+ } *item_coercions;
+
+ /* Default output coercion to use, can be overriden in JSON_QUERY */
+ JsonCoercionState result_coercion;
+
+ /* Special I/O coercion state for JSON_QUERY OMIT QUOTES */
+ JsonCoercionState io_coercion;
+
+ /* Can expression in DEFAULT ON EMPTY throw errors? */
+ bool default_on_empty_can_throw;
+
+ /* Can we skip coercion of NULLs (non-domain output type)? */
+ bool skip_null_coercion;
} JsonExprState;
/* functions in execExpr.c */
@@ -860,14 +877,10 @@ extern void ExecEvalSysVar(ExprState *state, ExprEvalStep *op,
ExprContext *econtext, TupleTableSlot *slot);
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,
- struct JsonCoercionsState *);
+extern void ExecEvalJsonExpr(ExprState *state, ExprEvalStep *op,
+ ExprContext *econtext);
+extern bool ExecEvalJsonNeedsSubTransaction(JsonExpr *jsexpr);
+extern bool ExecEvalExprCanThrowErrors(Node *expr);
extern Datum ExecEvalExprPassingCaseValue(ExprState *estate,
ExprContext *econtext, bool *isnull,
Datum caseval_datum,
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 3aa96bb6855..0a40d59be26 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -1551,6 +1551,15 @@ typedef struct JsonBehavior
Node *default_expr; /* default expression, if any */
} JsonBehavior;
+typedef enum JsonCoercionType
+{
+ JSON_COERCION_NONE = 0, /* coercion is not needed */
+ JSON_COERCION_VIA_EXPR = 1, /* coerce using expression */
+ JSON_COERCION_VIA_IO = 2, /* coerce using type input function */
+ JSON_COERCION_VIA_POPULATE = 3, /* coerce using json_populate_type() */
+ JSON_COERCION_ERROR = 4 /* no noercion is available */
+} JsonCoercionType;
+
/*
* JsonCoercion -
* coercion from SQL/JSON item types to SQL types
@@ -1558,10 +1567,11 @@ typedef struct JsonBehavior
typedef struct JsonCoercion
{
NodeTag type;
- Node *expr; /* resulting expression coerced to target type */
- bool via_populate; /* coerce result using json_populate_type()? */
- bool via_io; /* coerce result using type input function? */
- Oid collation; /* collation for coercion via I/O or populate */
+ JsonCoercionType ctype; /* coercion type */
+ Node *expr; /* resulting expression coerced to the
+ * target if type == JSON_COERCION_VIA_EXPR,
+ * NULL otherwise */
+ Oid collation; /* collation for coercion via I/O or populate */
} JsonCoercion;
/*
--
2.25.1
v7-0002-Fix-returning-of-json-b-domains-in-JSON_VALUE.patchtext/x-patch; charset=UTF-8; name=v7-0002-Fix-returning-of-json-b-domains-in-JSON_VALUE.patchDownload
From 8db33b187c1a032d2080b557327b8d7bc2cf2e5d 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 v7 2/4] Fix returning of json[b] domains in JSON_VALUE()
---
src/backend/parser/parse_expr.c | 52 ++++--
src/include/executor/execExpr.h | 3 +
src/test/regress/expected/jsonb_sqljson.out | 181 +++++++++++++++++++-
src/test/regress/sql/jsonb_sqljson.sql | 63 ++++++-
4 files changed, 287 insertions(+), 12 deletions(-)
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 3b82c0f3328..aff6fc1a1c4 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -4212,12 +4212,29 @@ coerceJsonExpr(ParseState *pstate, Node *expr,
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;
@@ -4234,11 +4251,19 @@ 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->ctype = JSON_COERCION_VIA_IO;
return;
@@ -4260,8 +4285,11 @@ transformJsonFuncExprOutput(ParseState *pstate, JsonFuncExpr *func,
}
}
else
+ {
assignDefaultJsonReturningType(jsexpr->formatted_expr, jsexpr->format,
jsexpr->returning);
+ *returning_json = true;
+ }
}
/*
@@ -4371,13 +4399,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;
@@ -4390,19 +4419,22 @@ transformJsonFuncExpr(ParseState *pstate, JsonFuncExpr *func)
coerceDefaultJsonExpr(pstate, jsexpr,
jsexpr->on_error->default_expr);
- if (jsexpr->returning->typid != JSONOID &&
- jsexpr->returning->typid != JSONBOID)
+ /*
+ * 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.h
index 7dd96b8d5ba..51e8d93178e 100644
--- a/src/include/executor/execExpr.h
+++ b/src/include/executor/execExpr.h
@@ -789,6 +789,9 @@ typedef struct JsonExprState
/*
* States for coercion of SQL/JSON items produced in JSON_VALUE
* directly to the output type.
+ *
+ * `item_coercions == NULL` means output type is json[b] or its
+ * domain and we use `result_coercion`.
*/
struct JsonItemsCoercionStates
{
diff --git a/src/test/regress/expected/jsonb_sqljson.out b/src/test/regress/expected/jsonb_sqljson.out
index 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.sql
index 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
v7-0003-Add-EEOP_SUBTRANS-executor-step.patchtext/x-patch; charset=UTF-8; name=v7-0003-Add-EEOP_SUBTRANS-executor-step.patchDownload
From ea071133208844d5843a4803075c153ede07e409 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 v7 3/4] Add EEOP_SUBTRANS executor step
---
src/backend/executor/execExprInterp.c | 102 +++++++++++-
src/backend/jit/llvm/llvmjit_expr.c | 218 ++++++++++++++++++++++++--
src/backend/jit/llvm/llvmjit_types.c | 2 +
src/include/executor/execExpr.h | 15 ++
src/include/nodes/execnodes.h | 5 +
5 files changed, 330 insertions(+), 12 deletions(-)
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index bf141a66066..66898198f33 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.c
index fd72630f5e6..6568228b996 100644
--- a/src/backend/jit/llvm/llvmjit_expr.c
+++ b/src/backend/jit/llvm/llvmjit_expr.c
@@ -45,14 +45,20 @@
#include "utils/typcache.h"
#include "utils/xml.h"
+#define LLVMJIT_SUBTRANS_RECURSIVE
+
typedef struct CompiledExprState
{
LLVMJitContext *context;
const char *funcname;
} CompiledExprState;
-
static Datum ExecRunCompiledExpr(ExprState *state, ExprContext *econtext, bool *isNull);
+static void ExecEvalSubtransCompiled(ExprState *state, ExprEvalStep *op,
+ ExprContext *econtext);
+#ifdef LLVMJIT_SUBTRANS_RECURSIVE
+static Datum ExecRunCompileExprRecursive(ExprState *state, ExprContext *econtext, bool *isNull);
+#endif
static LLVMValueRef BuildV1Call(LLVMJitContext *context, LLVMBuilderRef b,
LLVMModuleRef mod, FunctionCallInfo fcinfo,
@@ -70,15 +76,15 @@ static LLVMValueRef create_LifetimeEnd(LLVMModuleRef mod);
lengthof(((LLVMValueRef[]){__VA_ARGS__})), \
((LLVMValueRef[]){__VA_ARGS__}))
-
/*
- * 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;
+ const char *functypename = "TypeExprStateEvalFunc";
LLVMJitContext *context = NULL;
@@ -123,6 +129,12 @@ llvm_compile_expr(ExprState *state)
instr_time starttime;
instr_time endtime;
+#ifdef LLVMJIT_SUBTRANS_RECURSIVE
+ LLVMValueRef v_opno;
+ int num_recursive_opnos = 1;
+ bool recursive = false;
+#endif
+
llvm_enter_fatal_on_oom();
/*
@@ -148,9 +160,17 @@ llvm_compile_expr(ExprState *state)
funcname = llvm_expand_funcname(context, "evalexpr");
+#ifdef LLVMJIT_SUBTRANS_RECURSIVE
+ if (state->flags & EEO_FLAG_HAVE_SUBTRANS)
+ {
+ recursive = true;
+ functypename = "TypeExprStateEvalStepFunc";
+ }
+#endif
+
/* create function */
eval_fn = LLVMAddFunction(mod, funcname,
- llvm_pg_var_func_type("TypeExprStateEvalFunc"));
+ llvm_pg_var_func_type(functypename));
LLVMSetLinkage(eval_fn, LLVMExternalLinkage);
LLVMSetVisibility(eval_fn, LLVMDefaultVisibility);
llvm_copy_attributes(AttributeTemplate, eval_fn);
@@ -161,6 +181,10 @@ llvm_compile_expr(ExprState *state)
v_state = LLVMGetParam(eval_fn, 0);
v_econtext = LLVMGetParam(eval_fn, 1);
v_isnullp = LLVMGetParam(eval_fn, 2);
+#ifdef LLVMJIT_SUBTRANS_RECURSIVE
+ if (recursive)
+ v_opno = LLVMGetParam(eval_fn, 3);
+#endif
LLVMPositionBuilderAtEnd(b, entry);
@@ -224,13 +248,58 @@ 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);
+#ifdef LLVMJIT_SUBTRANS_RECURSIVE
+ if (recursive)
+ {
+ /* count recursive opnos */
+ ExprEvalStep *op = &state->steps[opno];
+
+ if (ExecEvalStepOp(state, op) == EEOP_SUBTRANS)
+ num_recursive_opnos++;
+
+ }
+#endif
+ }
+
+#ifdef LLVMJIT_SUBTRANS_RECURSIVE
+ if (recursive)
+ {
+ /*
+ * Emit switch with jump to each recursive opno.
+ *
+ * switch (opno) {
+ * case N:
+ * goto b.op.N.start;
+ * ...
+ * }
+ */
+ LLVMValueRef v_switch =
+ LLVMBuildSwitch(b, v_opno, opblocks[start_opno],
+ num_recursive_opnos);
+
+ LLVMAddCase(v_switch, l_int32_const(start_opno),
+ opblocks[start_opno]);
+
+ for (int opno = start_opno; opno < state->steps_len; opno++)
+ {
+ ExprEvalStep *op = &state->steps[opno];
+
+ if (ExecEvalStepOp(state, op) == EEOP_SUBTRANS)
+ /* SUBTRANS expression starts in the next step */
+ LLVMAddCase(v_switch, l_int32_const(opno + 1),
+ opblocks[opno + 1]);
+ }
+ }
+ else
+#endif
/* 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 +2464,72 @@ llvm_compile_expr(ExprState *state)
LLVMBuildBr(b, opblocks[opno + 1]);
break;
+ case EEOP_SUBTRANS:
+ {
+ LLVMValueRef v_ret;
+ LLVMTypeRef v_functype;
+
+#ifndef LLVMJIT_SUBTRANS_RECURSIVE
+ ExprState *substate;
+
+ /* 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;
+#endif
+
+ Assert(state->flags & EEO_FLAG_HAVE_SUBTRANS);
+
+ /*
+ * 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]);
+
+#ifndef LLVMJIT_SUBTRANS_RECURSIVE
+ /*
+ * Skip subexpression steps, which are compiled
+ * separately.
+ */
+ opno = op->d.subtrans.jump_next - 1;
+#endif
+ break;
+ }
+
case EEOP_JSON_CONSTRUCTOR:
build_EvalXFunc(b, mod, "ExecEvalJsonConstructor",
v_state, op, v_econtext);
@@ -2428,12 +2563,16 @@ llvm_compile_expr(ExprState *state)
* remapping overhead.
*/
{
-
CompiledExprState *cstate = palloc0(sizeof(CompiledExprState));
cstate->context = context;
cstate->funcname = funcname;
+#ifdef LLVMJIT_SUBTRANS_RECURSIVE
+ if (recursive)
+ state->evalfunc = ExecRunCompileExprRecursive;
+ else
+#endif
state->evalfunc = ExecRunCompiledExpr;
state->evalfunc_private = cstate;
}
@@ -2447,6 +2586,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 +2623,56 @@ ExecRunCompiledExpr(ExprState *state, ExprContext *econtext, bool *isNull)
return func(state, econtext, isNull);
}
+#ifdef LLVMJIT_SUBTRANS_RECURSIVE
+static Datum
+ExecRunCompiledExprRecursive(ExprState *state, ExprContext *econtext, bool *isNull)
+{
+ ExprStateEvalStepFunc func = state->evalfunc_private;
+
+ return func(state, econtext, isNull, 0);
+}
+
+static Datum
+ExecRunCompileExprRecursive(ExprState *state, ExprContext *econtext, bool *isNull)
+{
+ CompiledExprState *cstate = state->evalfunc_private;
+ ExprStateEvalStepFunc func;
+
+ CheckExprStillValid(state, econtext);
+
+ llvm_enter_fatal_on_oom();
+ func = (ExprStateEvalStepFunc) llvm_get_function(cstate->context,
+ cstate->funcname);
+ llvm_leave_fatal_on_oom();
+ Assert(func);
+
+ /* remove indirection via this function for future calls */
+ state->evalfunc = ExecRunCompiledExprRecursive;
+ state->evalfunc_private = func;
+
+ return func(state, econtext, isNull, 0);
+}
+#endif
+
+static void
+ExecEvalSubtransCompiled(ExprState *state, ExprEvalStep *op,
+ ExprContext *econtext)
+{
+ bool isnull;
+#ifdef LLVMJIT_SUBTRANS_RECURSIVE
+ /* Recursively execute next opno */
+ ExprStateEvalStepFunc func = state->evalfunc_private;
+ int opno = op - state->steps + 1;
+
+ (void) func(state, econtext, &isnull, opno);
+#else
+ /* Execute subexpression steps using compiled ExprState */
+ ExprState *substate = op->d.subtrans.private_data;
+
+ (void) ExecEvalExpr(substate, econtext, &isnull);
+#endif
+}
+
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.c
index 37fe64654b6..bb96d46048f 100644
--- a/src/backend/jit/llvm/llvmjit_types.c
+++ b/src/backend/jit/llvm/llvmjit_types.c
@@ -49,6 +49,7 @@ PGFunction TypePGFunction;
size_t TypeSizeT;
bool TypeStorageBool;
ExprStateEvalFunc TypeExprStateEvalFunc;
+ExprStateEvalStepFunc TypeExprStateEvalStepFunc;
ExecEvalSubroutine TypeExecEvalSubroutine;
ExecEvalBoolSubroutine TypeExecEvalBoolSubroutine;
@@ -133,6 +134,7 @@ void *referenced_functions[] =
ExecEvalSysVar,
ExecEvalWholeRowVar,
ExecEvalXmlExpr,
+ ExecEvalSubtrans,
ExecEvalJsonConstructor,
ExecEvalJsonIsPredicate,
ExecEvalJsonExpr,
diff --git a/src/include/executor/execExpr.h b/src/include/executor/execExpr.h
index 51e8d93178e..a51232b84de 100644
--- a/src/include/executor/execExpr.h
+++ b/src/include/executor/execExpr.h
@@ -30,6 +30,8 @@ struct JsonConstructorExprState;
#define EEO_FLAG_INTERPRETER_INITIALIZED (1 << 1)
/* jump-threading is in use */
#define EEO_FLAG_DIRECT_THREADED (1 << 2)
+/* expression contains EEOP_SUBTRANS steps */
+#define EEO_FLAG_HAVE_SUBTRANS (1 << 3)
/* Typical API for out-of-line evaluation subroutines */
typedef void (*ExecEvalSubroutine) (ExprState *state,
@@ -242,6 +244,7 @@ typedef enum ExprEvalOp
EEOP_GROUPING_FUNC,
EEOP_WINDOW_FUNC,
EEOP_SUBPLAN,
+ EEOP_SUBTRANS,
EEOP_JSON_CONSTRUCTOR,
EEOP_IS_JSON,
EEOP_JSONEXPR,
@@ -680,6 +683,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
{
@@ -878,6 +890,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 ExecEvalJsonExpr(ExprState *state, ExprEvalStep *op,
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 01b1727fc09..d6976b92828 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -70,6 +70,11 @@ typedef Datum (*ExprStateEvalFunc) (struct ExprState *expression,
struct ExprContext *econtext,
bool *isNull);
+typedef Datum (*ExprStateEvalStepFunc) (struct ExprState *expression,
+ struct ExprContext *econtext,
+ bool *isNull,
+ int stepno);
+
/* Bits in ExprState->flags (see also execExpr.h for private flag bits): */
/* expression is for use with ExecQual() */
#define EEO_FLAG_IS_QUAL (1 << 0)
--
2.25.1
v7-0004-Split-JsonExpr-execution-into-steps.patchtext/x-patch; charset=UTF-8; name=v7-0004-Split-JsonExpr-execution-into-steps.patchDownload
From 5bbb775e1fb6b69d47fceae7c2c4060a309fb719 Mon Sep 17 00:00:00 2001
From: Nikita Glukhov <n.gluhov@postgrespro.ru>
Date: Fri, 12 Aug 2022 19:35:56 +0300
Subject: [PATCH v7 4/4] Split JsonExpr execution into steps
Add test for errors in ON EMPTY
Add test for errors in ON EMPTY
Some improvements to JsonExpr evaluation
* Store a pointer to the returning type input function FmgrInfo,
not the struct itself, in JsonExprState
* Evaluate various JsonExpr sub-expressions using parent ExprState
These include PASSING args, ON ERROR, ON EMPTY default expressions.
PASSING args are simple, because they all must be evaluated before
the JSON path evaluation can occur anyway, so pushing those
expressions to be uncondionally evaluated before the step to
evaluate JsonExpr proper suffices. Evaluation of the ON ERROR and
the ON EMPTY expressions is conditional on what JSON item pops out
of the path evaluation (ExecEvalJson()), so needs more logic to
gate the steps for their expressions that are pushed into the parent
ExprState, rather than be imlemented using separate ExprStates.
To that end, this moves the ON ERROR, ON EMPTY behavior decision logic
out of ExecEvalJson() and ExecEvalJsonExpr() into a new function
ExecEvalJsonExprBehavior().
* Final result coercion is now also computed as a separate step that
gets pushed into the parent ExprState. Though given that any
coercion evaluation errors must be handled the same way as the errors
of JSON item evaluation, that is, to be handled according the ON ERROR
behavior specification, a sub-transaction must be used around coercion
evaluation, so the ExprState to use for the same must have to be a
different one from the JsonExpr's. However, instead of using one
ExprState for each JsonCoercion member of JsonItemCoercions, only one
is used for the whole JsonItemCoercions. The individual JsonCoercion
members (or their Exprs) are handled by steps pushed into that
ExprState, of which only the one that applies to a given JSON item is
chosen at the runtime and others "jumped" over.
* Move the logic of deciding whether to use a sub-transaction for JSON
path evaluation and subsequent coercion to ExecInitExprRec() instead
of recomputing the same thing on every evaluation.
Catch errors in JsonExpr ON EMPTY behavior
Remove ExecEvalJsonExprContext
Remove local variable
Refactor JSonExpr execution: remove ItemCoercion step, use new Subtrans step
Remove JsonExprBehavior step
Use context for ExecInitJsonExpr()
Simplify json item coercions
Replace JsonExprCoerce steps with JsonExprIoCoerce and JsonExprPopulate steps
Move and rename ExecEvalJsonNeedsSubTransaction(), consider more cases in it
Inline ExecEvalJsonExpr() into ExecEvalJson()
Move local variables
Use LLVMBuildSwitch() in EEOP_JSONEXPR
---
src/backend/executor/execExpr.c | 691 ++++++++++++++++----
src/backend/executor/execExprInterp.c | 630 +++++++-----------
src/backend/jit/llvm/llvmjit_expr.c | 122 +++-
src/backend/jit/llvm/llvmjit_types.c | 3 +
src/backend/parser/parse_expr.c | 6 +
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 +
12 files changed, 1085 insertions(+), 623 deletions(-)
diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
index 1c9782de15f..1aa843698ab 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,149 +2562,8 @@ ExecInitExprRec(Expr *node, ExprState *state,
case T_JsonExpr:
{
JsonExpr *jexpr = castNode(JsonExpr, node);
- JsonExprState *jsestate;
- JsonCoercion *result_coercion = jexpr->result_coercion;
- ListCell *argexprlc;
- ListCell *argnamelc;
-
- /* JSON_TABLE preudo-function returns context item as a result */
- if (jexpr->op == JSON_TABLE_OP)
- {
- ExecInitExprRec((Expr *) jexpr->formatted_expr, state,
- resv, resnull);
- break;
- }
-
- jsestate = palloc0(sizeof(JsonExprState));
- jsestate->jsexpr = jexpr;
-
- scratch.opcode = EEOP_JSONEXPR;
- scratch.d.jsonexpr.jsestate = jsestate;
-
- ExecInitExprRec((Expr *) jexpr->formatted_expr, state,
- &jsestate->formatted_expr.value,
- &jsestate->formatted_expr.isnull);
-
- ExecInitExprRec((Expr *) jexpr->path_spec, state,
- &jsestate->pathspec.value,
- &jsestate->pathspec.isnull);
-
- if (result_coercion)
- {
- jsestate->result_coercion.type = result_coercion->ctype;
-
- if (result_coercion->expr)
- jsestate->result_coercion.estate =
- ExecInitExprWithCaseValue((Expr *) result_coercion->expr,
- state->parent,
- &jsestate->coercion_expr.value,
- &jsestate->coercion_expr.isnull);
-
- /* Skip coercion of NULLs for non-domain types */
- jsestate->skip_null_coercion =
- getBaseType(jexpr->returning->typid) == jexpr->returning->typid;
- }
- else
- jsestate->result_coercion.type = JSON_COERCION_NONE;
-
- /* Additional coercion of JSON_QUERY(OMIT QUOTES) */
- jsestate->io_coercion.type = JSON_COERCION_VIA_IO;
-
- if (jexpr->on_empty)
- {
- jsestate->default_on_empty =
- ExecInitExpr((Expr *) jexpr->on_empty->default_expr,
- state->parent);
-
- jsestate->default_on_empty_can_throw =
- ExecEvalExprCanThrowErrors(jexpr->on_empty->default_expr);
- }
-
- jsestate->default_on_error =
- ExecInitExpr((Expr *) jexpr->on_error->default_expr,
- state->parent);
-
-
- /* Initialize type input info if I/O coercion can be used */
- if (jexpr->omit_quotes ||
- jsestate->result_coercion.type == JSON_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;
-
- if (jexpr->coercions)
- {
- JsonCoercion **coercion;
- JsonCoercionState *cstate;
- Datum *caseval;
- bool *casenull;
-
- jsestate->item_coercions = palloc0(sizeof(*jsestate->item_coercions));
-
- caseval = &jsestate->coercion_expr.value;
- casenull = &jsestate->coercion_expr.isnull;
-
- for (cstate = &jsestate->item_coercions->null,
- coercion = &jexpr->coercions->null;
- coercion <= &jexpr->coercions->composite;
- coercion++, cstate++)
- {
- if (!*coercion)
- /* Missing coercion here means missing cast */
- cstate->type = JSON_COERCION_ERROR;
- else if ((*coercion)->ctype == JSON_COERCION_VIA_EXPR)
- {
- Assert((*coercion)->expr);
- cstate->estate =
- ExecInitExprWithCaseValue((Expr *) (*coercion)->expr,
- state->parent,
- caseval,
- casenull);
- cstate->type = JSON_COERCION_VIA_EXPR;
- }
- else if ((*coercion)->ctype == JSON_COERCION_NONE)
- cstate->type = JSON_COERCION_NONE;
- else
- {
- /* There should not be other coercion types */
- Assert(0);
- cstate->type = JSON_COERCION_ERROR;
- }
- }
- }
-
- ExprEvalPushStep(state, &scratch);
+ ExecInitJsonExpr(&scratch, jexpr, state, resv, resnull);
break;
}
@@ -4296,3 +4156,546 @@ 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 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->ctype == JSON_COERCION_NONE)
+ return -1; /* no coercion is needed */
+ }
+
+ /* Check whether expression really needs sub-transactions */
+ if (need_subtrans)
+ need_subtrans = ExecEvalExprCanThrowErrors(expr);
+
+ /* Emit EEOP_JSONEXPR_SUBTRANS step */
+ if (need_subtrans)
+ {
+ cxt->adjust_subtrans =
+ lappend_int(cxt->adjust_subtrans, state->steps_len);
+
+ state->flags |= EEO_FLAG_HAVE_SUBTRANS;
+ 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->ctype == JSON_COERCION_VIA_EXPR)
+ /* Push step(s) to compute coercion->expr */
+ ExecInitJsonCoercionExpr(state, (Expr *) coercion->expr,
+ cxt->resv, cxt->resnull);
+ else if (coercion->ctype == JSON_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->ctype == JSON_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 JsonItemCoercionsState *
+ExecInitJsonItemCoercions(ExecInitJsonContext *cxt,
+ JsonItemCoercions *coercions,
+ bool need_subtrans)
+{
+ JsonItemCoercionsState *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)
+ {
+ if ((*coercion)->ctype == JSON_COERCION_VIA_EXPR)
+ *step = ExecInitJsonCoercion(cxt, (Node *) *coercion,
+ need_subtrans);
+ else if ((*coercion)->ctype == JSON_COERCION_NONE)
+ *step = 0; /* No coercion is needed */
+ else
+ {
+ Assert(0); /* There should not be other coercion types */
+ *step = -1;
+ }
+ }
+ 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;
+ JsonCoercion *coercion = cxt->jsestate->jsexpr->result_coercion;
+ 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 the special
+ * output parameter, else append it to jump_done list.
+ */
+ if (coercion && coercion->ctype != JSON_COERCION_NONE)
+ *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;
+ JsonExprPreEvalState *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;
+ bool skip_null_coercion;
+
+ /* JSON_TABLE preudo-function returns context item as a result */
+ if (jexpr->op == JSON_TABLE_OP)
+ {
+ ExecInitExprRec((Expr *) jexpr->formatted_expr, state, resv, resnull);
+ return;
+ }
+
+ jsestate = palloc0(sizeof(JsonExprState));
+ jsestate->jsexpr = jexpr;
+ pre_eval = &jsestate->pre_eval;
+
+ cxt.state = state;
+ cxt.scratch = scratch;
+ cxt.jsestate = jsestate;
+ cxt.resv = resv;
+ cxt.resnull = resnull;
+ cxt.adjust_jumps = NIL;
+ cxt.adjust_subtrans = NIL;
+
+ /*
+ * 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->ctype != JSON_COERCION_VIA_IO))
+ {
+ JsonCoercion io_coercion = {0};
+
+ io_coercion.type = T_JsonCoercion;
+ io_coercion.ctype = JSON_COERCION_VIA_IO;
+
+ 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);
+
+ /* Skip coercion of NULLs for non-domain types */
+ skip_null_coercion =
+ getBaseType(jexpr->returning->typid) == jexpr->returning->typid;
+
+ 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;
+ as->d.jsonexpr.jump_coercion_null =
+ skip_null_coercion ? done_off : coercion_step_off;
+ as->d.jsonexpr.jump_coercion_null_subtrans =
+ skip_null_coercion ? done_off : coercion_subtrans_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 =
+ jexpr->on_empty->btype == JSON_BEHAVIOR_NULL &&
+ skip_null_coercion ? done_off : 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 =
+ jexpr->on_error->btype == JSON_BEHAVIOR_NULL &&
+ skip_null_coercion ? done_off : 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 ||
+ ExecEvalJsonNeedsSubTransaction(jexpr));
+}
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index 66898198f33..af0049abd33 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 */
- ExecEvalJsonExpr(state, op, econtext);
+ EEO_JUMP(ExecEvalJsonExpr(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();
}
@@ -4798,252 +4823,6 @@ ExecEvalJsonConstructor(ExprState *state, ExprEvalStep *op,
*op->resnull = isnull;
}
-/*
- * Evaluate a JSON error/empty behavior result.
- */
-static Datum
-ExecEvalJsonBehavior(ExprContext *econtext, JsonBehavior *behavior,
- ExprState *default_estate, 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:
- return ExecEvalExpr(default_estate, econtext, is_null);
-
- default:
- elog(ERROR, "unrecognized SQL/JSON behavior %d", behavior->btype);
- return (Datum) 0;
- }
-}
-
-
-typedef struct EvalJsonSubtransState
-{
- MemoryContext oldcontext;
- ResourceOwner oldowner;
-} EvalJsonSubtransState;
-
-static void
-ExecEvalJsonStartSubtrans(EvalJsonSubtransState *state)
-{
- /*
- * We should catch exceptions of category ERRCODE_DATA_EXCEPTION and
- * execute the corresponding ON ERROR behavior then.
- */
- state->oldcontext = CurrentMemoryContext;
- state->oldowner = CurrentResourceOwner;
-
- BeginInternalSubTransaction(NULL);
-
- /* Want to execute expressions inside function's memory context */
- /*
- * AFIXME: I don't think that's OK, there might be lots of leaked memory
- * etc.
- */
- MemoryContextSwitchTo(state->oldcontext);
-}
-
-static void
-ExecEvalJsonEndSubtrans(EvalJsonSubtransState *state)
-{
- /* Commit the inner transaction, return to outer xact context */
- ReleaseCurrentSubTransaction();
- MemoryContextSwitchTo(state->oldcontext);
- CurrentResourceOwner = state->oldowner;
-}
-
-static void
-ExecEvalJsonCatchError(EvalJsonSubtransState *state)
-{
- ErrorData *edata;
- int ecategory;
-
- /* Save error info in oldcontext */
- MemoryContextSwitchTo(state->oldcontext);
- edata = CopyErrorData();
- FlushErrorState();
-
- /* Abort the inner transaction */
- RollbackAndReleaseCurrentSubTransaction();
- MemoryContextSwitchTo(state->oldcontext);
- CurrentResourceOwner = state->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);
- }
-}
-
-/*
- * If `error` is not NULL (we need to catch errors), execute the
- * statement passed in the third argument inside a subtransaction,
- * otherwise do it outside. In the subtrans case an error classified as
- * being catchable by ExecEvalJsonCatchError() will set `error` to true.
- */
-#define JSE_OPT_SUBTRANS(error, jexpr, res, ...) \
- do { \
- bool *error_ = (error); \
- \
- if (!error_) \
- { \
- res = __VA_ARGS__; \
- } \
- else \
- { \
- EvalJsonSubtransState substate = {0}; \
- \
- /* Check correctness of ExecEvalJsonNeedsSubTransaction() */ \
- AssertMacro(ExecEvalJsonNeedsSubTransaction(jexpr)); \
- \
- ExecEvalJsonStartSubtrans(&substate); \
- \
- PG_TRY(); \
- { \
- res = __VA_ARGS__; \
- ExecEvalJsonEndSubtrans(&substate); \
- } \
- PG_CATCH(); \
- { \
- ExecEvalJsonCatchError(&substate); \
- \
- res = (Datum) 0; \
- *error_ = true; \
- } \
- PG_END_TRY(); \
- } \
- } while (0)
-
-/* Execute one of possible coercions or report an error if there is no cast */
-static Datum
-ExecEvalJsonExprCoercion(JsonExprState *jsestate, ExprContext *econtext,
- JsonCoercionState *coercion,
- Datum res, bool *resnull, bool *error)
-{
- switch (coercion->type)
- {
- case JSON_COERCION_NONE:
- return (Datum) res;
-
- case JSON_COERCION_VIA_EXPR:
- jsestate->coercion_expr.value = res;
- jsestate->coercion_expr.isnull = *resnull;
- return ExecEvalExpr(coercion->estate, econtext, resnull);
-
- case JSON_COERCION_VIA_IO:
- {
- /* Strip quotes from jsonb strings and call typinput() */
- char *str;
-
- if (*resnull)
- str = NULL;
- else
- str = JsonbUnquote(DatumGetJsonbP(res));
-
- return InputFunctionCall(&jsestate->input.func, str,
- jsestate->input.typioparam,
- jsestate->jsexpr->returning->typmod);
- }
-
- case JSON_COERCION_VIA_POPULATE:
- return json_populate_type(res, JSONBOID,
- jsestate->jsexpr->returning->typid,
- jsestate->jsexpr->returning->typmod,
- &jsestate->cache,
- econtext->ecxt_per_query_memory,
- resnull);
-
- default:
- Assert(coercion->type == JSON_COERCION_ERROR);
-
- /* Report an error if there is no cast to the output type */
- if (!error)
- ereport(ERROR,
- (errcode(ERRCODE_SQL_JSON_ITEM_CANNOT_BE_CAST_TO_TARGET_TYPE),
- errmsg("SQL/JSON item cannot be cast to target type")));
-
- *error = true;
- *resnull = true;
- return (Datum) 0;
- }
-}
-
-/*
- * 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;
-
- if (!var->evaluated)
- {
- MemoryContext oldcxt = var->mcxt ?
- MemoryContextSwitchTo(var->mcxt) : NULL;
-
- var->value = ExecEvalExpr(var->estate, var->econtext, &var->isnull);
- var->evaluated = true;
-
- if (oldcxt)
- MemoryContextSwitchTo(oldcxt);
- }
-
- if (var->isnull)
- {
- val->type = jbvNull;
- return 0;
- }
-
- JsonItemFromDatum(var->value, var->typid, var->typmod, val);
-
- *baseObject = *val;
- return id;
-}
-
/*
* Check whether we need to override default coercion in
* JSON_QUERY(OMIT QUOTES) case.
@@ -5064,78 +4843,84 @@ ExecJsonQueryNeedsIOCoercion(JsonExpr *jsexpr, Datum res, bool isnull)
}
/*
- * Prepare SQL/JSON item coercion to the JSON_VALUE output type.
- * Returned a coercion status, a datum of the corresponding SQL type
- * and a pointer to selected coercion state.
+ * 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
-ExecPrepareJsonValueCoercion(JsonExprState *jsestate, JsonbValue *item,
- JsonCoercionState **coercion, bool *resnull)
+ExecPrepareJsonValueCoercion(JsonbValue *item, ExprEvalStep *op,
+ JsonItemCoercionsState *steps, int *jump)
{
- struct JsonItemsCoercionStates *coercions = jsestate->item_coercions;
- JsonbValue buf;
+ JsonbValue buf;
- *resnull = false;
-
- if (!coercions)
+ /*
+ * Special case for json and jsonb types for which we don't have
+ * casts like numeric::jsonb etc.
+ */
+ if (!steps)
{
+ Jsonb *jb = JsonbValueToJsonb(item);
+
/*
- * Special case for json[b] output types. Simply use
- * default coercion from jsonb to the output type.
+ * Use result coercion from json[b] to the output
+ * type when casting to json[b] types or their
+ * domains.
*/
- return JsonbPGetDatum(JsonbValueToJsonb(item));
+
+ *jump = op->d.jsonexpr.jump_coercion_subtrans;
+ return JsonbPGetDatum(jb);;
}
- /* Use coercion from SQL/JSON item type to the output type */
+ /* 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:
- Assert(0); /* must be handled by the caller */
- *coercion = &coercions->null;
- *resnull = true;
+ *jump = steps->null;
return (Datum) 0;
case jbvString:
- *coercion = &coercions->string;
- return PointerGetDatum(cstring_to_text_with_len(item->val.string.val,
- item->val.string.len));
+ *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;
+ *jump = steps->numeric;
return NumericGetDatum(item->val.numeric);
case jbvBool:
- *coercion = &coercions->boolean;
+ *jump = steps->boolean;
return BoolGetDatum(item->val.boolean);
case jbvDatetime:
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",
@@ -5149,8 +4934,9 @@ ExecPrepareJsonValueCoercion(JsonExprState *jsestate, JsonbValue *item,
case jbvObject:
case jbvBinary:
Assert(0); /* non-scalars must be rejected by JsonPathValue() */
- *coercion = &coercions->composite;
- return JsonbPGetDatum(JsonbValueToJsonb(item));
+ *jump = steps->composite;
+ return *jump < 0 ? (Datum) 0 :
+ JsonbPGetDatum(JsonbValueToJsonb(item));
default:
elog(ERROR, "unexpected jsonb value type %d", item->type);
@@ -5158,129 +4944,159 @@ ExecPrepareJsonValueCoercion(JsonExprState *jsestate, JsonbValue *item,
}
}
-static Datum
-ExecEvalJsonExprInternal(JsonExprState *jsestate, ExprContext *econtext,
- JsonPath *path, Datum item, bool *resnull,
- bool *error)
+/*
+ * Evaluate a SQL/JSON function.
+ */
+int
+ExecEvalJsonExpr(ExprState *state, ExprEvalStep *op)
{
+ JsonExprState *jsestate = op->d.jsonexpr.jsestate;
+ JsonExprPreEvalState *pre_eval = &jsestate->pre_eval;
JsonExpr *jexpr = jsestate->jsexpr;
- JsonCoercionState *coercion = &jsestate->result_coercion;
- 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;
- *resnull = true;
+ *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)
- return (Datum) 0;
+ res = JsonPathQuery(item, path, jexpr->wrapper, &empty,
+ p_error, pre_eval->args);
+
+ if (error)
+ return op->d.jsonexpr.jump_onerror;
if (empty)
break;
- *resnull = !DatumGetPointer(res);
+ *op->resnull = !DatumGetPointer(res);
+ *op->resvalue = res;
/* Override default coercion in OMIT QUOTES case */
- if (ExecJsonQueryNeedsIOCoercion(jexpr, res, *resnull))
- coercion = &jsestate->io_coercion;
+ if (ExecJsonQueryNeedsIOCoercion(jexpr, res, *op->resnull))
+ return op->d.jsonexpr.jump_coercion_via_io;
- break;
+ /* Maybe skip coercion of NULLs for non-domain types */
+ if (*op->resnull)
+ return op->d.jsonexpr.jump_coercion_null_subtrans;
+
+ /* Evaluate coercion expression inside subtransaction */
+ return op->d.jsonexpr.jump_coercion_subtrans;
case JSON_VALUE_OP:
{
- 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 */
+ if (empty)
break;
+ /*
+ * Execute coercion for NULL inside subtransaction, it
+ * can be skiped for non-domain types.
+ */
+ if (!jbv)
+ return op->d.jsonexpr.jump_coercion_null_subtrans;
+
+
Assert(!empty);
Assert(jbv->type != jbvNull);
- res = ExecPrepareJsonValueCoercion(jsestate, jbv,
- &coercion, resnull);
- break;
+ /*
+ * Prepare coercion from SQL/JSON item type to the
+ * output SQL type.
+ */
+ res = ExecPrepareJsonValueCoercion(jbv, op,
+ jsestate->item_coercions,
+ &jump_item_coercion);
+
+ *op->resvalue = res;
+ *op->resnull = false;
+
+ /* Jump to zero here means jump to the end. */
+ if (jump_item_coercion == 0)
+ return op->d.jsonexpr.jump_done;
+
+ /*
+ * Error out if no cast exists to coerce SQL/JSON item
+ * to the the output type.
+ */
+ if (jump_item_coercion < 0)
+ {
+ *op->resnull = true;
+
+ if (catch_errors)
+ return op->d.jsonexpr.jump_onerror;
+
+ ereport(ERROR,
+ (errcode(ERRCODE_SQL_JSON_ITEM_CANNOT_BE_CAST_TO_TARGET_TYPE),
+ errmsg("SQL/JSON item cannot be cast to target type")));
+ }
+
+ /* 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);
- break; /* always use result coercion */
- }
+ *op->resnull = error;
+ *op->resvalue = BoolGetDatum(exists);
- default:
- elog(ERROR, "unrecognized SQL/JSON expression op %d", jexpr->op);
- return (Datum) 0;
- }
+ if (error)
+ return op->d.jsonexpr.jump_onerror;
- if (empty)
- {
- Assert(jexpr->on_empty); /* it is not JSON_EXISTS */
+ /*
+ * Execute coercion of NULL inside subtransaction, it
+ * can be skiped for non-domain types.
+ */
+ if (*op->resnull)
+ return op->d.jsonexpr.jump_coercion_null_subtrans;
- if (jexpr->on_empty->btype == JSON_BEHAVIOR_ERROR)
- {
- if (error)
- {
- *error = true;
- return (Datum) 0;
+ /* Evaluate coercion inside subtransaction, if any */
+ return op->d.jsonexpr.jump_coercion_subtrans;
}
- ereport(ERROR,
- (errcode(ERRCODE_NO_SQL_JSON_ITEM),
- errmsg("no SQL/JSON item")));
- }
-
- if (jexpr->on_empty->btype == JSON_BEHAVIOR_DEFAULT)
- {
- /*
- * Execute DEFAULT expression in a subtransation if needed.
- * Coercion is not needed here, because expression is
- * already coerced to the target type by the parser.
- */
- JSE_OPT_SUBTRANS(jsestate->default_on_empty_can_throw ? error : NULL,
- jexpr, res,
- ExecEvalExpr(jsestate->default_on_empty,
- econtext, resnull));
- return res;
- }
-
- /* Execute ON EMPTY behavior, coercion is default */
- res = ExecEvalJsonBehavior(econtext, jexpr->on_empty,
- jsestate->default_on_empty,
- resnull);
+ default:
+ elog(ERROR, "unrecognized SQL/JSON expression op %d", jexpr->op);
+ return op->d.jsonexpr.jump_done;
}
- /*
- * Execute resulting coercion in a subtransaction if `error` != NULL.
- * In case of cast errors, there is no need to use subtransaction.
- */
- if (coercion->type != JSON_COERCION_NONE)
+ /* Execute ON EMPTY behavior if no SQL/JSON items were found */
+ Assert(empty);
+ Assert(jexpr->on_empty); /* it is not JSON_EXISTS */
+
+ if (jexpr->on_empty->btype == JSON_BEHAVIOR_ERROR)
{
- /* Skip coercion of NULLs for non-domain types */
- if (*resnull && jsestate->skip_null_coercion)
- return (Datum) 0;
+ /* Throw an error or jump to ON ERROR handler */
+ if (catch_errors)
+ return op->d.jsonexpr.jump_onerror;
- JSE_OPT_SUBTRANS(coercion->type != JSON_COERCION_ERROR ? error : NULL,
- jexpr, res,
- ExecEvalJsonExprCoercion(jsestate, econtext,
- coercion, res,
- resnull, error));
+ ereport(ERROR,
+ (errcode(ERRCODE_NO_SQL_JSON_ITEM),
+ errmsg("no SQL/JSON item")));
}
- return res;
+ /* Evaluate ON EMPTY behavior */
+ return op->d.jsonexpr.jump_onempty;
}
+/* Skip calling ExecEvalJson() on a JsonExpr? */
bool
ExecEvalExprCanThrowErrors(Node *expr)
{
@@ -5319,66 +5135,60 @@ ExecEvalJsonNeedsSubTransaction(JsonExpr *jsexpr)
return false; /* subtransactios should not be used */
}
-/* ----------------------------------------------------------------
- * ExecEvalJsonExpr
- * ----------------------------------------------------------------
- */
-void
-ExecEvalJsonExpr(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
+bool
+ExecEvalJsonExprSkip(ExprState *state, ExprEvalStep *op)
{
- JsonExprState *jsestate = op->d.jsonexpr.jsestate;
- JsonExpr *jexpr = jsestate->jsexpr;
- Datum item;
- Datum res;
- JsonPath *path;
- ListCell *lc;
- bool error = false;
- bool throwErrors = jexpr->on_error->btype == JSON_BEHAVIOR_ERROR;
+ JsonExprState *jsestate = op->d.jsonexpr_skip.jsestate;
- if (jsestate->formatted_expr.isnull || jsestate->pathspec.isnull)
+ /*
+ * 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->resnull = true;
*op->resvalue = (Datum) 0;
-
- /* execute domain checks for NULLs */
- (void) ExecEvalJsonExprCoercion(jsestate, econtext,
- &jsestate->result_coercion,
- *op->resvalue, op->resnull, NULL);
-
- Assert(*op->resnull);
- return;
+ *op->resnull = true;
+ return true;
}
- item = jsestate->formatted_expr.value;
- path = DatumGetJsonPathP(jsestate->pathspec.value);
+ /*
+ * Go evaluate the PASSING args if any and subsequently JSON path
+ * itself.
+ */
+ return false;
+}
+
+/* Apply I/O coercion to a JSON item */
+void
+ExecEvalJsonExprIOCoerce(ExprState *state, ExprEvalStep *op)
+{
+ /* Strip quotes and call typinput function */
+ char *str = NULL;
- /* 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);
}
- res = ExecEvalJsonExprInternal(jsestate, econtext,
- path, item, op->resnull,
- throwErrors ? NULL : &error);
- if (error)
- {
- Assert(jexpr->on_error->btype != JSON_BEHAVIOR_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(jsestate, econtext,
- &jsestate->result_coercion,
- res, op->resnull, 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.c
index 6568228b996..70d84d95175 100644
--- a/src/backend/jit/llvm/llvmjit_expr.c
+++ b/src/backend/jit/llvm/llvmjit_expr.c
@@ -2542,8 +2542,126 @@ llvm_compile_expr_step(ExprState *state, int start_opno)
LLVMBuildBr(b, opblocks[opno + 1]);
break;
- case EEOP_JSONEXPR:
- build_EvalXFunc(b, mod, "ExecEvalJsonExpr",
+ case EEOP_JSONEXPR_PATH:
+ {
+ LLVMValueRef v_ret;
+ LLVMValueRef v_switch;
+ JsonItemCoercionsState *jcstate =
+ op->d.jsonexpr.jsestate->item_coercions;
+ int jumps[6 + sizeof(*jcstate) / sizeof(jcstate->null)];
+ int njumps = 0;
+
+ /*
+ * ExecEvalJson() returns number of the step we
+ * need to jump.
+ */
+ v_ret = build_EvalXFunc(b, mod, "ExecEvalJsonExpr",
+ v_state, op);
+
+ /*
+ * Collect all unique jumps. All jumps can be
+ * equal to jump_done.
+ */
+ jumps[njumps++] = op->d.jsonexpr.jump_done;
+
+ if (op->d.jsonexpr.jump_coercion != op->d.jsonexpr.jump_done)
+ jumps[njumps++] = op->d.jsonexpr.jump_coercion;
+
+ /*
+ * jump_coercion_subtrans and jump_coercion can be
+ * the same if there are no subtransactions.
+ */
+ if (op->d.jsonexpr.jump_coercion_subtrans != op->d.jsonexpr.jump_done &&
+ op->d.jsonexpr.jump_coercion_subtrans != op->d.jsonexpr.jump_coercion)
+ jumps[njumps++] = op->d.jsonexpr.jump_coercion_subtrans;
+
+ /*
+ * jump_coercion_via_io and jump_coercion_subtrans
+ * can be the same whn only I/O coercion to output
+ * type is available. jump_coercion_via_io can
+ * also be negative.
+ */
+ if (op->d.jsonexpr.jump_coercion_via_io != op->d.jsonexpr.jump_done &&
+ op->d.jsonexpr.jump_coercion_via_io != op->d.jsonexpr.jump_coercion_subtrans &&
+ op->d.jsonexpr.jump_coercion_via_io >= 0)
+ jumps[njumps++] = op->d.jsonexpr.jump_coercion_via_io;
+
+ if (op->d.jsonexpr.jump_onempty != op->d.jsonexpr.jump_done)
+ jumps[njumps++] = op->d.jsonexpr.jump_onempty;
+
+ if (op->d.jsonexpr.jump_onerror != op->d.jsonexpr.jump_done)
+ jumps[njumps++] = op->d.jsonexpr.jump_onerror;
+
+ /*
+ * jump_coercion_null and jump_coercion_null_subtrans
+ * are always duplicate.
+ */
+
+ /* Collect non-zero jumps to item coercions, if any */
+ if (jcstate)
+ {
+ for (int *p_jump = &jcstate->null;
+ p_jump <= &jcstate->composite;
+ p_jump++)
+ {
+ if (*p_jump > 0)
+ jumps[njumps++] = *p_jump;
+ }
+ }
+
+ /* Emit switch for all possible jumps */
+ v_switch = LLVMBuildSwitch(b, v_ret,
+ opblocks[op->d.jsonexpr.jump_done],
+ njumps);
+
+ for (int i = 0; i < njumps; i++)
+ LLVMAddCase(v_switch,
+ l_int32_const(jumps[i]),
+ opblocks[jumps[i]]);
+
+ 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.c
index bb96d46048f..d7653288dd8 100644
--- a/src/backend/jit/llvm/llvmjit_types.c
+++ b/src/backend/jit/llvm/llvmjit_types.c
@@ -138,6 +138,9 @@ void *referenced_functions[] =
ExecEvalJsonConstructor,
ExecEvalJsonIsPredicate,
ExecEvalJsonExpr,
+ ExecEvalJsonExprSkip,
+ ExecEvalJsonExprIOCoerce,
+ ExecEvalJsonExprPopulate,
MakeExpandedObjectReadOnlyInternal,
slot_getmissingattrs,
slot_getsomeattrs_int,
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index aff6fc1a1c4..cad3c56a38f 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -4384,6 +4384,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);
@@ -4465,6 +4470,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);
Node *coercion_expr;
diff --git a/src/backend/utils/adt/jsonpath_exec.c b/src/backend/utils/adt/jsonpath_exec.c
index 5b6a4805721..3390038ff4c 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.h
index a51232b84de..3aa5cddbb8e 100644
--- a/src/include/executor/execExpr.h
+++ b/src/include/executor/execExpr.h
@@ -247,7 +247,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,
@@ -704,12 +707,50 @@ 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;
+ int jump_coercion_null;
+ int jump_coercion_null_subtrans;
} 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;
@@ -769,68 +810,57 @@ typedef struct JsonConstructorExprState
int nargs;
} JsonConstructorExprState;
-typedef struct JsonCoercionState
+/*
+ * Information computed before evaluating a JsonExpr expression.
+ */
+typedef struct JsonExprPreEvalState
{
- ExprState *estate; /* coercion expression state, if any */
- JsonCoercionType type;
-} JsonCoercionState;
+ /* 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 JsonItemCoercionsState
+{
+ /* 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;
+} JsonItemCoercionsState;
/* 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 */
- coercion_expr, /* input for JSON item coercion */
- pathspec; /* path specification value */
-
- 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() */
+ JsonExprPreEvalState pre_eval;
/*
- * States for coercion of SQL/JSON items produced in JSON_VALUE
- * directly to the output type.
- *
- * `item_coercions == NULL` means output type is json[b] or its
- * domain and we use `result_coercion`.
+ * Used only in JSON_VALUE(). `item_coercions == NULL` means that
+ * output type is json[b] or its domain and we use `result_coercion`.
*/
- struct JsonItemsCoercionStates
- {
- JsonCoercionState
- null,
- string,
- numeric,
- boolean,
- date,
- time,
- timetz,
- timestamp,
- timestamptz,
- composite;
- } *item_coercions;
-
- /* Default output coercion to use, can be overriden in JSON_QUERY */
- JsonCoercionState result_coercion;
-
- /* Special I/O coercion state for JSON_QUERY OMIT QUOTES */
- JsonCoercionState io_coercion;
-
- /* Can expression in DEFAULT ON EMPTY throw errors? */
- bool default_on_empty_can_throw;
-
- /* Can we skip coercion of NULLs (non-domain output type)? */
- bool skip_null_coercion;
+ JsonItemCoercionsState *item_coercions;
} JsonExprState;
/* functions in execExpr.c */
@@ -895,15 +925,13 @@ extern bool ExecEvalSubtrans(ExprState *state, ExprEvalStep *op,
ExecEvalSubroutine func);
extern void ExecEvalJsonConstructor(ExprState *state, ExprEvalStep *op,
ExprContext *econtext);
-extern void ExecEvalJsonExpr(ExprState *state, ExprEvalStep *op,
- ExprContext *econtext);
extern bool ExecEvalJsonNeedsSubTransaction(JsonExpr *jsexpr);
extern bool ExecEvalExprCanThrowErrors(Node *expr);
-extern Datum ExecEvalExprPassingCaseValue(ExprState *estate,
- ExprContext *econtext, bool *isnull,
- Datum caseval_datum,
- bool caseval_isnull);
-
+extern int ExecEvalJsonExpr(ExprState *state, ExprEvalStep *op);
+extern bool ExecEvalJsonExprSkip(ExprState *state, ExprEvalStep *op);
+extern void ExecEvalJsonExprIOCoerce(ExprState *state, ExprEvalStep *op);
+extern void ExecEvalJsonExprPopulate(ExprState *state, ExprEvalStep *op,
+ ExprContext *econtext);
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.h
index 0a40d59be26..0824a6186d1 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -1576,8 +1576,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
{
@@ -1591,7 +1594,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.h
index 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.h
index 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.out
index 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.sql
index 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
Hi,
On 8/17/22 11:45 PM, Nikita Glukhov wrote:
Hi,
On 17.08.2022 04:45, Jonathan S. Katz wrote:
On 8/15/22 10:14 PM, Andres Freund wrote:
I pushed a few cleanups to
https://github.com/anarazel/postgres/commits/json
while I was hacking on this (ignore that it's based on the meson
tree, that's
just faster for me). Some of them might not be applicable anymore,
but it
might still make sense for you to look at.With RMT hat on, this appears to be making progress. A few questions /
comments for the group:1. Nikita: Did you have a chance to review Andres's changes as well?
Yes, I have reviewed Andres's changes, they all are ok.
Thank you!
Then I started to do on the top of it other fixes that help to avoid
subtransactions when they are not needed. And it ended in the new
refactoring of coercion code. Also I moved here from v6-0003 fix of
ExecEvalJsonNeedSubtransaction() which considers more cases.
Great.
Andres, Robert: Do these changes address your concerns about the use of
substransactions and reduce the risk of xid wraparound?
On 16.08.2022 05:14, Andres Freund wrote:
But for JIT I still had to construct additional ExprState with a
function compiled from subexpression steps.Why did you have to do this?
I simply did not dare to implement compilation of recursively-callable
function with additional parameter stepno. In the v8 patch I did it
by adding a switch with all possible jump addresses of EEOP_SUBTRANS
steps in the beginning of the function. And it really seems to work
faster, but needs more exploration. See patch 0003, where both
variants preserved using #ifdef.The desciprion of the v7 patches:
0001 Simplify JsonExpr execution
Andres's changes + mine:
- Added JsonCoercionType enum, fields like via_io replaced with it
- Emit only context item steps in JSON_TABLE_OP case
- Skip coercion of NULLs to non-domain types (is it correct?)0002 Fix returning of json[b] domains in JSON_VALUE:
simply rebase of v6 onto 00010003 Add EEOP_SUBTRANS executor step
v6 + new recursive JIT0004 Split JsonExpr execution into steps
simply rebase of v6 + used LLMBuildSwitch() in EEOP_JSONEXPR
What do folks think of these patches?
Thanks,
Jonathan
On 8/19/22 10:11 AM, Jonathan S. Katz wrote:
Hi,
On 8/17/22 11:45 PM, Nikita Glukhov wrote:
Hi,
On 17.08.2022 04:45, Jonathan S. Katz wrote:
On 8/15/22 10:14 PM, Andres Freund wrote:
I pushed a few cleanups to
https://github.com/anarazel/postgres/commits/json
while I was hacking on this (ignore that it's based on the meson
tree, that's
just faster for me). Some of them might not be applicable anymore,
but it
might still make sense for you to look at.With RMT hat on, this appears to be making progress. A few questions
/ comments for the group:1. Nikita: Did you have a chance to review Andres's changes as well?
Yes, I have reviewed Andres's changes, they all are ok.
Thank you!
Then I started to do on the top of it other fixes that help to avoid
subtransactions when they are not needed. And it ended in the new
refactoring of coercion code. Also I moved here from v6-0003 fix of
ExecEvalJsonNeedSubtransaction() which considers more cases.Great.
Andres, Robert: Do these changes address your concerns about the use of
substransactions and reduce the risk of xid wraparound?On 16.08.2022 05:14, Andres Freund wrote:
But for JIT I still had to construct additional ExprState with a
function compiled from subexpression steps.Why did you have to do this?
I simply did not dare to implement compilation of recursively-callable
function with additional parameter stepno. In the v8 patch I did it
by adding a switch with all possible jump addresses of EEOP_SUBTRANS
steps in the beginning of the function. And it really seems to work
faster, but needs more exploration. See patch 0003, where both
variants preserved using #ifdef.The desciprion of the v7 patches:
0001 Simplify JsonExpr execution
Andres's changes + mine:
- Added JsonCoercionType enum, fields like via_io replaced with it
- Emit only context item steps in JSON_TABLE_OP case
- Skip coercion of NULLs to non-domain types (is it correct?)0002 Fix returning of json[b] domains in JSON_VALUE:
simply rebase of v6 onto 00010003 Add EEOP_SUBTRANS executor step
v6 + new recursive JIT0004 Split JsonExpr execution into steps
simply rebase of v6 + used LLMBuildSwitch() in EEOP_JSONEXPRWhat do folks think of these patches?
Andres, Andrew, Amit, Robert -- as you have either worked on this or
expressed opinions -- any thoughts on this current patch set?
Thanks,
Jonathan
On Tue, Aug 23, 2022 at 10:52 AM Jonathan S. Katz <jkatz@postgresql.org> wrote:
Andres, Andrew, Amit, Robert -- as you have either worked on this or
expressed opinions -- any thoughts on this current patch set?
FWIW, I've started looking at these patches.
--
Thanks, Amit Langote
EDB: http://www.enterprisedb.com
Hi,
On 2022-08-22 21:52:01 -0400, Jonathan S. Katz wrote:
Andres, Andrew, Amit, Robert -- as you have either worked on this or
expressed opinions -- any thoughts on this current patch set?
To me it feels like there's a probably too much work here to cram it at this
point. If several other committers shared the load of working on this it'd
perhaps be doable, but I've not seen many volunteers.
Greetings,
Andres Freund
On Mon, Aug 22, 2022 at 07:57:29PM -0700, Andres Freund wrote:
To me it feels like there's a probably too much work here to cram it at this
point. If several other committers shared the load of working on this it'd
perhaps be doable, but I've not seen many volunteers.
While 0002 is dead simple, I am worried about the complexity created
by 0001, 0003 (particularly tightening subtransactions with a
CASE_EEOP) and 0004 at this late stage of the release process:
/messages/by-id/7d83684b-7932-9f29-400b-0beedfafcdd4@postgrespro.ru
This is not a good sign after three betas for a feature as complex as
this one.
--
Michael
Hi Nikita,
On Thu, Aug 18, 2022 at 12:46 PM Nikita Glukhov <n.gluhov@postgrespro.ru> wrote:
The desciprion of the v7 patches:
0001 Simplify JsonExpr execution
Andres's changes + mine:
- Added JsonCoercionType enum, fields like via_io replaced with it
- Emit only context item steps in JSON_TABLE_OP case
- Skip coercion of NULLs to non-domain types (is it correct?)
I like the parser changes to add JsonCoercionType, because that makes
ExecEvalJsonExprCoercion() so much simpler to follow.
In coerceJsonExpr():
+ if (!allow_io_coercion)
+ return NULL;
+
Might it make more sense to create a JsonCoercion even in this case
and assign it the type JSON_COERCION_ERROR, rather than allow the
coercion to be NULL and doing the following in ExecInitExprRec():
+ if (!*coercion)
+ /* Missing coercion here means missing cast */
+ cstate->type = JSON_COERCION_ERROR;
Likewise in transformJsonFuncExpr():
+ if (coercion_expr != (Node *) placeholder)
+ {
+ jsexpr->result_coercion = makeNode(JsonCoercion);
+ jsexpr->result_coercion->expr = coercion_expr;
+ jsexpr->result_coercion->ctype = JSON_COERCION_VIA_EXPR;
+ }
How about creating a JSON_COERCION_NONE coercion in the else block of
this, just like coerceJsonExpr() does?
Related to that, the JSON_EXISTS_OP block in
ExecEvalJsonExprInternal() sounds to assume that result_coercion would
always be non-NULL, per the comment in the last line:
case JSON_EXISTS_OP:
{
bool exists = JsonPathExists(item, path,
jsestate->args,
error);
*resnull = error && *error;
res = BoolGetDatum(exists);
break; /* always use result coercion */
}
...but it won't be if the above condition is false?
0002 Fix returning of json[b] domains in JSON_VALUE:
simply rebase of v6 onto 0001
Especially after seeing the new comments in this one, I'm wondering if
it makes sense to rename result_coercion to, say, default_coercion?
0003 Add EEOP_SUBTRANS executor step
v6 + new recursive JIT0004 Split JsonExpr execution into steps
simply rebase of v6 + used LLMBuildSwitch() in EEOP_JSONEXPR
Will need to spend more time looking at these.
--
Thanks, Amit Langote
EDB: http://www.enterprisedb.com
On Tue, Aug 23, 2022 at 4:48 PM Amit Langote <amitlangote09@gmail.com> wrote:
On Thu, Aug 18, 2022 at 12:46 PM Nikita Glukhov <n.gluhov@postgrespro.ru> wrote:
The desciprion of the v7 patches:
0003 Add EEOP_SUBTRANS executor step
v6 + new recursive JIT0004 Split JsonExpr execution into steps
simply rebase of v6 + used LLMBuildSwitch() in EEOP_JSONEXPRWill need to spend more time looking at these.
0004 adds the following to initJsonItemCoercions():
+ /* When returning JSON types, no need to initialize coercions */
+ /* XXX domain types on json/jsonb */
+ if (returning->typid == JSONBOID || returning->typid == JSONOID)
+ return NULL;
But maybe it's dead code, because 0001 has this:
+ if (jsexpr->returning->typid != JSONOID &&
+ jsexpr->returning->typid != JSONBOID)
+ jsexpr->coercions =
+ initJsonItemCoercions(pstate, jsexpr->returning,
+ exprType(contextItemExpr));
+ /* We need to handle RETURNING int etc. */
Is this a TODO and what does it mean?
+ * "JsonCoercion == NULL" means no cast is available.
+ * "JsonCoercion.expr == NULL" means no coercion is needed.
As said in my previous email, I wonder if these cases are better
handled by adding JSON_COERCION_ERROR and JSON_COERCION_NONE
coercions?
+/* Skip calling ExecEvalJson() on a JsonExpr? */
ExecEvalJsonExpr()
Will look more.
--
Thanks, Amit Langote
EDB: http://www.enterprisedb.com
On 8/23/22 12:13 AM, Michael Paquier wrote:
On Mon, Aug 22, 2022 at 07:57:29PM -0700, Andres Freund wrote:
To me it feels like there's a probably too much work here to cram it at this
point. If several other committers shared the load of working on this it'd
perhaps be doable, but I've not seen many volunteers.While 0002 is dead simple, I am worried about the complexity created
by 0001, 0003 (particularly tightening subtransactions with a
CASE_EEOP) and 0004 at this late stage of the release process:
/messages/by-id/7d83684b-7932-9f29-400b-0beedfafcdd4@postgrespro.ruThis is not a good sign after three betas for a feature as complex as
this one.
I see Amit is taking a closer look at the patches.
The RMT had its regular meeting today and discussed the state of
progress on this and how it reflects release timing. Our feeling is that
regardless if the patchset is included/reverted, it would necessitate a
Beta 4 (to be discussed with release team). While no Beta 4 date is set,
given where we are this would probably push the release into early
October to allow for adequate testing time.
To say it another way, if we want to ensure we can have a 15.1 in the
November update releases, we need to make a decision soon on how we want
to proceed.
Thanks,
Jonathan
On Mon, Aug 22, 2022 at 9:52 PM Jonathan S. Katz <jkatz@postgresql.org> wrote:
Andres, Robert: Do these changes address your concerns about the use of
substransactions and reduce the risk of xid wraparound?Andres, Andrew, Amit, Robert -- as you have either worked on this or
expressed opinions -- any thoughts on this current patch set?
I do not think that using subtransactions as part of the expression
evaluation process is a sound idea pretty much under any
circumstances. Maybe if the subtransations aren't commonly created and
don't usually get XIDs there wouldn't be a big problem in practice,
but it's an awfully heavyweight operation to be done inside expression
evaluation even in corner cases. I think that if we need to make
certain operations that would throw errors not throw errors, we need
to refactor interfaces until it's possible to return an error
indicator up to the appropriate level, not just let the error be
thrown and catch it.
The patches in question are thousands of lines of new code that I
simply do not have time or interest to review in detail. I didn't
commit this feature, or write this feature, or review this feature.
I'm not familiar with any of the code. To really know what's going on
here, I would need to review not only the new patches but also all the
code in the original commits, and probably some of the preexisting
code from before those commits that I have never examined in the past.
That would take me quite a few months even if I had absolutely nothing
else to do. And because I haven't been involved in this patch set in
any way, I don't think it's really my responsibility.
At the end of the day, the RMT is going to have to take a call here.
It seems to me that Andres's concerns about code quality and lack of
comments are probably somewhat legitimate, and in particular I do not
think the use of subtransactions is a good idea. I also don't think
that trying to fix those problems or generally improve the code by
committing thousands of lines of new code in August when we're
targeting a release in September or October is necessarily a good
idea. But I'm also not in a position to say that the project is going
to be irreparably damaged if we just ship what we've got, perhaps
after fixing the most acute problems that we currently know about.
This is after all relatively isolated from the rest of the system.
Fixing the stuff that touches the core executor is probably pretty
important, but beyond that, the worst thing that happens is the
feature sucks and people who try to use it have bad experiences. That
would be bad, and might be a sufficient reason to revert, but it's not
nearly as bad as, say, the whole system being slow, or data loss for
every user, or something like that. And we do have other bad code in
the system. Is this a lot worse? I'm not in a position to say one way
or the other.
--
Robert Haas
EDB: http://www.enterprisedb.com
Robert Haas <robertmhaas@gmail.com> writes:
At the end of the day, the RMT is going to have to take a call here.
It seems to me that Andres's concerns about code quality and lack of
comments are probably somewhat legitimate, and in particular I do not
think the use of subtransactions is a good idea. I also don't think
that trying to fix those problems or generally improve the code by
committing thousands of lines of new code in August when we're
targeting a release in September or October is necessarily a good
idea. But I'm also not in a position to say that the project is going
to be irreparably damaged if we just ship what we've got, perhaps
after fixing the most acute problems that we currently know about.
The problem here is that this was going to be a headline new feature
for v15. Shipping what apparently is only an alpha-quality implementation
seems pretty problematic unless we advertise it as such, and that's
not something we've done very much in the past. I also wonder how
much any attempts at fixing it later would be constrained by concerns
about compatibility with the v15 version.
... And we do have other bad code in the system.
Can't deny that, but a lot of it is legacy code that we wish we could
rip out and can't because backwards compatibility. This is not legacy
code ... not yet anyway.
As you say, we've delegated this sort of decision to the RMT, but
if I were on the RMT I'd be voting to revert.
regards, tom lane
Hi,
On 2022-08-23 11:08:31 -0400, Tom Lane wrote:
As you say, we've delegated this sort of decision to the RMT, but
if I were on the RMT I'd be voting to revert.
Yea, I don't really see an alternative at this point. If we really wanted we
could try to cut the more complicated pieces out, e.g., by only supporting
ERROR ON ERROR, but I'm not sure it'd get us far enough.
Greetings,
Andres Freund
On 2022-08-23 Tu 11:08, Tom Lane wrote:
Robert Haas <robertmhaas@gmail.com> writes:
At the end of the day, the RMT is going to have to take a call here.
It seems to me that Andres's concerns about code quality and lack of
comments are probably somewhat legitimate, and in particular I do not
think the use of subtransactions is a good idea. I also don't think
that trying to fix those problems or generally improve the code by
committing thousands of lines of new code in August when we're
targeting a release in September or October is necessarily a good
idea. But I'm also not in a position to say that the project is going
to be irreparably damaged if we just ship what we've got, perhaps
after fixing the most acute problems that we currently know about.The problem here is that this was going to be a headline new feature
for v15. Shipping what apparently is only an alpha-quality implementation
seems pretty problematic unless we advertise it as such, and that's
not something we've done very much in the past. I also wonder how
much any attempts at fixing it later would be constrained by concerns
about compatibility with the v15 version.... And we do have other bad code in the system.
Can't deny that, but a lot of it is legacy code that we wish we could
rip out and can't because backwards compatibility. This is not legacy
code ... not yet anyway.As you say, we've delegated this sort of decision to the RMT, but
if I were on the RMT I'd be voting to revert.
I know I previously said that this was not really severable, but I've
started having second thoughts about that. If we disabled as Not
Implemented the DEFAULT form of the ON ERROR and ON EMPTY clauses, and
possibly the RETURNING clause in some cases, it's possible we could get
rid of most of what's been controversial. That could still leave us a
good deal of what we want, including JSON_TABLE, which is by far the
most interesting of these features. I haven't looked closely yet at how
possible this is, it only occurred to me today, but I think it's worth
exploring.
cheers
andrew
--
Andrew Dunstan
EDB: https://www.enterprisedb.com
Hi,
On 2022-08-23 10:51:04 -0400, Robert Haas wrote:
I do not think that using subtransactions as part of the expression
evaluation process is a sound idea pretty much under any
circumstances. Maybe if the subtransations aren't commonly created and
don't usually get XIDs there wouldn't be a big problem in practice,
but it's an awfully heavyweight operation to be done inside expression
evaluation even in corner cases. I think that if we need to make
certain operations that would throw errors not throw errors, we need
to refactor interfaces until it's possible to return an error
indicator up to the appropriate level, not just let the error be
thrown and catch it.
I don't think that's quite realistic - that's the input/output functions for
all types, basically. I'd be somewhat content if we'd a small list of very
common coercion paths we knew wouldn't error out, leaving things like OOM
aside. Even just knowing that for ->text conversions would be a huge deal in
the context of this patch. One problem here is that the whole type coercion
infrastructure doesn't make it easy to know what "happened inside" atm, one
has to reconstruct it from the emitted expressions, where there can be
multiple layers of things to poke through.
Greetings,
Andres Freund
Hi
út 23. 8. 2022 v 17:55 odesílatel Andres Freund <andres@anarazel.de> napsal:
Hi,
On 2022-08-23 10:51:04 -0400, Robert Haas wrote:
I do not think that using subtransactions as part of the expression
evaluation process is a sound idea pretty much under any
circumstances. Maybe if the subtransations aren't commonly created and
don't usually get XIDs there wouldn't be a big problem in practice,
but it's an awfully heavyweight operation to be done inside expression
evaluation even in corner cases. I think that if we need to make
certain operations that would throw errors not throw errors, we need
to refactor interfaces until it's possible to return an error
indicator up to the appropriate level, not just let the error be
thrown and catch it.I don't think that's quite realistic - that's the input/output functions
for
all types, basically. I'd be somewhat content if we'd a small list of very
common coercion paths we knew wouldn't error out, leaving things like OOM
aside. Even just knowing that for ->text conversions would be a huge deal
in
the context of this patch. One problem here is that the whole type
coercion
infrastructure doesn't make it easy to know what "happened inside" atm, one
has to reconstruct it from the emitted expressions, where there can be
multiple layers of things to poke through.
The errors that should be handled are related to json structure errors. I
don't think so we have to handle all errors and all conversions.
The JSON knows only three types - and these conversions can be written
specially for this case - or we can write json io routines to be able to
signal error
without an exception.
Regards
Pavel
Show quoted text
Greetings,
Andres Freund
On Tue, Aug 23, 2022 at 11:55 AM Andres Freund <andres@anarazel.de> wrote:
I don't think that's quite realistic - that's the input/output functions for
all types, basically. I'd be somewhat content if we'd a small list of very
common coercion paths we knew wouldn't error out, leaving things like OOM
aside. Even just knowing that for ->text conversions would be a huge deal in
the context of this patch. One problem here is that the whole type coercion
infrastructure doesn't make it easy to know what "happened inside" atm, one
has to reconstruct it from the emitted expressions, where there can be
multiple layers of things to poke through.
But that's exactly what I'm complaining about. Catching an error that
unwound a bunch of stack frames where complicated things are happening
is fraught with peril. There's probably a bunch of errors that could
be thrown from somewhere in that code - out of memory being a great
example - that should not be caught. What you (probably) want is to
know whether one specific error happened or not, and catch only that
one. And the error machinery isn't designed for that. It's not
designed to let you catch specific errors for specific call sites, and
it's also not designed to be particularly efficient if lots of errors
need to be caught over and over again. If you decide to ignore all
that and do it anyway, you'll end up with, at best, code that is
complicated, hard to maintain, and probably slow when a lot of errors
are trapped, and at worst, code that is fragile or outright buggy.
--
Robert Haas
EDB: http://www.enterprisedb.com
On 23.08.2022 19:06, Pavel Stehule wrote:
Hi
út 23. 8. 2022 v 17:55 odesílatel Andres Freund <andres@anarazel.de>
napsal:Hi,
On 2022-08-23 10:51:04 -0400, Robert Haas wrote:
I do not think that using subtransactions as part of the expression
evaluation process is a sound idea pretty much under any
circumstances. Maybe if the subtransations aren't commonlycreated and
don't usually get XIDs there wouldn't be a big problem in practice,
but it's an awfully heavyweight operation to be done insideexpression
evaluation even in corner cases. I think that if we need to make
certain operations that would throw errors not throw errors, we need
to refactor interfaces until it's possible to return an error
indicator up to the appropriate level, not just let the error be
thrown and catch it.I don't think that's quite realistic - that's the input/output
functions for
all types, basically. I'd be somewhat content if we'd a small
list of very
common coercion paths we knew wouldn't error out, leaving things
like OOM
aside. Even just knowing that for ->text conversions would be a
huge deal in
the context of this patch. One problem here is that the whole
type coercion
infrastructure doesn't make it easy to know what "happened inside"
atm, one
has to reconstruct it from the emitted expressions, where there can be
multiple layers of things to poke through.The errors that should be handled are related to json structure
errors. I don't think so we have to handle all errors and all conversions.The JSON knows only three types - and these conversions can be written
specially for this case - or we can write json io routines to be able
to signal error
without an exception.
I also wanted to suggest to limit the set of returning types to the
predefined set of JSON-compatible types for which can write safe
conversion functions: character types (text, char), boolean, number
types (integers, floats types, numeric), datetime types. The SQL
standard even does not require support of other returning types.
For the float8 and datetime types we already have safe input functions
like float8in_internal_opt_error() and parse_datetime() which are used
inside jsonpath and return error code instead of throwing errors.
We need to implement numeric_intN_safe() and maybe a few other trivial
functions like that.
The set of returning types, for which we do not need any special
coercions, is very limited: json, jsonb, text. More precisely,
even RETURNING json[b] can throw errors in JSON_QUERY(OMIT QUOTES),
and we also need safe json parsing, but it can be easily done
with pg_parse_json(), which returns error code.
On 8/23/22 11:08 AM, Tom Lane wrote:
Robert Haas <robertmhaas@gmail.com> writes:
At the end of the day, the RMT is going to have to take a call here.
It seems to me that Andres's concerns about code quality and lack of
comments are probably somewhat legitimate, and in particular I do not
think the use of subtransactions is a good idea. I also don't think
that trying to fix those problems or generally improve the code by
committing thousands of lines of new code in August when we're
targeting a release in September or October is necessarily a good
idea. But I'm also not in a position to say that the project is going
to be irreparably damaged if we just ship what we've got, perhaps
after fixing the most acute problems that we currently know about.The problem here is that this was going to be a headline new feature
for v15. Shipping what apparently is only an alpha-quality implementation
seems pretty problematic unless we advertise it as such, and that's
not something we've done very much in the past.
With my user hat on, we have done this before -- if inadvertently -- but
agree it's not recommended nor a habit we should get into.
As you say, we've delegated this sort of decision to the RMT, but
if I were on the RMT I'd be voting to revert.
With RMT hat on,the RMT does have power of forced commit/revert in
absence of consensus through regular community processes[1]https://wiki.postgresql.org/wiki/Release_Management_Team. We did
explicitly discuss at our meeting today if we were going to make the
decision right now. We decided that we would come back and set a
deadline on letting the community processes play out, otherwise we will
make the decision.
For decision deadline: if there is no community consensus by end of Aug
28, 2022 AoE, the RMT will make the decision. I know Andrew has been
prepping for the outcome of a revert -- this should give enough for
review and merge prior to a Beta 4 release (targeted for Sep 8). If
there is concern about that, the RMT can move up the decision timeframe.
Taking RMT hat off, if the outcome is "revert", I do want to ensure we
don't lose momentum on getting this into v16. I know a lot of time and
effort has gone into this featureset and it seems to be trending in the
right direction. We have a mixed history on reverts in terms of if/when
they are committed and I don't want to see that happen to these
features. I do think this will remain a headline feature even if we
delay it for v16.
I saw Andrew suggest that the controversial parts of the patchset may be
severable from some of the new functionality, so I would like to see
that proposal and if it is enough to overcome concerns.
Thanks,
Jonathan
[1]: https://wiki.postgresql.org/wiki/Release_Management_Team
Hi,
On 2022-08-23 12:26:55 -0400, Robert Haas wrote:
On Tue, Aug 23, 2022 at 11:55 AM Andres Freund <andres@anarazel.de> wrote:
I don't think that's quite realistic - that's the input/output functions for
all types, basically. I'd be somewhat content if we'd a small list of very
common coercion paths we knew wouldn't error out, leaving things like OOM
aside. Even just knowing that for ->text conversions would be a huge deal in
the context of this patch. One problem here is that the whole type coercion
infrastructure doesn't make it easy to know what "happened inside" atm, one
has to reconstruct it from the emitted expressions, where there can be
multiple layers of things to poke through.But that's exactly what I'm complaining about. Catching an error that
unwound a bunch of stack frames where complicated things are happening
is fraught with peril. There's probably a bunch of errors that could
be thrown from somewhere in that code - out of memory being a great
example - that should not be caught.
The code as is handles this to some degree. Only ERRCODE_DATA_EXCEPTION,
ERRCODE_INTEGRITY_CONSTRAINT_VIOLATION are caught, the rest is immediately
rethrown.
What you (probably) want is to know whether one specific error happened or
not, and catch only that one. And the error machinery isn't designed for
that. It's not designed to let you catch specific errors for specific call
sites, and it's also not designed to be particularly efficient if lots of
errors need to be caught over and over again. If you decide to ignore all
that and do it anyway, you'll end up with, at best, code that is
complicated, hard to maintain, and probably slow when a lot of errors are
trapped, and at worst, code that is fragile or outright buggy.
I'm not sure what the general alternative is though. Part of the feature is
generating a composite type from json - there's just no way we can make all
possible coercion pathways not error out. That'd necessitate requiring all
builtin types and extensions types out there to provide input functions that
don't throw on invalid input and all coercions to not throw either. That just
seems unrealistic.
I think the best we could without subtransactions do perhaps is to add
metadata to pg_cast, pg_type telling us whether certain types of errors are
possible, and requiring ERROR ON ERROR when coercion paths are required that
don't have those options.
Greetings,
Andres Freund
"Jonathan S. Katz" <jkatz@postgresql.org> writes:
I saw Andrew suggest that the controversial parts of the patchset may be
severable from some of the new functionality, so I would like to see
that proposal and if it is enough to overcome concerns.
It's an interesting suggestion. Do people have the cycles available
to make it happen in the next few days?
regards, tom lane
Hi,
On 2022-08-23 13:18:49 -0400, Jonathan S. Katz wrote:
Taking RMT hat off, if the outcome is "revert", I do want to ensure we don't
lose momentum on getting this into v16. I know a lot of time and effort has
gone into this featureset and it seems to be trending in the right
direction. We have a mixed history on reverts in terms of if/when they are
committed and I don't want to see that happen to these features. I do think
this will remain a headline feature even if we delay it for v16.
We could decide to revert this for 15, but leave it in tree for HEAD.
Greetings,
Andres Freund
Hi,
On 2022-08-23 18:06:22 +0200, Pavel Stehule wrote:
The errors that should be handled are related to json structure errors. I
don't think so we have to handle all errors and all conversions.The JSON knows only three types - and these conversions can be written
specially for this case - or we can write json io routines to be able to
signal error
without an exception.
I think that's not true unfortunately. You can specify return types, and
composite types can be populated. Which essentially requires arbitrary
coercions.
Greetings,
Andres Freund
Andres Freund <andres@anarazel.de> writes:
On 2022-08-23 12:26:55 -0400, Robert Haas wrote:
But that's exactly what I'm complaining about. Catching an error that
unwound a bunch of stack frames where complicated things are happening
is fraught with peril. There's probably a bunch of errors that could
be thrown from somewhere in that code - out of memory being a great
example - that should not be caught.
The code as is handles this to some degree. Only ERRCODE_DATA_EXCEPTION,
ERRCODE_INTEGRITY_CONSTRAINT_VIOLATION are caught, the rest is immediately
rethrown.
That's still a lot of territory, considering how nonspecific most
SQLSTATEs are. Even if you can prove that only the intended cases
are caught today, somebody could inadvertently break it next week
by using one of those codes somewhere else.
I agree with the upthread comments that we only need/want to catch
foreseeable incorrect-input errors, and that the way to make that
happen is to refactor the related type input functions, and that
a lot of the heavy lifting for that has been done already.
regards, tom lane
On Tue, Aug 23, 2022 at 1:23 PM Andres Freund <andres@anarazel.de> wrote:
But that's exactly what I'm complaining about. Catching an error that
unwound a bunch of stack frames where complicated things are happening
is fraught with peril. There's probably a bunch of errors that could
be thrown from somewhere in that code - out of memory being a great
example - that should not be caught.The code as is handles this to some degree. Only ERRCODE_DATA_EXCEPTION,
ERRCODE_INTEGRITY_CONSTRAINT_VIOLATION are caught, the rest is immediately
rethrown.
AFAIK, Tom has rejected every previous effort to introduce this type
of coding into the tree rather forcefully. What makes it OK now?
I'm not sure what the general alternative is though. Part of the feature is
generating a composite type from json - there's just no way we can make all
possible coercion pathways not error out. That'd necessitate requiring all
builtin types and extensions types out there to provide input functions that
don't throw on invalid input and all coercions to not throw either. That just
seems unrealistic.
Well, I think that having input functions report input that is not
valid for the data type in some way other than just chucking an error
as they'd also do for a missing TOAST chunk would be a pretty sensible
plan. I'd support doing that if we forced a hard compatibility break,
and I'd support that if we provided some way for old code to continue
running in degraded mode. I haven't thought too much about the
coercion case, but I suppose the issues are similar. What I don't
support is saying -- well, upgrading our infrastructure is hard, so
let's just kludge it.
--
Robert Haas
EDB: http://www.enterprisedb.com
út 23. 8. 2022 v 19:27 odesílatel Andres Freund <andres@anarazel.de> napsal:
Hi,
On 2022-08-23 18:06:22 +0200, Pavel Stehule wrote:
The errors that should be handled are related to json structure errors. I
don't think so we have to handle all errors and all conversions.The JSON knows only three types - and these conversions can be written
specially for this case - or we can write json io routines to be able to
signal error
without an exception.I think that's not true unfortunately. You can specify return types, and
composite types can be populated. Which essentially requires arbitrary
coercions.
Please, can you send an example? Maybe we try to fix a feature that is not
required by standard.
Regards
Pavel
Show quoted text
Greetings,
Andres Freund
Pavel Stehule <pavel.stehule@gmail.com> writes:
út 23. 8. 2022 v 19:27 odesílatel Andres Freund <andres@anarazel.de> napsal:
I think that's not true unfortunately. You can specify return types, and
composite types can be populated. Which essentially requires arbitrary
coercions.
Please, can you send an example? Maybe we try to fix a feature that is not
required by standard.
Even if it is required by spec, I'd have zero hesitation about tossing
that case overboard if that's what we need to do to get to a shippable
feature.
regards, tom lane
On 2022-08-23 Tu 13:24, Tom Lane wrote:
"Jonathan S. Katz" <jkatz@postgresql.org> writes:
I saw Andrew suggest that the controversial parts of the patchset may be
severable from some of the new functionality, so I would like to see
that proposal and if it is enough to overcome concerns.It's an interesting suggestion. Do people have the cycles available
to make it happen in the next few days?
I will make time although probably Nikita and/or Amit would be quicker
than I would be.
cheers
andrew
--
Andrew Dunstan
EDB: https://www.enterprisedb.com
On 23.08.2022 20:38, Pavel Stehule wrote:
út 23. 8. 2022 v 19:27 odesílatel Andres Freund <andres@anarazel.de>
napsal:Hi,
On 2022-08-23 18:06:22 +0200, Pavel Stehule wrote:
The errors that should be handled are related to json structure
errors. I
don't think so we have to handle all errors and all conversions.
The JSON knows only three types - and these conversions can be
written
specially for this case - or we can write json io routines to be
able to
signal error
without an exception.I think that's not true unfortunately. You can specify return
types, and
composite types can be populated. Which essentially requires arbitrary
coercions.Please, can you send an example? Maybe we try to fix a feature that is
not required by standard.
- Returning arbitrary types in JSON_VALUE using I/O coercion
from JSON string (more precisely, text::arbitrary_type cast):
SELECT JSON_QUERY(jsonb '"1, 2"', '$' RETURNING point);
json_query
------------
(1,2)
(1 row)
- Returning composite and array types in JSON_QUERY, which is implemented
reusing the code of our json[b]_populate_record[set]():
SELECT JSON_QUERY(jsonb '[1, "2", null]', '$' RETURNING int[]);
json_query
------------
{1,2,NULL}
(1 row)
--
Nikita Glukhov
Postgres Professional:http://www.postgrespro.com
The Russian Postgres Company
Hi,
On 2022-08-23 13:33:42 -0400, Robert Haas wrote:
On Tue, Aug 23, 2022 at 1:23 PM Andres Freund <andres@anarazel.de> wrote:
But that's exactly what I'm complaining about. Catching an error that
unwound a bunch of stack frames where complicated things are happening
is fraught with peril. There's probably a bunch of errors that could
be thrown from somewhere in that code - out of memory being a great
example - that should not be caught.The code as is handles this to some degree. Only ERRCODE_DATA_EXCEPTION,
ERRCODE_INTEGRITY_CONSTRAINT_VIOLATION are caught, the rest is immediately
rethrown.AFAIK, Tom has rejected every previous effort to introduce this type
of coding into the tree rather forcefully. What makes it OK now?
I didn't say it was! I don't like it much - I was just saying that it handles
that case to some degree.
I'm not sure what the general alternative is though. Part of the feature is
generating a composite type from json - there's just no way we can make all
possible coercion pathways not error out. That'd necessitate requiring all
builtin types and extensions types out there to provide input functions that
don't throw on invalid input and all coercions to not throw either. That just
seems unrealistic.Well, I think that having input functions report input that is not
valid for the data type in some way other than just chucking an error
as they'd also do for a missing TOAST chunk would be a pretty sensible
plan. I'd support doing that if we forced a hard compatibility break,
and I'd support that if we provided some way for old code to continue
running in degraded mode. I haven't thought too much about the
coercion case, but I suppose the issues are similar. What I don't
support is saying -- well, upgrading our infrastructure is hard, so
let's just kludge it.
I guess the 'degraded mode' approach is kind of what I was trying to describe
with:
I think the best we could without subtransactions do perhaps is to add
metadata to pg_cast, pg_type telling us whether certain types of errors are
possible, and requiring ERROR ON ERROR when coercion paths are required that
don't have those options.
Greetings,
Andres Freund
Hi,
On 2022-08-23 13:28:50 -0400, Tom Lane wrote:
I agree with the upthread comments that we only need/want to catch
foreseeable incorrect-input errors, and that the way to make that
happen is to refactor the related type input functions, and that
a lot of the heavy lifting for that has been done already.
I think it's a good direction to go in. What of the heavy lifting for that has
been done already? I'd have guessed that the hard part is to add different,
optional, type input, type coercion signatures, and then converting a lot of
types to that?
Greetings,
Andres Freund
On 8/23/22 2:10 PM, Andrew Dunstan wrote:
On 2022-08-23 Tu 13:24, Tom Lane wrote:
"Jonathan S. Katz" <jkatz@postgresql.org> writes:
I saw Andrew suggest that the controversial parts of the patchset may be
severable from some of the new functionality, so I would like to see
that proposal and if it is enough to overcome concerns.It's an interesting suggestion. Do people have the cycles available
to make it happen in the next few days?I will make time although probably Nikita and/or Amit would be quicker
than I would be.
If you all can, you have my +1 to try it and see what folks think.
Thanks,
Jonathan
On 8/23/22 1:26 PM, Andres Freund wrote:
Hi,
On 2022-08-23 13:18:49 -0400, Jonathan S. Katz wrote:
Taking RMT hat off, if the outcome is "revert", I do want to ensure we don't
lose momentum on getting this into v16. I know a lot of time and effort has
gone into this featureset and it seems to be trending in the right
direction. We have a mixed history on reverts in terms of if/when they are
committed and I don't want to see that happen to these features. I do think
this will remain a headline feature even if we delay it for v16.We could decide to revert this for 15, but leave it in tree for HEAD.
If it comes to that, I think that is a reasonable suggestion so long as
we're committed to making the requisite changes.
Thanks,
Jonathan
On 2022-08-23 Tu 15:32, Jonathan S. Katz wrote:
On 8/23/22 1:26 PM, Andres Freund wrote:
Hi,
On 2022-08-23 13:18:49 -0400, Jonathan S. Katz wrote:
Taking RMT hat off, if the outcome is "revert", I do want to ensure
we don't
lose momentum on getting this into v16. I know a lot of time and
effort has
gone into this featureset and it seems to be trending in the right
direction. We have a mixed history on reverts in terms of if/when
they are
committed and I don't want to see that happen to these features. I
do think
this will remain a headline feature even if we delay it for v16.We could decide to revert this for 15, but leave it in tree for HEAD.
If it comes to that, I think that is a reasonable suggestion so long
as we're committed to making the requisite changes.
One good reason for this is that way we're not fighting against the node
changes, which complicate any reversion significantly.
cheers
andrew
--
Andrew Dunstan
EDB: https://www.enterprisedb.com
Andres Freund <andres@anarazel.de> writes:
On 2022-08-23 13:28:50 -0400, Tom Lane wrote:
I agree with the upthread comments that we only need/want to catch
foreseeable incorrect-input errors, and that the way to make that
happen is to refactor the related type input functions, and that
a lot of the heavy lifting for that has been done already.
I think it's a good direction to go in. What of the heavy lifting for that has
been done already? I'd have guessed that the hard part is to add different,
optional, type input, type coercion signatures, and then converting a lot of
types to that?
I was assuming that we would only bother to do this for a few core types.
Of those, at least the datetime types were already done for previous
JSON-related features. If we want extensibility, then as Robert said
there's going to have to be work done to create a common API that type
input functions can implement, which seems like a pretty heavy lift.
We could get it done for v16 if we start now, I imagine.
regards, tom lane
út 23. 8. 2022 v 21:54 odesílatel Tom Lane <tgl@sss.pgh.pa.us> napsal:
Andres Freund <andres@anarazel.de> writes:
On 2022-08-23 13:28:50 -0400, Tom Lane wrote:
I agree with the upthread comments that we only need/want to catch
foreseeable incorrect-input errors, and that the way to make that
happen is to refactor the related type input functions, and that
a lot of the heavy lifting for that has been done already.I think it's a good direction to go in. What of the heavy lifting for
that has
been done already? I'd have guessed that the hard part is to add
different,
optional, type input, type coercion signatures, and then converting a
lot of
types to that?
I was assuming that we would only bother to do this for a few core types.
Of those, at least the datetime types were already done for previous
JSON-related features. If we want extensibility, then as Robert said
there's going to have to be work done to create a common API that type
input functions can implement, which seems like a pretty heavy lift.
We could get it done for v16 if we start now, I imagine.
+1
Pavel
Show quoted text
regards, tom lane
Andrew Dunstan <andrew@dunslane.net> writes:
On 2022-08-23 Tu 15:32, Jonathan S. Katz wrote:
On 8/23/22 1:26 PM, Andres Freund wrote:
We could decide to revert this for 15, but leave it in tree for HEAD.
If it comes to that, I think that is a reasonable suggestion so long
as we're committed to making the requisite changes.
I'm not particularly on board with that. In the first place, I'm
unconvinced that very much of the current code will survive, and
I don't want people contorting the rewrite in order to salvage
committed code that would be better off junked. In the second
place, if we still don't have a shippable feature in a year, then
undoing it again is going to be just that much harder.
One good reason for this is that way we're not fighting against the node
changes, which complicate any reversion significantly.
Having said that, I'm prepared to believe that a lot of the node
infrastructure won't change because it's dictated by the SQL-spec
grammar. So we could leave that part alone in HEAD; at worst
it adds some dead code in backend/nodes.
regards, tom lane
On 23.08.2022 22:31, Jonathan S. Katz wrote:
On 8/23/22 2:10 PM, Andrew Dunstan wrote:
On 2022-08-23 Tu 13:24, Tom Lane wrote:
"Jonathan S. Katz" <jkatz@postgresql.org> writes:
I saw Andrew suggest that the controversial parts of the patchset
may be
severable from some of the new functionality, so I would like to see
that proposal and if it is enough to overcome concerns.It's an interesting suggestion. Do people have the cycles available
to make it happen in the next few days?I will make time although probably Nikita and/or Amit would be quicker
than I would be.If you all can, you have my +1 to try it and see what folks think.
I am ready to start hacking now, but it's already night in Moscow, so
any result will be only tomorrow.
Here is my plan:
0. Take my last v7-0001 patch as a base. It already contains refactoring
of JsonCoercion code. (Fix 0002 is not needed anymore, because it is for
json[b] domains, which simply will not be supported.)
1. Replace JSON_COERCION_VIA_EXPR in JsonCoercion with new
JsonCoercionType(s) for hardcoded coercions.
2. Disable all non-JSON-compatible output types in coerceJsonFuncExpr().
3. Add missing safe type input functions for integers, numerics, and
maybe others.
4. Implement hardcoded coercions using these functions in
ExecEvalJsonExprCoercion().
5. Try to allow only constants (and also maybe column/parameter
references) in JSON_VALUE's DEFAULT expressions. This should be enough
for the most of practical cases. JSON_QUERY even does not have DEFAULT
expressions -- it has only EMPTY ARRAY and EMPTY OBJECT, which can be
treated as simple JSON constants.
But it is possible to allow all other expressions in ERROR ON ERROR
case, and I don't know if it will be consistent enough to allow some
expressions in one case and deny in other.
And there is another problem: expressions can be only checked for
Const-ness only after expression simplification. AFAIU, at the
parsing stage they look like 'string'::type. So, it's unclear if it
is correct to check expressions in ExecInitExpr().
6. Remove subtransactions.
--
Nikita Glukhov
Postgres Professional:http://www.postgrespro.com
The Russian Postgres Company
On 2022-08-23 Tu 17:29, Nikita Glukhov wrote:
On 23.08.2022 22:31, Jonathan S. Katz wrote:
On 8/23/22 2:10 PM, Andrew Dunstan wrote:
On 2022-08-23 Tu 13:24, Tom Lane wrote:
"Jonathan S. Katz" <jkatz@postgresql.org> writes:
I saw Andrew suggest that the controversial parts of the patchset
may be
severable from some of the new functionality, so I would like to see
that proposal and if it is enough to overcome concerns.It's an interesting suggestion. Do people have the cycles available
to make it happen in the next few days?I will make time although probably Nikita and/or Amit would be quicker
than I would be.If you all can, you have my +1 to try it and see what folks think.
I am ready to start hacking now, but it's already night in Moscow, so
any result will be only tomorrow.Here is my plan:
0. Take my last v7-0001 patch as a base. It already contains refactoring
of JsonCoercion code. (Fix 0002 is not needed anymore, because it is for
json[b] domains, which simply will not be supported.)1. Replace JSON_COERCION_VIA_EXPR in JsonCoercion with new
JsonCoercionType(s) for hardcoded coercions.2. Disable all non-JSON-compatible output types in coerceJsonFuncExpr().
3. Add missing safe type input functions for integers, numerics, and
maybe others.4. Implement hardcoded coercions using these functions in
ExecEvalJsonExprCoercion().5. Try to allow only constants (and also maybe column/parameter
references) in JSON_VALUE's DEFAULT expressions. This should be enough
for the most of practical cases. JSON_QUERY even does not have DEFAULT
expressions -- it has only EMPTY ARRAY and EMPTY OBJECT, which can be
treated as simple JSON constants.
er, really? This is from the regression output:
SELECT JSON_QUERY(jsonb '[]', '$[*]' DEFAULT '"empty"' ON EMPTY);
json_query
------------
"empty"
(1 row)
SELECT JSON_QUERY(jsonb '[1,2]', '$[*]' DEFAULT '"empty"' ON ERROR);
json_query
------------
"empty"
(1 row)
But it is possible to allow all other expressions in ERROR ON ERROR
case, and I don't know if it will be consistent enough to allow some
expressions in one case and deny in other.And there is another problem: expressions can be only checked for
Const-ness only after expression simplification. AFAIU, at the
parsing stage they look like 'string'::type. So, it's unclear if it
is correct to check expressions in ExecInitExpr().6. Remove subtransactions.
Sounds like a good plan, modulo the issues in item 5. I would rather
lose some features temporarily than try to turn handsprings to make them
work and jeopardize the rest.
I'll look forward to seeing your patch in the morning :-)
cheers
andrew
--
Andrew Dunstan
EDB: https://www.enterprisedb.com
Hi Nikita,
On Wed, Aug 24, 2022 at 6:29 AM Nikita Glukhov <n.gluhov@postgrespro.ru> wrote:
Here is my plan:
0. Take my last v7-0001 patch as a base. It already contains refactoring
of JsonCoercion code. (Fix 0002 is not needed anymore, because it is for
json[b] domains, which simply will not be supported.)1. Replace JSON_COERCION_VIA_EXPR in JsonCoercion with new
JsonCoercionType(s) for hardcoded coercions.2. Disable all non-JSON-compatible output types in coerceJsonFuncExpr().
3. Add missing safe type input functions for integers, numerics, and
maybe others.4. Implement hardcoded coercions using these functions in
ExecEvalJsonExprCoercion().5. Try to allow only constants (and also maybe column/parameter
references) in JSON_VALUE's DEFAULT expressions. This should be enough
for the most of practical cases. JSON_QUERY even does not have DEFAULT
expressions -- it has only EMPTY ARRAY and EMPTY OBJECT, which can be
treated as simple JSON constants.But it is possible to allow all other expressions in ERROR ON ERROR
case, and I don't know if it will be consistent enough to allow some
expressions in one case and deny in other.And there is another problem: expressions can be only checked for
Const-ness only after expression simplification. AFAIU, at the
parsing stage they look like 'string'::type. So, it's unclear if it
is correct to check expressions in ExecInitExpr().
IIUC, the idea is to remove the support for `DEFAULT expression` in
the following, no?
json_value ( context_item, path_expression
...
[ { ERROR | NULL | DEFAULT expression } ON EMPTY ]
[ { ERROR | NULL | DEFAULT expression } ON ERROR ])
json_query ( context_item, path_expression
...
[ { ERROR | NULL | EMPTY { [ ARRAY ] | OBJECT } | DEFAULT expression }
ON EMPTY ]
[ { ERROR | NULL | EMPTY { [ ARRAY ] | OBJECT } | DEFAULT expression }
ON ERROR ])
If that's the case, I'd imagine that `default_expr` in the following
will be NULL for now:
/*
* JsonBehavior -
* representation of JSON ON ... BEHAVIOR clause
*/
typedef struct JsonBehavior
{
NodeTag type;
JsonBehaviorType btype; /* behavior type */
Node *default_expr; /* default expression, if any */
} JsonBehavior;
And if so, no expression left to check the Const-ness of?
--
Thanks, Amit Langote
EDB: http://www.enterprisedb.com
On Wed, Aug 24, 2022 at 11:55 AM Amit Langote <amitlangote09@gmail.com> wrote:
On Wed, Aug 24, 2022 at 6:29 AM Nikita Glukhov <n.gluhov@postgrespro.ru> wrote:
Here is my plan:
0. Take my last v7-0001 patch as a base. It already contains refactoring
of JsonCoercion code. (Fix 0002 is not needed anymore, because it is for
json[b] domains, which simply will not be supported.)1. Replace JSON_COERCION_VIA_EXPR in JsonCoercion with new
JsonCoercionType(s) for hardcoded coercions.2. Disable all non-JSON-compatible output types in coerceJsonFuncExpr().
3. Add missing safe type input functions for integers, numerics, and
maybe others.4. Implement hardcoded coercions using these functions in
ExecEvalJsonExprCoercion().5. Try to allow only constants (and also maybe column/parameter
references) in JSON_VALUE's DEFAULT expressions. This should be enough
for the most of practical cases. JSON_QUERY even does not have DEFAULT
expressions -- it has only EMPTY ARRAY and EMPTY OBJECT, which can be
treated as simple JSON constants.But it is possible to allow all other expressions in ERROR ON ERROR
case, and I don't know if it will be consistent enough to allow some
expressions in one case and deny in other.And there is another problem: expressions can be only checked for
Const-ness only after expression simplification. AFAIU, at the
parsing stage they look like 'string'::type. So, it's unclear if it
is correct to check expressions in ExecInitExpr().IIUC, the idea is to remove the support for `DEFAULT expression` in
the following, no?json_value ( context_item, path_expression
...
[ { ERROR | NULL | DEFAULT expression } ON EMPTY ]
[ { ERROR | NULL | DEFAULT expression } ON ERROR ])json_query ( context_item, path_expression
...
[ { ERROR | NULL | EMPTY { [ ARRAY ] | OBJECT } | DEFAULT expression }
ON EMPTY ]
[ { ERROR | NULL | EMPTY { [ ARRAY ] | OBJECT } | DEFAULT expression }
ON ERROR ])
Or is the idea rather to restrict the set of data types we allow in `[
RETURNING data_type ]`?
--
Thanks, Amit Langote
EDB: http://www.enterprisedb.com
Hi,
On 24.08.2022 01:12, Andrew Dunstan wrote:
On 2022-08-23 Tu 17:29, Nikita Glukhov wrote:
Here is my plan:
0. Take my last v7-0001 patch as a base. It already contains refactoring
of JsonCoercion code. (Fix 0002 is not needed anymore, because it is for
json[b] domains, which simply will not be supported.)1. Replace JSON_COERCION_VIA_EXPR in JsonCoercion with new
JsonCoercionType(s) for hardcoded coercions.
JsonCoerion node were completely removed because they are not needed
anymore (see p. 4).
2. Disable all non-JSON-compatible output types in coerceJsonFuncExpr().
3. Add missing safe type input functions for integers, numerics, and
maybe others.
We need to write much more functions, than I expected. And I still didn't
implemented safe input functions for numeric and datetime types.
I will start to do it tomorrow.
4. Implement hardcoded coercions using these functions in
ExecEvalJsonExprCoercion().
That was done using simple `switch (returning_typid) { .. }`,
which can be nested into `switch (jbv->type)`.
5. Try to allow only constants (and also maybe column/parameter
references) in JSON_VALUE's DEFAULT expressions. This should be enough
for the most of practical cases. JSON_QUERY even does not have DEFAULT
expressions -- it has only EMPTY ARRAY and EMPTY OBJECT, which can be
treated as simple JSON constants.
I have not tried to implement this yet.
er, really? This is from the regression output:
SELECT JSON_QUERY(jsonb '[]', '$[*]' DEFAULT '"empty"' ON EMPTY);
json_query
------------
"empty"
(1 row)SELECT JSON_QUERY(jsonb '[1,2]', '$[*]' DEFAULT '"empty"' ON ERROR);
json_query
------------
"empty"
(1 row)
This is another extension. SQL standard defines only
EMPTY ARRAY an EMPTY OBJECT behavior for JSON_QUERY:
<JSON query empty behavior> ::=
ERROR
| NULL
| EMPTY ARRAY
| EMPTY OBJECT
<JSON query error behavior> ::=
ERROR
| NULL
| EMPTY ARRAY
| EMPTY OBJECT
But it is possible to allow all other expressions in ERROR ON ERROR
case, and I don't know if it will be consistent enough to allow some
expressions in one case and deny in other.And there is another problem: expressions can be only checked for
Const-ness only after expression simplification. AFAIU, at the
parsing stage they look like 'string'::type. So, it's unclear if it
is correct to check expressions in ExecInitExpr().6. Remove subtransactions.
They were completely removed. Only DEFAULT expression needs to be fixed now.
Sounds like a good plan, modulo the issues in item 5. I would rather
lose some features temporarily than try to turn handsprings to make them
work and jeopardize the rest.I'll look forward to seeing your patch in the morning :-)
v8 - is a highly WIP patch, which I failed to finish today.
Even some test cases fail now, and they simply show unfinished
things like casts to bytea (they can be simply removed) and missing
safe input functions.
--
Nikita Glukhov
Postgres Professional:http://www.postgrespro.com
The Russian Postgres Company
Attachments:
v8-0001-WIP-Remove-subtransactions-in-JsonExpr-execution.patchtext/x-patch; charset=UTF-8; name=v8-0001-WIP-Remove-subtransactions-in-JsonExpr-execution.patchDownload
From d02fea59799aceaf95dd81b8f2416434788e04c0 Mon Sep 17 00:00:00 2001
From: Nikita Glukhov <n.gluhov@postgrespro.ru>
Date: Thu, 25 Aug 2022 02:42:17 +0300
Subject: [PATCH v8] WIP: Remove subtransactions in JsonExpr execution
---
src/backend/executor/execExpr.c | 84 +-
src/backend/executor/execExprInterp.c | 892 ++++++++++++--------
src/backend/jit/llvm/llvmjit_expr.c | 2 +-
src/backend/jit/llvm/llvmjit_types.c | 2 +-
src/backend/nodes/nodeFuncs.c | 91 +-
src/backend/optimizer/util/clauses.c | 12 -
src/backend/parser/parse_expr.c | 200 ++---
src/backend/utils/adt/bool.c | 31 +-
src/backend/utils/adt/date.c | 158 +++-
src/backend/utils/adt/jsonb.c | 26 +-
src/backend/utils/adt/numeric.c | 64 +-
src/backend/utils/adt/numutils.c | 20 +-
src/backend/utils/adt/timestamp.c | 27 +-
src/include/executor/execExpr.h | 49 +-
src/include/nodes/primnodes.h | 36 +-
src/include/utils/builtins.h | 2 +
src/include/utils/date.h | 10 +
src/include/utils/jsonb.h | 2 +
src/include/utils/numeric.h | 4 +-
src/include/utils/timestamp.h | 2 +
src/test/regress/expected/jsonb_sqljson.out | 330 ++++++--
src/test/regress/sql/jsonb_sqljson.sql | 76 +-
22 files changed, 1244 insertions(+), 876 deletions(-)
diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
index d0a57c7aaee..558992df909 100644
--- a/src/backend/executor/execExpr.c
+++ b/src/backend/executor/execExpr.c
@@ -2561,58 +2561,41 @@ ExecInitExprRec(Expr *node, ExprState *state,
case T_JsonExpr:
{
JsonExpr *jexpr = castNode(JsonExpr, node);
- JsonExprState *jsestate = palloc0(sizeof(JsonExprState));
+ JsonExprState *jsestate;
ListCell *argexprlc;
ListCell *argnamelc;
- scratch.opcode = EEOP_JSONEXPR;
- scratch.d.jsonexpr.jsestate = jsestate;
+ /* JSON_TABLE preudo-function returns context item as a result */
+ if (jexpr->op == JSON_TABLE_OP)
+ {
+ ExecInitExprRec((Expr *) jexpr->formatted_expr, state,
+ resv, resnull);
+ break;
+ }
+ jsestate = palloc0(sizeof(JsonExprState));
jsestate->jsexpr = jexpr;
- jsestate->formatted_expr =
- palloc(sizeof(*jsestate->formatted_expr));
+ scratch.opcode = EEOP_JSONEXPR;
+ scratch.d.jsonexpr.jsestate = jsestate;
ExecInitExprRec((Expr *) jexpr->formatted_expr, state,
- &jsestate->formatted_expr->value,
- &jsestate->formatted_expr->isnull);
-
- jsestate->pathspec =
- palloc(sizeof(*jsestate->pathspec));
+ &jsestate->formatted_expr.value,
+ &jsestate->formatted_expr.isnull);
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->pathspec.value,
+ &jsestate->pathspec.isnull);
- jsestate->default_on_empty = !jexpr->on_empty ? NULL :
- ExecInitExpr((Expr *) jexpr->on_empty->default_expr,
- state->parent);
+ if (jexpr->on_empty)
+ jsestate->default_on_empty =
+ 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,
@@ -2636,35 +2619,8 @@ ExecInitExprRec(Expr *node, ExprState *state,
lappend(jsestate->args, var);
}
- jsestate->cache = NULL;
-
- if (jexpr->coercions)
- {
- JsonCoercion **coercion;
- struct JsonCoercionState *cstate;
- Datum *caseval;
- bool *casenull;
-
- 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;
- }
- }
-
ExprEvalPushStep(state, &scratch);
+
break;
}
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index 636794ca6f1..b2accedf8ca 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -1844,7 +1844,7 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
EEO_CASE(EEOP_JSONEXPR)
{
/* too complex for an inline implementation */
- ExecEvalJson(state, op, econtext);
+ ExecEvalJsonExpr(state, op, econtext);
EEO_NEXT();
}
@@ -4700,96 +4700,6 @@ ExecEvalJsonConstructor(ExprState *state, ExprEvalStep *op,
*op->resnull = isnull;
}
-/*
- * Evaluate a JSON error/empty behavior result.
- */
-static Datum
-ExecEvalJsonBehavior(ExprContext *econtext, JsonBehavior *behavior,
- ExprState *default_estate, 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:
- 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.
*/
@@ -4844,84 +4754,394 @@ EvalJsonPathVar(void *cxt, char *varName, int varNameLen,
}
/*
- * Prepare SQL/JSON item coercion to the output type. Returned a datum of the
- * corresponding SQL type and a pointer to the coercion state.
+ * Check whether we need to override default coercion in
+ * JSON_QUERY(OMIT QUOTES) case.
*/
-Datum
-ExecPrepareJsonItemCoercion(JsonbValue *item,
- JsonReturning *returning,
- struct JsonCoercionsState *coercions,
- struct JsonCoercionState **pcoercion)
+static bool
+ExecJsonQueryNeedsIOCoercion(JsonExpr *jsexpr, Datum res, bool isnull)
{
- struct JsonCoercionState *coercion;
- Datum res;
- JsonbValue buf;
+ if (jsexpr->omit_quotes && !isnull)
+ {
+ Jsonb *jb = DatumGetJsonbP(res);
+ JsonbValue jbv;
+
+ /* Coerce string items via I/O in OMIT QUOTES case */
+ return JsonbExtractScalar(&jb->root, &jbv) &&
+ jbv.type == jbvString;
+ }
+
+ return false;
+}
+
+/* Coerce C string to text, varchar(N), or bpchar(N) */
+static Datum
+ExecJsonStringCoercion(const char *str, int32 len, Oid typid, int32 typmod)
+{
+ if (typid == TEXTOID || typid == JSONOID || typmod <= VARHDRSZ)
+ return PointerGetDatum(cstring_to_text_with_len(str, len));
+ else
+ {
+ char *txt;
+ int32 size;
+
+ len = VARHDRSZ + len;
- if (item->type == jbvBinary &&
- JsonContainerIsScalar(item->val.binary.data))
+ if (typid == BPCHAROID)
+ size = typmod;
+ else
+ size = Min(len, typmod);
+
+ txt = palloc(size);
+ SET_VARSIZE(txt, size);
+
+ memcpy(VARDATA(txt), str, Min(size, len) - VARHDRSZ);
+
+ if (len < size)
+ memset(txt + len, ' ', size - len);
+
+ return PointerGetDatum(txt);
+ }
+}
+
+/* Coerce SQL/JSON item to text */
+static Datum
+ExecJsonCoercionToText(PGFunction outfunc, Datum value, Oid typid, int32 typmod)
+{
+ char *str = DatumGetCString(DirectFunctionCall1(outfunc, value));
+
+ return ExecJsonStringCoercion(str, strlen(str), typid, typmod);
+}
+
+/* Coerce datetime SQL/JSON item to the output typid */
+static Datum
+ExecJsonDatetimeCoercion(Datum val, Oid val_typid, Oid typid, int32 typmod,
+ bool *isnull, bool *error)
+{
+ if (val_typid == typid)
+ return val;
+
+ switch (val_typid)
{
- bool res PG_USED_FOR_ASSERTS_ONLY;
+ case DATEOID:
+ if (typid == TEXTOID || typid == VARCHAROID || typid == BPCHAROID)
+ return ExecJsonCoercionToText(date_out, val, typid, typmod);
+ else if (typid == DATEOID)
+ return val;
+ else if (typid == TIMESTAMPOID)
+ {
+ int overflow = 0;
+ Timestamp ts =
+ date2timestamp_opt_overflow(DatumGetDateADT(val),
+ error ? &overflow : NULL);
+
+ if (overflow)
+ {
+ *error = true;
+ return (Datum) 0;
+ }
- res = JsonbExtractScalar(item->val.binary.data, &buf);
- item = &buf;
- Assert(res);
+ return TimestampGetDatum(ts);
+ }
+ else if (typid == TIMESTAMPTZOID)
+ {
+ int overflow = 0;
+ TimestampTz res =
+ date2timestamptz_opt_overflow(DatumGetDateADT(val),
+ error ? &overflow : NULL);
+
+ if (overflow)
+ {
+ *error = true;
+ return (Datum) 0;
+ }
+
+ return TimestampTzGetDatum(res);
+ }
+ else
+ break; /* No cast */
+
+ case TIMEOID:
+ if (typid == TEXTOID || typid == VARCHAROID || typid == BPCHAROID)
+ return ExecJsonCoercionToText(time_out, val, typid, typmod);
+ else if (typid == TIMEOID)
+ return val;
+ else if (typid == TIMETZOID)
+ return DirectFunctionCall1(time_timetz, val);
+ else
+ break; /* No cast */
+
+ case TIMETZOID:
+ if (typid == TEXTOID || typid == VARCHAROID || typid == BPCHAROID)
+ return ExecJsonCoercionToText(timetz_out, val, typid, typmod);
+ else if (typid == TIMETZOID)
+ return val;
+ else if (typid == TIMEOID)
+ return DirectFunctionCall1(timetz_time, val);
+ else
+ break;
+
+ case TIMESTAMPOID:
+ if (typid == TEXTOID || typid == VARCHAROID || typid == BPCHAROID)
+ return ExecJsonCoercionToText(timestamp_out, val, typid, typmod);
+ else if (typid == TIMESTAMPOID)
+ return val;
+ else if (typid == DATEOID)
+ return timestamp_date_opt_error(DatumGetTimestamp(val), error);
+ else if (typid == TIMEOID)
+ {
+ TimeADT time =
+ timestamp_time_opt_error(DatumGetTimestamp(val),
+ isnull, error);
+
+ if ((error && *error) || *isnull)
+ return (Datum) 0;
+ else
+ return TimeADTGetDatum(time);
+ }
+ else if (typid == TIMESTAMPTZOID)
+ {
+ int overflow = 0;
+ TimestampTz tstz =
+ timestamp2timestamptz_opt_overflow(DatumGetTimestamp(val),
+ error ? &overflow : NULL);
+
+ if (overflow)
+ {
+ *error = true;
+ return (Datum) 0;
+ }
+
+ return TimestampTzGetDatum(tstz);
+ }
+ else
+ break; /* No cast */
+
+ case TIMESTAMPTZOID:
+ if (typid == TEXTOID || typid == VARCHAROID || typid == BPCHAROID)
+ return ExecJsonCoercionToText(timestamptz_out, val, typid, typmod);
+ else if (typid == TIMESTAMPTZOID)
+ return val;
+ else if (typid == DATEOID)
+ return timestamptz_date_opt_error(DatumGetTimestampTz(val), error);
+ else if (typid == TIMEOID)
+ {
+ TimeADT time =
+ timestamptz_time_opt_error(DatumGetTimestampTz(val),
+ isnull, error);
+
+ if ((error && *error) || *isnull)
+ return (Datum) 0;
+ else
+ return TimeADTGetDatum(time);
+ }
+ else if (typid == TIMETZOID)
+ {
+ TimeTzADT *result =
+ timestamptz_timetz_opt_error(DatumGetTimestampTz(val), error);
+
+ if ((error && *error) || !result)
+ {
+ *isnull = true;
+ return (Datum) 0;
+ }
+
+ return TimeTzADTPGetDatum(result);
+ }
+ else if (typid == TIMESTAMPOID)
+ {
+ Timestamp ts =
+ timestamptz2timestamp_opt_error(DatumGetTimestampTz(val),
+ error);
+
+ if (error && *error)
+ return (Datum) 0;
+
+ return TimestampGetDatum(ts);
+ }
+ else
+ break; /* No cast */
+
+ default:
+ elog(ERROR, "unexpected jsonb datetime type oid %u", val_typid);
+ break;
}
- /* get coercion state reference and datum of the corresponding SQL type */
+ if (!error)
+ ereport(ERROR,
+ (errcode(ERRCODE_SQL_JSON_ITEM_CANNOT_BE_CAST_TO_TARGET_TYPE),
+ errmsg("SQL/JSON item cannot be cast to target type")));
+
+ *error = true;
+ *isnull = true;
+
+ return (Datum) 0;
+}
+
+/* Coerce boolean SQL/JSON item or JSON_EXISTS result to the output type */
+static bool
+ExecJsonBoolCoercion(bool val, Oid typid, int32 typmod, Datum *res)
+{
+ if (typid == BOOLOID)
+ *res = BoolGetDatum(val);
+ else if (typid == INT4OID)
+ /* We only have cast bool::int4 in the catalog. */
+ *res = Int32GetDatum(val ? 1 : 0);
+ else if (typid == TEXTOID || typid == VARCHAROID || typid == BPCHAROID)
+ /*
+ * bool::text returns 'true' / 'false',
+ * boolout() returns 't' / 'f'.
+ */
+ *res = ExecJsonStringCoercion(val ? "true" : "false", val ? 4 : 5,
+ typid, typmod);
+ else
+ return false;
+
+ return true;
+}
+
+static Datum
+JsonbPGetTextDatum(Jsonb *jb)
+{
+ char *str = JsonbToCString(NULL, &jb->root, VARSIZE(jb));
+ Datum res = CStringGetTextDatum(str);
+
+ pfree(str);
+ return res;
+}
+
+/* Coerce SQL/JSON item to the output typid */
+static Datum
+ExecJsonValueCoercion(JsonbValue *item, Oid typid, int32 typmod,
+ bool *isnull, bool *error)
+{
+ *isnull = false;
+
+ /* Special case for json and jsonb output types */
+ if (typid == JSONBOID)
+ return JsonbPGetDatum(JsonbValueToJsonb(item));
+
+ if (typid == JSONOID)
+ return JsonbPGetTextDatum(JsonbValueToJsonb(item));
+
+ /*
+ * Coercion method and set of supported output types are determined
+ * by the item type.
+ */
switch (item->type)
{
case jbvNull:
- coercion = &coercions->null;
- res = (Datum) 0;
- break;
+ Assert(0); /* must be handled by the caller */
+ *isnull = true;
+ return (Datum) 0;
case jbvString:
- coercion = &coercions->string;
- res = PointerGetDatum(cstring_to_text_with_len(item->val.string.val,
+ if (typid == TEXTOID || typid == VARCHAROID || typid == BPCHAROID)
+ {
+ return ExecJsonStringCoercion(item->val.string.val,
+ item->val.string.len,
+ typid, typmod);
+ }
+ else if (typid == INT2OID || typid == INT4OID || typid == INT8OID)
+ {
+ char *str = pnstrdup(item->val.string.val,
+ item->val.string.len);
+
+ if (error)
+ {
+ int64 val = pg_strtoint64_opt_error(str, error);
+
+ if (*error)
+ return (Datum) 0;
+
+ if (typid == INT2OID)
+ {
+ if (val <= PG_INT16_MAX || val >= PG_INT16_MIN)
+ return Int16GetDatum((int16) val);
+ }
+ else if (typid == INT4OID)
+ {
+ if (val <= PG_INT32_MAX || val >= PG_INT32_MIN)
+ return Int32GetDatum((int32) val);
+ }
+ else
+ return Int64GetDatum(val);
+
+ *error = true;
+ return (Datum) 0;
+ }
+ else if (typid == INT2OID)
+ return Int16GetDatum(pg_strtoint16(str));
+ else if (typid == INT4OID)
+ return Int32GetDatum(pg_strtoint32(str));
+ else
+ return Int64GetDatum(pg_strtoint64(str));
+ }
+ else if (typid == BOOLOID)
+ {
+ return BoolGetDatum(boolin_opt_error(item->val.string.val,
+ item->val.string.len,
+ error));
+ }
+ else
+ {
+ Datum str = CStringGetDatum(pnstrdup(item->val.string.val,
item->val.string.len));
- break;
+
+ if (typid == NUMERICOID)
+ return DirectFunctionCall3(numeric_in, str,
+ ObjectIdGetDatum(0),
+ Int32GetDatum(typmod)); /* FIXME errors */
+ else if (typid == DATEOID)
+ return DirectFunctionCall1(date_in, str); /* FIXME errors */
+ else if (typid == TIMEOID)
+ return DirectFunctionCall1(time_in, str); /* FIXME errors */
+ else if (typid == TIMETZOID)
+ return DirectFunctionCall1(timetz_in, str); /* FIXME errors */
+ else if (typid == TIMESTAMPOID)
+ return DirectFunctionCall1(timestamp_in, str); /* FIXME errors */
+ else if (typid == TIMESTAMPTZOID)
+ return DirectFunctionCall1(timestamptz_in, str); /* FIXME errors */
+ else
+ break; /* No cast */
+ }
case jbvNumeric:
- coercion = &coercions->numeric;
- res = NumericGetDatum(item->val.numeric);
- break;
+ {
+ Numeric num = item->val.numeric;
+
+ if (typid == NUMERICOID)
+ return NumericGetDatum(num);
+ else if (typid == TEXTOID || typid == VARCHAROID || typid == BPCHAROID)
+ return ExecJsonCoercionToText(numeric_out, NumericGetDatum(num), typid, typmod);
+ else if (typid == INT2OID)
+ return Int16GetDatum(numeric_int2_opt_error(num, error));
+ else if (typid == INT4OID)
+ return Int32GetDatum(numeric_int4_opt_error(num, error));
+ else if (typid == INT8OID)
+ return Int64GetDatum(numeric_int8_opt_error(num, error));
+ else
+ break; /* No cast */
+ }
case jbvBool:
- coercion = &coercions->boolean;
- res = BoolGetDatum(item->val.boolean);
- break;
-
- case jbvDatetime:
- res = item->val.datetime.value;
- switch (item->val.datetime.typid)
{
- case DATEOID:
- coercion = &coercions->date;
- break;
- case TIMEOID:
- coercion = &coercions->time;
- break;
- case TIMETZOID:
- coercion = &coercions->timetz;
- break;
- case TIMESTAMPOID:
- coercion = &coercions->timestamp;
- break;
- case TIMESTAMPTZOID:
- coercion = &coercions->timestamptz;
- break;
- default:
- elog(ERROR, "unexpected jsonb datetime type oid %u",
- item->val.datetime.typid);
- return (Datum) 0;
+ Datum res;
+
+ if (ExecJsonBoolCoercion(item->val.boolean, typid, typmod, &res))
+ return res;
+
+ break; /* No cast */
}
- break;
+
+ case jbvDatetime:
+ return ExecJsonDatetimeCoercion(item->val.datetime.value,
+ item->val.datetime.typid,
+ typid, typmod, isnull, error);
case jbvArray:
case jbvObject:
case jbvBinary:
- coercion = &coercions->composite;
- res = JsonbPGetDatum(JsonbValueToJsonb(item));
+ Assert(0); /* non-scalars must be rejected by JsonPathValue() */
break;
default:
@@ -4929,99 +5149,98 @@ ExecPrepareJsonItemCoercion(JsonbValue *item,
return (Datum) 0;
}
- *pcoercion = coercion;
+ if (!error)
+ ereport(ERROR,
+ (errcode(ERRCODE_SQL_JSON_ITEM_CANNOT_BE_CAST_TO_TARGET_TYPE),
+ errmsg("SQL/JSON item cannot be cast to target type")));
- return res;
-}
+ *error = true;
+ *isnull = true;
-typedef Datum (*JsonFunc) (ExprEvalStep *op, ExprContext *econtext,
- Datum item, bool *resnull, void *p, bool *error);
+ return (Datum) 0;
+}
+/*
+ * Evaluate a JSON error/empty behavior and coerce result to the output
+ * type.
+ */
static Datum
-ExecEvalJsonExprSubtrans(JsonFunc func, ExprEvalStep *op,
- ExprContext *econtext,
- Datum res, bool *resnull,
- void *p, bool *error, bool subtrans)
+ExecEvalJsonBehavior(ExprContext *econtext, JsonBehavior *behavior,
+ ExprState *default_estate,
+ Oid ret_typid, int32 ret_typmod, bool *is_null)
{
- MemoryContext oldcontext;
- ResourceOwner oldowner;
-
- if (!subtrans)
- /* No need to use subtransactions. */
- return func(op, econtext, res, resnull, p, error);
+ *is_null = false;
- /*
- * We should catch exceptions of category ERRCODE_DATA_EXCEPTION and
- * execute the corresponding ON ERROR behavior then.
- */
- oldcontext = CurrentMemoryContext;
- oldowner = CurrentResourceOwner;
+ switch (behavior->btype)
+ {
+ case JSON_BEHAVIOR_EMPTY_ARRAY:
+ if (ret_typid == JSONBOID)
+ return JsonbPGetDatum(JsonbMakeEmptyArray());
+ else
+ {
+ Assert(ret_typid == JSONOID || ret_typid == TEXTOID ||
+ ret_typid == VARCHAROID || ret_typid == BPCHAROID);
- Assert(error);
+ return ExecJsonStringCoercion("[]", 2, ret_typid, ret_typmod);
+ }
- BeginInternalSubTransaction(NULL);
- /* Want to execute expressions inside function's memory context */
- MemoryContextSwitchTo(oldcontext);
+ case JSON_BEHAVIOR_EMPTY_OBJECT:
+ if (ret_typid == JSONBOID)
+ return JsonbPGetDatum(JsonbMakeEmptyObject());
+ else
+ {
+ Assert(ret_typid == JSONOID || ret_typid == TEXTOID ||
+ ret_typid == VARCHAROID || ret_typid == BPCHAROID);
- PG_TRY();
- {
- res = func(op, econtext, res, resnull, p, error);
+ return ExecJsonStringCoercion("{}", 2, ret_typid, ret_typmod);
+ }
- /* Commit the inner transaction, return to outer xact context */
- ReleaseCurrentSubTransaction();
- MemoryContextSwitchTo(oldcontext);
- CurrentResourceOwner = oldowner;
- }
- PG_CATCH();
- {
- ErrorData *edata;
- int ecategory;
+ case JSON_BEHAVIOR_TRUE:
+ case JSON_BEHAVIOR_FALSE:
+ {
+ Datum res;
+ bool ok =
+ ExecJsonBoolCoercion(behavior->btype == JSON_BEHAVIOR_TRUE,
+ ret_typid, ret_typmod, &res);
- /* Save error info in oldcontext */
- MemoryContextSwitchTo(oldcontext);
- edata = CopyErrorData();
- FlushErrorState();
+ Assert(ok); /* returning type must be check in parser */
- /* Abort the inner transaction */
- RollbackAndReleaseCurrentSubTransaction();
- MemoryContextSwitchTo(oldcontext);
- CurrentResourceOwner = oldowner;
+ return res;
+ }
- ecategory = ERRCODE_TO_CATEGORY(edata->sqlerrcode);
+ case JSON_BEHAVIOR_NULL:
+ case JSON_BEHAVIOR_UNKNOWN:
+ case JSON_BEHAVIOR_EMPTY:
+ *is_null = true;
+ return (Datum) 0;
- if (ecategory != ERRCODE_DATA_EXCEPTION && /* jsonpath and other data
- * errors */
- ecategory != ERRCODE_INTEGRITY_CONSTRAINT_VIOLATION) /* domain errors */
- ReThrowError(edata);
+ case JSON_BEHAVIOR_DEFAULT:
+ /*
+ * Execute DEFAULT expression.
+ * Coercion is not needed here, because expression is
+ * already coerced to the target type by the parser.
+ */
+ return ExecEvalExpr(default_estate, econtext, is_null);
- res = (Datum) 0;
- *error = true;
+ default:
+ elog(ERROR, "unrecognized SQL/JSON behavior %d", behavior->btype);
+ return (Datum) 0;
}
- 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)
+ExecEvalJsonExprInternal(ExprState *state,
+ JsonExprState *jsestate, ExprContext *econtext,
+ JsonPath *path, Datum item, bool *resnull,
+ bool *error)
{
- ExecEvalJsonExprContext *cxt = pcxt;
- JsonPath *path = cxt->path;
- JsonExprState *jsestate = op->d.jsonexpr.jsestate;
JsonExpr *jexpr = jsestate->jsexpr;
- ExprState *estate = NULL;
bool empty = false;
Datum res = (Datum) 0;
+ Oid ret_typid = jexpr->returning->typid;
+ int32 ret_typmod = jexpr->returning->typmod;
+
+ *resnull = true;
switch (jexpr->op)
{
@@ -5029,70 +5248,94 @@ ExecEvalJsonExpr(ExprEvalStep *op, ExprContext *econtext,
res = JsonPathQuery(item, path, jexpr->wrapper, &empty, error,
jsestate->args);
if (error && *error)
- {
- *resnull = true;
return (Datum) 0;
- }
+
+ if (empty)
+ break;
+
*resnull = !DatumGetPointer(res);
- break;
+
+ /* Override default coercion in OMIT QUOTES case */
+ if (ExecJsonQueryNeedsIOCoercion(jexpr, res, *resnull))
+ {
+ char *str = JsonbUnquote(DatumGetJsonbP(res));
+
+ if (ret_typid == JSONBOID)
+ {
+ res = jsonb_from_cstring(str, strlen(str), false, error);
+
+ if (error && *error)
+ return (Datum) 0;
+
+ return res;
+ }
+ else if (ret_typid == JSONOID)
+ {
+ text *json = cstring_to_text(str);
+ JsonLexContext *lex;
+ JsonParseErrorType result;
+
+ /* validate it */
+ lex = makeJsonLexContext(json, false);
+ result = pg_parse_json(lex, &nullSemAction);
+
+ if (result != JSON_SUCCESS)
+ {
+ if (error)
+ {
+ *error = true;
+ return (Datum) 0;
+ }
+
+ json_ereport_error(result, lex);
+ }
+
+ return PointerGetDatum(json);
+ }
+ else if (ret_typid == TEXTOID ||
+ ret_typid == VARCHAROID ||
+ ret_typid == BPCHAROID)
+ return ExecJsonStringCoercion(str, strlen(str), ret_typid, ret_typmod);
+ }
+ else if (ret_typid == JSONBOID)
+ return res;
+ else if (ret_typid == JSONOID || ret_typid == TEXTOID)
+ return JsonbPGetTextDatum(DatumGetJsonbP(res));
+ else if (ret_typid == VARCHAROID || ret_typid == BPCHAROID)
+ {
+ Jsonb *jb = DatumGetJsonbP(res);
+ char *str = JsonbToCString(NULL, &jb->root, VARSIZE(jb));
+
+ return ExecJsonStringCoercion(str, strlen(str), ret_typid, ret_typmod);
+ }
+
+ Assert(0); /* unsupported output type */
+ *error = *resnull = true;
+ return (Datum) 0;
case JSON_VALUE_OP:
{
- struct JsonCoercionState *jcstate;
JsonbValue *jbv = JsonPathValue(item, path, &empty, error,
jsestate->args);
if (error && *error)
return (Datum) 0;
- if (!jbv) /* NULL or empty */
+ if (empty)
break;
- Assert(!empty);
+ if (!jbv)
+ return (Datum) 0; /* NULL */
- *resnull = false;
+ Assert(jbv->type != jbvNull);
- /* coerce scalar item to the output type */
- if (jexpr->returning->typid == JSONOID ||
- jexpr->returning->typid == JSONBOID)
- {
- /* Use result coercion from json[b] to the output type */
- res = JsonbPGetDatum(JsonbValueToJsonb(jbv));
- break;
- }
+ res = ExecJsonValueCoercion(jbv, ret_typid, ret_typmod,
+ resnull, error);
- /* Use coercion from SQL/JSON item type to the output type */
- res = ExecPrepareJsonItemCoercion(jbv,
- jsestate->jsexpr->returning,
- &jsestate->coercions,
- &jcstate);
-
- if (jcstate->coercion &&
- (jcstate->coercion->via_io ||
- jcstate->coercion->via_populate))
- {
- if (error)
- {
- *error = true;
- return (Datum) 0;
- }
-
- /*
- * 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 */
+ if (error && *error)
+ return (Datum) 0;
- /* coerce using specific expression */
- estate = jcstate->estate;
- jsestate->coercion_expr->value = res;
- jsestate->coercion_expr->isnull = *resnull;
- break;
+ return res;
}
case JSON_EXISTS_OP:
@@ -5101,113 +5344,81 @@ ExecEvalJsonExpr(ExprEvalStep *op, ExprContext *econtext,
jsestate->args,
error);
- *resnull = error && *error;
- res = BoolGetDatum(exists);
-
- if (!jsestate->result_expr)
- return res;
+ if (!error || !*error)
+ {
+ /* Should succeed, output type is checked by parser */
+ (void) ExecJsonBoolCoercion(exists, ret_typid, ret_typmod, &res);
+ *resnull = false;
+ }
- /* coerce using result expression */
- estate = jsestate->result_expr;
- jsestate->res_expr->value = res;
- jsestate->res_expr->isnull = *resnull;
- break;
+ return res;
}
- case JSON_TABLE_OP:
- *resnull = false;
- return item;
-
default:
elog(ERROR, "unrecognized SQL/JSON expression op %d", jexpr->op);
return (Datum) 0;
}
- if (empty)
- {
- Assert(jexpr->on_empty); /* it is not JSON_EXISTS */
+ Assert(empty);
+ Assert(jexpr->on_empty); /* it is not JSON_EXISTS */
- if (jexpr->on_empty->btype == JSON_BEHAVIOR_ERROR)
+ if (jexpr->on_empty->btype == JSON_BEHAVIOR_ERROR)
+ {
+ if (error)
{
- if (error)
- {
- *error = true;
- return (Datum) 0;
- }
-
- ereport(ERROR,
- (errcode(ERRCODE_NO_SQL_JSON_ITEM),
- errmsg("no SQL/JSON item")));
+ *error = true;
+ return (Datum) 0;
}
- 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);
+ ereport(ERROR,
+ (errcode(ERRCODE_NO_SQL_JSON_ITEM),
+ errmsg("no SQL/JSON item")));
}
- return ExecEvalJsonExprSubtrans(ExecEvalJsonExprCoercion, op, econtext,
- res, resnull, estate, error,
- cxt->coercionInSubtrans);
+ /* Execute ON EMPTY behavior */
+ return ExecEvalJsonBehavior(econtext, jexpr->on_empty,
+ jsestate->default_on_empty,
+ ret_typid, ret_typmod, resnull);
}
bool
-ExecEvalJsonNeedsSubTransaction(JsonExpr *jsexpr,
- struct JsonCoercionsState *coercions)
+ExecEvalExprCanThrowErrors(Node *expr)
{
- if (jsexpr->on_error->btype == JSON_BEHAVIOR_ERROR)
+ if (!expr)
return false;
- if (jsexpr->op == JSON_EXISTS_OP && !jsexpr->result_coercion)
+ if (IsA(expr, Const))
return false;
- if (!coercions)
- return true;
-
- return false;
+ /* TODO consider more cases */
+ return true;
}
/* ----------------------------------------------------------------
- * ExecEvalJson
+ * ExecEvalJsonExpr
* ----------------------------------------------------------------
*/
void
-ExecEvalJson(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
+ExecEvalJsonExpr(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
{
- ExecEvalJsonExprContext cxt;
JsonExprState *jsestate = op->d.jsonexpr.jsestate;
JsonExpr *jexpr = jsestate->jsexpr;
Datum item;
- Datum res = (Datum) 0;
+ Datum res;
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)
+ if (jsestate->formatted_expr.isnull || jsestate->pathspec.isnull)
{
- /* execute domain checks for NULLs */
- (void) ExecEvalJsonExprCoercion(op, econtext, res, op->resnull,
- NULL, NULL);
-
- Assert(*op->resnull);
+ *op->resnull = true;
+ *op->resvalue = (Datum) 0;
return;
}
- item = jsestate->formatted_expr->value;
- path = DatumGetJsonPathP(jsestate->pathspec->value);
+ item = jsestate->formatted_expr.value;
+ path = DatumGetJsonPathP(jsestate->pathspec.value);
/* reset JSON path variable contexts */
foreach(lc, jsestate->args)
@@ -5218,29 +5429,20 @@ ExecEvalJson(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
var->evaluated = false;
}
- needSubtrans = ExecEvalJsonNeedsSubTransaction(jexpr, &jsestate->coercions);
-
- 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);
+ res = ExecEvalJsonExprInternal(state, jsestate, econtext,
+ path, item, op->resnull,
+ throwErrors ? NULL : &error);
if (error)
{
+ Assert(!throwErrors);
+
/* Execute ON ERROR behavior */
res = ExecEvalJsonBehavior(econtext, jexpr->on_error,
jsestate->default_on_error,
+ jexpr->returning->typid,
+ jexpr->returning->typmod,
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 = res;
diff --git a/src/backend/jit/llvm/llvmjit_expr.c b/src/backend/jit/llvm/llvmjit_expr.c
index bd3965143da..fd72630f5e6 100644
--- a/src/backend/jit/llvm/llvmjit_expr.c
+++ b/src/backend/jit/llvm/llvmjit_expr.c
@@ -2408,7 +2408,7 @@ llvm_compile_expr(ExprState *state)
break;
case EEOP_JSONEXPR:
- build_EvalXFunc(b, mod, "ExecEvalJson",
+ build_EvalXFunc(b, mod, "ExecEvalJsonExpr",
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.c
index 373471ad27f..37fe64654b6 100644
--- a/src/backend/jit/llvm/llvmjit_types.c
+++ b/src/backend/jit/llvm/llvmjit_types.c
@@ -135,7 +135,7 @@ void *referenced_functions[] =
ExecEvalXmlExpr,
ExecEvalJsonConstructor,
ExecEvalJsonIsPredicate,
- ExecEvalJson,
+ ExecEvalJsonExpr,
MakeExpandedObjectReadOnlyInternal,
slot_getmissingattrs,
slot_getsomeattrs_int,
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index c334daae392..e0240beeeab 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -266,9 +266,6 @@ exprType(const Node *expr)
case T_JsonExpr:
type = ((const JsonExpr *) expr)->returning->typid;
break;
- case T_JsonCoercion:
- type = exprType(((const JsonCoercion *) expr)->expr);
- break;
default:
elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
type = InvalidOid; /* keep compiler quiet */
@@ -507,8 +504,6 @@ exprTypmod(const Node *expr)
return ((const JsonConstructorExpr *) expr)->returning->typmod;
case T_JsonExpr:
return ((JsonExpr *) expr)->returning->typmod;
- case T_JsonCoercion:
- return exprTypmod(((const JsonCoercion *) expr)->expr);
default:
break;
}
@@ -1010,14 +1005,12 @@ exprCollation(const Node *expr)
case T_JsonExpr:
{
JsonExpr *jexpr = (JsonExpr *) expr;
- JsonCoercion *coercion = jexpr->result_coercion;
- if (!coercion)
- coll = InvalidOid;
- else if (coercion->expr)
- coll = exprCollation(coercion->expr);
- else if (coercion->via_io || coercion->via_populate)
- coll = coercion->collation;
+ if (jexpr->returning->typid == TEXTOID ||
+ jexpr->returning->typid == CHAROID ||
+ jexpr->returning->typid == VARCHAROID ||
+ jexpr->returning->typid == BPCHAROID)
+ coll = jexpr->collation;
else
coll = InvalidOid;
}
@@ -1255,14 +1248,12 @@ exprSetCollation(Node *expr, Oid collation)
case T_JsonExpr:
{
JsonExpr *jexpr = (JsonExpr *) expr;
- JsonCoercion *coercion = jexpr->result_coercion;
- if (!coercion)
- Assert(!OidIsValid(collation));
- else if (coercion->expr)
- exprSetCollation(coercion->expr, collation);
- else if (coercion->via_io || coercion->via_populate)
- coercion->collation = collation;
+ if (jexpr->returning->typid == TEXTOID ||
+ jexpr->returning->typid == CHAROID ||
+ jexpr->returning->typid == VARCHAROID ||
+ jexpr->returning->typid == BPCHAROID)
+ jexpr->collation = collation;
else
Assert(!OidIsValid(collation));
}
@@ -2507,8 +2498,6 @@ expression_tree_walker(Node *node,
if (walker(jexpr->formatted_expr, context))
return true;
- if (walker(jexpr->result_coercion, context))
- return true;
if (walker(jexpr->passing_values, context))
return true;
/* we assume walker doesn't care about passing_names */
@@ -2517,36 +2506,6 @@ expression_tree_walker(Node *node,
return true;
if (walker(jexpr->on_error->default_expr, context))
return true;
- if (walker(jexpr->coercions, context))
- return true;
- }
- break;
- case T_JsonCoercion:
- return walker(((JsonCoercion *) node)->expr, context);
- case T_JsonItemCoercions:
- {
- JsonItemCoercions *coercions = (JsonItemCoercions *) node;
-
- if (walker(coercions->null, context))
- return true;
- if (walker(coercions->string, context))
- return true;
- if (walker(coercions->numeric, context))
- return true;
- if (walker(coercions->boolean, context))
- return true;
- if (walker(coercions->date, context))
- return true;
- if (walker(coercions->time, context))
- return true;
- if (walker(coercions->timetz, context))
- return true;
- if (walker(coercions->timestamp, context))
- return true;
- if (walker(coercions->timestamptz, context))
- return true;
- if (walker(coercions->composite, context))
- return true;
}
break;
default:
@@ -3576,7 +3535,6 @@ expression_tree_mutator(Node *node,
FLATCOPY(newnode, jexpr, JsonExpr);
MUTATE(newnode->path_spec, jexpr->path_spec, Node *);
MUTATE(newnode->formatted_expr, jexpr->formatted_expr, Node *);
- MUTATE(newnode->result_coercion, jexpr->result_coercion, JsonCoercion *);
MUTATE(newnode->passing_values, jexpr->passing_values, List *);
/* assume mutator does not care about passing_names */
if (newnode->on_empty)
@@ -3587,35 +3545,6 @@ expression_tree_mutator(Node *node,
return (Node *) newnode;
}
break;
- case T_JsonCoercion:
- {
- JsonCoercion *coercion = (JsonCoercion *) node;
- JsonCoercion *newnode;
-
- FLATCOPY(newnode, coercion, JsonCoercion);
- MUTATE(newnode->expr, coercion->expr, Node *);
- return (Node *) newnode;
- }
- break;
- case T_JsonItemCoercions:
- {
- JsonItemCoercions *coercions = (JsonItemCoercions *) node;
- JsonItemCoercions *newnode;
-
- FLATCOPY(newnode, coercions, JsonItemCoercions);
- MUTATE(newnode->null, coercions->null, JsonCoercion *);
- MUTATE(newnode->string, coercions->string, JsonCoercion *);
- MUTATE(newnode->numeric, coercions->numeric, JsonCoercion *);
- MUTATE(newnode->boolean, coercions->boolean, JsonCoercion *);
- MUTATE(newnode->date, coercions->date, JsonCoercion *);
- MUTATE(newnode->time, coercions->time, JsonCoercion *);
- MUTATE(newnode->timetz, coercions->timetz, JsonCoercion *);
- MUTATE(newnode->timestamp, coercions->timestamp, JsonCoercion *);
- MUTATE(newnode->timestamptz, coercions->timestamptz, JsonCoercion *);
- MUTATE(newnode->composite, coercions->composite, JsonCoercion *);
- return (Node *) newnode;
- }
- break;
default:
elog(ERROR, "unrecognized node type: %d",
(int) nodeTag(node));
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 533df86ff77..3c14ddfba2f 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -896,18 +896,6 @@ max_parallel_hazard_walker(Node *node, max_parallel_hazard_context *context)
context, 0);
}
- /* JsonExpr is parallel-unsafe if subtransactions can be used. */
- else if (IsA(node, JsonExpr))
- {
- JsonExpr *jsexpr = (JsonExpr *) node;
-
- if (ExecEvalJsonNeedsSubTransaction(jsexpr, NULL))
- {
- context->max_hazard = PROPARALLEL_UNSAFE;
- return true;
- }
- }
-
/* Recurse to check arguments */
return expression_tree_walker(node,
max_parallel_hazard_walker,
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index fabb5f72076..a6e5fe1eb93 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -4108,7 +4108,6 @@ transformJsonExprCommon(ParseState *pstate, JsonFuncExpr *func)
/* format is determined by context item type */
format = exprType(jsexpr->formatted_expr) == JSONBOID ? JS_FORMAT_JSONB : JS_FORMAT_JSON;
- jsexpr->result_coercion = NULL;
jsexpr->omit_quotes = false;
jsexpr->format = func->common->expr->format;
@@ -4169,40 +4168,6 @@ assignDefaultJsonReturningType(Node *context_item, JsonFormat *context_format,
ret->typmod = -1;
}
-/*
- * Try to coerce expression to the output type or
- * use json_populate_type() for composite, array and domain types or
- * use coercion via I/O.
- */
-static JsonCoercion *
-coerceJsonExpr(ParseState *pstate, Node *expr, const JsonReturning *returning)
-{
- char typtype;
- JsonCoercion *coercion = makeNode(JsonCoercion);
-
- coercion->expr = coerceJsonFuncExpr(pstate, expr, returning, false);
-
- if (coercion->expr)
- {
- if (coercion->expr == expr)
- coercion->expr = NULL;
-
- return coercion;
- }
-
- typtype = get_typtype(returning->typid);
-
- if (returning->typid == RECORDOID ||
- typtype == TYPTYPE_COMPOSITE ||
- typtype == TYPTYPE_DOMAIN ||
- type_is_array(returning->typid))
- coercion->via_populate = true;
- else
- coercion->via_io = true;
-
- return coercion;
-}
-
/*
* Transform a JSON output clause of JSON_VALUE and JSON_QUERY.
*/
@@ -4210,8 +4175,6 @@ static void
transformJsonFuncExprOutput(ParseState *pstate, JsonFuncExpr *func,
JsonExpr *jsexpr)
{
- Node *expr = jsexpr->formatted_expr;
-
jsexpr->returning = transformJsonOutput(pstate, func->output, false);
/* JSON_VALUE returns text by default */
@@ -4225,14 +4188,41 @@ transformJsonFuncExprOutput(ParseState *pstate, JsonFuncExpr *func,
{
JsonReturning ret;
- if (func->op == JSON_VALUE_OP &&
- jsexpr->returning->typid != JSONOID &&
- jsexpr->returning->typid != JSONBOID)
+ if (func->op == JSON_VALUE_OP)
{
- /* Forced coercion via I/O for JSON_VALUE for non-JSON types */
- jsexpr->result_coercion = makeNode(JsonCoercion);
- jsexpr->result_coercion->expr = NULL;
- jsexpr->result_coercion->via_io = true;
+ /*
+ * Only a limited list of output types is supported in
+ * JSON_VALUE() now.
+ */
+ switch (jsexpr->returning->typid)
+ {
+ case JSONBOID:
+ case JSONOID:
+ case TEXTOID:
+ case VARCHAROID:
+ case BPCHAROID:
+ case BOOLOID:
+ case NUMERICOID:
+ case INT2OID:
+ case INT4OID:
+ case INT8OID:
+ /* case FLOAT4OID: FIXME */
+ /* case FLOAT8OID: FIXME */
+ case DATEOID:
+ case TIMEOID:
+ case TIMETZOID:
+ case TIMESTAMPOID:
+ case TIMESTAMPTZOID:
+ break; /* Ok */
+
+ default:
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("returning type %s is not supported in JSON_VALUE()",
+ format_type_be(jsexpr->returning->typid)),
+ parser_coercion_errposition(pstate, func->location, (Node *) jsexpr)));
+ }
+
return;
}
@@ -4241,13 +4231,22 @@ transformJsonFuncExprOutput(ParseState *pstate, JsonFuncExpr *func,
if (ret.typid != jsexpr->returning->typid ||
ret.typmod != jsexpr->returning->typmod)
{
- Node *placeholder = makeCaseTestExpr(expr);
-
- Assert(((CaseTestExpr *) placeholder)->typeId == ret.typid);
- Assert(((CaseTestExpr *) placeholder)->typeMod == ret.typmod);
+ switch (jsexpr->returning->typid)
+ {
+ case JSONBOID:
+ case JSONOID:
+ case TEXTOID:
+ case VARCHAROID:
+ case BPCHAROID:
+ break;
- jsexpr->result_coercion = coerceJsonExpr(pstate, placeholder,
- jsexpr->returning);
+ default:
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("returning type %s is not supported in JSON_QUERY()",
+ format_type_be(jsexpr->returning->typid)),
+ parser_coercion_errposition(pstate, func->location, (Node *) jsexpr)));
+ }
}
}
else
@@ -4293,62 +4292,6 @@ coerceDefaultJsonExpr(ParseState *pstate, JsonExpr *jsexpr, Node *defexpr)
return defexpr;
}
-/*
- * Initialize SQL/JSON item coercion from the SQL type "typid" to the target
- * "returning" type.
- */
-static JsonCoercion *
-initJsonItemCoercion(ParseState *pstate, Oid typid,
- const JsonReturning *returning)
-{
- Node *expr;
-
- if (typid == UNKNOWNOID)
- {
- expr = (Node *) makeNullConst(UNKNOWNOID, -1, InvalidOid);
- }
- else
- {
- CaseTestExpr *placeholder = makeNode(CaseTestExpr);
-
- placeholder->typeId = typid;
- placeholder->typeMod = -1;
- placeholder->collation = InvalidOid;
-
- expr = (Node *) placeholder;
- }
-
- return coerceJsonExpr(pstate, expr, returning);
-}
-
-static void
-initJsonItemCoercions(ParseState *pstate, JsonItemCoercions *coercions,
- const JsonReturning *returning, Oid contextItemTypeId)
-{
- struct
- {
- JsonCoercion **coercion;
- Oid typid;
- } *p,
- coercionTypids[] =
- {
- {&coercions->null, UNKNOWNOID},
- {&coercions->string, TEXTOID},
- {&coercions->numeric, NUMERICOID},
- {&coercions->boolean, BOOLOID},
- {&coercions->date, DATEOID},
- {&coercions->time, TIMEOID},
- {&coercions->timetz, TIMETZOID},
- {&coercions->timestamp, TIMESTAMPOID},
- {&coercions->timestamptz, TIMESTAMPTZOID},
- {&coercions->composite, contextItemTypeId},
- {NULL, InvalidOid}
- };
-
- for (p = coercionTypids; p->coercion; p++)
- *p->coercion = initJsonItemCoercion(pstate, p->typid, returning);
-}
-
/*
* Transform JSON_VALUE, JSON_QUERY, JSON_EXISTS functions into a JsonExpr node.
*/
@@ -4377,10 +4320,6 @@ 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));
-
break;
case JSON_QUERY_OP:
@@ -4409,6 +4348,7 @@ transformJsonFuncExpr(ParseState *pstate, JsonFuncExpr *func)
jsexpr->returning->format->format_type = JS_FORMAT_DEFAULT;
jsexpr->returning->format->encoding = JS_ENC_DEFAULT;
+ /* Coerce intermediate boolean result to the output type if needed */
if (!OidIsValid(jsexpr->returning->typid))
{
jsexpr->returning->typid = BOOLOID;
@@ -4416,32 +4356,21 @@ transformJsonFuncExpr(ParseState *pstate, JsonFuncExpr *func)
}
else if (jsexpr->returning->typid != BOOLOID)
{
- CaseTestExpr *placeholder = makeNode(CaseTestExpr);
- int location = exprLocation((Node *) jsexpr);
-
- placeholder->typeId = BOOLOID;
- placeholder->typeMod = -1;
- placeholder->collation = InvalidOid;
-
- jsexpr->result_coercion = makeNode(JsonCoercion);
- jsexpr->result_coercion->expr =
- coerce_to_target_type(pstate, (Node *) placeholder, BOOLOID,
- jsexpr->returning->typid,
- jsexpr->returning->typmod,
- COERCION_EXPLICIT,
- COERCE_IMPLICIT_CAST,
- location);
-
- if (!jsexpr->result_coercion->expr)
- ereport(ERROR,
- (errcode(ERRCODE_CANNOT_COERCE),
- errmsg("cannot cast type %s to %s",
- format_type_be(BOOLOID),
- format_type_be(jsexpr->returning->typid)),
- parser_coercion_errposition(pstate, location, (Node *) jsexpr)));
+ switch (jsexpr->returning->typid)
+ {
+ case INT4OID:
+ case TEXTOID:
+ case VARCHAROID:
+ case BPCHAROID:
+ break;
- if (jsexpr->result_coercion->expr == (Node *) placeholder)
- jsexpr->result_coercion->expr = NULL;
+ default:
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("returning type %s is not supported in JSON_EXISTS()",
+ format_type_be(jsexpr->returning->typid)),
+ parser_coercion_errposition(pstate, func->location, (Node *) jsexpr)));
+ }
}
break;
@@ -4457,7 +4386,6 @@ transformJsonFuncExpr(ParseState *pstate, JsonFuncExpr *func)
errmsg("JSON_TABLE() is not yet implemented for the json type"),
errhint("Try casting the argument to jsonb"),
parser_errposition(pstate, func->location)));
-
break;
}
diff --git a/src/backend/utils/adt/bool.c b/src/backend/utils/adt/bool.c
index cd7335287f9..78557c99dc5 100644
--- a/src/backend/utils/adt/bool.c
+++ b/src/backend/utils/adt/bool.c
@@ -126,12 +126,10 @@ parse_bool_with_len(const char *value, size_t len, bool *result)
*
* In the switch statement, check the most-used possibilities first.
*/
-Datum
-boolin(PG_FUNCTION_ARGS)
+bool
+boolin_opt_error(const char *in_str, size_t len, bool *error)
{
- const char *in_str = PG_GETARG_CSTRING(0);
const char *str;
- size_t len;
bool result;
/*
@@ -141,20 +139,29 @@ boolin(PG_FUNCTION_ARGS)
while (isspace((unsigned char) *str))
str++;
- len = strlen(str);
+ len -= str - in_str;
while (len > 0 && isspace((unsigned char) str[len - 1]))
len--;
if (parse_bool_with_len(str, len, &result))
- PG_RETURN_BOOL(result);
+ return result;
+
+ if (!error)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+ errmsg("invalid input syntax for type %s: \"%s\"",
+ "boolean", in_str)));
+
+ *error = true;
+ return false;
+}
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
- errmsg("invalid input syntax for type %s: \"%s\"",
- "boolean", in_str)));
+Datum
+boolin(PG_FUNCTION_ARGS)
+{
+ char *str = PG_GETARG_CSTRING(0);
- /* not reached */
- PG_RETURN_BOOL(false);
+ PG_RETURN_BOOL(boolin_opt_error(str, strlen(str), NULL));
}
/*
diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index 081dfa2450f..778bfc90687 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -1286,10 +1286,9 @@ date_timestamp(PG_FUNCTION_ARGS)
/* timestamp_date()
* Convert timestamp to date data type.
*/
-Datum
-timestamp_date(PG_FUNCTION_ARGS)
+DateADT
+timestamp_date_opt_error(Timestamp timestamp, bool *error)
{
- Timestamp timestamp = PG_GETARG_TIMESTAMP(0);
DateADT result;
struct pg_tm tt,
*tm = &tt;
@@ -1302,16 +1301,32 @@ timestamp_date(PG_FUNCTION_ARGS)
else
{
if (timestamp2tm(timestamp, NULL, tm, &fsec, NULL, NULL) != 0)
+ {
+ if (error)
+ {
+ *error = true;
+ return 0;
+ }
+
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("timestamp out of range")));
+ }
result = date2j(tm->tm_year, tm->tm_mon, tm->tm_mday) - POSTGRES_EPOCH_JDATE;
}
- PG_RETURN_DATEADT(result);
+ return result;
}
+/* timestamp_date()
+ * Convert timestamp to date data type.
+ */
+Datum
+timestamp_date(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_DATEADT(timestamp_date_opt_error(PG_GETARG_TIMESTAMP(0), NULL));
+}
/* date_timestamptz()
* Convert date to timestamp with time zone data type.
@@ -1327,14 +1342,9 @@ date_timestamptz(PG_FUNCTION_ARGS)
PG_RETURN_TIMESTAMP(result);
}
-
-/* timestamptz_date()
- * Convert timestamp with time zone to date data type.
- */
-Datum
-timestamptz_date(PG_FUNCTION_ARGS)
+DateADT
+timestamptz_date_opt_error(TimestampTz timestamp, bool *error)
{
- TimestampTz timestamp = PG_GETARG_TIMESTAMP(0);
DateADT result;
struct pg_tm tt,
*tm = &tt;
@@ -1348,14 +1358,31 @@ timestamptz_date(PG_FUNCTION_ARGS)
else
{
if (timestamp2tm(timestamp, &tz, tm, &fsec, NULL, NULL) != 0)
+ {
+ if (error)
+ {
+ *error = true;
+ return 0;
+ }
+
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("timestamp out of range")));
+ }
result = date2j(tm->tm_year, tm->tm_mon, tm->tm_mday) - POSTGRES_EPOCH_JDATE;
}
- PG_RETURN_DATEADT(result);
+ return result;
+}
+
+/* timestamptz_date()
+ * Convert timestamp with time zone to date data type.
+ */
+Datum
+timestamptz_date(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_DATEADT(timestamptz_date_opt_error(PG_GETARG_TIMESTAMP(0), NULL));
}
@@ -1889,22 +1916,32 @@ overlaps_time(PG_FUNCTION_ARGS)
/* timestamp_time()
* Convert timestamp to time data type.
*/
-Datum
-timestamp_time(PG_FUNCTION_ARGS)
+TimeADT
+timestamp_time_opt_error(Timestamp timestamp, bool *isnull, bool *error)
{
- Timestamp timestamp = PG_GETARG_TIMESTAMP(0);
TimeADT result;
struct pg_tm tt,
*tm = &tt;
fsec_t fsec;
if (TIMESTAMP_NOT_FINITE(timestamp))
- PG_RETURN_NULL();
+ {
+ *isnull = true;
+ return 0;
+ }
if (timestamp2tm(timestamp, NULL, tm, &fsec, NULL, NULL) != 0)
+ {
+ if (error)
+ {
+ *error = *isnull = true;
+ return (Datum) 0;
+ }
+
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("timestamp out of range")));
+ }
/*
* Could also do this with time = (timestamp / USECS_PER_DAY *
@@ -1912,17 +1949,33 @@ timestamp_time(PG_FUNCTION_ARGS)
*/
result = ((((tm->tm_hour * MINS_PER_HOUR + tm->tm_min) * SECS_PER_MINUTE) + tm->tm_sec) *
USECS_PER_SEC) + fsec;
+ *isnull = false;
- PG_RETURN_TIMEADT(result);
+ return result;
}
-/* timestamptz_time()
- * Convert timestamptz to time data type.
+/* timestamp_time()
+ * Convert timestamp to time data type.
*/
Datum
-timestamptz_time(PG_FUNCTION_ARGS)
+timestamp_time(PG_FUNCTION_ARGS)
+{
+ bool isnull;
+ TimeADT time =
+ timestamp_time_opt_error(PG_GETARG_TIMESTAMP(0), &isnull, NULL);
+
+ if (isnull)
+ PG_RETURN_NULL();
+ else
+ PG_RETURN_TIMEADT(time);
+}
+
+/* timestamptz_time_opt_error()
+ * Convert timestamptz to time data type.
+ */
+TimeADT
+timestamptz_time_opt_error(TimestampTz timestamp, bool *isnull, bool *error)
{
- TimestampTz timestamp = PG_GETARG_TIMESTAMP(0);
TimeADT result;
struct pg_tm tt,
*tm = &tt;
@@ -1930,12 +1983,23 @@ timestamptz_time(PG_FUNCTION_ARGS)
fsec_t fsec;
if (TIMESTAMP_NOT_FINITE(timestamp))
- PG_RETURN_NULL();
+ {
+ *isnull = true;
+ return 0;
+ }
if (timestamp2tm(timestamp, &tz, tm, &fsec, NULL, NULL) != 0)
+ {
+ if (error)
+ {
+ *error = *isnull = true;
+ return 0;
+ }
+
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("timestamp out of range")));
+ }
/*
* Could also do this with time = (timestamp / USECS_PER_DAY *
@@ -1943,8 +2007,25 @@ timestamptz_time(PG_FUNCTION_ARGS)
*/
result = ((((tm->tm_hour * MINS_PER_HOUR + tm->tm_min) * SECS_PER_MINUTE) + tm->tm_sec) *
USECS_PER_SEC) + fsec;
+ *isnull = false;
- PG_RETURN_TIMEADT(result);
+ return result;
+}
+
+/* timestamptz_time()
+ * Convert timestamptz to time data type.
+ */
+Datum
+timestamptz_time(PG_FUNCTION_ARGS)
+{
+ bool isnull;
+ TimeADT result =
+ timestamptz_time_opt_error(PG_GETARG_TIMESTAMP(0), &isnull, NULL);
+
+ if (isnull)
+ PG_RETURN_NULL();
+ else
+ PG_RETURN_TIMEADT(result);
}
/* datetime_timestamp()
@@ -2812,10 +2893,9 @@ time_timetz(PG_FUNCTION_ARGS)
/* timestamptz_timetz()
* Convert timestamp to timetz data type.
*/
-Datum
-timestamptz_timetz(PG_FUNCTION_ARGS)
+TimeTzADT *
+timestamptz_timetz_opt_error(TimestampTz timestamp, bool *error)
{
- TimestampTz timestamp = PG_GETARG_TIMESTAMP(0);
TimeTzADT *result;
struct pg_tm tt,
*tm = &tt;
@@ -2823,20 +2903,42 @@ timestamptz_timetz(PG_FUNCTION_ARGS)
fsec_t fsec;
if (TIMESTAMP_NOT_FINITE(timestamp))
- PG_RETURN_NULL();
+ return NULL;
if (timestamp2tm(timestamp, &tz, tm, &fsec, NULL, NULL) != 0)
+ {
+ if (error)
+ {
+ *error = true;
+ return NULL;
+ }
+
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("timestamp out of range")));
+ }
result = (TimeTzADT *) palloc(sizeof(TimeTzADT));
tm2timetz(tm, fsec, tz, result);
- PG_RETURN_TIMETZADT_P(result);
+ return result;
}
+/* timestamptz_timetz()
+ * Convert timestamp to timetz data type.
+ */
+Datum
+timestamptz_timetz(PG_FUNCTION_ARGS)
+{
+ TimeTzADT *result =
+ timestamptz_timetz_opt_error(PG_GETARG_TIMESTAMP(0), NULL);
+
+ if (result)
+ PG_RETURN_TIMETZADT_P(result);
+ else
+ PG_RETURN_NULL();
+}
/* datetimetz_timestamptz()
* Convert date and timetz to timestamp with time zone data type.
diff --git a/src/backend/utils/adt/jsonb.c b/src/backend/utils/adt/jsonb.c
index f700c5b4c93..1da519daaef 100644
--- a/src/backend/utils/adt/jsonb.c
+++ b/src/backend/utils/adt/jsonb.c
@@ -46,7 +46,6 @@ typedef struct JsonbAggState
Oid val_output_func;
} JsonbAggState;
-static inline Datum jsonb_from_cstring(char *json, int len, bool unique_keys);
static size_t checkStringLen(size_t len);
static void jsonb_in_object_start(void *pstate);
static void jsonb_in_object_end(void *pstate);
@@ -77,7 +76,7 @@ jsonb_in(PG_FUNCTION_ARGS)
{
char *json = PG_GETARG_CSTRING(0);
- return jsonb_from_cstring(json, strlen(json), false);
+ PG_RETURN_DATUM(jsonb_from_cstring(json, strlen(json), false, NULL));
}
/*
@@ -101,7 +100,7 @@ jsonb_recv(PG_FUNCTION_ARGS)
else
elog(ERROR, "unsupported jsonb version number %d", version);
- return jsonb_from_cstring(str, nbytes, false);
+ return jsonb_from_cstring(str, nbytes, false, NULL);
}
/*
@@ -147,7 +146,7 @@ jsonb_from_text(text *js, bool unique_keys)
{
return jsonb_from_cstring(VARDATA_ANY(js),
VARSIZE_ANY_EXHDR(js),
- unique_keys);
+ unique_keys, NULL);
}
/*
@@ -239,12 +238,13 @@ jsonb_typeof(PG_FUNCTION_ARGS)
*
* Uses the json parser (with hooks) to construct a jsonb.
*/
-static inline Datum
-jsonb_from_cstring(char *json, int len, bool unique_keys)
+Datum
+jsonb_from_cstring(char *json, int len, bool unique_keys, bool *error)
{
JsonLexContext *lex;
JsonbInState state;
JsonSemAction sem;
+ JsonParseErrorType result;
memset(&state, 0, sizeof(state));
memset(&sem, 0, sizeof(sem));
@@ -261,10 +261,20 @@ jsonb_from_cstring(char *json, int len, bool unique_keys)
sem.scalar = jsonb_in_scalar;
sem.object_field_start = jsonb_in_object_field_start;
- pg_parse_json_or_ereport(lex, &sem);
+ result = pg_parse_json(lex, &sem);
+ if (result != JSON_SUCCESS)
+ {
+ if (error)
+ {
+ *error = true;
+ return (Datum) 0;
+ }
+
+ json_ereport_error(result, lex);
+ }
/* after parsing, the item member has the composed jsonb structure */
- PG_RETURN_POINTER(JsonbValueToJsonb(state.res));
+ return JsonbPGetDatum(JsonbValueToJsonb(state.res));
}
static size_t
diff --git a/src/backend/utils/adt/numeric.c b/src/backend/utils/adt/numeric.c
index 920a63b0081..98b1a9ac9ee 100644
--- a/src/backend/utils/adt/numeric.c
+++ b/src/backend/utils/adt/numeric.c
@@ -4308,15 +4308,20 @@ int8_numeric(PG_FUNCTION_ARGS)
}
-Datum
-numeric_int8(PG_FUNCTION_ARGS)
+int64
+numeric_int8_opt_error(Numeric num, bool *error)
{
- Numeric num = PG_GETARG_NUMERIC(0);
NumericVar x;
int64 result;
if (NUMERIC_IS_SPECIAL(num))
{
+ if (error)
+ {
+ *error = true;
+ return 0;
+ }
+
if (NUMERIC_IS_NAN(num))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
@@ -4331,13 +4336,26 @@ numeric_int8(PG_FUNCTION_ARGS)
init_var_from_num(num, &x);
if (!numericvar_to_int64(&x, &result))
+ {
+ if (error)
+ {
+ *error = true;
+ return 0;
+ }
+
ereport(ERROR,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("bigint out of range")));
+ }
- PG_RETURN_INT64(result);
+ return result;
}
+Datum
+numeric_int8(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_INT64(numeric_int8_opt_error(PG_GETARG_NUMERIC(0), NULL));
+}
Datum
int2_numeric(PG_FUNCTION_ARGS)
@@ -4347,17 +4365,20 @@ int2_numeric(PG_FUNCTION_ARGS)
PG_RETURN_NUMERIC(int64_to_numeric(val));
}
-
-Datum
-numeric_int2(PG_FUNCTION_ARGS)
+int16
+numeric_int2_opt_error(Numeric num, bool *error)
{
- Numeric num = PG_GETARG_NUMERIC(0);
NumericVar x;
int64 val;
- int16 result;
if (NUMERIC_IS_SPECIAL(num))
{
+ if (error)
+ {
+ *error = true;
+ return 0;
+ }
+
if (NUMERIC_IS_NAN(num))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
@@ -4372,21 +4393,40 @@ numeric_int2(PG_FUNCTION_ARGS)
init_var_from_num(num, &x);
if (!numericvar_to_int64(&x, &val))
+ {
+ if (error)
+ {
+ *error = true;
+ return 0;
+ }
+
ereport(ERROR,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("smallint out of range")));
+ }
if (unlikely(val < PG_INT16_MIN) || unlikely(val > PG_INT16_MAX))
+ {
+ if (error)
+ {
+ *error = true;
+ return 0;
+ }
+
ereport(ERROR,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("smallint out of range")));
+ }
/* Down-convert to int2 */
- result = (int16) val;
-
- PG_RETURN_INT16(result);
+ return (int16) val;
}
+Datum
+numeric_int2(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_INT16(numeric_int2_opt_error(PG_GETARG_NUMERIC(0), NULL));
+}
Datum
float8_numeric(PG_FUNCTION_ARGS)
diff --git a/src/backend/utils/adt/numutils.c b/src/backend/utils/adt/numutils.c
index cc3f95d3990..b82b30f9013 100644
--- a/src/backend/utils/adt/numutils.c
+++ b/src/backend/utils/adt/numutils.c
@@ -248,7 +248,7 @@ invalid_syntax:
* positive number.
*/
int64
-pg_strtoint64(const char *s)
+pg_strtoint64_opt_error(const char *s, bool *error)
{
const char *ptr = s;
int64 tmp = 0;
@@ -307,12 +307,24 @@ pg_strtoint64(const char *s)
return tmp;
out_of_range:
+ if (error)
+ {
+ *error = true;
+ return 0;
+ }
+
ereport(ERROR,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("value \"%s\" is out of range for type %s",
s, "bigint")));
invalid_syntax:
+ if (error)
+ {
+ *error = true;
+ return 0;
+ }
+
ereport(ERROR,
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
errmsg("invalid input syntax for type %s: \"%s\"",
@@ -321,6 +333,12 @@ invalid_syntax:
return 0; /* keep compiler quiet */
}
+int64
+pg_strtoint64(const char *s)
+{
+ return pg_strtoint64_opt_error(s, NULL);
+}
+
/*
* pg_itoa: converts a signed 16-bit integer to its string representation
* and returns strlen(a).
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 49cdb290ac2..889d3476e56 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -5614,8 +5614,8 @@ timestamptz_timestamp(PG_FUNCTION_ARGS)
PG_RETURN_TIMESTAMP(timestamptz2timestamp(timestamp));
}
-static Timestamp
-timestamptz2timestamp(TimestampTz timestamp)
+Timestamp
+timestamptz2timestamp_opt_error(TimestampTz timestamp, bool *error)
{
Timestamp result;
struct pg_tm tt,
@@ -5628,17 +5628,40 @@ timestamptz2timestamp(TimestampTz timestamp)
else
{
if (timestamp2tm(timestamp, &tz, tm, &fsec, NULL, NULL) != 0)
+ {
+ if (error)
+ {
+ *error = true;
+ return 0;
+ }
+
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("timestamp out of range")));
+ }
+
if (tm2timestamp(tm, fsec, NULL, &result) != 0)
+ {
+ if (error)
+ {
+ *error = true;
+ return 0;
+ }
+
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("timestamp out of range")));
+ }
}
return result;
}
+static Timestamp
+timestamptz2timestamp(TimestampTz timestamp)
+{
+ return timestamptz2timestamp_opt_error(timestamp, NULL);
+}
+
/* timestamptz_zone()
* Evaluate timestamp with time zone type at the specified time zone.
* Returns a timestamp without time zone.
diff --git a/src/include/executor/execExpr.h b/src/include/executor/execExpr.h
index c8ef917ffe0..adabf57c97b 100644
--- a/src/include/executor/execExpr.h
+++ b/src/include/executor/execExpr.h
@@ -762,43 +762,14 @@ 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 */
+ formatted_expr, /* formatted context item value */
+ coercion_expr, /* input for JSON item coercion */
+ pathspec; /* path specification value */
- 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() */
-
- struct JsonCoercionsState
- {
- struct JsonCoercionState
- {
- JsonCoercion *coercion; /* coercion expression */
- ExprState *estate; /* coercion expression state */
- } null,
- string,
- numeric ,
- boolean,
- date,
- time,
- timetz,
- timestamp,
- timestamptz,
- composite;
- } coercions; /* states for coercion from SQL/JSON item
- * types directly to the output type */
} JsonExprState;
/* functions in execExpr.c */
@@ -860,18 +831,8 @@ extern void ExecEvalSysVar(ExprState *state, ExprEvalStep *op,
ExprContext *econtext, TupleTableSlot *slot);
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,
- struct JsonCoercionsState *);
-extern Datum ExecEvalExprPassingCaseValue(ExprState *estate,
- ExprContext *econtext, bool *isnull,
- Datum caseval_datum,
- bool caseval_isnull);
+extern void ExecEvalJsonExpr(ExprState *state, ExprEvalStep *op,
+ ExprContext *econtext);
extern void ExecAggInitGroup(AggState *aggstate, AggStatePerTrans pertrans, AggStatePerGroup pergroup,
ExprContext *aggcontext);
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 3aa96bb6855..14fbb32ce9c 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -1551,39 +1551,6 @@ typedef struct JsonBehavior
Node *default_expr; /* default expression, if any */
} JsonBehavior;
-/*
- * JsonCoercion -
- * coercion from SQL/JSON item types to SQL types
- */
-typedef struct JsonCoercion
-{
- NodeTag type;
- Node *expr; /* resulting expression coerced to target type */
- bool via_populate; /* coerce result using json_populate_type()? */
- bool via_io; /* coerce result using type input function? */
- Oid collation; /* collation for coercion via I/O or populate */
-} JsonCoercion;
-
-/*
- * JsonItemCoercions -
- * expressions for coercion from SQL/JSON item types directly to the
- * output SQL type
- */
-typedef struct JsonItemCoercions
-{
- NodeTag type;
- JsonCoercion *null;
- JsonCoercion *string;
- JsonCoercion *numeric;
- JsonCoercion *boolean;
- JsonCoercion *date;
- JsonCoercion *time;
- JsonCoercion *timetz;
- JsonCoercion *timestamp;
- JsonCoercion *timestamptz;
- JsonCoercion *composite; /* arrays and objects */
-} JsonItemCoercions;
-
/*
* JsonExpr -
* transformed representation of JSON_VALUE(), JSON_QUERY(), JSON_EXISTS()
@@ -1593,7 +1560,6 @@ typedef struct JsonExpr
Expr xpr;
JsonExprOp op; /* json function ID */
Node *formatted_expr; /* formatted context item expression */
- JsonCoercion *result_coercion; /* resulting coercion to RETURNING type */
JsonFormat *format; /* context item format (JSON/JSONB) */
Node *path_spec; /* JSON path specification expression */
List *passing_names; /* PASSING argument names */
@@ -1601,8 +1567,8 @@ typedef struct JsonExpr
JsonReturning *returning; /* RETURNING clause type/format info */
JsonBehavior *on_empty; /* ON EMPTY behavior */
JsonBehavior *on_error; /* ON ERROR behavior */
- JsonItemCoercions *coercions; /* coercions for JSON_VALUE */
JsonWrapper wrapper; /* WRAPPER for JSON_QUERY */
+ Oid collation; /* OID of collation, or InvalidOid if none */
bool omit_quotes; /* KEEP/OMIT QUOTES for JSON_QUERY */
int location; /* token location, or -1 if unknown */
} JsonExpr;
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index 221c3e6c3de..5e1aa37c1f5 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -24,6 +24,7 @@
/* bool.c */
extern bool parse_bool(const char *value, bool *result);
extern bool parse_bool_with_len(const char *value, size_t len, bool *result);
+extern bool boolin_opt_error(const char *value, size_t len, bool *error);
/* domains.c */
extern void domain_check(Datum value, bool isnull, Oid domainType,
@@ -46,6 +47,7 @@ extern int namestrcmp(Name name, const char *str);
extern int16 pg_strtoint16(const char *s);
extern int32 pg_strtoint32(const char *s);
extern int64 pg_strtoint64(const char *s);
+extern int64 pg_strtoint64_opt_error(const char *s, bool *error);
extern int pg_itoa(int16 i, char *a);
extern int pg_ultoa_n(uint32 l, char *a);
extern int pg_ulltoa_n(uint64 l, char *a);
diff --git a/src/include/utils/date.h b/src/include/utils/date.h
index 91ae24254df..9059f2b5ce7 100644
--- a/src/include/utils/date.h
+++ b/src/include/utils/date.h
@@ -87,4 +87,14 @@ extern bool time_overflows(int hour, int min, int sec, fsec_t fsec);
extern bool float_time_overflows(int hour, int min, double sec);
extern void AdjustTimeForTypmod(TimeADT *time, int32 typmod);
+extern DateADT timestamp_date_opt_error(Timestamp timestamp, bool *error);
+extern TimeADT timestamp_time_opt_error(Timestamp timestamp,
+ bool *isnull, bool *error);
+
+extern DateADT timestamptz_date_opt_error(Timestamp timestamp, bool *error);
+extern TimeADT timestamptz_time_opt_error(TimestampTz timestamp,
+ bool *isnull, bool *error);
+extern TimeTzADT *timestamptz_timetz_opt_error(TimestampTz timestamp,
+ bool *error);
+
#endif /* DATE_H */
diff --git a/src/include/utils/jsonb.h b/src/include/utils/jsonb.h
index bae466b5234..d5574faa79e 100644
--- a/src/include/utils/jsonb.h
+++ b/src/include/utils/jsonb.h
@@ -420,6 +420,8 @@ extern void JsonbHashScalarValueExtended(const JsonbValue *scalarVal,
/* jsonb.c support functions */
extern Datum jsonb_from_text(text *js, bool unique_keys);
+extern Datum jsonb_from_cstring(char *json, int len, bool unique_keys,
+ bool *error);
extern char *JsonbToCString(StringInfo out, JsonbContainer *in,
int estimated_len);
extern char *JsonbToCStringIndent(StringInfo out, JsonbContainer *in,
diff --git a/src/include/utils/numeric.h b/src/include/utils/numeric.h
index 3caa74dfe7a..ede68ab81b8 100644
--- a/src/include/utils/numeric.h
+++ b/src/include/utils/numeric.h
@@ -85,6 +85,8 @@ extern Numeric numeric_div_opt_error(Numeric num1, Numeric num2,
bool *have_error);
extern Numeric numeric_mod_opt_error(Numeric num1, Numeric num2,
bool *have_error);
-extern int32 numeric_int4_opt_error(Numeric num, bool *error);
+extern int16 numeric_int2_opt_error(Numeric num, bool *have_error);
+extern int32 numeric_int4_opt_error(Numeric num, bool *have_error);
+extern int64 numeric_int8_opt_error(Numeric num, bool *have_error);
#endif /* _PG_NUMERIC_H_ */
diff --git a/src/include/utils/timestamp.h b/src/include/utils/timestamp.h
index edf3a973186..f9382d7193f 100644
--- a/src/include/utils/timestamp.h
+++ b/src/include/utils/timestamp.h
@@ -102,6 +102,8 @@ extern int timestamp_cmp_internal(Timestamp dt1, Timestamp dt2);
extern TimestampTz timestamp2timestamptz_opt_overflow(Timestamp timestamp,
int *overflow);
+extern Timestamp timestamptz2timestamp_opt_error(TimestampTz timestamp,
+ bool *error);
extern int32 timestamp_cmp_timestamptz_internal(Timestamp timestampVal,
TimestampTz dt2);
diff --git a/src/test/regress/expected/jsonb_sqljson.out b/src/test/regress/expected/jsonb_sqljson.out
index ef496110af3..0d0705e6a8f 100644
--- a/src/test/regress/expected/jsonb_sqljson.out
+++ b/src/test/regress/expected/jsonb_sqljson.out
@@ -184,11 +184,11 @@ SELECT JSON_EXISTS(jsonb '1', 'strict $[1]' RETURNING text FALSE ON ERROR);
(1 row)
SELECT JSON_EXISTS(jsonb '1', '$[0]' RETURNING jsonb);
-ERROR: cannot cast type boolean to jsonb
+ERROR: returning type jsonb is not supported in JSON_EXISTS()
LINE 1: SELECT JSON_EXISTS(jsonb '1', '$[0]' RETURNING jsonb);
^
SELECT JSON_EXISTS(jsonb '1', '$[0]' RETURNING float4);
-ERROR: cannot cast type boolean to real
+ERROR: returning type real is not supported in JSON_EXISTS()
LINE 1: SELECT JSON_EXISTS(jsonb '1', '$[0]' RETURNING float4);
^
-- JSON_VALUE
@@ -242,7 +242,9 @@ SELECT JSON_VALUE(jsonb '123', '$' RETURNING text);
/* jsonb bytea ??? */
SELECT JSON_VALUE(jsonb '123', '$' RETURNING bytea ERROR ON ERROR);
-ERROR: SQL/JSON item cannot be cast to target type
+ERROR: returning type bytea is not supported in JSON_VALUE()
+LINE 2: SELECT JSON_VALUE(jsonb '123', '$' RETURNING bytea ERROR ON ...
+ ^
SELECT JSON_VALUE(jsonb '1.23', '$');
json_value
------------
@@ -349,14 +351,213 @@ 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);
+ERROR: returning type sqljsonb_json_not_null is not supported in JSON_VALUE()
+LINE 1: SELECT JSON_VALUE(jsonb '"1"', '$' RETURNING sqljsonb_json_n...
+ ^
+SELECT JSON_VALUE(jsonb '"1"', '$' RETURNING sqljsonb_jsonb_not_null);
+ERROR: returning type sqljsonb_jsonb_not_null is not supported in JSON_VALUE()
+LINE 1: SELECT JSON_VALUE(jsonb '"1"', '$' RETURNING sqljsonb_jsonb_...
+ ^
+SELECT JSON_VALUE(jsonb '1', '$' RETURNING sqljsonb_json_not_null);
+ERROR: returning type sqljsonb_json_not_null is not supported in JSON_VALUE()
+LINE 1: SELECT JSON_VALUE(jsonb '1', '$' RETURNING sqljsonb_json_not...
+ ^
+SELECT JSON_VALUE(jsonb '1', '$' RETURNING sqljsonb_jsonb_not_null);
+ERROR: returning type sqljsonb_jsonb_not_null is not supported in JSON_VALUE()
+LINE 1: SELECT JSON_VALUE(jsonb '1', '$' RETURNING sqljsonb_jsonb_no...
+ ^
+SELECT JSON_VALUE(jsonb 'null', '$' RETURNING sqljsonb_json_not_null);
+ERROR: returning type sqljsonb_json_not_null is not supported in JSON_VALUE()
+LINE 1: SELECT JSON_VALUE(jsonb 'null', '$' RETURNING sqljsonb_json_...
+ ^
+SELECT JSON_VALUE(jsonb 'null', '$' RETURNING sqljsonb_jsonb_not_null);
+ERROR: returning type sqljsonb_jsonb_not_null is not supported in JSON_VALUE()
+LINE 1: SELECT JSON_VALUE(jsonb 'null', '$' RETURNING sqljsonb_jsonb...
+ ^
+SELECT JSON_VALUE(jsonb 'true', '$' RETURNING sqljsonb_json_not_null);
+ERROR: returning type sqljsonb_json_not_null is not supported in JSON_VALUE()
+LINE 1: SELECT JSON_VALUE(jsonb 'true', '$' RETURNING sqljsonb_json_...
+ ^
+SELECT JSON_VALUE(jsonb 'true', '$' RETURNING sqljsonb_jsonb_not_null);
+ERROR: returning type sqljsonb_jsonb_not_null is not supported in JSON_VALUE()
+LINE 1: SELECT JSON_VALUE(jsonb 'true', '$' RETURNING sqljsonb_jsonb...
+ ^
+-- 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
+ERROR: returning type sqljsonb_int_not_null is not supported in JSON_VALUE()
+LINE 1: SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_int_no...
+ ^
+SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_int_not_null DEFAULT 2 ON EMPTY);
+ERROR: returning type sqljsonb_int_not_null is not supported in JSON_VALUE()
+LINE 1: SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_int_no...
+ ^
+SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_int_not_null DEFAULT NULL ON EMPTY);
+ERROR: returning type sqljsonb_int_not_null is not supported in JSON_VALUE()
+LINE 1: SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_int_no...
+ ^
+SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_int_not_null DEFAULT 2 ON ERROR);
+ERROR: returning type sqljsonb_int_not_null is not supported in JSON_VALUE()
+LINE 1: SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_int_no...
+ ^
+SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_int_not_null DEFAULT NULL ON EMPTY DEFAULT 2 ON ERROR);
+ERROR: returning type sqljsonb_int_not_null is not supported in JSON_VALUE()
+LINE 1: SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_int_no...
+ ^
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
+ERROR: returning type sqljsonb_int_not_null is not supported in JSON_VALUE()
+LINE 1: SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_int_no...
+ ^
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
+ERROR: returning type sqljsonb_int_not_null is not supported in JSON_VALUE()
+LINE 1: SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_int_no...
+ ^
+SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_json_not_null);
+ERROR: returning type sqljsonb_json_not_null is not supported in JSON_VALUE()
+LINE 1: SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_json_n...
+ ^
+SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_json_not_null DEFAULT '2' ON EMPTY);
+ERROR: returning type sqljsonb_json_not_null is not supported in JSON_VALUE()
+LINE 1: SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_json_n...
+ ^
+SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_json_not_null DEFAULT NULL ON EMPTY);
+ERROR: returning type sqljsonb_json_not_null is not supported in JSON_VALUE()
+LINE 1: SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_json_n...
+ ^
+SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_json_not_null DEFAULT '2' ON ERROR);
+ERROR: returning type sqljsonb_json_not_null is not supported in JSON_VALUE()
+LINE 1: SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_json_n...
+ ^
+SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_json_not_null DEFAULT NULL ON EMPTY DEFAULT '2' ON ERROR);
+ERROR: returning type sqljsonb_json_not_null is not supported in JSON_VALUE()
+LINE 1: SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_json_n...
+ ^
+SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_json_not_null NULL ON ERROR);
+ERROR: returning type sqljsonb_json_not_null is not supported in JSON_VALUE()
+LINE 1: SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_json_n...
+ ^
+SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_json_not_null DEFAULT NULL ON ERROR);
+ERROR: returning type sqljsonb_json_not_null is not supported in JSON_VALUE()
+LINE 1: SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_json_n...
+ ^
+SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_jsonb_not_null);
+ERROR: returning type sqljsonb_jsonb_not_null is not supported in JSON_VALUE()
+LINE 1: SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_jsonb_...
+ ^
+SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_jsonb_not_null DEFAULT '2' ON EMPTY);
+ERROR: returning type sqljsonb_jsonb_not_null is not supported in JSON_VALUE()
+LINE 1: SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_jsonb_...
+ ^
+SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_jsonb_not_null DEFAULT NULL ON EMPTY);
+ERROR: returning type sqljsonb_jsonb_not_null is not supported in JSON_VALUE()
+LINE 1: SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_jsonb_...
+ ^
+SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_jsonb_not_null DEFAULT '2' ON ERROR);
+ERROR: returning type sqljsonb_jsonb_not_null is not supported in JSON_VALUE()
+LINE 1: SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_jsonb_...
+ ^
+SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_jsonb_not_null DEFAULT NULL ON EMPTY DEFAULT '2' ON ERROR);
+ERROR: returning type sqljsonb_jsonb_not_null is not supported in JSON_VALUE()
+LINE 1: SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_jsonb_...
+ ^
+SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_jsonb_not_null NULL ON ERROR);
+ERROR: returning type sqljsonb_jsonb_not_null is not supported in JSON_VALUE()
+LINE 1: SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_jsonb_...
+ ^
+SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_jsonb_not_null DEFAULT NULL ON ERROR);
+ERROR: returning type sqljsonb_jsonb_not_null is not supported in JSON_VALUE()
+LINE 1: SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_jsonb_...
+ ^
+SELECT JSON_VALUE(jsonb 'null', '$' RETURNING sqljsonb_int_not_null);
+ERROR: returning type sqljsonb_int_not_null is not supported in JSON_VALUE()
+LINE 1: SELECT JSON_VALUE(jsonb 'null', '$' RETURNING sqljsonb_int_n...
+ ^
+SELECT JSON_VALUE(jsonb 'null', '$' RETURNING sqljsonb_int_not_null DEFAULT 2 ON EMPTY);
+ERROR: returning type sqljsonb_int_not_null is not supported in JSON_VALUE()
+LINE 1: SELECT JSON_VALUE(jsonb 'null', '$' RETURNING sqljsonb_int_n...
+ ^
+SELECT JSON_VALUE(jsonb 'null', '$' RETURNING sqljsonb_int_not_null DEFAULT 2 ON ERROR);
+ERROR: returning type sqljsonb_int_not_null is not supported in JSON_VALUE()
+LINE 1: SELECT JSON_VALUE(jsonb 'null', '$' RETURNING sqljsonb_int_n...
+ ^
+SELECT JSON_VALUE(jsonb 'null', '$' RETURNING sqljsonb_int_not_null NULL ON ERROR);
+ERROR: returning type sqljsonb_int_not_null is not supported in JSON_VALUE()
+LINE 1: SELECT JSON_VALUE(jsonb 'null', '$' RETURNING sqljsonb_int_n...
+ ^
+SELECT JSON_VALUE(jsonb 'null', '$' RETURNING sqljsonb_int_not_null DEFAULT NULL ON ERROR);
+ERROR: returning type sqljsonb_int_not_null is not supported in JSON_VALUE()
+LINE 1: SELECT JSON_VALUE(jsonb 'null', '$' RETURNING sqljsonb_int_n...
+ ^
+SELECT JSON_VALUE(jsonb 'null', '$' RETURNING sqljsonb_json_not_null);
+ERROR: returning type sqljsonb_json_not_null is not supported in JSON_VALUE()
+LINE 1: SELECT JSON_VALUE(jsonb 'null', '$' RETURNING sqljsonb_json_...
+ ^
+SELECT JSON_VALUE(jsonb 'null', '$' RETURNING sqljsonb_json_not_null DEFAULT '2' ON EMPTY);
+ERROR: returning type sqljsonb_json_not_null is not supported in JSON_VALUE()
+LINE 1: SELECT JSON_VALUE(jsonb 'null', '$' RETURNING sqljsonb_json_...
+ ^
+SELECT JSON_VALUE(jsonb 'null', '$' RETURNING sqljsonb_json_not_null DEFAULT '2' ON ERROR);
+ERROR: returning type sqljsonb_json_not_null is not supported in JSON_VALUE()
+LINE 1: SELECT JSON_VALUE(jsonb 'null', '$' RETURNING sqljsonb_json_...
+ ^
+SELECT JSON_VALUE(jsonb 'null', '$' RETURNING sqljsonb_json_not_null NULL ON ERROR);
+ERROR: returning type sqljsonb_json_not_null is not supported in JSON_VALUE()
+LINE 1: SELECT JSON_VALUE(jsonb 'null', '$' RETURNING sqljsonb_json_...
+ ^
+SELECT JSON_VALUE(jsonb 'null', '$' RETURNING sqljsonb_json_not_null DEFAULT NULL ON ERROR);
+ERROR: returning type sqljsonb_json_not_null is not supported in JSON_VALUE()
+LINE 1: SELECT JSON_VALUE(jsonb 'null', '$' RETURNING sqljsonb_json_...
+ ^
+SELECT JSON_VALUE(jsonb 'null', '$' RETURNING sqljsonb_jsonb_not_null);
+ERROR: returning type sqljsonb_jsonb_not_null is not supported in JSON_VALUE()
+LINE 1: SELECT JSON_VALUE(jsonb 'null', '$' RETURNING sqljsonb_jsonb...
+ ^
+SELECT JSON_VALUE(jsonb 'null', '$' RETURNING sqljsonb_jsonb_not_null DEFAULT '2' ON EMPTY);
+ERROR: returning type sqljsonb_jsonb_not_null is not supported in JSON_VALUE()
+LINE 1: SELECT JSON_VALUE(jsonb 'null', '$' RETURNING sqljsonb_jsonb...
+ ^
+SELECT JSON_VALUE(jsonb 'null', '$' RETURNING sqljsonb_jsonb_not_null DEFAULT '2' ON ERROR);
+ERROR: returning type sqljsonb_jsonb_not_null is not supported in JSON_VALUE()
+LINE 1: SELECT JSON_VALUE(jsonb 'null', '$' RETURNING sqljsonb_jsonb...
+ ^
+SELECT JSON_VALUE(jsonb 'null', '$' RETURNING sqljsonb_jsonb_not_null NULL ON ERROR);
+ERROR: returning type sqljsonb_jsonb_not_null is not supported in JSON_VALUE()
+LINE 1: SELECT JSON_VALUE(jsonb 'null', '$' RETURNING sqljsonb_jsonb...
+ ^
+SELECT JSON_VALUE(jsonb 'null', '$' RETURNING sqljsonb_jsonb_not_null DEFAULT NULL ON ERROR);
+ERROR: returning type sqljsonb_jsonb_not_null is not supported in JSON_VALUE()
+LINE 1: SELECT JSON_VALUE(jsonb 'null', '$' RETURNING sqljsonb_jsonb...
+ ^
+SELECT JSON_VALUE(jsonb '0', '$' RETURNING sqljsonb_json_not_null);
+ERROR: returning type sqljsonb_json_not_null is not supported in JSON_VALUE()
+LINE 1: SELECT JSON_VALUE(jsonb '0', '$' RETURNING sqljsonb_json_not...
+ ^
+SELECT JSON_VALUE(jsonb '0', '$' RETURNING sqljsonb_json_not_null ERROR ON ERROR);
+ERROR: returning type sqljsonb_json_not_null is not supported in JSON_VALUE()
+LINE 1: SELECT JSON_VALUE(jsonb '0', '$' RETURNING sqljsonb_json_not...
+ ^
+SELECT JSON_VALUE(jsonb '0', '$' RETURNING sqljsonb_json_not_null DEFAULT '2' ON ERROR);
+ERROR: returning type sqljsonb_json_not_null is not supported in JSON_VALUE()
+LINE 1: SELECT JSON_VALUE(jsonb '0', '$' RETURNING sqljsonb_json_not...
+ ^
+SELECT JSON_VALUE(jsonb '0', '$' RETURNING sqljsonb_jsonb_not_null);
+ERROR: returning type sqljsonb_jsonb_not_null is not supported in JSON_VALUE()
+LINE 1: SELECT JSON_VALUE(jsonb '0', '$' RETURNING sqljsonb_jsonb_no...
+ ^
+SELECT JSON_VALUE(jsonb '0', '$' RETURNING sqljsonb_jsonb_not_null ERROR ON ERROR);
+ERROR: returning type sqljsonb_jsonb_not_null is not supported in JSON_VALUE()
+LINE 1: SELECT JSON_VALUE(jsonb '0', '$' RETURNING sqljsonb_jsonb_no...
+ ^
+SELECT JSON_VALUE(jsonb '0', '$' RETURNING sqljsonb_jsonb_not_null DEFAULT '2' ON ERROR);
+ERROR: returning type sqljsonb_jsonb_not_null is not supported in JSON_VALUE()
+LINE 1: SELECT JSON_VALUE(jsonb '0', '$' RETURNING sqljsonb_jsonb_no...
+ ^
+-- Test returning of non-scalar items
SELECT JSON_VALUE(jsonb '[]', '$');
json_value
------------
@@ -478,11 +679,9 @@ SELECT JSON_VALUE(jsonb 'null', '$a' PASSING point ' (1, 2 )' AS a);
(1 row)
SELECT JSON_VALUE(jsonb 'null', '$a' PASSING point ' (1, 2 )' AS a RETURNING point);
- json_value
-------------
- (1,2)
-(1 row)
-
+ERROR: returning type point is not supported in JSON_VALUE()
+LINE 1: SELECT JSON_VALUE(jsonb 'null', '$a' PASSING point ' (1, 2 )...
+ ^
-- Test timestamptz passing and output
SELECT JSON_VALUE(jsonb 'null', '$ts' PASSING timestamptz '2018-02-21 12:34:56 +10' AS ts);
json_value
@@ -852,49 +1051,35 @@ FROM
CREATE TYPE sqljsonb_rec AS (a int, t text, js json, jb jsonb, jsa json[]);
CREATE TYPE sqljsonb_reca AS (reca sqljsonb_rec[]);
SELECT JSON_QUERY(jsonb '[{"a": 1, "b": "foo", "t": "aaa", "js": [1, "2", {}], "jb": {"x": [1, "2", {}]}}, {"a": 2}]', '$[0]' RETURNING sqljsonb_rec);
- json_query
------------------------------------------------------
- (1,aaa,"[1, ""2"", {}]","{""x"": [1, ""2"", {}]}",)
-(1 row)
-
+ERROR: returning type sqljsonb_rec is not supported in JSON_QUERY()
+LINE 1: SELECT JSON_QUERY(jsonb '[{"a": 1, "b": "foo", "t": "aaa", "...
+ ^
SELECT * FROM unnest((JSON_QUERY(jsonb '{"jsa": [{"a": 1, "b": ["foo"]}, {"a": 2, "c": {}}, 123]}', '$' RETURNING sqljsonb_rec)).jsa);
- unnest
-------------------------
- {"a": 1, "b": ["foo"]}
- {"a": 2, "c": {}}
- 123
-(3 rows)
-
+ERROR: returning type sqljsonb_rec is not supported in JSON_QUERY()
+LINE 1: SELECT * FROM unnest((JSON_QUERY(jsonb '{"jsa": [{"a": 1, "...
+ ^
SELECT * FROM unnest((JSON_QUERY(jsonb '{"reca": [{"a": 1, "t": ["foo", []]}, {"a": 2, "jb": [{}, true]}]}', '$' RETURNING sqljsonb_reca)).reca);
- a | t | js | jb | jsa
----+-------------+----+------------+-----
- 1 | ["foo", []] | | |
- 2 | | | [{}, true] |
-(2 rows)
-
+ERROR: returning type sqljsonb_reca is not supported in JSON_QUERY()
+LINE 1: SELECT * FROM unnest((JSON_QUERY(jsonb '{"reca": [{"a": 1, "...
+ ^
-- Extension: array types returning
SELECT JSON_QUERY(jsonb '[1,2,null,"3"]', '$[*]' RETURNING int[] WITH WRAPPER);
- json_query
---------------
- {1,2,NULL,3}
-(1 row)
-
+ERROR: returning type integer[] is not supported in JSON_QUERY()
+LINE 1: SELECT JSON_QUERY(jsonb '[1,2,null,"3"]', '$[*]' RETURNING i...
+ ^
SELECT * FROM unnest(JSON_QUERY(jsonb '[{"a": 1, "t": ["foo", []]}, {"a": 2, "jb": [{}, true]}]', '$' RETURNING sqljsonb_rec[]));
- a | t | js | jb | jsa
----+-------------+----+------------+-----
- 1 | ["foo", []] | | |
- 2 | | | [{}, true] |
-(2 rows)
-
+ERROR: returning type sqljsonb_rec[] is not supported in JSON_QUERY()
+LINE 1: SELECT * FROM unnest(JSON_QUERY(jsonb '[{"a": 1, "t": ["foo"...
+ ^
-- Extension: domain types returning
SELECT JSON_QUERY(jsonb '{"a": 1}', '$.a' RETURNING sqljsonb_int_not_null);
- json_query
-------------
- 1
-(1 row)
-
+ERROR: returning type sqljsonb_int_not_null is not supported in JSON_QUERY()
+LINE 1: SELECT JSON_QUERY(jsonb '{"a": 1}', '$.a' RETURNING sqljsonb...
+ ^
SELECT JSON_QUERY(jsonb '{"a": 1}', '$.b' RETURNING sqljsonb_int_not_null);
-ERROR: domain sqljsonb_int_not_null does not allow null values
+ERROR: returning type sqljsonb_int_not_null is not supported in JSON_QUERY()
+LINE 1: SELECT JSON_QUERY(jsonb '{"a": 1}', '$.b' RETURNING sqljsonb...
+ ^
-- Test timestamptz passing and output
SELECT JSON_QUERY(jsonb 'null', '$ts' PASSING timestamptz '2018-02-21 12:34:56 +10' AS ts);
json_query
@@ -1048,7 +1233,6 @@ SELECT * FROM JSON_TABLE(jsonb '123', '$'
(1 row)
-- JSON_TABLE: basic functionality
-CREATE DOMAIN jsonb_test_domain AS text CHECK (value <> 'foo');
SELECT *
FROM
(VALUES
@@ -1069,7 +1253,6 @@ FROM
"char(4)" char(4) PATH '$',
"bool" bool PATH '$',
"numeric" numeric PATH '$',
- "domain" jsonb_test_domain PATH '$',
js json PATH '$',
jb jsonb PATH '$',
jst text FORMAT JSON PATH '$',
@@ -1085,10 +1268,7 @@ FROM
exists4 text EXISTS PATH 'strict $.aaa' FALSE ON ERROR,
js2 json PATH '$',
jsb2w jsonb PATH '$' WITH WRAPPER,
- jsb2q jsonb PATH '$' OMIT QUOTES,
- ia int[] PATH '$',
- ta text[] PATH '$',
- jba jsonb[] PATH '$'
+ jsb2q jsonb PATH '$' OMIT QUOTES
)
) jt
ON true;
@@ -1123,7 +1303,6 @@ SELECT * FROM
"char(4)" char(4) PATH '$',
"bool" bool PATH '$',
"numeric" numeric PATH '$',
- "domain" jsonb_test_domain PATH '$',
js json PATH '$',
jb jsonb PATH '$',
jst text FORMAT JSON PATH '$',
@@ -1139,9 +1318,6 @@ SELECT * FROM
js2 json PATH '$',
jsb2w jsonb PATH '$' WITH WRAPPER,
jsb2q jsonb PATH '$' OMIT QUOTES,
- ia int[] PATH '$',
- ta text[] PATH '$',
- jba jsonb[] PATH '$',
NESTED PATH '$[1]' AS p1 COLUMNS (
a1 int,
NESTED PATH '$[*]' AS "p1 1" COLUMNS (
@@ -1168,7 +1344,6 @@ CREATE OR REPLACE VIEW public.jsonb_table_view AS
"json_table"."char(4)",
"json_table".bool,
"json_table"."numeric",
- "json_table".domain,
"json_table".js,
"json_table".jb,
"json_table".jst,
@@ -1184,9 +1359,6 @@ CREATE OR REPLACE VIEW public.jsonb_table_view AS
"json_table".js2,
"json_table".jsb2w,
"json_table".jsb2q,
- "json_table".ia,
- "json_table".ta,
- "json_table".jba,
"json_table".a1,
"json_table".b1,
"json_table".a11,
@@ -1205,7 +1377,6 @@ CREATE OR REPLACE VIEW public.jsonb_table_view AS
"char(4)" character(4) PATH '$',
bool boolean PATH '$',
"numeric" numeric PATH '$',
- domain jsonb_test_domain PATH '$',
js json PATH '$',
jb jsonb PATH '$',
jst text FORMAT JSON PATH '$',
@@ -1221,9 +1392,6 @@ CREATE OR REPLACE VIEW public.jsonb_table_view AS
js2 json PATH '$',
jsb2w jsonb PATH '$' WITH UNCONDITIONAL WRAPPER,
jsb2q jsonb PATH '$' OMIT QUOTES,
- ia integer[] PATH '$',
- ta text[] PATH '$',
- jba jsonb[] PATH '$',
NESTED PATH '$[1]' AS p1
COLUMNS (
a1 integer PATH '$."a1"',
@@ -1248,15 +1416,14 @@ CREATE OR REPLACE VIEW public.jsonb_table_view AS
PLAN (json_table_path_1 OUTER ((p1 OUTER "p1 1") UNION (p2 OUTER ("p2:1" UNION p22))))
)
EXPLAIN (COSTS OFF, VERBOSE) SELECT * FROM jsonb_table_view;
- QUERY PLAN
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ QUERY PLAN
+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Table Function Scan on "json_table"
- Output: "json_table".id, "json_table".id2, "json_table"."int", "json_table".text, "json_table"."char(4)", "json_table".bool, "json_table"."numeric", "json_table".domain, "json_table".js, "json_table".jb, "json_table".jst, "json_table".jsc, "json_table".jsv, "json_table".jsb, "json_table".jsbq, "json_table".aaa, "json_table".aaa1, "json_table".exists1, "json_table".exists2, "json_table".exists3, "json_table".js2, "json_table".jsb2w, "json_table".jsb2q, "json_table".ia, "json_table".ta, "json_table".jba, "json_table".a1, "json_table".b1, "json_table".a11, "json_table".a21, "json_table".a22
- Table Function Call: JSON_TABLE('null'::jsonb, '$[*]' AS json_table_path_1 PASSING 3 AS a, '"foo"'::jsonb AS "b c" COLUMNS (id FOR ORDINALITY, id2 FOR ORDINALITY, "int" integer PATH '$', text text PATH '$', "char(4)" character(4) PATH '$', bool boolean PATH '$', "numeric" numeric PATH '$', domain jsonb_test_domain PATH '$', js json PATH '$', jb jsonb PATH '$', jst text FORMAT JSON PATH '$', jsc character(4) FORMAT JSON PATH '$', jsv character varying(4) FORMAT JSON PATH '$', jsb jsonb PATH '$', jsbq jsonb PATH '$' OMIT QUOTES, aaa integer PATH '$."aaa"', aaa1 integer PATH '$."aaa"', exists1 boolean EXISTS PATH '$."aaa"', exists2 integer EXISTS PATH '$."aaa"' TRUE ON ERROR, exists3 text EXISTS PATH 'strict $."aaa"' UNKNOWN ON ERROR, js2 json PATH '$', jsb2w jsonb PATH '$' WITH UNCONDITIONAL WRAPPER, jsb2q jsonb PATH '$' OMIT QUOTES, ia integer[] PATH '$', ta text[] PATH '$', jba jsonb[] PATH '$', NESTED PATH '$[1]' AS p1 COLUMNS (a1 integer PATH '$."a1"', b1 text PATH '$."b1"', NESTED PATH '$[*]' AS "p1 1" COLUMNS (a11 text PATH '$."a11"')), NESTED PATH '$[2]' AS p2 COLUMNS ( NESTED PATH '$[*]' AS "p2:1" COLUMNS (a21 text PATH '$."a21"'), NESTED PATH '$[*]' AS p22 COLUMNS (a22 text PATH '$."a22"'))) PLAN (json_table_path_1 OUTER ((p1 OUTER "p1 1") UNION (p2 OUTER ("p2:1" UNION p22)))))
+ Output: "json_table".id, "json_table".id2, "json_table"."int", "json_table".text, "json_table"."char(4)", "json_table".bool, "json_table"."numeric", "json_table".js, "json_table".jb, "json_table".jst, "json_table".jsc, "json_table".jsv, "json_table".jsb, "json_table".jsbq, "json_table".aaa, "json_table".aaa1, "json_table".exists1, "json_table".exists2, "json_table".exists3, "json_table".js2, "json_table".jsb2w, "json_table".jsb2q, "json_table".a1, "json_table".b1, "json_table".a11, "json_table".a21, "json_table".a22
+ Table Function Call: JSON_TABLE('null'::jsonb, '$[*]' AS json_table_path_1 PASSING 3 AS a, '"foo"'::jsonb AS "b c" COLUMNS (id FOR ORDINALITY, id2 FOR ORDINALITY, "int" integer PATH '$', text text PATH '$', "char(4)" character(4) PATH '$', bool boolean PATH '$', "numeric" numeric PATH '$', js json PATH '$', jb jsonb PATH '$', jst text FORMAT JSON PATH '$', jsc character(4) FORMAT JSON PATH '$', jsv character varying(4) FORMAT JSON PATH '$', jsb jsonb PATH '$', jsbq jsonb PATH '$' OMIT QUOTES, aaa integer PATH '$."aaa"', aaa1 integer PATH '$."aaa"', exists1 boolean EXISTS PATH '$."aaa"', exists2 integer EXISTS PATH '$."aaa"' TRUE ON ERROR, exists3 text EXISTS PATH 'strict $."aaa"' UNKNOWN ON ERROR, js2 json PATH '$', jsb2w jsonb PATH '$' WITH UNCONDITIONAL WRAPPER, jsb2q jsonb PATH '$' OMIT QUOTES, NESTED PATH '$[1]' AS p1 COLUMNS (a1 integer PATH '$."a1"', b1 text PATH '$."b1"', NESTED PATH '$[*]' AS "p1 1" COLUMNS (a11 text PATH '$."a11"')), NESTED PATH '$[2]' AS p2 COLUMNS ( NESTED PATH '$[*]' AS "p2:1" COLUMNS (a21 text PATH '$."a21"'), NESTED PATH '$[*]' AS p22 COLUMNS (a22 text PATH '$."a22"'))) PLAN (json_table_path_1 OUTER ((p1 OUTER "p1 1") UNION (p2 OUTER ("p2:1" UNION p22)))))
(3 rows)
DROP VIEW jsonb_table_view;
-DROP DOMAIN jsonb_test_domain;
-- JSON_TABLE: ON EMPTY/ON ERROR behavior
SELECT *
FROM
@@ -1318,15 +1485,15 @@ SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a int4 EXISTS PATH '$.a'));
(1 row)
SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a int2 EXISTS PATH '$.a'));
-ERROR: cannot cast type boolean to smallint
+ERROR: returning type smallint is not supported in JSON_EXISTS()
LINE 1: ...ELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a int2 EXI...
^
SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a int8 EXISTS PATH '$.a'));
-ERROR: cannot cast type boolean to bigint
+ERROR: returning type bigint is not supported in JSON_EXISTS()
LINE 1: ...ELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a int8 EXI...
^
SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a float4 EXISTS PATH '$.a'));
-ERROR: cannot cast type boolean to real
+ERROR: returning type real is not supported in JSON_EXISTS()
LINE 1: ...ELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a float4 E...
^
SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a char(3) EXISTS PATH '$.a'));
@@ -1336,11 +1503,11 @@ SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a char(3) EXISTS PATH '$.a'))
(1 row)
SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a json EXISTS PATH '$.a'));
-ERROR: cannot cast type boolean to json
+ERROR: returning type json is not supported in JSON_EXISTS()
LINE 1: ...ELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a json EXI...
^
SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a jsonb EXISTS PATH '$.a'));
-ERROR: cannot cast type boolean to jsonb
+ERROR: returning type jsonb is not supported in JSON_EXISTS()
LINE 1: ...ELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a jsonb EX...
^
-- JSON_TABLE: nested paths and plans
@@ -2102,11 +2269,14 @@ set parallel_leader_participation = off;
-- Should be non-parallel due to subtransactions
EXPLAIN (COSTS OFF)
SELECT sum(JSON_VALUE(js, '$' RETURNING numeric)) FROM test_parallel_jsonb_value;
- QUERY PLAN
----------------------------------------------
- Aggregate
- -> Seq Scan on test_parallel_jsonb_value
-(2 rows)
+ QUERY PLAN
+------------------------------------------------------------------
+ Finalize Aggregate
+ -> Gather
+ Workers Planned: 4
+ -> Partial Aggregate
+ -> Parallel Seq Scan on test_parallel_jsonb_value
+(5 rows)
SELECT sum(JSON_VALUE(js, '$' RETURNING numeric)) FROM test_parallel_jsonb_value;
sum
diff --git a/src/test/regress/sql/jsonb_sqljson.sql b/src/test/regress/sql/jsonb_sqljson.sql
index fff25374808..e74a4a5dbbe 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 '{}', '$');
@@ -338,8 +399,6 @@ SELECT * FROM JSON_TABLE(jsonb '123', '$'
COLUMNS (item int PATH '$', foo int)) bar;
-- JSON_TABLE: basic functionality
-CREATE DOMAIN jsonb_test_domain AS text CHECK (value <> 'foo');
-
SELECT *
FROM
(VALUES
@@ -360,7 +419,6 @@ FROM
"char(4)" char(4) PATH '$',
"bool" bool PATH '$',
"numeric" numeric PATH '$',
- "domain" jsonb_test_domain PATH '$',
js json PATH '$',
jb jsonb PATH '$',
jst text FORMAT JSON PATH '$',
@@ -377,10 +435,7 @@ FROM
js2 json PATH '$',
jsb2w jsonb PATH '$' WITH WRAPPER,
- jsb2q jsonb PATH '$' OMIT QUOTES,
- ia int[] PATH '$',
- ta text[] PATH '$',
- jba jsonb[] PATH '$'
+ jsb2q jsonb PATH '$' OMIT QUOTES
)
) jt
ON true;
@@ -399,7 +454,6 @@ SELECT * FROM
"char(4)" char(4) PATH '$',
"bool" bool PATH '$',
"numeric" numeric PATH '$',
- "domain" jsonb_test_domain PATH '$',
js json PATH '$',
jb jsonb PATH '$',
jst text FORMAT JSON PATH '$',
@@ -416,9 +470,6 @@ SELECT * FROM
js2 json PATH '$',
jsb2w jsonb PATH '$' WITH WRAPPER,
jsb2q jsonb PATH '$' OMIT QUOTES,
- ia int[] PATH '$',
- ta text[] PATH '$',
- jba jsonb[] PATH '$',
NESTED PATH '$[1]' AS p1 COLUMNS (
a1 int,
@@ -443,7 +494,6 @@ SELECT * FROM
EXPLAIN (COSTS OFF, VERBOSE) SELECT * FROM jsonb_table_view;
DROP VIEW jsonb_table_view;
-DROP DOMAIN jsonb_test_domain;
-- JSON_TABLE: ON EMPTY/ON ERROR behavior
SELECT *
--
2.17.1
On 2022-08-24 We 20:05, Nikita Glukhov wrote:
v8 - is a highly WIP patch, which I failed to finish today.
Even some test cases fail now, and they simply show unfinished
things like casts to bytea (they can be simply removed) and missing
safe input functions.
Thanks for your work, please keep going.
cheers
andrew
--
Andrew Dunstan
EDB: https://www.enterprisedb.com
On 8/24/22 8:16 PM, Andrew Dunstan wrote:
On 2022-08-24 We 20:05, Nikita Glukhov wrote:
v8 - is a highly WIP patch, which I failed to finish today.
Even some test cases fail now, and they simply show unfinished
things like casts to bytea (they can be simply removed) and missing
safe input functions.Thanks for your work, please keep going.
Thanks for the efforts Nikita.
With RMT hat on, I want to point out that it's nearing the end of the
week, and if we are going to go forward with this path, we do need to
review soon. The Beta 4 release date is set to 9/8, and if we are going
to commit or revert, we should leave enough time to ensure that we have
enough time to review and the patches are able to successfully get
through the buildfarm.
Thanks,
Jonathan
On 2022-08-26 Fr 12:36, Jonathan S. Katz wrote:
On 8/24/22 8:16 PM, Andrew Dunstan wrote:
On 2022-08-24 We 20:05, Nikita Glukhov wrote:
v8 - is a highly WIP patch, which I failed to finish today.
Even some test cases fail now, and they simply show unfinished
things like casts to bytea (they can be simply removed) and missing
safe input functions.Thanks for your work, please keep going.
Thanks for the efforts Nikita.
With RMT hat on, I want to point out that it's nearing the end of the
week, and if we are going to go forward with this path, we do need to
review soon. The Beta 4 release date is set to 9/8, and if we are
going to commit or revert, we should leave enough time to ensure that
we have enough time to review and the patches are able to successfully
get through the buildfarm.
Also I'm going to be traveling and more or less offline from Sept 5th,
so if I'm going to be involved we'd need a decision by Sept 1st or 2nd,
I think, so time is running very short. Of course, others could do the
required commit work either way a bit later, but not much.
cheers
andrew
--
Andrew Dunstan
EDB: https://www.enterprisedb.com
Hi,
On 26.08.2022 22:25, Andrew Dunstan wrote:
On 2022-08-24 We 20:05, Nikita Glukhov wrote:
v8 - is a highly WIP patch, which I failed to finish today.
Even some test cases fail now, and they simply show unfinished
things like casts to bytea (they can be simply removed) and missing
safe input functions.Thanks for your work, please keep going.
I have completed in v9 all the things I previously planned:
- Added missing safe I/O and type conversion functions for
datetime, float4, varchar, bpchar. This introduces a lot
of boilerplate code for returning errors and also maybe
adds some overhead.
- Added JSON_QUERY coercion to UTF8 bytea using pg_convert_to().
- Added immutability checks that were missed with elimination
of coercion expressions.
Coercions text::datetime, datetime1::datetime2 and even
datetime::text for some datetime types are mutable.
datetime::text can be made immutable by passing ISO date
style into output functions (like in jsonpath).
- Disabled non-Const expressions in DEFAULT ON EMPTY in non
ERROR ON ERROR case. Non-constant expressions are tried to
evaluate into Const directly inside transformExpr().
Maybe it would be better to simply remove DEFAULT ON EMPTY.
It is possible to easily split this patch into several subpatches,
I will do it if needed.
--
Nikita Glukhov
Postgres Professional:http://www.postgrespro.com
The Russian Postgres Company
Attachments:
v9-0001-Remove-subtransactions-in-SQL-JSON-execution.patchtext/x-patch; charset=UTF-8; name=v9-0001-Remove-subtransactions-in-SQL-JSON-execution.patchDownload
From 95417c337c2ab6899df28017641e5342cb925a11 Mon Sep 17 00:00:00 2001
From: Nikita Glukhov <n.gluhov@postgrespro.ru>
Date: Fri, 26 Aug 2022 22:46:02 +0300
Subject: [PATCH v9] Remove subtransactions in SQL/JSON execution
---
contrib/adminpack/adminpack.c | 2 +-
src/backend/executor/execExpr.c | 84 +-
src/backend/executor/execExprInterp.c | 946 ++++++++++++--------
src/backend/jit/llvm/llvmjit_expr.c | 2 +-
src/backend/jit/llvm/llvmjit_types.c | 2 +-
src/backend/nodes/nodeFuncs.c | 91 +-
src/backend/optimizer/util/clauses.c | 73 +-
src/backend/parser/parse_expr.c | 226 ++---
src/backend/utils/adt/bool.c | 31 +-
src/backend/utils/adt/date.c | 277 ++++--
src/backend/utils/adt/datetime.c | 16 +-
src/backend/utils/adt/float.c | 70 +-
src/backend/utils/adt/jsonb.c | 26 +-
src/backend/utils/adt/jsonpath.c | 8 +-
src/backend/utils/adt/numeric.c | 230 +++--
src/backend/utils/adt/numutils.c | 20 +-
src/backend/utils/adt/timestamp.c | 126 ++-
src/backend/utils/misc/guc.c | 2 +-
src/include/executor/execExpr.h | 49 +-
src/include/nodes/primnodes.h | 36 +-
src/include/optimizer/clauses.h | 2 +
src/include/utils/builtins.h | 2 +
src/include/utils/date.h | 13 +
src/include/utils/datetime.h | 6 +-
src/include/utils/float.h | 1 +
src/include/utils/jsonb.h | 2 +
src/include/utils/jsonpath.h | 3 +-
src/include/utils/numeric.h | 6 +-
src/include/utils/timestamp.h | 6 +
src/test/regress/expected/jsonb_sqljson.out | 327 +++++--
src/test/regress/sql/jsonb_sqljson.sql | 72 +-
31 files changed, 1704 insertions(+), 1053 deletions(-)
diff --git a/contrib/adminpack/adminpack.c b/contrib/adminpack/adminpack.c
index 03addf1dc5f..4bf795d42a4 100644
--- a/contrib/adminpack/adminpack.c
+++ b/contrib/adminpack/adminpack.c
@@ -571,7 +571,7 @@ pg_logdir_ls_internal(FunctionCallInfo fcinfo)
if (ParseDateTime(timestampbuf, lowstr, MAXDATELEN, field, ftype, MAXDATEFIELDS, &nf))
continue;
- if (DecodeDateTime(field, ftype, nf, &dtype, &date, &fsec, &tz))
+ if (DecodeDateTime(field, ftype, nf, &dtype, &date, &fsec, &tz, true))
continue;
/* Seems the timestamp is OK; prepare and return tuple */
diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
index d0a57c7aaee..558992df909 100644
--- a/src/backend/executor/execExpr.c
+++ b/src/backend/executor/execExpr.c
@@ -2561,58 +2561,41 @@ ExecInitExprRec(Expr *node, ExprState *state,
case T_JsonExpr:
{
JsonExpr *jexpr = castNode(JsonExpr, node);
- JsonExprState *jsestate = palloc0(sizeof(JsonExprState));
+ JsonExprState *jsestate;
ListCell *argexprlc;
ListCell *argnamelc;
- scratch.opcode = EEOP_JSONEXPR;
- scratch.d.jsonexpr.jsestate = jsestate;
+ /* JSON_TABLE preudo-function returns context item as a result */
+ if (jexpr->op == JSON_TABLE_OP)
+ {
+ ExecInitExprRec((Expr *) jexpr->formatted_expr, state,
+ resv, resnull);
+ break;
+ }
+ jsestate = palloc0(sizeof(JsonExprState));
jsestate->jsexpr = jexpr;
- jsestate->formatted_expr =
- palloc(sizeof(*jsestate->formatted_expr));
+ scratch.opcode = EEOP_JSONEXPR;
+ scratch.d.jsonexpr.jsestate = jsestate;
ExecInitExprRec((Expr *) jexpr->formatted_expr, state,
- &jsestate->formatted_expr->value,
- &jsestate->formatted_expr->isnull);
-
- jsestate->pathspec =
- palloc(sizeof(*jsestate->pathspec));
+ &jsestate->formatted_expr.value,
+ &jsestate->formatted_expr.isnull);
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->pathspec.value,
+ &jsestate->pathspec.isnull);
- jsestate->default_on_empty = !jexpr->on_empty ? NULL :
- ExecInitExpr((Expr *) jexpr->on_empty->default_expr,
- state->parent);
+ if (jexpr->on_empty)
+ jsestate->default_on_empty =
+ 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,
@@ -2636,35 +2619,8 @@ ExecInitExprRec(Expr *node, ExprState *state,
lappend(jsestate->args, var);
}
- jsestate->cache = NULL;
-
- if (jexpr->coercions)
- {
- JsonCoercion **coercion;
- struct JsonCoercionState *cstate;
- Datum *caseval;
- bool *casenull;
-
- 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;
- }
- }
-
ExprEvalPushStep(state, &scratch);
+
break;
}
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index 636794ca6f1..45c648817ba 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -75,6 +75,7 @@
#include "utils/date.h"
#include "utils/datum.h"
#include "utils/expandedrecord.h"
+#include "utils/float.h"
#include "utils/json.h"
#include "utils/jsonb.h"
#include "utils/jsonfuncs.h"
@@ -1844,7 +1845,7 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
EEO_CASE(EEOP_JSONEXPR)
{
/* too complex for an inline implementation */
- ExecEvalJson(state, op, econtext);
+ ExecEvalJsonExpr(state, op, econtext);
EEO_NEXT();
}
@@ -4700,96 +4701,6 @@ ExecEvalJsonConstructor(ExprState *state, ExprEvalStep *op,
*op->resnull = isnull;
}
-/*
- * Evaluate a JSON error/empty behavior result.
- */
-static Datum
-ExecEvalJsonBehavior(ExprContext *econtext, JsonBehavior *behavior,
- ExprState *default_estate, 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:
- 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.
*/
@@ -4844,84 +4755,438 @@ EvalJsonPathVar(void *cxt, char *varName, int varNameLen,
}
/*
- * Prepare SQL/JSON item coercion to the output type. Returned a datum of the
- * corresponding SQL type and a pointer to the coercion state.
+ * Check whether we need to override default coercion in
+ * JSON_QUERY(OMIT QUOTES) case.
*/
-Datum
-ExecPrepareJsonItemCoercion(JsonbValue *item,
- JsonReturning *returning,
- struct JsonCoercionsState *coercions,
- struct JsonCoercionState **pcoercion)
+static bool
+ExecJsonQueryNeedsIOCoercion(JsonExpr *jsexpr, Datum res, bool isnull)
{
- struct JsonCoercionState *coercion;
- Datum res;
- JsonbValue buf;
+ if (jsexpr->omit_quotes && !isnull)
+ {
+ Jsonb *jb = DatumGetJsonbP(res);
+ JsonbValue jbv;
+
+ /* Coerce string items via I/O in OMIT QUOTES case */
+ return JsonbExtractScalar(&jb->root, &jbv) &&
+ jbv.type == jbvString;
+ }
+
+ return false;
+}
- if (item->type == jbvBinary &&
- JsonContainerIsScalar(item->val.binary.data))
+/* Coerce C string to text, varchar(N), or bpchar(N) */
+static Datum
+ExecJsonStringCoercion(const char *str, int32 len, Oid typid, int32 typmod)
+{
+ if (typid == BYTEAOID)
{
- bool res PG_USED_FOR_ASSERTS_ONLY;
+ text *txt = cstring_to_text_with_len(str, len);
+ NameData encoding = {0};
+
+ /* UTF8 is the only one supported encoding */
+ strcpy(NameStr(encoding), "UTF8");
- res = JsonbExtractScalar(item->val.binary.data, &buf);
- item = &buf;
- Assert(res);
+ return DirectFunctionCall2(pg_convert_to,
+ PointerGetDatum(txt),
+ PointerGetDatum(&encoding));
}
+ else if (typid == TEXTOID || typid == JSONOID || typmod <= VARHDRSZ)
+ return PointerGetDatum(cstring_to_text_with_len(str, len));
+ else
+ {
+ char *txt;
+ int32 size;
+
+ len = VARHDRSZ + len;
+
+ if (typid == BPCHAROID)
+ size = typmod;
+ else
+ size = Min(len, typmod);
+
+ txt = palloc(size);
+ SET_VARSIZE(txt, size);
+
+ memcpy(VARDATA(txt), str, Min(size, len) - VARHDRSZ);
+
+ if (len < size)
+ memset(txt + len, ' ', size - len);
- /* get coercion state reference and datum of the corresponding SQL type */
+ return PointerGetDatum(txt);
+ }
+}
+
+/* Coerce SQL/JSON item to text */
+static Datum
+ExecJsonCoercionToText(PGFunction outfunc, Datum value, Oid typid, int32 typmod)
+{
+ char *str = DatumGetCString(DirectFunctionCall1(outfunc, value));
+
+ return ExecJsonStringCoercion(str, strlen(str), typid, typmod);
+}
+
+/* Coerce datetime SQL/JSON item to the output typid */
+static Datum
+ExecJsonDatetimeCoercion(Datum val, Oid val_typid, Oid typid, int32 typmod,
+ bool *isnull, bool *error)
+{
+ if (val_typid == typid)
+ return val;
+
+ /*
+ * XXX coercion to text is done using output functions, and they
+ * are mutable for non-time[tz] types due to using of DateStyle.
+ * We can pass USE_ISO_DATES, which is used inside jsonpath, to
+ * make these coercions and JSON_VALUE(RETURNING text) immutable.
+ *
+ * XXX Also timestamp[tz] output functions can throw "out of range"
+ * error, but this error seem to be not possible.
+ */
+ switch (val_typid)
+ {
+ case DATEOID:
+ if (typid == TEXTOID || typid == VARCHAROID || typid == BPCHAROID)
+ return ExecJsonCoercionToText(date_out, val, typid, typmod);
+ else if (typid == DATEOID)
+ return val;
+ else if (typid == TIMESTAMPOID)
+ {
+ int overflow = 0;
+ Timestamp ts =
+ date2timestamp_opt_overflow(DatumGetDateADT(val),
+ error ? &overflow : NULL);
+
+ if (overflow)
+ {
+ *error = true;
+ return (Datum) 0;
+ }
+
+ return TimestampGetDatum(ts);
+ }
+ else if (typid == TIMESTAMPTZOID)
+ {
+ int overflow = 0;
+ TimestampTz res =
+ date2timestamptz_opt_overflow(DatumGetDateADT(val),
+ error ? &overflow : NULL);
+
+ if (overflow)
+ {
+ *error = true;
+ return (Datum) 0;
+ }
+
+ return TimestampTzGetDatum(res);
+ }
+ else
+ break; /* No cast */
+
+ case TIMEOID:
+ if (typid == TEXTOID || typid == VARCHAROID || typid == BPCHAROID)
+ return ExecJsonCoercionToText(time_out, val, typid, typmod);
+ else if (typid == TIMEOID)
+ return val;
+ else if (typid == TIMETZOID)
+ return DirectFunctionCall1(time_timetz, val);
+ else
+ break; /* No cast */
+
+ case TIMETZOID:
+ if (typid == TEXTOID || typid == VARCHAROID || typid == BPCHAROID)
+ return ExecJsonCoercionToText(timetz_out, val, typid, typmod);
+ else if (typid == TIMETZOID)
+ return val;
+ else if (typid == TIMEOID)
+ return DirectFunctionCall1(timetz_time, val);
+ else
+ break;
+
+ case TIMESTAMPOID:
+ if (typid == TEXTOID || typid == VARCHAROID || typid == BPCHAROID)
+ return ExecJsonCoercionToText(timestamp_out, val, typid, typmod);
+ else if (typid == TIMESTAMPOID)
+ return val;
+ else if (typid == DATEOID)
+ return timestamp_date_opt_error(DatumGetTimestamp(val), error);
+ else if (typid == TIMEOID)
+ {
+ TimeADT time =
+ timestamp_time_opt_error(DatumGetTimestamp(val),
+ isnull, error);
+
+ if ((error && *error) || *isnull)
+ return (Datum) 0;
+ else
+ return TimeADTGetDatum(time);
+ }
+ else if (typid == TIMESTAMPTZOID)
+ {
+ int overflow = 0;
+ TimestampTz tstz =
+ timestamp2timestamptz_opt_overflow(DatumGetTimestamp(val),
+ error ? &overflow : NULL);
+
+ if (overflow)
+ {
+ *error = true;
+ return (Datum) 0;
+ }
+
+ return TimestampTzGetDatum(tstz);
+ }
+ else
+ break; /* No cast */
+
+ case TIMESTAMPTZOID:
+ if (typid == TEXTOID || typid == VARCHAROID || typid == BPCHAROID)
+ return ExecJsonCoercionToText(timestamptz_out, val, typid, typmod);
+ else if (typid == TIMESTAMPTZOID)
+ return val;
+ else if (typid == DATEOID)
+ return timestamptz_date_opt_error(DatumGetTimestampTz(val), error);
+ else if (typid == TIMEOID)
+ {
+ TimeADT time =
+ timestamptz_time_opt_error(DatumGetTimestampTz(val),
+ isnull, error);
+
+ if ((error && *error) || *isnull)
+ return (Datum) 0;
+ else
+ return TimeADTGetDatum(time);
+ }
+ else if (typid == TIMETZOID)
+ {
+ TimeTzADT *result =
+ timestamptz_timetz_opt_error(DatumGetTimestampTz(val), error);
+
+ if ((error && *error) || !result)
+ {
+ *isnull = true;
+ return (Datum) 0;
+ }
+
+ return TimeTzADTPGetDatum(result);
+ }
+ else if (typid == TIMESTAMPOID)
+ {
+ Timestamp ts =
+ timestamptz2timestamp_opt_error(DatumGetTimestampTz(val),
+ error);
+
+ if (error && *error)
+ return (Datum) 0;
+
+ return TimestampGetDatum(ts);
+ }
+ else
+ break; /* No cast */
+
+ default:
+ elog(ERROR, "unexpected jsonb datetime type oid %u", val_typid);
+ break;
+ }
+
+ if (!error)
+ ereport(ERROR,
+ (errcode(ERRCODE_SQL_JSON_ITEM_CANNOT_BE_CAST_TO_TARGET_TYPE),
+ errmsg("SQL/JSON item cannot be cast to target type")));
+
+ *error = true;
+ *isnull = true;
+
+ return (Datum) 0;
+}
+
+/* Coerce boolean SQL/JSON item or JSON_EXISTS result to the output type */
+static bool
+ExecJsonBoolCoercion(bool val, Oid typid, int32 typmod, Datum *res)
+{
+ if (typid == BOOLOID)
+ *res = BoolGetDatum(val);
+ else if (typid == INT4OID)
+ /* We only have cast bool::int4 in the catalog. */
+ *res = Int32GetDatum(val ? 1 : 0);
+ else if (typid == TEXTOID || typid == VARCHAROID || typid == BPCHAROID)
+ /*
+ * bool::text returns 'true' / 'false',
+ * boolout() returns 't' / 'f'.
+ */
+ *res = ExecJsonStringCoercion(val ? "true" : "false", val ? 4 : 5,
+ typid, typmod);
+ else
+ return false;
+
+ return true;
+}
+
+static Datum
+JsonbPGetTextDatum(Jsonb *jb)
+{
+ char *str = JsonbToCString(NULL, &jb->root, VARSIZE(jb));
+ Datum res = CStringGetTextDatum(str);
+
+ pfree(str);
+ return res;
+}
+
+/* Coerce SQL/JSON item to the output typid */
+static Datum
+ExecJsonValueCoercion(JsonbValue *item, Oid typid, int32 typmod,
+ bool *isnull, bool *error)
+{
+ *isnull = false;
+
+ /* Special case for json and jsonb output types */
+ if (typid == JSONBOID)
+ return JsonbPGetDatum(JsonbValueToJsonb(item));
+
+ if (typid == JSONOID)
+ return JsonbPGetTextDatum(JsonbValueToJsonb(item));
+
+ /*
+ * Coercion method and set of supported output types are determined
+ * by the item type.
+ */
switch (item->type)
{
case jbvNull:
- coercion = &coercions->null;
- res = (Datum) 0;
- break;
+ Assert(0); /* must be handled by the caller */
+ *isnull = true;
+ return (Datum) 0;
case jbvString:
- coercion = &coercions->string;
- res = PointerGetDatum(cstring_to_text_with_len(item->val.string.val,
- item->val.string.len));
- break;
+ if (typid == TEXTOID || typid == VARCHAROID || typid == BPCHAROID)
+ {
+ return ExecJsonStringCoercion(item->val.string.val,
+ item->val.string.len,
+ typid, typmod);
+ }
+ else if (typid == INT2OID || typid == INT4OID || typid == INT8OID)
+ {
+ char *str = pnstrdup(item->val.string.val,
+ item->val.string.len);
+
+ if (error)
+ {
+ int64 val = pg_strtoint64_opt_error(str, error);
+
+ if (*error)
+ return (Datum) 0;
+
+ if (typid == INT2OID)
+ {
+ if (val <= PG_INT16_MAX || val >= PG_INT16_MIN)
+ return Int16GetDatum((int16) val);
+ }
+ else if (typid == INT4OID)
+ {
+ if (val <= PG_INT32_MAX || val >= PG_INT32_MIN)
+ return Int32GetDatum((int32) val);
+ }
+ else
+ return Int64GetDatum(val);
+
+ *error = true;
+ return (Datum) 0;
+ }
+ else if (typid == INT2OID)
+ return Int16GetDatum(pg_strtoint16(str));
+ else if (typid == INT4OID)
+ return Int32GetDatum(pg_strtoint32(str));
+ else
+ return Int64GetDatum(pg_strtoint64(str));
+ }
+ else if (typid == BOOLOID)
+ {
+ return BoolGetDatum(boolin_opt_error(item->val.string.val,
+ item->val.string.len,
+ error));
+ }
+ else
+ {
+ char *str = pnstrdup(item->val.string.val,
+ item->val.string.len);
+
+ if (typid == FLOAT4OID)
+ return Float4GetDatum(float4in_opt_error(str, error));
+ else if (typid == FLOAT8OID)
+ return Float8GetDatum(
+ float8in_internal_opt_error(str, NULL,
+ "double precision",
+ str, error));
+ else if (typid == NUMERICOID)
+ return NumericGetDatum(numeric_in_opt_error(str, typmod, error));
+ else if (typid == DATEOID)
+ return DateADTGetDatum(date_in_opt_error(str, error));
+ else if (typid == TIMEOID)
+ return TimeADTGetDatum(time_in_opt_error(str, typmod, error));
+ else if (typid == TIMETZOID)
+ return TimeTzADTPGetDatum(timetz_in_opt_error(str, typmod, error));
+ else if (typid == TIMESTAMPOID)
+ return TimestampGetDatum(timestamp_in_opt_error(str, typmod, error));
+ else if (typid == TIMESTAMPTZOID)
+ return TimestampTzGetDatum(timestamptz_in_opt_error(str, typmod, error));
+ else
+ break; /* No cast */
+ }
case jbvNumeric:
- coercion = &coercions->numeric;
- res = NumericGetDatum(item->val.numeric);
- break;
+ {
+ Numeric num = item->val.numeric;
+
+ if (typid == NUMERICOID)
+ return NumericGetDatum(num);
+ else if (typid == TEXTOID || typid == VARCHAROID || typid == BPCHAROID)
+ return ExecJsonCoercionToText(numeric_out, NumericGetDatum(num), typid, typmod);
+ else if (typid == INT2OID)
+ return Int16GetDatum(numeric_int2_opt_error(num, error));
+ else if (typid == INT4OID)
+ return Int32GetDatum(numeric_int4_opt_error(num, error));
+ else if (typid == INT8OID)
+ return Int64GetDatum(numeric_int8_opt_error(num, error));
+ else if (typid == FLOAT4OID || typid == FLOAT8OID)
+ {
+ /*
+ * XXX numeric_float8() also uses I/O coercion, but
+ * it has special handling of Inf and NaN.
+ */
+ Datum cstr = DirectFunctionCall1(numeric_out,
+ NumericGetDatum(num));
+ char *str = DatumGetCString(cstr);
- case jbvBool:
- coercion = &coercions->boolean;
- res = BoolGetDatum(item->val.boolean);
- break;
+ if (typid == FLOAT4OID)
+ return Float4GetDatum(float4in_opt_error(str, error));
+ else
+ return Float8GetDatum(
+ float8in_internal_opt_error(str, NULL,
+ "double precision",
+ str, error));
+ }
+ else
+ break; /* No cast */
+ }
- case jbvDatetime:
- res = item->val.datetime.value;
- switch (item->val.datetime.typid)
+ case jbvBool:
{
- case DATEOID:
- coercion = &coercions->date;
- break;
- case TIMEOID:
- coercion = &coercions->time;
- break;
- case TIMETZOID:
- coercion = &coercions->timetz;
- break;
- case TIMESTAMPOID:
- coercion = &coercions->timestamp;
- break;
- case TIMESTAMPTZOID:
- coercion = &coercions->timestamptz;
- break;
- default:
- elog(ERROR, "unexpected jsonb datetime type oid %u",
- item->val.datetime.typid);
- return (Datum) 0;
+ Datum res;
+
+ if (ExecJsonBoolCoercion(item->val.boolean, typid, typmod, &res))
+ return res;
+
+ break; /* No cast */
}
- break;
+
+ case jbvDatetime:
+ return ExecJsonDatetimeCoercion(item->val.datetime.value,
+ item->val.datetime.typid,
+ typid, typmod, isnull, error);
case jbvArray:
case jbvObject:
case jbvBinary:
- coercion = &coercions->composite;
- res = JsonbPGetDatum(JsonbValueToJsonb(item));
+ Assert(0); /* non-scalars must be rejected by JsonPathValue() */
break;
default:
@@ -4929,99 +5194,100 @@ ExecPrepareJsonItemCoercion(JsonbValue *item,
return (Datum) 0;
}
- *pcoercion = coercion;
+ if (!error)
+ ereport(ERROR,
+ (errcode(ERRCODE_SQL_JSON_ITEM_CANNOT_BE_CAST_TO_TARGET_TYPE),
+ errmsg("SQL/JSON item cannot be cast to target type")));
- return res;
-}
+ *error = true;
+ *isnull = true;
-typedef Datum (*JsonFunc) (ExprEvalStep *op, ExprContext *econtext,
- Datum item, bool *resnull, void *p, bool *error);
+ return (Datum) 0;
+}
+/*
+ * Evaluate a JSON error/empty behavior and coerce result to the output
+ * type.
+ */
static Datum
-ExecEvalJsonExprSubtrans(JsonFunc func, ExprEvalStep *op,
- ExprContext *econtext,
- Datum res, bool *resnull,
- void *p, bool *error, bool subtrans)
+ExecEvalJsonBehavior(ExprContext *econtext, JsonBehavior *behavior,
+ ExprState *default_estate,
+ Oid ret_typid, int32 ret_typmod, bool *is_null)
{
- MemoryContext oldcontext;
- ResourceOwner oldowner;
-
- if (!subtrans)
- /* No need to use subtransactions. */
- return func(op, econtext, res, resnull, p, error);
+ *is_null = false;
- /*
- * We should catch exceptions of category ERRCODE_DATA_EXCEPTION and
- * execute the corresponding ON ERROR behavior then.
- */
- oldcontext = CurrentMemoryContext;
- oldowner = CurrentResourceOwner;
+ switch (behavior->btype)
+ {
+ case JSON_BEHAVIOR_EMPTY_ARRAY:
+ if (ret_typid == JSONBOID)
+ return JsonbPGetDatum(JsonbMakeEmptyArray());
+ else
+ {
+ Assert(ret_typid == JSONOID || ret_typid == TEXTOID ||
+ ret_typid == VARCHAROID || ret_typid == BPCHAROID ||
+ ret_typid == BYTEAOID);
- Assert(error);
+ return ExecJsonStringCoercion("[]", 2, ret_typid, ret_typmod);
+ }
- BeginInternalSubTransaction(NULL);
- /* Want to execute expressions inside function's memory context */
- MemoryContextSwitchTo(oldcontext);
+ case JSON_BEHAVIOR_EMPTY_OBJECT:
+ if (ret_typid == JSONBOID)
+ return JsonbPGetDatum(JsonbMakeEmptyObject());
+ else
+ {
+ Assert(ret_typid == JSONOID || ret_typid == TEXTOID ||
+ ret_typid == VARCHAROID || ret_typid == BPCHAROID ||
+ ret_typid == BYTEAOID);
- PG_TRY();
- {
- res = func(op, econtext, res, resnull, p, error);
+ return ExecJsonStringCoercion("{}", 2, ret_typid, ret_typmod);
+ }
- /* Commit the inner transaction, return to outer xact context */
- ReleaseCurrentSubTransaction();
- MemoryContextSwitchTo(oldcontext);
- CurrentResourceOwner = oldowner;
- }
- PG_CATCH();
- {
- ErrorData *edata;
- int ecategory;
+ case JSON_BEHAVIOR_TRUE:
+ case JSON_BEHAVIOR_FALSE:
+ {
+ Datum res;
+ bool ok =
+ ExecJsonBoolCoercion(behavior->btype == JSON_BEHAVIOR_TRUE,
+ ret_typid, ret_typmod, &res);
- /* Save error info in oldcontext */
- MemoryContextSwitchTo(oldcontext);
- edata = CopyErrorData();
- FlushErrorState();
+ Assert(ok); /* returning type must be checked in parser */
- /* Abort the inner transaction */
- RollbackAndReleaseCurrentSubTransaction();
- MemoryContextSwitchTo(oldcontext);
- CurrentResourceOwner = oldowner;
+ return res;
+ }
- ecategory = ERRCODE_TO_CATEGORY(edata->sqlerrcode);
+ case JSON_BEHAVIOR_NULL:
+ case JSON_BEHAVIOR_UNKNOWN:
+ case JSON_BEHAVIOR_EMPTY:
+ *is_null = true;
+ return (Datum) 0;
- if (ecategory != ERRCODE_DATA_EXCEPTION && /* jsonpath and other data
- * errors */
- ecategory != ERRCODE_INTEGRITY_CONSTRAINT_VIOLATION) /* domain errors */
- ReThrowError(edata);
+ case JSON_BEHAVIOR_DEFAULT:
+ /*
+ * Execute DEFAULT expression.
+ * Coercion is not needed here, because expression is
+ * already coerced to the target type by the parser.
+ */
+ return ExecEvalExpr(default_estate, econtext, is_null);
- res = (Datum) 0;
- *error = true;
+ default:
+ elog(ERROR, "unrecognized SQL/JSON behavior %d", behavior->btype);
+ return (Datum) 0;
}
- 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)
+ExecEvalJsonExprInternal(ExprState *state,
+ JsonExprState *jsestate, ExprContext *econtext,
+ JsonPath *path, Datum item, bool *resnull,
+ bool *error)
{
- ExecEvalJsonExprContext *cxt = pcxt;
- JsonPath *path = cxt->path;
- JsonExprState *jsestate = op->d.jsonexpr.jsestate;
JsonExpr *jexpr = jsestate->jsexpr;
- ExprState *estate = NULL;
bool empty = false;
Datum res = (Datum) 0;
+ Oid ret_typid = jexpr->returning->typid;
+ int32 ret_typmod = jexpr->returning->typmod;
+
+ *resnull = true;
switch (jexpr->op)
{
@@ -5029,70 +5295,96 @@ ExecEvalJsonExpr(ExprEvalStep *op, ExprContext *econtext,
res = JsonPathQuery(item, path, jexpr->wrapper, &empty, error,
jsestate->args);
if (error && *error)
- {
- *resnull = true;
return (Datum) 0;
- }
+
+ if (empty)
+ break;
+
*resnull = !DatumGetPointer(res);
- break;
+
+ /* Override default coercion in OMIT QUOTES case */
+ if (ExecJsonQueryNeedsIOCoercion(jexpr, res, *resnull))
+ {
+ char *str = JsonbUnquote(DatumGetJsonbP(res));
+
+ if (ret_typid == JSONBOID)
+ {
+ res = jsonb_from_cstring(str, strlen(str), false, error);
+
+ if (error && *error)
+ return (Datum) 0;
+
+ return res;
+ }
+ else if (ret_typid == JSONOID)
+ {
+ text *json = cstring_to_text(str);
+ JsonLexContext *lex;
+ JsonParseErrorType result;
+
+ /* validate it */
+ lex = makeJsonLexContext(json, false);
+ result = pg_parse_json(lex, &nullSemAction);
+
+ if (result != JSON_SUCCESS)
+ {
+ if (error)
+ {
+ *error = true;
+ return (Datum) 0;
+ }
+
+ json_ereport_error(result, lex);
+ }
+
+ return PointerGetDatum(json);
+ }
+ else if (ret_typid == TEXTOID ||
+ ret_typid == VARCHAROID ||
+ ret_typid == BPCHAROID ||
+ ret_typid == BYTEAOID)
+ return ExecJsonStringCoercion(str, strlen(str), ret_typid, ret_typmod);
+ }
+ else if (ret_typid == JSONBOID)
+ return res;
+ else if (ret_typid == JSONOID || ret_typid == TEXTOID)
+ return JsonbPGetTextDatum(DatumGetJsonbP(res));
+ else if (ret_typid == VARCHAROID || ret_typid == BPCHAROID ||
+ ret_typid == BYTEAOID)
+ {
+ Jsonb *jb = DatumGetJsonbP(res);
+ char *str = JsonbToCString(NULL, &jb->root, VARSIZE(jb));
+
+ return ExecJsonStringCoercion(str, strlen(str), ret_typid, ret_typmod);
+ }
+
+ Assert(0); /* unsupported output type */
+ *error = *resnull = true;
+ return (Datum) 0;
case JSON_VALUE_OP:
{
- struct JsonCoercionState *jcstate;
JsonbValue *jbv = JsonPathValue(item, path, &empty, error,
jsestate->args);
if (error && *error)
return (Datum) 0;
- if (!jbv) /* NULL or empty */
+ if (empty)
break;
- Assert(!empty);
-
- *resnull = false;
+ if (!jbv)
+ return (Datum) 0; /* NULL */
- /* coerce scalar item to the output type */
- if (jexpr->returning->typid == JSONOID ||
- jexpr->returning->typid == JSONBOID)
- {
- /* Use result coercion from json[b] to the output type */
- res = JsonbPGetDatum(JsonbValueToJsonb(jbv));
- break;
- }
+ Assert(jbv->type != jbvNull);
- /* Use coercion from SQL/JSON item type to the output type */
- res = ExecPrepareJsonItemCoercion(jbv,
- jsestate->jsexpr->returning,
- &jsestate->coercions,
- &jcstate);
+ res = ExecJsonValueCoercion(jbv, ret_typid, ret_typmod,
+ resnull, error);
- if (jcstate->coercion &&
- (jcstate->coercion->via_io ||
- jcstate->coercion->via_populate))
- {
- if (error)
- {
- *error = true;
- return (Datum) 0;
- }
-
- /*
- * 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 */
+ if (error && *error)
+ return (Datum) 0;
- /* coerce using specific expression */
- estate = jcstate->estate;
- jsestate->coercion_expr->value = res;
- jsestate->coercion_expr->isnull = *resnull;
- break;
+ return res;
}
case JSON_EXISTS_OP:
@@ -5101,113 +5393,68 @@ ExecEvalJsonExpr(ExprEvalStep *op, ExprContext *econtext,
jsestate->args,
error);
- *resnull = error && *error;
- res = BoolGetDatum(exists);
-
- if (!jsestate->result_expr)
- return res;
+ if (!error || !*error)
+ {
+ /* Should succeed, output type is checked by parser */
+ (void) ExecJsonBoolCoercion(exists, ret_typid, ret_typmod, &res);
+ *resnull = false;
+ }
- /* coerce using result expression */
- estate = jsestate->result_expr;
- jsestate->res_expr->value = res;
- jsestate->res_expr->isnull = *resnull;
- break;
+ return res;
}
- case JSON_TABLE_OP:
- *resnull = false;
- return item;
-
default:
elog(ERROR, "unrecognized SQL/JSON expression op %d", jexpr->op);
return (Datum) 0;
}
- if (empty)
- {
- Assert(jexpr->on_empty); /* it is not JSON_EXISTS */
+ Assert(empty);
+ Assert(jexpr->on_empty); /* it is not JSON_EXISTS */
- if (jexpr->on_empty->btype == JSON_BEHAVIOR_ERROR)
+ if (jexpr->on_empty->btype == JSON_BEHAVIOR_ERROR)
+ {
+ if (error)
{
- if (error)
- {
- *error = true;
- return (Datum) 0;
- }
-
- ereport(ERROR,
- (errcode(ERRCODE_NO_SQL_JSON_ITEM),
- errmsg("no SQL/JSON item")));
+ *error = true;
+ return (Datum) 0;
}
- 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);
+ ereport(ERROR,
+ (errcode(ERRCODE_NO_SQL_JSON_ITEM),
+ errmsg("no SQL/JSON item")));
}
- return ExecEvalJsonExprSubtrans(ExecEvalJsonExprCoercion, op, econtext,
- res, resnull, estate, error,
- cxt->coercionInSubtrans);
-}
-
-bool
-ExecEvalJsonNeedsSubTransaction(JsonExpr *jsexpr,
- struct JsonCoercionsState *coercions)
-{
- if (jsexpr->on_error->btype == JSON_BEHAVIOR_ERROR)
- return false;
-
- if (jsexpr->op == JSON_EXISTS_OP && !jsexpr->result_coercion)
- return false;
-
- if (!coercions)
- return true;
-
- return false;
+ /* Execute ON EMPTY behavior */
+ return ExecEvalJsonBehavior(econtext, jexpr->on_empty,
+ jsestate->default_on_empty,
+ ret_typid, ret_typmod, resnull);
}
/* ----------------------------------------------------------------
- * ExecEvalJson
+ * ExecEvalJsonExpr
* ----------------------------------------------------------------
*/
void
-ExecEvalJson(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
+ExecEvalJsonExpr(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
{
- ExecEvalJsonExprContext cxt;
JsonExprState *jsestate = op->d.jsonexpr.jsestate;
JsonExpr *jexpr = jsestate->jsexpr;
Datum item;
- Datum res = (Datum) 0;
+ Datum res;
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)
+ if (jsestate->formatted_expr.isnull || jsestate->pathspec.isnull)
{
- /* execute domain checks for NULLs */
- (void) ExecEvalJsonExprCoercion(op, econtext, res, op->resnull,
- NULL, NULL);
-
- Assert(*op->resnull);
+ *op->resnull = true;
+ *op->resvalue = (Datum) 0;
return;
}
- item = jsestate->formatted_expr->value;
- path = DatumGetJsonPathP(jsestate->pathspec->value);
+ item = jsestate->formatted_expr.value;
+ path = DatumGetJsonPathP(jsestate->pathspec.value);
/* reset JSON path variable contexts */
foreach(lc, jsestate->args)
@@ -5218,29 +5465,20 @@ ExecEvalJson(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
var->evaluated = false;
}
- needSubtrans = ExecEvalJsonNeedsSubTransaction(jexpr, &jsestate->coercions);
-
- 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);
+ res = ExecEvalJsonExprInternal(state, jsestate, econtext,
+ path, item, op->resnull,
+ throwErrors ? NULL : &error);
if (error)
{
+ Assert(!throwErrors);
+
/* Execute ON ERROR behavior */
res = ExecEvalJsonBehavior(econtext, jexpr->on_error,
jsestate->default_on_error,
+ jexpr->returning->typid,
+ jexpr->returning->typmod,
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 = res;
diff --git a/src/backend/jit/llvm/llvmjit_expr.c b/src/backend/jit/llvm/llvmjit_expr.c
index bd3965143da..fd72630f5e6 100644
--- a/src/backend/jit/llvm/llvmjit_expr.c
+++ b/src/backend/jit/llvm/llvmjit_expr.c
@@ -2408,7 +2408,7 @@ llvm_compile_expr(ExprState *state)
break;
case EEOP_JSONEXPR:
- build_EvalXFunc(b, mod, "ExecEvalJson",
+ build_EvalXFunc(b, mod, "ExecEvalJsonExpr",
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.c
index 373471ad27f..37fe64654b6 100644
--- a/src/backend/jit/llvm/llvmjit_types.c
+++ b/src/backend/jit/llvm/llvmjit_types.c
@@ -135,7 +135,7 @@ void *referenced_functions[] =
ExecEvalXmlExpr,
ExecEvalJsonConstructor,
ExecEvalJsonIsPredicate,
- ExecEvalJson,
+ ExecEvalJsonExpr,
MakeExpandedObjectReadOnlyInternal,
slot_getmissingattrs,
slot_getsomeattrs_int,
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index c334daae392..e0240beeeab 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -266,9 +266,6 @@ exprType(const Node *expr)
case T_JsonExpr:
type = ((const JsonExpr *) expr)->returning->typid;
break;
- case T_JsonCoercion:
- type = exprType(((const JsonCoercion *) expr)->expr);
- break;
default:
elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
type = InvalidOid; /* keep compiler quiet */
@@ -507,8 +504,6 @@ exprTypmod(const Node *expr)
return ((const JsonConstructorExpr *) expr)->returning->typmod;
case T_JsonExpr:
return ((JsonExpr *) expr)->returning->typmod;
- case T_JsonCoercion:
- return exprTypmod(((const JsonCoercion *) expr)->expr);
default:
break;
}
@@ -1010,14 +1005,12 @@ exprCollation(const Node *expr)
case T_JsonExpr:
{
JsonExpr *jexpr = (JsonExpr *) expr;
- JsonCoercion *coercion = jexpr->result_coercion;
- if (!coercion)
- coll = InvalidOid;
- else if (coercion->expr)
- coll = exprCollation(coercion->expr);
- else if (coercion->via_io || coercion->via_populate)
- coll = coercion->collation;
+ if (jexpr->returning->typid == TEXTOID ||
+ jexpr->returning->typid == CHAROID ||
+ jexpr->returning->typid == VARCHAROID ||
+ jexpr->returning->typid == BPCHAROID)
+ coll = jexpr->collation;
else
coll = InvalidOid;
}
@@ -1255,14 +1248,12 @@ exprSetCollation(Node *expr, Oid collation)
case T_JsonExpr:
{
JsonExpr *jexpr = (JsonExpr *) expr;
- JsonCoercion *coercion = jexpr->result_coercion;
- if (!coercion)
- Assert(!OidIsValid(collation));
- else if (coercion->expr)
- exprSetCollation(coercion->expr, collation);
- else if (coercion->via_io || coercion->via_populate)
- coercion->collation = collation;
+ if (jexpr->returning->typid == TEXTOID ||
+ jexpr->returning->typid == CHAROID ||
+ jexpr->returning->typid == VARCHAROID ||
+ jexpr->returning->typid == BPCHAROID)
+ jexpr->collation = collation;
else
Assert(!OidIsValid(collation));
}
@@ -2507,8 +2498,6 @@ expression_tree_walker(Node *node,
if (walker(jexpr->formatted_expr, context))
return true;
- if (walker(jexpr->result_coercion, context))
- return true;
if (walker(jexpr->passing_values, context))
return true;
/* we assume walker doesn't care about passing_names */
@@ -2517,36 +2506,6 @@ expression_tree_walker(Node *node,
return true;
if (walker(jexpr->on_error->default_expr, context))
return true;
- if (walker(jexpr->coercions, context))
- return true;
- }
- break;
- case T_JsonCoercion:
- return walker(((JsonCoercion *) node)->expr, context);
- case T_JsonItemCoercions:
- {
- JsonItemCoercions *coercions = (JsonItemCoercions *) node;
-
- if (walker(coercions->null, context))
- return true;
- if (walker(coercions->string, context))
- return true;
- if (walker(coercions->numeric, context))
- return true;
- if (walker(coercions->boolean, context))
- return true;
- if (walker(coercions->date, context))
- return true;
- if (walker(coercions->time, context))
- return true;
- if (walker(coercions->timetz, context))
- return true;
- if (walker(coercions->timestamp, context))
- return true;
- if (walker(coercions->timestamptz, context))
- return true;
- if (walker(coercions->composite, context))
- return true;
}
break;
default:
@@ -3576,7 +3535,6 @@ expression_tree_mutator(Node *node,
FLATCOPY(newnode, jexpr, JsonExpr);
MUTATE(newnode->path_spec, jexpr->path_spec, Node *);
MUTATE(newnode->formatted_expr, jexpr->formatted_expr, Node *);
- MUTATE(newnode->result_coercion, jexpr->result_coercion, JsonCoercion *);
MUTATE(newnode->passing_values, jexpr->passing_values, List *);
/* assume mutator does not care about passing_names */
if (newnode->on_empty)
@@ -3587,35 +3545,6 @@ expression_tree_mutator(Node *node,
return (Node *) newnode;
}
break;
- case T_JsonCoercion:
- {
- JsonCoercion *coercion = (JsonCoercion *) node;
- JsonCoercion *newnode;
-
- FLATCOPY(newnode, coercion, JsonCoercion);
- MUTATE(newnode->expr, coercion->expr, Node *);
- return (Node *) newnode;
- }
- break;
- case T_JsonItemCoercions:
- {
- JsonItemCoercions *coercions = (JsonItemCoercions *) node;
- JsonItemCoercions *newnode;
-
- FLATCOPY(newnode, coercions, JsonItemCoercions);
- MUTATE(newnode->null, coercions->null, JsonCoercion *);
- MUTATE(newnode->string, coercions->string, JsonCoercion *);
- MUTATE(newnode->numeric, coercions->numeric, JsonCoercion *);
- MUTATE(newnode->boolean, coercions->boolean, JsonCoercion *);
- MUTATE(newnode->date, coercions->date, JsonCoercion *);
- MUTATE(newnode->time, coercions->time, JsonCoercion *);
- MUTATE(newnode->timetz, coercions->timetz, JsonCoercion *);
- MUTATE(newnode->timestamp, coercions->timestamp, JsonCoercion *);
- MUTATE(newnode->timestamptz, coercions->timestamptz, JsonCoercion *);
- MUTATE(newnode->composite, coercions->composite, JsonCoercion *);
- return (Node *) newnode;
- }
- break;
default:
elog(ERROR, "unrecognized node type: %d",
(int) nodeTag(node));
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 533df86ff77..6eb923892f4 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -411,6 +411,26 @@ contain_mutable_functions_walker(Node *node, void *context)
{
JsonExpr *jexpr = castNode(JsonExpr, node);
Const *cnst;
+ bool returns_datetime;
+
+ /*
+ * Input fuctions for datetime types are stable. They can be
+ * called in JSON_VALUE(), when the resulting SQL/JSON is a
+ * string.
+ */
+ if (jexpr->returning->typid == DATEOID ||
+ jexpr->returning->typid == TIMEOID ||
+ jexpr->returning->typid == TIMETZOID ||
+ jexpr->returning->typid == TIMESTAMPOID ||
+ jexpr->returning->typid == TIMESTAMPTZOID)
+ return true;
+
+ /*
+ * pg_convert_to(), which is used for implementation of
+ * JSON_QUERY(RETURNING bytea FORMAT JSON), is stable.
+ */
+ if (jexpr->returning->typid == BYTEAOID)
+ return true;
if (!IsA(jexpr->path_spec, Const))
return true;
@@ -421,8 +441,32 @@ contain_mutable_functions_walker(Node *node, void *context)
if (cnst->constisnull)
return false;
- return jspIsMutable(DatumGetJsonPathP(cnst->constvalue),
- jexpr->passing_names, jexpr->passing_values);
+ if (jspIsMutable(DatumGetJsonPathP(cnst->constvalue),
+ jexpr->passing_names, jexpr->passing_values,
+ &returns_datetime))
+ return true;
+
+ if (returns_datetime && jexpr->op == JSON_VALUE_OP)
+ {
+ /*
+ * Some datetime types have mutable output functions,
+ * so if returning string types whole expression is mutable.
+ * TODO check individual datetime type if it is known.
+ *
+ * Other non-JSON output types have no conversion from
+ * datetime types.
+ *
+ * Datetime returning types were checked above, no need to
+ * check them here. But conversion between them also can
+ * be mutable.
+ */
+ if (jexpr->returning->typid == TEXTOID ||
+ jexpr->returning->typid == VARCHAROID ||
+ jexpr->returning->typid == BPCHAROID)
+ return true;
+ }
+
+ return false;
}
if (IsA(node, SQLValueFunction))
@@ -896,18 +940,6 @@ max_parallel_hazard_walker(Node *node, max_parallel_hazard_context *context)
context, 0);
}
- /* JsonExpr is parallel-unsafe if subtransactions can be used. */
- else if (IsA(node, JsonExpr))
- {
- JsonExpr *jsexpr = (JsonExpr *) node;
-
- if (ExecEvalJsonNeedsSubTransaction(jsexpr, NULL))
- {
- context->max_hazard = PROPARALLEL_UNSAFE;
- return true;
- }
- }
-
/* Recurse to check arguments */
return expression_tree_walker(node,
max_parallel_hazard_walker,
@@ -5323,3 +5355,16 @@ pull_paramids_walker(Node *node, Bitmapset **context)
return expression_tree_walker(node, pull_paramids_walker,
(void *) context);
}
+
+bool
+expr_can_throw_errors(Node *expr)
+{
+ if (!expr)
+ return false;
+
+ if (IsA(expr, Const))
+ return false;
+
+ /* TODO consider more cases */
+ return true;
+}
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index fabb5f72076..01c2fdf3859 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -22,6 +22,7 @@
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
+#include "optimizer/clauses.h"
#include "optimizer/optimizer.h"
#include "parser/analyze.h"
#include "parser/parse_agg.h"
@@ -4108,7 +4109,6 @@ transformJsonExprCommon(ParseState *pstate, JsonFuncExpr *func)
/* format is determined by context item type */
format = exprType(jsexpr->formatted_expr) == JSONBOID ? JS_FORMAT_JSONB : JS_FORMAT_JSON;
- jsexpr->result_coercion = NULL;
jsexpr->omit_quotes = false;
jsexpr->format = func->common->expr->format;
@@ -4169,40 +4169,6 @@ assignDefaultJsonReturningType(Node *context_item, JsonFormat *context_format,
ret->typmod = -1;
}
-/*
- * Try to coerce expression to the output type or
- * use json_populate_type() for composite, array and domain types or
- * use coercion via I/O.
- */
-static JsonCoercion *
-coerceJsonExpr(ParseState *pstate, Node *expr, const JsonReturning *returning)
-{
- char typtype;
- JsonCoercion *coercion = makeNode(JsonCoercion);
-
- coercion->expr = coerceJsonFuncExpr(pstate, expr, returning, false);
-
- if (coercion->expr)
- {
- if (coercion->expr == expr)
- coercion->expr = NULL;
-
- return coercion;
- }
-
- typtype = get_typtype(returning->typid);
-
- if (returning->typid == RECORDOID ||
- typtype == TYPTYPE_COMPOSITE ||
- typtype == TYPTYPE_DOMAIN ||
- type_is_array(returning->typid))
- coercion->via_populate = true;
- else
- coercion->via_io = true;
-
- return coercion;
-}
-
/*
* Transform a JSON output clause of JSON_VALUE and JSON_QUERY.
*/
@@ -4210,8 +4176,6 @@ static void
transformJsonFuncExprOutput(ParseState *pstate, JsonFuncExpr *func,
JsonExpr *jsexpr)
{
- Node *expr = jsexpr->formatted_expr;
-
jsexpr->returning = transformJsonOutput(pstate, func->output, false);
/* JSON_VALUE returns text by default */
@@ -4225,14 +4189,41 @@ transformJsonFuncExprOutput(ParseState *pstate, JsonFuncExpr *func,
{
JsonReturning ret;
- if (func->op == JSON_VALUE_OP &&
- jsexpr->returning->typid != JSONOID &&
- jsexpr->returning->typid != JSONBOID)
+ if (func->op == JSON_VALUE_OP)
{
- /* Forced coercion via I/O for JSON_VALUE for non-JSON types */
- jsexpr->result_coercion = makeNode(JsonCoercion);
- jsexpr->result_coercion->expr = NULL;
- jsexpr->result_coercion->via_io = true;
+ /*
+ * Only a limited list of output types is supported in
+ * JSON_VALUE() now.
+ */
+ switch (jsexpr->returning->typid)
+ {
+ case JSONBOID:
+ case JSONOID:
+ case TEXTOID:
+ case VARCHAROID:
+ case BPCHAROID:
+ case BOOLOID:
+ case NUMERICOID:
+ case INT2OID:
+ case INT4OID:
+ case INT8OID:
+ case FLOAT4OID:
+ case FLOAT8OID:
+ case DATEOID:
+ case TIMEOID:
+ case TIMETZOID:
+ case TIMESTAMPOID:
+ case TIMESTAMPTZOID:
+ break; /* Ok */
+
+ default:
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("returning type %s is not supported in JSON_VALUE()",
+ format_type_be(jsexpr->returning->typid)),
+ parser_coercion_errposition(pstate, func->location, (Node *) jsexpr)));
+ }
+
return;
}
@@ -4241,13 +4232,23 @@ transformJsonFuncExprOutput(ParseState *pstate, JsonFuncExpr *func,
if (ret.typid != jsexpr->returning->typid ||
ret.typmod != jsexpr->returning->typmod)
{
- Node *placeholder = makeCaseTestExpr(expr);
-
- Assert(((CaseTestExpr *) placeholder)->typeId == ret.typid);
- Assert(((CaseTestExpr *) placeholder)->typeMod == ret.typmod);
+ switch (jsexpr->returning->typid)
+ {
+ case JSONBOID:
+ case JSONOID:
+ case TEXTOID:
+ case VARCHAROID:
+ case BPCHAROID:
+ case BYTEAOID:
+ break;
- jsexpr->result_coercion = coerceJsonExpr(pstate, placeholder,
- jsexpr->returning);
+ default:
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("returning type %s is not supported in JSON_QUERY()",
+ format_type_be(jsexpr->returning->typid)),
+ parser_coercion_errposition(pstate, func->location, (Node *) jsexpr)));
+ }
}
}
else
@@ -4259,8 +4260,10 @@ transformJsonFuncExprOutput(ParseState *pstate, JsonFuncExpr *func,
* Coerce an expression in JSON DEFAULT behavior to the target output type.
*/
static Node *
-coerceDefaultJsonExpr(ParseState *pstate, JsonExpr *jsexpr, Node *defexpr)
+coerceDefaultJsonExpr(ParseState *pstate, JsonExpr *jsexpr,
+ bool is_on_empty, Node *defexpr)
{
+ Node *orig_defexpr = defexpr;
int location;
Oid exprtype;
@@ -4290,63 +4293,31 @@ coerceDefaultJsonExpr(ParseState *pstate, JsonExpr *jsexpr, Node *defexpr)
format_type_be(jsexpr->returning->typid)),
parser_errposition(pstate, location)));
- return defexpr;
-}
+ /* Don't allow unsafe DEFAUL ON EMPTY expressions, unless ERROR ON ERROR */
+ if (!is_on_empty ||
+ jsexpr->on_error->btype == JSON_BEHAVIOR_ERROR)
+ return defexpr;
-/*
- * Initialize SQL/JSON item coercion from the SQL type "typid" to the target
- * "returning" type.
- */
-static JsonCoercion *
-initJsonItemCoercion(ParseState *pstate, Oid typid,
- const JsonReturning *returning)
-{
- Node *expr;
+ if (!expr_can_throw_errors(defexpr))
+ return defexpr;
- if (typid == UNKNOWNOID)
+ /* Try to simplify expression if there is non-empty coercion */
+ if (defexpr != orig_defexpr &&
+ !expr_can_throw_errors(orig_defexpr))
{
- expr = (Node *) makeNullConst(UNKNOWNOID, -1, InvalidOid);
- }
- else
- {
- CaseTestExpr *placeholder = makeNode(CaseTestExpr);
+ defexpr = eval_const_expressions(NULL, defexpr);
- placeholder->typeId = typid;
- placeholder->typeMod = -1;
- placeholder->collation = InvalidOid;
-
- expr = (Node *) placeholder;
+ if (!expr_can_throw_errors(defexpr))
+ return defexpr;
}
- return coerceJsonExpr(pstate, expr, returning);
-}
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("unsafe DEFAULT ON EMPTY expressions are not supported"),
+ errhint("Use ERROR ON ERROR clause or try to simplify expression into constant-like form"),
+ parser_errposition(pstate, location)));
-static void
-initJsonItemCoercions(ParseState *pstate, JsonItemCoercions *coercions,
- const JsonReturning *returning, Oid contextItemTypeId)
-{
- struct
- {
- JsonCoercion **coercion;
- Oid typid;
- } *p,
- coercionTypids[] =
- {
- {&coercions->null, UNKNOWNOID},
- {&coercions->string, TEXTOID},
- {&coercions->numeric, NUMERICOID},
- {&coercions->boolean, BOOLOID},
- {&coercions->date, DATEOID},
- {&coercions->time, TIMEOID},
- {&coercions->timetz, TIMETZOID},
- {&coercions->timestamp, TIMESTAMPOID},
- {&coercions->timestamptz, TIMESTAMPTZOID},
- {&coercions->composite, contextItemTypeId},
- {NULL, InvalidOid}
- };
-
- for (p = coercionTypids; p->coercion; p++)
- *p->coercion = initJsonItemCoercion(pstate, p->typid, returning);
+ return NULL;
}
/*
@@ -4370,17 +4341,13 @@ transformJsonFuncExpr(ParseState *pstate, JsonFuncExpr *func)
jsexpr->returning->format->encoding = JS_ENC_DEFAULT;
jsexpr->on_empty->default_expr =
- coerceDefaultJsonExpr(pstate, jsexpr,
+ coerceDefaultJsonExpr(pstate, jsexpr, true,
jsexpr->on_empty->default_expr);
jsexpr->on_error->default_expr =
- coerceDefaultJsonExpr(pstate, jsexpr,
+ coerceDefaultJsonExpr(pstate, jsexpr, false,
jsexpr->on_error->default_expr);
- jsexpr->coercions = makeNode(JsonItemCoercions);
- initJsonItemCoercions(pstate, jsexpr->coercions, jsexpr->returning,
- exprType(contextItemExpr));
-
break;
case JSON_QUERY_OP:
@@ -4389,11 +4356,11 @@ transformJsonFuncExpr(ParseState *pstate, JsonFuncExpr *func)
transformJsonFuncExprOutput(pstate, func, jsexpr);
jsexpr->on_empty->default_expr =
- coerceDefaultJsonExpr(pstate, jsexpr,
+ coerceDefaultJsonExpr(pstate, jsexpr, true,
jsexpr->on_empty->default_expr);
jsexpr->on_error->default_expr =
- coerceDefaultJsonExpr(pstate, jsexpr,
+ coerceDefaultJsonExpr(pstate, jsexpr, false,
jsexpr->on_error->default_expr);
jsexpr->wrapper = func->wrapper;
@@ -4409,6 +4376,7 @@ transformJsonFuncExpr(ParseState *pstate, JsonFuncExpr *func)
jsexpr->returning->format->format_type = JS_FORMAT_DEFAULT;
jsexpr->returning->format->encoding = JS_ENC_DEFAULT;
+ /* Coerce intermediate boolean result to the output type if needed */
if (!OidIsValid(jsexpr->returning->typid))
{
jsexpr->returning->typid = BOOLOID;
@@ -4416,32 +4384,21 @@ transformJsonFuncExpr(ParseState *pstate, JsonFuncExpr *func)
}
else if (jsexpr->returning->typid != BOOLOID)
{
- CaseTestExpr *placeholder = makeNode(CaseTestExpr);
- int location = exprLocation((Node *) jsexpr);
-
- placeholder->typeId = BOOLOID;
- placeholder->typeMod = -1;
- placeholder->collation = InvalidOid;
-
- jsexpr->result_coercion = makeNode(JsonCoercion);
- jsexpr->result_coercion->expr =
- coerce_to_target_type(pstate, (Node *) placeholder, BOOLOID,
- jsexpr->returning->typid,
- jsexpr->returning->typmod,
- COERCION_EXPLICIT,
- COERCE_IMPLICIT_CAST,
- location);
-
- if (!jsexpr->result_coercion->expr)
- ereport(ERROR,
- (errcode(ERRCODE_CANNOT_COERCE),
- errmsg("cannot cast type %s to %s",
- format_type_be(BOOLOID),
- format_type_be(jsexpr->returning->typid)),
- parser_coercion_errposition(pstate, location, (Node *) jsexpr)));
+ switch (jsexpr->returning->typid)
+ {
+ case INT4OID:
+ case TEXTOID:
+ case VARCHAROID:
+ case BPCHAROID:
+ break;
- if (jsexpr->result_coercion->expr == (Node *) placeholder)
- jsexpr->result_coercion->expr = NULL;
+ default:
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("returning type %s is not supported in JSON_EXISTS()",
+ format_type_be(jsexpr->returning->typid)),
+ parser_coercion_errposition(pstate, func->location, (Node *) jsexpr)));
+ }
}
break;
@@ -4457,7 +4414,6 @@ transformJsonFuncExpr(ParseState *pstate, JsonFuncExpr *func)
errmsg("JSON_TABLE() is not yet implemented for the json type"),
errhint("Try casting the argument to jsonb"),
parser_errposition(pstate, func->location)));
-
break;
}
diff --git a/src/backend/utils/adt/bool.c b/src/backend/utils/adt/bool.c
index cd7335287f9..78557c99dc5 100644
--- a/src/backend/utils/adt/bool.c
+++ b/src/backend/utils/adt/bool.c
@@ -126,12 +126,10 @@ parse_bool_with_len(const char *value, size_t len, bool *result)
*
* In the switch statement, check the most-used possibilities first.
*/
-Datum
-boolin(PG_FUNCTION_ARGS)
+bool
+boolin_opt_error(const char *in_str, size_t len, bool *error)
{
- const char *in_str = PG_GETARG_CSTRING(0);
const char *str;
- size_t len;
bool result;
/*
@@ -141,20 +139,29 @@ boolin(PG_FUNCTION_ARGS)
while (isspace((unsigned char) *str))
str++;
- len = strlen(str);
+ len -= str - in_str;
while (len > 0 && isspace((unsigned char) str[len - 1]))
len--;
if (parse_bool_with_len(str, len, &result))
- PG_RETURN_BOOL(result);
+ return result;
+
+ if (!error)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+ errmsg("invalid input syntax for type %s: \"%s\"",
+ "boolean", in_str)));
+
+ *error = true;
+ return false;
+}
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
- errmsg("invalid input syntax for type %s: \"%s\"",
- "boolean", in_str)));
+Datum
+boolin(PG_FUNCTION_ARGS)
+{
+ char *str = PG_GETARG_CSTRING(0);
- /* not reached */
- PG_RETURN_BOOL(false);
+ PG_RETURN_BOOL(boolin_opt_error(str, strlen(str), NULL));
}
/*
diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index 081dfa2450f..43a96fc5142 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -108,10 +108,9 @@ anytime_typmodout(bool istz, int32 typmod)
/* date_in()
* Given date text string, convert to internal date format.
*/
-Datum
-date_in(PG_FUNCTION_ARGS)
+DateADT
+date_in_opt_error(char *str, bool *error)
{
- char *str = PG_GETARG_CSTRING(0);
DateADT date;
fsec_t fsec;
struct pg_tm tt,
@@ -127,9 +126,18 @@ date_in(PG_FUNCTION_ARGS)
dterr = ParseDateTime(str, workbuf, sizeof(workbuf),
field, ftype, MAXDATEFIELDS, &nf);
if (dterr == 0)
- dterr = DecodeDateTime(field, ftype, nf, &dtype, tm, &fsec, &tzp);
+ dterr = DecodeDateTime(field, ftype, nf, &dtype, tm, &fsec, &tzp,
+ !error);
if (dterr != 0)
+ {
+ if (error)
+ {
+ *error = true;
+ return 0;
+ }
+
DateTimeParseError(dterr, str, "date");
+ }
switch (dtype)
{
@@ -149,25 +157,56 @@ date_in(PG_FUNCTION_ARGS)
PG_RETURN_DATEADT(date);
default:
+ if (*error)
+ {
+ *error = true;
+ return 0;
+ }
+
DateTimeParseError(DTERR_BAD_FORMAT, str, "date");
break;
}
/* Prevent overflow in Julian-day routines */
if (!IS_VALID_JULIAN(tm->tm_year, tm->tm_mon, tm->tm_mday))
+ {
+ if (error)
+ {
+ *error = true;
+ return 0;
+ }
+
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("date out of range: \"%s\"", str)));
+ }
date = date2j(tm->tm_year, tm->tm_mon, tm->tm_mday) - POSTGRES_EPOCH_JDATE;
/* Now check for just-out-of-range dates */
if (!IS_VALID_DATE(date))
+ {
+ if (error)
+ {
+ *error = true;
+ return 0;
+ }
+
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("date out of range: \"%s\"", str)));
+ }
- PG_RETURN_DATEADT(date);
+ return date;
+}
+
+/* date_in()
+ * Given date text string, convert to internal date format.
+ */
+Datum
+date_in(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_DATEADT(date_in_opt_error(PG_GETARG_CSTRING(0), NULL));
}
/* date_out()
@@ -1286,10 +1325,9 @@ date_timestamp(PG_FUNCTION_ARGS)
/* timestamp_date()
* Convert timestamp to date data type.
*/
-Datum
-timestamp_date(PG_FUNCTION_ARGS)
+DateADT
+timestamp_date_opt_error(Timestamp timestamp, bool *error)
{
- Timestamp timestamp = PG_GETARG_TIMESTAMP(0);
DateADT result;
struct pg_tm tt,
*tm = &tt;
@@ -1302,16 +1340,32 @@ timestamp_date(PG_FUNCTION_ARGS)
else
{
if (timestamp2tm(timestamp, NULL, tm, &fsec, NULL, NULL) != 0)
+ {
+ if (error)
+ {
+ *error = true;
+ return 0;
+ }
+
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("timestamp out of range")));
+ }
result = date2j(tm->tm_year, tm->tm_mon, tm->tm_mday) - POSTGRES_EPOCH_JDATE;
}
- PG_RETURN_DATEADT(result);
+ return result;
}
+/* timestamp_date()
+ * Convert timestamp to date data type.
+ */
+Datum
+timestamp_date(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_DATEADT(timestamp_date_opt_error(PG_GETARG_TIMESTAMP(0), NULL));
+}
/* date_timestamptz()
* Convert date to timestamp with time zone data type.
@@ -1327,14 +1381,9 @@ date_timestamptz(PG_FUNCTION_ARGS)
PG_RETURN_TIMESTAMP(result);
}
-
-/* timestamptz_date()
- * Convert timestamp with time zone to date data type.
- */
-Datum
-timestamptz_date(PG_FUNCTION_ARGS)
+DateADT
+timestamptz_date_opt_error(TimestampTz timestamp, bool *error)
{
- TimestampTz timestamp = PG_GETARG_TIMESTAMP(0);
DateADT result;
struct pg_tm tt,
*tm = &tt;
@@ -1348,14 +1397,31 @@ timestamptz_date(PG_FUNCTION_ARGS)
else
{
if (timestamp2tm(timestamp, &tz, tm, &fsec, NULL, NULL) != 0)
+ {
+ if (error)
+ {
+ *error = true;
+ return 0;
+ }
+
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("timestamp out of range")));
+ }
result = date2j(tm->tm_year, tm->tm_mon, tm->tm_mday) - POSTGRES_EPOCH_JDATE;
}
- PG_RETURN_DATEADT(result);
+ return result;
+}
+
+/* timestamptz_date()
+ * Convert timestamp with time zone to date data type.
+ */
+Datum
+timestamptz_date(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_DATEADT(timestamptz_date_opt_error(PG_GETARG_TIMESTAMP(0), NULL));
}
@@ -1363,15 +1429,9 @@ timestamptz_date(PG_FUNCTION_ARGS)
* Time ADT
*****************************************************************************/
-Datum
-time_in(PG_FUNCTION_ARGS)
+TimeADT
+time_in_opt_error(char *str, int32 typmod, bool *error)
{
- char *str = PG_GETARG_CSTRING(0);
-
-#ifdef NOT_USED
- Oid typelem = PG_GETARG_OID(1);
-#endif
- int32 typmod = PG_GETARG_INT32(2);
TimeADT result;
fsec_t fsec;
struct pg_tm tt,
@@ -1387,14 +1447,35 @@ time_in(PG_FUNCTION_ARGS)
dterr = ParseDateTime(str, workbuf, sizeof(workbuf),
field, ftype, MAXDATEFIELDS, &nf);
if (dterr == 0)
- dterr = DecodeTimeOnly(field, ftype, nf, &dtype, tm, &fsec, &tz);
+ dterr = DecodeTimeOnly(field, ftype, nf, &dtype, tm, &fsec, &tz,
+ !error);
if (dterr != 0)
+ {
+ if (error)
+ {
+ *error = true;
+ return 0;
+ }
+
DateTimeParseError(dterr, str, "time");
+ }
tm2time(tm, fsec, &result);
AdjustTimeForTypmod(&result, typmod);
- PG_RETURN_TIMEADT(result);
+ return result;
+}
+
+Datum
+time_in(PG_FUNCTION_ARGS)
+{
+ char *str = PG_GETARG_CSTRING(0);
+#ifdef NOT_USED
+ Oid typelem = PG_GETARG_OID(1);
+#endif
+ int32 typmod = PG_GETARG_INT32(2);
+
+ PG_RETURN_TIMEADT(time_in_opt_error(str, typmod, NULL));
}
/* tm2time()
@@ -1889,22 +1970,32 @@ overlaps_time(PG_FUNCTION_ARGS)
/* timestamp_time()
* Convert timestamp to time data type.
*/
-Datum
-timestamp_time(PG_FUNCTION_ARGS)
+TimeADT
+timestamp_time_opt_error(Timestamp timestamp, bool *isnull, bool *error)
{
- Timestamp timestamp = PG_GETARG_TIMESTAMP(0);
TimeADT result;
struct pg_tm tt,
*tm = &tt;
fsec_t fsec;
if (TIMESTAMP_NOT_FINITE(timestamp))
- PG_RETURN_NULL();
+ {
+ *isnull = true;
+ return 0;
+ }
if (timestamp2tm(timestamp, NULL, tm, &fsec, NULL, NULL) != 0)
+ {
+ if (error)
+ {
+ *error = *isnull = true;
+ return (Datum) 0;
+ }
+
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("timestamp out of range")));
+ }
/*
* Could also do this with time = (timestamp / USECS_PER_DAY *
@@ -1912,17 +2003,33 @@ timestamp_time(PG_FUNCTION_ARGS)
*/
result = ((((tm->tm_hour * MINS_PER_HOUR + tm->tm_min) * SECS_PER_MINUTE) + tm->tm_sec) *
USECS_PER_SEC) + fsec;
+ *isnull = false;
- PG_RETURN_TIMEADT(result);
+ return result;
}
-/* timestamptz_time()
- * Convert timestamptz to time data type.
+/* timestamp_time()
+ * Convert timestamp to time data type.
*/
Datum
-timestamptz_time(PG_FUNCTION_ARGS)
+timestamp_time(PG_FUNCTION_ARGS)
+{
+ bool isnull;
+ TimeADT time =
+ timestamp_time_opt_error(PG_GETARG_TIMESTAMP(0), &isnull, NULL);
+
+ if (isnull)
+ PG_RETURN_NULL();
+ else
+ PG_RETURN_TIMEADT(time);
+}
+
+/* timestamptz_time_opt_error()
+ * Convert timestamptz to time data type.
+ */
+TimeADT
+timestamptz_time_opt_error(TimestampTz timestamp, bool *isnull, bool *error)
{
- TimestampTz timestamp = PG_GETARG_TIMESTAMP(0);
TimeADT result;
struct pg_tm tt,
*tm = &tt;
@@ -1930,12 +2037,23 @@ timestamptz_time(PG_FUNCTION_ARGS)
fsec_t fsec;
if (TIMESTAMP_NOT_FINITE(timestamp))
- PG_RETURN_NULL();
+ {
+ *isnull = true;
+ return 0;
+ }
if (timestamp2tm(timestamp, &tz, tm, &fsec, NULL, NULL) != 0)
+ {
+ if (error)
+ {
+ *error = *isnull = true;
+ return 0;
+ }
+
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("timestamp out of range")));
+ }
/*
* Could also do this with time = (timestamp / USECS_PER_DAY *
@@ -1943,8 +2061,25 @@ timestamptz_time(PG_FUNCTION_ARGS)
*/
result = ((((tm->tm_hour * MINS_PER_HOUR + tm->tm_min) * SECS_PER_MINUTE) + tm->tm_sec) *
USECS_PER_SEC) + fsec;
+ *isnull = false;
- PG_RETURN_TIMEADT(result);
+ return result;
+}
+
+/* timestamptz_time()
+ * Convert timestamptz to time data type.
+ */
+Datum
+timestamptz_time(PG_FUNCTION_ARGS)
+{
+ bool isnull;
+ TimeADT result =
+ timestamptz_time_opt_error(PG_GETARG_TIMESTAMP(0), &isnull, NULL);
+
+ if (isnull)
+ PG_RETURN_NULL();
+ else
+ PG_RETURN_TIMEADT(result);
}
/* datetime_timestamp()
@@ -2249,15 +2384,9 @@ tm2timetz(struct pg_tm *tm, fsec_t fsec, int tz, TimeTzADT *result)
return 0;
}
-Datum
-timetz_in(PG_FUNCTION_ARGS)
+TimeTzADT *
+timetz_in_opt_error(char *str, int32 typmod, bool *error)
{
- char *str = PG_GETARG_CSTRING(0);
-
-#ifdef NOT_USED
- Oid typelem = PG_GETARG_OID(1);
-#endif
- int32 typmod = PG_GETARG_INT32(2);
TimeTzADT *result;
fsec_t fsec;
struct pg_tm tt,
@@ -2273,17 +2402,38 @@ timetz_in(PG_FUNCTION_ARGS)
dterr = ParseDateTime(str, workbuf, sizeof(workbuf),
field, ftype, MAXDATEFIELDS, &nf);
if (dterr == 0)
- dterr = DecodeTimeOnly(field, ftype, nf, &dtype, tm, &fsec, &tz);
+ dterr = DecodeTimeOnly(field, ftype, nf, &dtype, tm, &fsec, &tz,
+ !error);
if (dterr != 0)
+ {
+ if (error)
+ {
+ *error = true;
+ return NULL;
+ }
+
DateTimeParseError(dterr, str, "time with time zone");
+ }
result = (TimeTzADT *) palloc(sizeof(TimeTzADT));
tm2timetz(tm, fsec, tz, result);
AdjustTimeForTypmod(&(result->time), typmod);
- PG_RETURN_TIMETZADT_P(result);
+ return result;
}
+Datum
+timetz_in(PG_FUNCTION_ARGS)
+{
+ char *str = PG_GETARG_CSTRING(0);
+
+#ifdef NOT_USED
+ Oid typelem = PG_GETARG_OID(1);
+#endif
+ int32 typmod = PG_GETARG_INT32(2);
+
+ PG_RETURN_TIMETZADT_P(timetz_in_opt_error(str, typmod, NULL));
+}
Datum
timetz_out(PG_FUNCTION_ARGS)
{
@@ -2812,10 +2962,9 @@ time_timetz(PG_FUNCTION_ARGS)
/* timestamptz_timetz()
* Convert timestamp to timetz data type.
*/
-Datum
-timestamptz_timetz(PG_FUNCTION_ARGS)
+TimeTzADT *
+timestamptz_timetz_opt_error(TimestampTz timestamp, bool *error)
{
- TimestampTz timestamp = PG_GETARG_TIMESTAMP(0);
TimeTzADT *result;
struct pg_tm tt,
*tm = &tt;
@@ -2823,20 +2972,42 @@ timestamptz_timetz(PG_FUNCTION_ARGS)
fsec_t fsec;
if (TIMESTAMP_NOT_FINITE(timestamp))
- PG_RETURN_NULL();
+ return NULL;
if (timestamp2tm(timestamp, &tz, tm, &fsec, NULL, NULL) != 0)
+ {
+ if (error)
+ {
+ *error = true;
+ return NULL;
+ }
+
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("timestamp out of range")));
+ }
result = (TimeTzADT *) palloc(sizeof(TimeTzADT));
tm2timetz(tm, fsec, tz, result);
- PG_RETURN_TIMETZADT_P(result);
+ return result;
}
+/* timestamptz_timetz()
+ * Convert timestamp to timetz data type.
+ */
+Datum
+timestamptz_timetz(PG_FUNCTION_ARGS)
+{
+ TimeTzADT *result =
+ timestamptz_timetz_opt_error(PG_GETARG_TIMESTAMP(0), NULL);
+
+ if (result)
+ PG_RETURN_TIMETZADT_P(result);
+ else
+ PG_RETURN_NULL();
+}
/* datetimetz_timestamptz()
* Convert date and timetz to timestamp with time zone data type.
diff --git a/src/backend/utils/adt/datetime.c b/src/backend/utils/adt/datetime.c
index 43fff50d490..3275e5fd948 100644
--- a/src/backend/utils/adt/datetime.c
+++ b/src/backend/utils/adt/datetime.c
@@ -971,7 +971,8 @@ ParseDateTime(const char *timestr, char *workbuf, size_t buflen,
*/
int
DecodeDateTime(char **field, int *ftype, int nf,
- int *dtype, struct pg_tm *tm, fsec_t *fsec, int *tzp)
+ int *dtype, struct pg_tm *tm, fsec_t *fsec, int *tzp,
+ bool throw_errors)
{
int fmask = 0,
tmask,
@@ -1114,8 +1115,13 @@ DecodeDateTime(char **field, int *ftype, int nf,
/*
* We should return an error code instead of
* ereport'ing directly, but then there is no way
- * to report the bad time zone name.
+ * to report the bad time zone name. But
+ * throwing errors is disallowed, simply
+ * return error code.
*/
+ if (!throw_errors)
+ return DTERR_BAD_FORMAT;
+
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("time zone \"%s\" not recognized",
@@ -1921,7 +1927,8 @@ DetermineTimeZoneAbbrevOffsetInternal(pg_time_t t, const char *abbr, pg_tz *tzp,
*/
int
DecodeTimeOnly(char **field, int *ftype, int nf,
- int *dtype, struct pg_tm *tm, fsec_t *fsec, int *tzp)
+ int *dtype, struct pg_tm *tm, fsec_t *fsec, int *tzp,
+ bool throw_errors)
{
int fmask = 0,
tmask,
@@ -2022,6 +2029,9 @@ DecodeTimeOnly(char **field, int *ftype, int nf,
* ereport'ing directly, but then there is no way
* to report the bad time zone name.
*/
+ if (!throw_errors)
+ return DTERR_BAD_FORMAT;
+
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("time zone \"%s\" not recognized",
diff --git a/src/backend/utils/adt/float.c b/src/backend/utils/adt/float.c
index fc8f39a7a98..d9da8c8466d 100644
--- a/src/backend/utils/adt/float.c
+++ b/src/backend/utils/adt/float.c
@@ -73,6 +73,16 @@ static double sind_q1(double x);
static double cosd_q1(double x);
static void init_degree_constants(void);
+/* Convenience macro: set *have_error flag (if provided) or throw error */
+#define RETURN_ERROR(throw_error, have_error) \
+do { \
+ if (have_error) { \
+ *have_error = true; \
+ return 0.0; \
+ } else { \
+ throw_error; \
+ } \
+} while (0)
/*
* We use these out-of-line ereport() calls to report float overflow,
@@ -159,10 +169,9 @@ is_infinite(double val)
* result of 0xAE43FEp-107.
*
*/
-Datum
-float4in(PG_FUNCTION_ARGS)
+float
+float4in_opt_error(char *num, bool *have_error)
{
- char *num = PG_GETARG_CSTRING(0);
char *orig_num;
float val;
char *endptr;
@@ -183,10 +192,11 @@ float4in(PG_FUNCTION_ARGS)
* strtod() on different platforms.
*/
if (*num == '\0')
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
- errmsg("invalid input syntax for type %s: \"%s\"",
- "real", orig_num)));
+ RETURN_ERROR(ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+ errmsg("invalid input syntax for type %s: \"%s\"",
+ "real", orig_num))),
+ have_error);
errno = 0;
val = strtof(num, &endptr);
@@ -257,16 +267,18 @@ float4in(PG_FUNCTION_ARGS)
(val >= HUGE_VALF || val <= -HUGE_VALF)
#endif
)
- ereport(ERROR,
- (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
- errmsg("\"%s\" is out of range for type real",
- orig_num)));
+ RETURN_ERROR(ereport(ERROR,
+ (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+ errmsg("\"%s\" is out of range for type real",
+ orig_num))),
+ have_error);
}
else
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
- errmsg("invalid input syntax for type %s: \"%s\"",
- "real", orig_num)));
+ RETURN_ERROR(ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+ errmsg("invalid input syntax for type %s: \"%s\"",
+ "real", orig_num))),
+ have_error);
}
/* skip trailing whitespace */
@@ -275,12 +287,19 @@ float4in(PG_FUNCTION_ARGS)
/* if there is any junk left at the end of the string, bail out */
if (*endptr != '\0')
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
- errmsg("invalid input syntax for type %s: \"%s\"",
- "real", orig_num)));
+ RETURN_ERROR(ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+ errmsg("invalid input syntax for type %s: \"%s\"",
+ "real", orig_num))),
+ have_error);
+
+ return val;
+}
- PG_RETURN_FLOAT4(val);
+Datum
+float4in(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_FLOAT4(float4in_opt_error(PG_GETARG_CSTRING(0), NULL));
}
/*
@@ -340,17 +359,6 @@ float8in(PG_FUNCTION_ARGS)
PG_RETURN_FLOAT8(float8in_internal(num, NULL, "double precision", num));
}
-/* Convenience macro: set *have_error flag (if provided) or throw error */
-#define RETURN_ERROR(throw_error, have_error) \
-do { \
- if (have_error) { \
- *have_error = true; \
- return 0.0; \
- } else { \
- throw_error; \
- } \
-} while (0)
-
/*
* float8in_internal_opt_error - guts of float8in()
*
diff --git a/src/backend/utils/adt/jsonb.c b/src/backend/utils/adt/jsonb.c
index f700c5b4c93..1da519daaef 100644
--- a/src/backend/utils/adt/jsonb.c
+++ b/src/backend/utils/adt/jsonb.c
@@ -46,7 +46,6 @@ typedef struct JsonbAggState
Oid val_output_func;
} JsonbAggState;
-static inline Datum jsonb_from_cstring(char *json, int len, bool unique_keys);
static size_t checkStringLen(size_t len);
static void jsonb_in_object_start(void *pstate);
static void jsonb_in_object_end(void *pstate);
@@ -77,7 +76,7 @@ jsonb_in(PG_FUNCTION_ARGS)
{
char *json = PG_GETARG_CSTRING(0);
- return jsonb_from_cstring(json, strlen(json), false);
+ PG_RETURN_DATUM(jsonb_from_cstring(json, strlen(json), false, NULL));
}
/*
@@ -101,7 +100,7 @@ jsonb_recv(PG_FUNCTION_ARGS)
else
elog(ERROR, "unsupported jsonb version number %d", version);
- return jsonb_from_cstring(str, nbytes, false);
+ return jsonb_from_cstring(str, nbytes, false, NULL);
}
/*
@@ -147,7 +146,7 @@ jsonb_from_text(text *js, bool unique_keys)
{
return jsonb_from_cstring(VARDATA_ANY(js),
VARSIZE_ANY_EXHDR(js),
- unique_keys);
+ unique_keys, NULL);
}
/*
@@ -239,12 +238,13 @@ jsonb_typeof(PG_FUNCTION_ARGS)
*
* Uses the json parser (with hooks) to construct a jsonb.
*/
-static inline Datum
-jsonb_from_cstring(char *json, int len, bool unique_keys)
+Datum
+jsonb_from_cstring(char *json, int len, bool unique_keys, bool *error)
{
JsonLexContext *lex;
JsonbInState state;
JsonSemAction sem;
+ JsonParseErrorType result;
memset(&state, 0, sizeof(state));
memset(&sem, 0, sizeof(sem));
@@ -261,10 +261,20 @@ jsonb_from_cstring(char *json, int len, bool unique_keys)
sem.scalar = jsonb_in_scalar;
sem.object_field_start = jsonb_in_object_field_start;
- pg_parse_json_or_ereport(lex, &sem);
+ result = pg_parse_json(lex, &sem);
+ if (result != JSON_SUCCESS)
+ {
+ if (error)
+ {
+ *error = true;
+ return (Datum) 0;
+ }
+
+ json_ereport_error(result, lex);
+ }
/* after parsing, the item member has the composed jsonb structure */
- PG_RETURN_POINTER(JsonbValueToJsonb(state.res));
+ return JsonbPGetDatum(JsonbValueToJsonb(state.res));
}
static size_t
diff --git a/src/backend/utils/adt/jsonpath.c b/src/backend/utils/adt/jsonpath.c
index da9df4ae766..9c9f9ad703f 100644
--- a/src/backend/utils/adt/jsonpath.c
+++ b/src/backend/utils/adt/jsonpath.c
@@ -1318,9 +1318,11 @@ jspIsMutableWalker(JsonPathItem *jpi, JsonPathMutableContext *cxt)
* Check whether jsonpath expression is immutable or not.
*/
bool
-jspIsMutable(JsonPath *path, List *varnames, List *varexprs)
+jspIsMutable(JsonPath *path, List *varnames, List *varexprs,
+ bool *returns_datetime)
{
JsonPathMutableContext cxt;
+ JsonPathDatatypeStatus status;
JsonPathItem jpi;
cxt.varnames = varnames;
@@ -1330,7 +1332,9 @@ jspIsMutable(JsonPath *path, List *varnames, List *varexprs)
cxt.mutable = false;
jspInit(&jpi, path);
- jspIsMutableWalker(&jpi, &cxt);
+ status = jspIsMutableWalker(&jpi, &cxt);
+
+ *returns_datetime = status != jpdsNonDateTime;
return cxt.mutable;
}
diff --git a/src/backend/utils/adt/numeric.c b/src/backend/utils/adt/numeric.c
index 920a63b0081..951b2f43ab1 100644
--- a/src/backend/utils/adt/numeric.c
+++ b/src/backend/utils/adt/numeric.c
@@ -498,7 +498,7 @@ static void free_var(NumericVar *var);
static void zero_var(NumericVar *var);
static const char *set_var_from_str(const char *str, const char *cp,
- NumericVar *dest);
+ NumericVar *dest, bool *have_error);
static void set_var_from_num(Numeric value, NumericVar *dest);
static void init_var_from_num(Numeric num, NumericVar *dest);
static void set_var_from_var(const NumericVar *value, NumericVar *dest);
@@ -512,8 +512,8 @@ static Numeric duplicate_numeric(Numeric num);
static Numeric make_result(const NumericVar *var);
static Numeric make_result_opt_error(const NumericVar *var, bool *error);
-static void apply_typmod(NumericVar *var, int32 typmod);
-static void apply_typmod_special(Numeric num, int32 typmod);
+static bool apply_typmod(NumericVar *var, int32 typmod, bool *error);
+static void apply_typmod_special(Numeric num, int32 typmod, bool *error);
static bool numericvar_to_int32(const NumericVar *var, int32 *result);
static bool numericvar_to_int64(const NumericVar *var, int64 *result);
@@ -607,21 +607,25 @@ static void accum_sum_combine(NumericSumAccum *accum, NumericSumAccum *accum2);
* ----------------------------------------------------------------------
*/
+/* Convenience macro: set *have_error flag (if provided) or throw error */
+#define RETURN_ERROR(throw_error, have_error) \
+do { \
+ if (have_error) { \
+ *(have_error) = true; \
+ return NULL; \
+ } else { \
+ throw_error; \
+ } \
+} while (0)
/*
* numeric_in() -
*
* Input function for numeric data type
*/
-Datum
-numeric_in(PG_FUNCTION_ARGS)
+Numeric
+numeric_in_opt_error(char *str, int32 typmod, bool *have_error)
{
- char *str = PG_GETARG_CSTRING(0);
-
-#ifdef NOT_USED
- Oid typelem = PG_GETARG_OID(1);
-#endif
- int32 typmod = PG_GETARG_INT32(2);
Numeric res;
const char *cp;
@@ -682,7 +686,10 @@ numeric_in(PG_FUNCTION_ARGS)
init_var(&value);
- cp = set_var_from_str(str, cp, &value);
+ cp = set_var_from_str(str, cp, &value, have_error);
+
+ if (!cp)
+ return NULL; /* error */
/*
* We duplicate a few lines of code here because we would like to
@@ -693,38 +700,59 @@ numeric_in(PG_FUNCTION_ARGS)
while (*cp)
{
if (!isspace((unsigned char) *cp))
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
- errmsg("invalid input syntax for type %s: \"%s\"",
- "numeric", str)));
+ RETURN_ERROR(ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+ errmsg("invalid input syntax for type %s: \"%s\"",
+ "numeric", str))),
+ have_error);
cp++;
}
- apply_typmod(&value, typmod);
+ if (!apply_typmod(&value, typmod, have_error))
+ return NULL; /* error */
- res = make_result(&value);
+ res = make_result_opt_error(&value, have_error);
free_var(&value);
- PG_RETURN_NUMERIC(res);
+ return res;
}
/* Should be nothing left but spaces */
while (*cp)
{
if (!isspace((unsigned char) *cp))
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
- errmsg("invalid input syntax for type %s: \"%s\"",
- "numeric", str)));
+ RETURN_ERROR(ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+ errmsg("invalid input syntax for type %s: \"%s\"",
+ "numeric", str))),
+ have_error);
+
cp++;
}
/* As above, throw any typmod error after finishing syntax check */
- apply_typmod_special(res, typmod);
+ apply_typmod_special(res, typmod, have_error);
- PG_RETURN_NUMERIC(res);
+ return res;
}
+/*
+ * numeric_in() -
+ *
+ * Input function for numeric data type
+ */
+Datum
+numeric_in(PG_FUNCTION_ARGS)
+{
+ char *str = PG_GETARG_CSTRING(0);
+
+#ifdef NOT_USED
+ Oid typelem = PG_GETARG_OID(1);
+#endif
+ int32 typmod = PG_GETARG_INT32(2);
+
+ PG_RETURN_NUMERIC(numeric_in_opt_error(str, typmod, NULL));
+}
/*
* numeric_out() -
@@ -1058,7 +1086,7 @@ numeric_recv(PG_FUNCTION_ARGS)
{
trunc_var(&value, value.dscale);
- apply_typmod(&value, typmod);
+ apply_typmod(&value, typmod, NULL);
res = make_result(&value);
}
@@ -1067,7 +1095,7 @@ numeric_recv(PG_FUNCTION_ARGS)
/* apply_typmod_special wants us to make the Numeric first */
res = make_result(&value);
- apply_typmod_special(res, typmod);
+ apply_typmod_special(res, typmod, NULL);
}
free_var(&value);
@@ -1180,7 +1208,7 @@ numeric (PG_FUNCTION_ARGS)
*/
if (NUMERIC_IS_SPECIAL(num))
{
- apply_typmod_special(num, typmod);
+ apply_typmod_special(num, typmod, NULL);
PG_RETURN_NUMERIC(duplicate_numeric(num));
}
@@ -1231,7 +1259,7 @@ numeric (PG_FUNCTION_ARGS)
init_var(&var);
set_var_from_num(num, &var);
- apply_typmod(&var, typmod);
+ apply_typmod(&var, typmod, NULL);
new = make_result(&var);
free_var(&var);
@@ -4308,15 +4336,20 @@ int8_numeric(PG_FUNCTION_ARGS)
}
-Datum
-numeric_int8(PG_FUNCTION_ARGS)
+int64
+numeric_int8_opt_error(Numeric num, bool *error)
{
- Numeric num = PG_GETARG_NUMERIC(0);
NumericVar x;
int64 result;
if (NUMERIC_IS_SPECIAL(num))
{
+ if (error)
+ {
+ *error = true;
+ return 0;
+ }
+
if (NUMERIC_IS_NAN(num))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
@@ -4331,13 +4364,26 @@ numeric_int8(PG_FUNCTION_ARGS)
init_var_from_num(num, &x);
if (!numericvar_to_int64(&x, &result))
+ {
+ if (error)
+ {
+ *error = true;
+ return 0;
+ }
+
ereport(ERROR,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("bigint out of range")));
+ }
- PG_RETURN_INT64(result);
+ return result;
}
+Datum
+numeric_int8(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_INT64(numeric_int8_opt_error(PG_GETARG_NUMERIC(0), NULL));
+}
Datum
int2_numeric(PG_FUNCTION_ARGS)
@@ -4347,17 +4393,20 @@ int2_numeric(PG_FUNCTION_ARGS)
PG_RETURN_NUMERIC(int64_to_numeric(val));
}
-
-Datum
-numeric_int2(PG_FUNCTION_ARGS)
+int16
+numeric_int2_opt_error(Numeric num, bool *error)
{
- Numeric num = PG_GETARG_NUMERIC(0);
NumericVar x;
int64 val;
- int16 result;
if (NUMERIC_IS_SPECIAL(num))
{
+ if (error)
+ {
+ *error = true;
+ return 0;
+ }
+
if (NUMERIC_IS_NAN(num))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
@@ -4372,21 +4421,40 @@ numeric_int2(PG_FUNCTION_ARGS)
init_var_from_num(num, &x);
if (!numericvar_to_int64(&x, &val))
+ {
+ if (error)
+ {
+ *error = true;
+ return 0;
+ }
+
ereport(ERROR,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("smallint out of range")));
+ }
if (unlikely(val < PG_INT16_MIN) || unlikely(val > PG_INT16_MAX))
+ {
+ if (error)
+ {
+ *error = true;
+ return 0;
+ }
+
ereport(ERROR,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("smallint out of range")));
+ }
/* Down-convert to int2 */
- result = (int16) val;
-
- PG_RETURN_INT16(result);
+ return (int16) val;
}
+Datum
+numeric_int2(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_INT16(numeric_int2_opt_error(PG_GETARG_NUMERIC(0), NULL));
+}
Datum
float8_numeric(PG_FUNCTION_ARGS)
@@ -4412,7 +4480,7 @@ float8_numeric(PG_FUNCTION_ARGS)
init_var(&result);
/* Assume we need not worry about leading/trailing spaces */
- (void) set_var_from_str(buf, buf, &result);
+ (void) set_var_from_str(buf, buf, &result, NULL);
res = make_result(&result);
@@ -4505,7 +4573,7 @@ float4_numeric(PG_FUNCTION_ARGS)
init_var(&result);
/* Assume we need not worry about leading/trailing spaces */
- (void) set_var_from_str(buf, buf, &result);
+ (void) set_var_from_str(buf, buf, &result, NULL);
res = make_result(&result);
@@ -6811,7 +6879,8 @@ zero_var(NumericVar *var)
* reports. (Typically cp would be the same except advanced over spaces.)
*/
static const char *
-set_var_from_str(const char *str, const char *cp, NumericVar *dest)
+set_var_from_str(const char *str, const char *cp, NumericVar *dest,
+ bool *have_error)
{
bool have_dp = false;
int i;
@@ -6849,10 +6918,11 @@ set_var_from_str(const char *str, const char *cp, NumericVar *dest)
}
if (!isdigit((unsigned char) *cp))
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
- errmsg("invalid input syntax for type %s: \"%s\"",
- "numeric", str)));
+ RETURN_ERROR(ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+ errmsg("invalid input syntax for type %s: \"%s\"",
+ "numeric", str))),
+ have_error);
decdigits = (unsigned char *) palloc(strlen(cp) + DEC_DIGITS * 2);
@@ -6873,10 +6943,11 @@ set_var_from_str(const char *str, const char *cp, NumericVar *dest)
else if (*cp == '.')
{
if (have_dp)
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
- errmsg("invalid input syntax for type %s: \"%s\"",
- "numeric", str)));
+ RETURN_ERROR(ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+ errmsg("invalid input syntax for type %s: \"%s\"",
+ "numeric", str))),
+ have_error);
have_dp = true;
cp++;
}
@@ -6897,10 +6968,11 @@ set_var_from_str(const char *str, const char *cp, NumericVar *dest)
cp++;
exponent = strtol(cp, &endptr, 10);
if (endptr == cp)
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
- errmsg("invalid input syntax for type %s: \"%s\"",
- "numeric", str)));
+ RETURN_ERROR(ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+ errmsg("invalid input syntax for type %s: \"%s\"",
+ "numeric", str))),
+ have_error);
cp = endptr;
/*
@@ -6912,9 +6984,10 @@ set_var_from_str(const char *str, const char *cp, NumericVar *dest)
* for consistency use the same ereport errcode/text as make_result().
*/
if (exponent >= INT_MAX / 2 || exponent <= -(INT_MAX / 2))
- ereport(ERROR,
- (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
- errmsg("value overflows numeric format")));
+ RETURN_ERROR(ereport(ERROR,
+ (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+ errmsg("value overflows numeric format"))),
+ have_error);
dweight += (int) exponent;
dscale -= (int) exponent;
if (dscale < 0)
@@ -7420,17 +7493,10 @@ make_result_opt_error(const NumericVar *var, bool *have_error)
if (NUMERIC_WEIGHT(result) != weight ||
NUMERIC_DSCALE(result) != var->dscale)
{
- if (have_error)
- {
- *have_error = true;
- return NULL;
- }
- else
- {
- ereport(ERROR,
- (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
- errmsg("value overflows numeric format")));
- }
+ RETURN_ERROR(ereport(ERROR,
+ (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+ errmsg("value overflows numeric format"))),
+ have_error);
}
dump_numeric("make_result()", result);
@@ -7456,8 +7522,8 @@ make_result(const NumericVar *var)
* Do bounds checking and rounding according to the specified typmod.
* Note that this is only applied to normal finite values.
*/
-static void
-apply_typmod(NumericVar *var, int32 typmod)
+static bool
+apply_typmod(NumericVar *var, int32 typmod, bool *have_error)
{
int precision;
int scale;
@@ -7467,7 +7533,7 @@ apply_typmod(NumericVar *var, int32 typmod)
/* Do nothing if we have an invalid typmod */
if (!is_valid_numeric_typmod(typmod))
- return;
+ return true;
precision = numeric_typmod_precision(typmod);
scale = numeric_typmod_scale(typmod);
@@ -7514,6 +7580,13 @@ apply_typmod(NumericVar *var, int32 typmod)
#error unsupported NBASE
#endif
if (ddigits > maxdigits)
+ {
+ if (have_error)
+ {
+ *have_error = true;
+ return false;
+ }
+
ereport(ERROR,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("numeric field overflow"),
@@ -7523,11 +7596,14 @@ apply_typmod(NumericVar *var, int32 typmod)
maxdigits ? "10^" : "",
maxdigits ? maxdigits : 1
)));
+ }
break;
}
ddigits -= DEC_DIGITS;
}
}
+
+ return true;
}
/*
@@ -7537,7 +7613,7 @@ apply_typmod(NumericVar *var, int32 typmod)
* For convenience of most callers, the value is presented in packed form.
*/
static void
-apply_typmod_special(Numeric num, int32 typmod)
+apply_typmod_special(Numeric num, int32 typmod, bool *have_error)
{
int precision;
int scale;
@@ -7560,6 +7636,12 @@ apply_typmod_special(Numeric num, int32 typmod)
precision = numeric_typmod_precision(typmod);
scale = numeric_typmod_scale(typmod);
+ if (have_error)
+ {
+ *have_error = true;
+ return;
+ }
+
ereport(ERROR,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("numeric field overflow"),
diff --git a/src/backend/utils/adt/numutils.c b/src/backend/utils/adt/numutils.c
index cc3f95d3990..b82b30f9013 100644
--- a/src/backend/utils/adt/numutils.c
+++ b/src/backend/utils/adt/numutils.c
@@ -248,7 +248,7 @@ invalid_syntax:
* positive number.
*/
int64
-pg_strtoint64(const char *s)
+pg_strtoint64_opt_error(const char *s, bool *error)
{
const char *ptr = s;
int64 tmp = 0;
@@ -307,12 +307,24 @@ pg_strtoint64(const char *s)
return tmp;
out_of_range:
+ if (error)
+ {
+ *error = true;
+ return 0;
+ }
+
ereport(ERROR,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("value \"%s\" is out of range for type %s",
s, "bigint")));
invalid_syntax:
+ if (error)
+ {
+ *error = true;
+ return 0;
+ }
+
ereport(ERROR,
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
errmsg("invalid input syntax for type %s: \"%s\"",
@@ -321,6 +333,12 @@ invalid_syntax:
return 0; /* keep compiler quiet */
}
+int64
+pg_strtoint64(const char *s)
+{
+ return pg_strtoint64_opt_error(s, NULL);
+}
+
/*
* pg_itoa: converts a signed 16-bit integer to its string representation
* and returns strlen(a).
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 49cdb290ac2..04a0e3eb063 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -139,18 +139,12 @@ anytimestamp_typmodout(bool istz, int32 typmod)
* USER I/O ROUTINES *
*****************************************************************************/
-/* timestamp_in()
- * Convert a string to internal form.
+/* timestamp_in_opt_error()
+ * Convert a string to internal form, returning error instead of throwing if needed.
*/
-Datum
-timestamp_in(PG_FUNCTION_ARGS)
+Timestamp
+timestamp_in_opt_error(char *str, int32 typmod, bool *error)
{
- char *str = PG_GETARG_CSTRING(0);
-
-#ifdef NOT_USED
- Oid typelem = PG_GETARG_OID(1);
-#endif
- int32 typmod = PG_GETARG_INT32(2);
Timestamp result;
fsec_t fsec;
struct pg_tm tt,
@@ -166,17 +160,34 @@ timestamp_in(PG_FUNCTION_ARGS)
dterr = ParseDateTime(str, workbuf, sizeof(workbuf),
field, ftype, MAXDATEFIELDS, &nf);
if (dterr == 0)
- dterr = DecodeDateTime(field, ftype, nf, &dtype, tm, &fsec, &tz);
+ dterr = DecodeDateTime(field, ftype, nf, &dtype, tm, &fsec, &tz,
+ !error);
if (dterr != 0)
+ {
+ if (error)
+ {
+ *error = true;
+ return 0;
+ }
+
DateTimeParseError(dterr, str, "timestamp");
+ }
switch (dtype)
{
case DTK_DATE:
if (tm2timestamp(tm, fsec, NULL, &result) != 0)
+ {
+ if (error)
+ {
+ *error = true;
+ return 0;
+ }
+
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("timestamp out of range: \"%s\"", str)));
+ }
break;
case DTK_EPOCH:
@@ -197,11 +208,27 @@ timestamp_in(PG_FUNCTION_ARGS)
TIMESTAMP_NOEND(result);
}
- AdjustTimestampForTypmod(&result, typmod);
+ AdjustTimestampForTypmodError(&result, typmod, error);
PG_RETURN_TIMESTAMP(result);
}
+/* timestamp_in()
+ * Convert a string to internal form.
+ */
+Datum
+timestamp_in(PG_FUNCTION_ARGS)
+{
+ char *str = PG_GETARG_CSTRING(0);
+
+#ifdef NOT_USED
+ Oid typelem = PG_GETARG_OID(1);
+#endif
+ int32 typmod = PG_GETARG_INT32(2);
+
+ PG_RETURN_TIMESTAMP(timestamp_in_opt_error(str, typmod, NULL));
+}
+
/* timestamp_out()
* Convert a timestamp to external form.
*/
@@ -400,15 +427,9 @@ AdjustTimestampForTypmod(Timestamp *time, int32 typmod)
/* timestamptz_in()
* Convert a string to internal form.
*/
-Datum
-timestamptz_in(PG_FUNCTION_ARGS)
+TimestampTz
+timestamptz_in_opt_error(char *str, int32 typmod, bool *error)
{
- char *str = PG_GETARG_CSTRING(0);
-
-#ifdef NOT_USED
- Oid typelem = PG_GETARG_OID(1);
-#endif
- int32 typmod = PG_GETARG_INT32(2);
TimestampTz result;
fsec_t fsec;
struct pg_tm tt,
@@ -424,17 +445,34 @@ timestamptz_in(PG_FUNCTION_ARGS)
dterr = ParseDateTime(str, workbuf, sizeof(workbuf),
field, ftype, MAXDATEFIELDS, &nf);
if (dterr == 0)
- dterr = DecodeDateTime(field, ftype, nf, &dtype, tm, &fsec, &tz);
+ dterr = DecodeDateTime(field, ftype, nf, &dtype, tm, &fsec, &tz,
+ !error);
if (dterr != 0)
+ {
+ if (error)
+ {
+ *error = true;
+ return 0;
+ }
+
DateTimeParseError(dterr, str, "timestamp with time zone");
+ }
switch (dtype)
{
case DTK_DATE:
if (tm2timestamp(tm, fsec, &tz, &result) != 0)
+ {
+ if (error)
+ {
+ *error = true;
+ return 0;
+ }
+
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("timestamp out of range: \"%s\"", str)));
+ }
break;
case DTK_EPOCH:
@@ -455,9 +493,24 @@ timestamptz_in(PG_FUNCTION_ARGS)
TIMESTAMP_NOEND(result);
}
- AdjustTimestampForTypmod(&result, typmod);
+ AdjustTimestampForTypmodError(&result, typmod, error);
- PG_RETURN_TIMESTAMPTZ(result);
+ return result;
+}
+
+/* timestamptz_in()
+ * Convert a string to internal form.
+ */
+Datum
+timestamptz_in(PG_FUNCTION_ARGS)
+{
+ char *str = PG_GETARG_CSTRING(0);
+#ifdef NOT_USED
+ Oid typelem = PG_GETARG_OID(1);
+#endif
+ int32 typmod = PG_GETARG_INT32(2);
+
+ PG_RETURN_TIMESTAMPTZ(timestamptz_in_opt_error(str, typmod, NULL));
}
/*
@@ -5614,8 +5667,8 @@ timestamptz_timestamp(PG_FUNCTION_ARGS)
PG_RETURN_TIMESTAMP(timestamptz2timestamp(timestamp));
}
-static Timestamp
-timestamptz2timestamp(TimestampTz timestamp)
+Timestamp
+timestamptz2timestamp_opt_error(TimestampTz timestamp, bool *error)
{
Timestamp result;
struct pg_tm tt,
@@ -5628,17 +5681,40 @@ timestamptz2timestamp(TimestampTz timestamp)
else
{
if (timestamp2tm(timestamp, &tz, tm, &fsec, NULL, NULL) != 0)
+ {
+ if (error)
+ {
+ *error = true;
+ return 0;
+ }
+
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("timestamp out of range")));
+ }
+
if (tm2timestamp(tm, fsec, NULL, &result) != 0)
+ {
+ if (error)
+ {
+ *error = true;
+ return 0;
+ }
+
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("timestamp out of range")));
+ }
}
return result;
}
+static Timestamp
+timestamptz2timestamp(TimestampTz timestamp)
+{
+ return timestamptz2timestamp_opt_error(timestamp, NULL);
+}
+
/* timestamptz_zone()
* Evaluate timestamp with time zone type at the specified time zone.
* Returns a timestamp without time zone.
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 9fbbfb1be54..7877bce6043 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -12866,7 +12866,7 @@ check_recovery_target_time(char **newval, void **extra, GucSource source)
dterr = ParseDateTime(str, workbuf, sizeof(workbuf),
field, ftype, MAXDATEFIELDS, &nf);
if (dterr == 0)
- dterr = DecodeDateTime(field, ftype, nf, &dtype, tm, &fsec, &tz);
+ dterr = DecodeDateTime(field, ftype, nf, &dtype, tm, &fsec, &tz, true);
if (dterr != 0)
return false;
if (dtype != DTK_DATE)
diff --git a/src/include/executor/execExpr.h b/src/include/executor/execExpr.h
index c8ef917ffe0..adabf57c97b 100644
--- a/src/include/executor/execExpr.h
+++ b/src/include/executor/execExpr.h
@@ -762,43 +762,14 @@ 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 */
+ formatted_expr, /* formatted context item value */
+ coercion_expr, /* input for JSON item coercion */
+ pathspec; /* path specification value */
- 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() */
-
- struct JsonCoercionsState
- {
- struct JsonCoercionState
- {
- JsonCoercion *coercion; /* coercion expression */
- ExprState *estate; /* coercion expression state */
- } null,
- string,
- numeric ,
- boolean,
- date,
- time,
- timetz,
- timestamp,
- timestamptz,
- composite;
- } coercions; /* states for coercion from SQL/JSON item
- * types directly to the output type */
} JsonExprState;
/* functions in execExpr.c */
@@ -860,18 +831,8 @@ extern void ExecEvalSysVar(ExprState *state, ExprEvalStep *op,
ExprContext *econtext, TupleTableSlot *slot);
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,
- struct JsonCoercionsState *);
-extern Datum ExecEvalExprPassingCaseValue(ExprState *estate,
- ExprContext *econtext, bool *isnull,
- Datum caseval_datum,
- bool caseval_isnull);
+extern void ExecEvalJsonExpr(ExprState *state, ExprEvalStep *op,
+ ExprContext *econtext);
extern void ExecAggInitGroup(AggState *aggstate, AggStatePerTrans pertrans, AggStatePerGroup pergroup,
ExprContext *aggcontext);
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 3aa96bb6855..14fbb32ce9c 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -1551,39 +1551,6 @@ typedef struct JsonBehavior
Node *default_expr; /* default expression, if any */
} JsonBehavior;
-/*
- * JsonCoercion -
- * coercion from SQL/JSON item types to SQL types
- */
-typedef struct JsonCoercion
-{
- NodeTag type;
- Node *expr; /* resulting expression coerced to target type */
- bool via_populate; /* coerce result using json_populate_type()? */
- bool via_io; /* coerce result using type input function? */
- Oid collation; /* collation for coercion via I/O or populate */
-} JsonCoercion;
-
-/*
- * JsonItemCoercions -
- * expressions for coercion from SQL/JSON item types directly to the
- * output SQL type
- */
-typedef struct JsonItemCoercions
-{
- NodeTag type;
- JsonCoercion *null;
- JsonCoercion *string;
- JsonCoercion *numeric;
- JsonCoercion *boolean;
- JsonCoercion *date;
- JsonCoercion *time;
- JsonCoercion *timetz;
- JsonCoercion *timestamp;
- JsonCoercion *timestamptz;
- JsonCoercion *composite; /* arrays and objects */
-} JsonItemCoercions;
-
/*
* JsonExpr -
* transformed representation of JSON_VALUE(), JSON_QUERY(), JSON_EXISTS()
@@ -1593,7 +1560,6 @@ typedef struct JsonExpr
Expr xpr;
JsonExprOp op; /* json function ID */
Node *formatted_expr; /* formatted context item expression */
- JsonCoercion *result_coercion; /* resulting coercion to RETURNING type */
JsonFormat *format; /* context item format (JSON/JSONB) */
Node *path_spec; /* JSON path specification expression */
List *passing_names; /* PASSING argument names */
@@ -1601,8 +1567,8 @@ typedef struct JsonExpr
JsonReturning *returning; /* RETURNING clause type/format info */
JsonBehavior *on_empty; /* ON EMPTY behavior */
JsonBehavior *on_error; /* ON ERROR behavior */
- JsonItemCoercions *coercions; /* coercions for JSON_VALUE */
JsonWrapper wrapper; /* WRAPPER for JSON_QUERY */
+ Oid collation; /* OID of collation, or InvalidOid if none */
bool omit_quotes; /* KEEP/OMIT QUOTES for JSON_QUERY */
int location; /* token location, or -1 if unknown */
} JsonExpr;
diff --git a/src/include/optimizer/clauses.h b/src/include/optimizer/clauses.h
index 6c5203dc448..4d61b88c161 100644
--- a/src/include/optimizer/clauses.h
+++ b/src/include/optimizer/clauses.h
@@ -55,4 +55,6 @@ extern Query *inline_set_returning_function(PlannerInfo *root,
extern Bitmapset *pull_paramids(Expr *expr);
+extern bool expr_can_throw_errors(Node *expr);
+
#endif /* CLAUSES_H */
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index 221c3e6c3de..5e1aa37c1f5 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -24,6 +24,7 @@
/* bool.c */
extern bool parse_bool(const char *value, bool *result);
extern bool parse_bool_with_len(const char *value, size_t len, bool *result);
+extern bool boolin_opt_error(const char *value, size_t len, bool *error);
/* domains.c */
extern void domain_check(Datum value, bool isnull, Oid domainType,
@@ -46,6 +47,7 @@ extern int namestrcmp(Name name, const char *str);
extern int16 pg_strtoint16(const char *s);
extern int32 pg_strtoint32(const char *s);
extern int64 pg_strtoint64(const char *s);
+extern int64 pg_strtoint64_opt_error(const char *s, bool *error);
extern int pg_itoa(int16 i, char *a);
extern int pg_ultoa_n(uint32 l, char *a);
extern int pg_ulltoa_n(uint64 l, char *a);
diff --git a/src/include/utils/date.h b/src/include/utils/date.h
index 91ae24254df..f29168a025b 100644
--- a/src/include/utils/date.h
+++ b/src/include/utils/date.h
@@ -87,4 +87,17 @@ extern bool time_overflows(int hour, int min, int sec, fsec_t fsec);
extern bool float_time_overflows(int hour, int min, double sec);
extern void AdjustTimeForTypmod(TimeADT *time, int32 typmod);
+extern DateADT timestamp_date_opt_error(Timestamp timestamp, bool *error);
+extern TimeADT timestamp_time_opt_error(Timestamp timestamp,
+ bool *isnull, bool *error);
+
+extern DateADT timestamptz_date_opt_error(Timestamp timestamp, bool *error);
+extern TimeADT timestamptz_time_opt_error(TimestampTz timestamp,
+ bool *isnull, bool *error);
+extern TimeTzADT *timestamptz_timetz_opt_error(TimestampTz timestamp,
+ bool *error);
+extern DateADT date_in_opt_error(char *str, bool *error);
+extern TimeADT time_in_opt_error(char *str, int32 typmod, bool *error);
+extern TimeTzADT *timetz_in_opt_error(char *str, int32 typmod, bool *error);
+
#endif /* DATE_H */
diff --git a/src/include/utils/datetime.h b/src/include/utils/datetime.h
index 4527e825177..68241d7c4cc 100644
--- a/src/include/utils/datetime.h
+++ b/src/include/utils/datetime.h
@@ -295,11 +295,13 @@ extern int ParseDateTime(const char *timestr, char *workbuf, size_t buflen,
int maxfields, int *numfields);
extern int DecodeDateTime(char **field, int *ftype,
int nf, int *dtype,
- struct pg_tm *tm, fsec_t *fsec, int *tzp);
+ struct pg_tm *tm, fsec_t *fsec, int *tzp,
+ bool throw_errors);
extern int DecodeTimezone(char *str, int *tzp);
extern int DecodeTimeOnly(char **field, int *ftype,
int nf, int *dtype,
- struct pg_tm *tm, fsec_t *fsec, int *tzp);
+ struct pg_tm *tm, fsec_t *fsec, int *tzp,
+ bool throw_errors);
extern int DecodeInterval(char **field, int *ftype, int nf, int range,
int *dtype, struct pg_itm_in *itm_in);
extern int DecodeISO8601Interval(char *str,
diff --git a/src/include/utils/float.h b/src/include/utils/float.h
index 4bf0e3ac07a..0ad4d9f3973 100644
--- a/src/include/utils/float.h
+++ b/src/include/utils/float.h
@@ -46,6 +46,7 @@ extern float8 float8in_internal(char *num, char **endptr_p,
extern float8 float8in_internal_opt_error(char *num, char **endptr_p,
const char *type_name, const char *orig_string,
bool *have_error);
+extern float float4in_opt_error(char *num, bool *have_error);
extern char *float8out_internal(float8 num);
extern int float4_cmp_internal(float4 a, float4 b);
extern int float8_cmp_internal(float8 a, float8 b);
diff --git a/src/include/utils/jsonb.h b/src/include/utils/jsonb.h
index bae466b5234..d5574faa79e 100644
--- a/src/include/utils/jsonb.h
+++ b/src/include/utils/jsonb.h
@@ -420,6 +420,8 @@ extern void JsonbHashScalarValueExtended(const JsonbValue *scalarVal,
/* jsonb.c support functions */
extern Datum jsonb_from_text(text *js, bool unique_keys);
+extern Datum jsonb_from_cstring(char *json, int len, bool unique_keys,
+ bool *error);
extern char *JsonbToCString(StringInfo out, JsonbContainer *in,
int estimated_len);
extern char *JsonbToCStringIndent(StringInfo out, JsonbContainer *in,
diff --git a/src/include/utils/jsonpath.h b/src/include/utils/jsonpath.h
index 8e79b8dc9f0..e9134b7eab4 100644
--- a/src/include/utils/jsonpath.h
+++ b/src/include/utils/jsonpath.h
@@ -177,7 +177,8 @@ extern bool jspGetBool(JsonPathItem *v);
extern char *jspGetString(JsonPathItem *v, int32 *len);
extern bool jspGetArraySubscript(JsonPathItem *v, JsonPathItem *from,
JsonPathItem *to, int i);
-extern bool jspIsMutable(JsonPath *path, List *varnames, List *varexprs);
+extern bool jspIsMutable(JsonPath *path, List *varnames, List *varexprs,
+ bool *returns_datetime);
extern const char *jspOperationName(JsonPathItemType type);
diff --git a/src/include/utils/numeric.h b/src/include/utils/numeric.h
index 3caa74dfe7a..f32807cfdaa 100644
--- a/src/include/utils/numeric.h
+++ b/src/include/utils/numeric.h
@@ -85,6 +85,10 @@ extern Numeric numeric_div_opt_error(Numeric num1, Numeric num2,
bool *have_error);
extern Numeric numeric_mod_opt_error(Numeric num1, Numeric num2,
bool *have_error);
-extern int32 numeric_int4_opt_error(Numeric num, bool *error);
+extern int16 numeric_int2_opt_error(Numeric num, bool *have_error);
+extern int32 numeric_int4_opt_error(Numeric num, bool *have_error);
+extern int64 numeric_int8_opt_error(Numeric num, bool *have_error);
+extern Numeric numeric_in_opt_error(char *str, int32 typmod,
+ bool *have_error);
#endif /* _PG_NUMERIC_H_ */
diff --git a/src/include/utils/timestamp.h b/src/include/utils/timestamp.h
index edf3a973186..2685d14342d 100644
--- a/src/include/utils/timestamp.h
+++ b/src/include/utils/timestamp.h
@@ -102,8 +102,14 @@ extern int timestamp_cmp_internal(Timestamp dt1, Timestamp dt2);
extern TimestampTz timestamp2timestamptz_opt_overflow(Timestamp timestamp,
int *overflow);
+extern Timestamp timestamptz2timestamp_opt_error(TimestampTz timestamp,
+ bool *error);
extern int32 timestamp_cmp_timestamptz_internal(Timestamp timestampVal,
TimestampTz dt2);
+extern Timestamp timestamp_in_opt_error(char *str, int32 typmod,
+ bool *error);
+extern TimestampTz timestamptz_in_opt_error(char *str, int32 typmod,
+ bool *error);
extern int isoweek2j(int year, int week);
extern void isoweek2date(int woy, int *year, int *mon, int *mday);
diff --git a/src/test/regress/expected/jsonb_sqljson.out b/src/test/regress/expected/jsonb_sqljson.out
index ef496110af3..2982c191a80 100644
--- a/src/test/regress/expected/jsonb_sqljson.out
+++ b/src/test/regress/expected/jsonb_sqljson.out
@@ -184,11 +184,11 @@ SELECT JSON_EXISTS(jsonb '1', 'strict $[1]' RETURNING text FALSE ON ERROR);
(1 row)
SELECT JSON_EXISTS(jsonb '1', '$[0]' RETURNING jsonb);
-ERROR: cannot cast type boolean to jsonb
+ERROR: returning type jsonb is not supported in JSON_EXISTS()
LINE 1: SELECT JSON_EXISTS(jsonb '1', '$[0]' RETURNING jsonb);
^
SELECT JSON_EXISTS(jsonb '1', '$[0]' RETURNING float4);
-ERROR: cannot cast type boolean to real
+ERROR: returning type real is not supported in JSON_EXISTS()
LINE 1: SELECT JSON_EXISTS(jsonb '1', '$[0]' RETURNING float4);
^
-- JSON_VALUE
@@ -242,7 +242,9 @@ SELECT JSON_VALUE(jsonb '123', '$' RETURNING text);
/* jsonb bytea ??? */
SELECT JSON_VALUE(jsonb '123', '$' RETURNING bytea ERROR ON ERROR);
-ERROR: SQL/JSON item cannot be cast to target type
+ERROR: returning type bytea is not supported in JSON_VALUE()
+LINE 2: SELECT JSON_VALUE(jsonb '123', '$' RETURNING bytea ERROR ON ...
+ ^
SELECT JSON_VALUE(jsonb '1.23', '$');
json_value
------------
@@ -349,14 +351,129 @@ SELECT JSON_VALUE(jsonb '"2017-02-20"', '$' RETURNING date) + 9;
03-01-2017
(1 row)
--- Test NULL checks execution in domain types
+SELECT JSON_VALUE(jsonb '1.23', '$' RETURNING float4);
+ json_value
+------------
+ 1.23
+(1 row)
+
+SELECT JSON_VALUE(jsonb '"1.23"', '$' RETURNING float4);
+ json_value
+------------
+ 1.23
+(1 row)
+
+SELECT JSON_VALUE(jsonb '"nan"', '$' RETURNING float4);
+ json_value
+------------
+ NaN
+(1 row)
+
+SELECT JSON_VALUE(jsonb '"-inf"', '$' RETURNING float4);
+ json_value
+------------
+ -Infinity
+(1 row)
+
+SELECT JSON_VALUE(jsonb '"err"', '$' RETURNING float4 DEFAULT -1 ON ERROR);
+ json_value
+------------
+ -1
+(1 row)
+
+SELECT JSON_VALUE(jsonb '"err"', '$' RETURNING float4 ERROR ON ERROR);
+ERROR: invalid input syntax for type real: "err"
+SELECT JSON_VALUE(jsonb '1.23', '$' RETURNING float8);
+ json_value
+------------
+ 1.23
+(1 row)
+
+SELECT JSON_VALUE(jsonb '"1.23"', '$' RETURNING float8);
+ json_value
+------------
+ 1.23
+(1 row)
+
+SELECT JSON_VALUE(jsonb '"nan"', '$' RETURNING float8);
+ json_value
+------------
+ NaN
+(1 row)
+
+SELECT JSON_VALUE(jsonb '"-inf"', '$' RETURNING float8);
+ json_value
+------------
+ -Infinity
+(1 row)
+
+SELECT JSON_VALUE(jsonb '"err"', '$' RETURNING float8 DEFAULT -1 ON ERROR);
+ json_value
+------------
+ -1
+(1 row)
+
+SELECT JSON_VALUE(jsonb '"err"', '$' RETURNING float8 ERROR ON ERROR);
+ERROR: invalid input syntax for type double precision: "err"
+SELECT JSON_VALUE(jsonb '1.23', '$' RETURNING numeric);
+ json_value
+------------
+ 1.23
+(1 row)
+
+SELECT JSON_VALUE(jsonb '"1.23"', '$' RETURNING numeric);
+ json_value
+------------
+ 1.23
+(1 row)
+
+SELECT JSON_VALUE(jsonb '"nan"', '$' RETURNING numeric);
+ json_value
+------------
+ NaN
+(1 row)
+
+SELECT JSON_VALUE(jsonb '"-inf"', '$' RETURNING numeric);
+ json_value
+------------
+ -Infinity
+(1 row)
+
+SELECT JSON_VALUE(jsonb '"err"', '$' RETURNING numeric DEFAULT -1 ON ERROR);
+ json_value
+------------
+ -1
+(1 row)
+
+SELECT JSON_VALUE(jsonb '"err"', '$' RETURNING numeric ERROR ON ERROR);
+ERROR: invalid input syntax for type numeric: "err"
+-- 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);
+ERROR: returning type sqljsonb_json_not_null is not supported in JSON_VALUE()
+LINE 1: SELECT JSON_VALUE(jsonb '"1"', '$' RETURNING sqljsonb_json_n...
+ ^
+SELECT JSON_VALUE(jsonb '"1"', '$' RETURNING sqljsonb_jsonb_not_null);
+ERROR: returning type sqljsonb_jsonb_not_null is not supported in JSON_VALUE()
+LINE 1: SELECT JSON_VALUE(jsonb '"1"', '$' RETURNING sqljsonb_jsonb_...
+ ^
+-- 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
+ERROR: returning type sqljsonb_int_not_null is not supported in JSON_VALUE()
+LINE 1: SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_int_no...
+ ^
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
+ERROR: returning type sqljsonb_int_not_null is not supported in JSON_VALUE()
+LINE 1: SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_int_no...
+ ^
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
+ERROR: returning type sqljsonb_int_not_null is not supported in JSON_VALUE()
+LINE 1: SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_int_no...
+ ^
+-- Test returning of non-scalar items
SELECT JSON_VALUE(jsonb '[]', '$');
json_value
------------
@@ -478,11 +595,9 @@ SELECT JSON_VALUE(jsonb 'null', '$a' PASSING point ' (1, 2 )' AS a);
(1 row)
SELECT JSON_VALUE(jsonb 'null', '$a' PASSING point ' (1, 2 )' AS a RETURNING point);
- json_value
-------------
- (1,2)
-(1 row)
-
+ERROR: returning type point is not supported in JSON_VALUE()
+LINE 1: SELECT JSON_VALUE(jsonb 'null', '$a' PASSING point ' (1, 2 )...
+ ^
-- Test timestamptz passing and output
SELECT JSON_VALUE(jsonb 'null', '$ts' PASSING timestamptz '2018-02-21 12:34:56 +10' AS ts);
json_value
@@ -514,6 +629,36 @@ 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);
+ERROR: unsafe DEFAULT ON EMPTY expressions are not supported
+LINE 2: DEFAULT 1 / x ON EMPTY
+ ^
+HINT: Use ERROR ON ERROR clause or try to simplify expression into constant-like form
+SELECT JSON_VALUE('1'::jsonb, '$.x' RETURNING int DEFAULT -1 ON EMPTY);
+ json_value
+------------
+ -1
+(1 row)
+
+SELECT JSON_VALUE('1'::jsonb, '$.x' RETURNING int DEFAULT '-1' ON EMPTY);
+ json_value
+------------
+ -1
+(1 row)
+
+SELECT JSON_VALUE('1'::jsonb, '$.x' RETURNING int DEFAULT 'err' ON EMPTY);
+ERROR: invalid input syntax for type integer: "err"
+LINE 1: ...SON_VALUE('1'::jsonb, '$.x' RETURNING int DEFAULT 'err' ON E...
+ ^
+SELECT JSON_VALUE('1'::jsonb, '$.x' RETURNING int DEFAULT -1::float ON EMPTY);
+ERROR: unsafe DEFAULT ON EMPTY expressions are not supported
+LINE 1: ...SON_VALUE('1'::jsonb, '$.x' RETURNING int DEFAULT -1::float ...
+ ^
+HINT: Use ERROR ON ERROR clause or try to simplify expression into constant-like form
-- JSON_QUERY
SELECT
JSON_QUERY(js, '$'),
@@ -852,49 +997,35 @@ FROM
CREATE TYPE sqljsonb_rec AS (a int, t text, js json, jb jsonb, jsa json[]);
CREATE TYPE sqljsonb_reca AS (reca sqljsonb_rec[]);
SELECT JSON_QUERY(jsonb '[{"a": 1, "b": "foo", "t": "aaa", "js": [1, "2", {}], "jb": {"x": [1, "2", {}]}}, {"a": 2}]', '$[0]' RETURNING sqljsonb_rec);
- json_query
------------------------------------------------------
- (1,aaa,"[1, ""2"", {}]","{""x"": [1, ""2"", {}]}",)
-(1 row)
-
+ERROR: returning type sqljsonb_rec is not supported in JSON_QUERY()
+LINE 1: SELECT JSON_QUERY(jsonb '[{"a": 1, "b": "foo", "t": "aaa", "...
+ ^
SELECT * FROM unnest((JSON_QUERY(jsonb '{"jsa": [{"a": 1, "b": ["foo"]}, {"a": 2, "c": {}}, 123]}', '$' RETURNING sqljsonb_rec)).jsa);
- unnest
-------------------------
- {"a": 1, "b": ["foo"]}
- {"a": 2, "c": {}}
- 123
-(3 rows)
-
+ERROR: returning type sqljsonb_rec is not supported in JSON_QUERY()
+LINE 1: SELECT * FROM unnest((JSON_QUERY(jsonb '{"jsa": [{"a": 1, "...
+ ^
SELECT * FROM unnest((JSON_QUERY(jsonb '{"reca": [{"a": 1, "t": ["foo", []]}, {"a": 2, "jb": [{}, true]}]}', '$' RETURNING sqljsonb_reca)).reca);
- a | t | js | jb | jsa
----+-------------+----+------------+-----
- 1 | ["foo", []] | | |
- 2 | | | [{}, true] |
-(2 rows)
-
+ERROR: returning type sqljsonb_reca is not supported in JSON_QUERY()
+LINE 1: SELECT * FROM unnest((JSON_QUERY(jsonb '{"reca": [{"a": 1, "...
+ ^
-- Extension: array types returning
SELECT JSON_QUERY(jsonb '[1,2,null,"3"]', '$[*]' RETURNING int[] WITH WRAPPER);
- json_query
---------------
- {1,2,NULL,3}
-(1 row)
-
+ERROR: returning type integer[] is not supported in JSON_QUERY()
+LINE 1: SELECT JSON_QUERY(jsonb '[1,2,null,"3"]', '$[*]' RETURNING i...
+ ^
SELECT * FROM unnest(JSON_QUERY(jsonb '[{"a": 1, "t": ["foo", []]}, {"a": 2, "jb": [{}, true]}]', '$' RETURNING sqljsonb_rec[]));
- a | t | js | jb | jsa
----+-------------+----+------------+-----
- 1 | ["foo", []] | | |
- 2 | | | [{}, true] |
-(2 rows)
-
+ERROR: returning type sqljsonb_rec[] is not supported in JSON_QUERY()
+LINE 1: SELECT * FROM unnest(JSON_QUERY(jsonb '[{"a": 1, "t": ["foo"...
+ ^
-- Extension: domain types returning
SELECT JSON_QUERY(jsonb '{"a": 1}', '$.a' RETURNING sqljsonb_int_not_null);
- json_query
-------------
- 1
-(1 row)
-
+ERROR: returning type sqljsonb_int_not_null is not supported in JSON_QUERY()
+LINE 1: SELECT JSON_QUERY(jsonb '{"a": 1}', '$.a' RETURNING sqljsonb...
+ ^
SELECT JSON_QUERY(jsonb '{"a": 1}', '$.b' RETURNING sqljsonb_int_not_null);
-ERROR: domain sqljsonb_int_not_null does not allow null values
+ERROR: returning type sqljsonb_int_not_null is not supported in JSON_QUERY()
+LINE 1: SELECT JSON_QUERY(jsonb '{"a": 1}', '$.b' RETURNING sqljsonb...
+ ^
-- Test timestamptz passing and output
SELECT JSON_QUERY(jsonb 'null', '$ts' PASSING timestamptz '2018-02-21 12:34:56 +10' AS ts);
json_query
@@ -1019,6 +1150,25 @@ ERROR: functions in index expression must be marked IMMUTABLE
CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$[1, 0 to $.a ? (@.datetime() == $x)]' PASSING '12:34'::time AS x));
ERROR: functions in index expression must be marked IMMUTABLE
CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$[1, $.a ? (@.datetime("HH:MI") == $x)]' PASSING '12:34'::time AS x));
+CREATE INDEX ON test_jsonb_mutability (JSON_VALUE(js, '$' RETURNING int)); -- immutable
+CREATE INDEX ON test_jsonb_mutability (JSON_VALUE(js, '$' RETURNING date));
+ERROR: functions in index expression must be marked IMMUTABLE
+CREATE INDEX ON test_jsonb_mutability (JSON_VALUE(js, '$' RETURNING time));
+ERROR: functions in index expression must be marked IMMUTABLE
+CREATE INDEX ON test_jsonb_mutability (JSON_VALUE(js, '$' RETURNING timetz));
+ERROR: functions in index expression must be marked IMMUTABLE
+CREATE INDEX ON test_jsonb_mutability (JSON_VALUE(js, '$' RETURNING timestamp));
+ERROR: functions in index expression must be marked IMMUTABLE
+CREATE INDEX ON test_jsonb_mutability (JSON_VALUE(js, '$' RETURNING timestamptz));
+ERROR: functions in index expression must be marked IMMUTABLE
+CREATE INDEX ON test_jsonb_mutability (JSON_VALUE(js, '$.datetime()'));
+ERROR: functions in index expression must be marked IMMUTABLE
+CREATE INDEX ON test_jsonb_mutability (JSON_VALUE(js, '$.datetime()[*][1,2].**'));
+ERROR: functions in index expression must be marked IMMUTABLE
+CREATE INDEX ON test_jsonb_mutability (JSON_VALUE(js, '$x' PASSING '2022-08-25'::date AS x));
+ERROR: functions in index expression must be marked IMMUTABLE
+CREATE INDEX ON test_jsonb_mutability (JSON_VALUE(js, '$.datetime()' RETURNING int)); -- immutable
+CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.datetime()')); -- immutable
DROP TABLE test_jsonb_mutability;
-- JSON_TABLE
-- Should fail (JSON_TABLE can be used only in FROM clause)
@@ -1048,7 +1198,6 @@ SELECT * FROM JSON_TABLE(jsonb '123', '$'
(1 row)
-- JSON_TABLE: basic functionality
-CREATE DOMAIN jsonb_test_domain AS text CHECK (value <> 'foo');
SELECT *
FROM
(VALUES
@@ -1069,7 +1218,6 @@ FROM
"char(4)" char(4) PATH '$',
"bool" bool PATH '$',
"numeric" numeric PATH '$',
- "domain" jsonb_test_domain PATH '$',
js json PATH '$',
jb jsonb PATH '$',
jst text FORMAT JSON PATH '$',
@@ -1085,29 +1233,26 @@ FROM
exists4 text EXISTS PATH 'strict $.aaa' FALSE ON ERROR,
js2 json PATH '$',
jsb2w jsonb PATH '$' WITH WRAPPER,
- jsb2q jsonb PATH '$' OMIT QUOTES,
- ia int[] PATH '$',
- ta text[] PATH '$',
- jba jsonb[] PATH '$'
+ jsb2q jsonb PATH '$' OMIT QUOTES
)
) jt
ON true;
- js | id | id2 | int | text | char(4) | bool | numeric | domain | js | jb | jst | jsc | jsv | jsb | jsbq | aaa | aaa1 | exists1 | exists2 | exists3 | exists4 | js2 | jsb2w | jsb2q | ia | ta | jba
----------------------------------------------------------------------------------------+----+-----+-----+---------+---------+------+---------+---------+--------------+--------------+--------------+------+------+--------------+--------------+-----+------+---------+---------+---------+---------+--------------+----------------+--------------+----+----+-----
- 1 | 1 | 1 | 1 | 1 | 1 | | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | | | f | 0 | | false | 1 | [1] | 1 | | |
- [] | | | | | | | | | | | | | | | | | | | | | | | | | | |
- {} | 1 | 1 | | | | | | | {} | {} | {} | {} | {} | {} | {} | | | f | 0 | | false | {} | [{}] | {} | | |
- [1, 1.23, "2", "aaaaaaa", "foo", null, false, true, {"aaa": 123}, "[1,2]", "\"str\""] | 1 | 1 | 1 | 1 | 1 | | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | | | f | 0 | | false | 1 | [1] | 1 | | |
- [1, 1.23, "2", "aaaaaaa", "foo", null, false, true, {"aaa": 123}, "[1,2]", "\"str\""] | 2 | 2 | 1 | 1.23 | 1.23 | | 1.23 | 1.23 | 1.23 | 1.23 | 1.23 | 1.23 | 1.23 | 1.23 | 1.23 | | | f | 0 | | false | 1.23 | [1.23] | 1.23 | | |
- [1, 1.23, "2", "aaaaaaa", "foo", null, false, true, {"aaa": 123}, "[1,2]", "\"str\""] | 3 | 3 | 2 | 2 | 2 | | 2 | 2 | "2" | "2" | "2" | "2" | "2" | "2" | 2 | | | f | 0 | | false | "2" | ["2"] | 2 | | |
- [1, 1.23, "2", "aaaaaaa", "foo", null, false, true, {"aaa": 123}, "[1,2]", "\"str\""] | 4 | 4 | | aaaaaaa | aaaa | | | aaaaaaa | "aaaaaaa" | "aaaaaaa" | "aaaaaaa" | "aaa | "aaa | "aaaaaaa" | | | | f | 0 | | false | "aaaaaaa" | ["aaaaaaa"] | | | |
- [1, 1.23, "2", "aaaaaaa", "foo", null, false, true, {"aaa": 123}, "[1,2]", "\"str\""] | 5 | 5 | | foo | foo | | | | "foo" | "foo" | "foo" | "foo | "foo | "foo" | | | | f | 0 | | false | "foo" | ["foo"] | | | |
- [1, 1.23, "2", "aaaaaaa", "foo", null, false, true, {"aaa": 123}, "[1,2]", "\"str\""] | 6 | 6 | | | | | | | null | null | null | null | null | null | null | | | f | 0 | | false | null | [null] | null | | |
- [1, 1.23, "2", "aaaaaaa", "foo", null, false, true, {"aaa": 123}, "[1,2]", "\"str\""] | 7 | 7 | 0 | false | fals | f | | false | false | false | false | fals | fals | false | false | | | f | 0 | | false | false | [false] | false | | |
- [1, 1.23, "2", "aaaaaaa", "foo", null, false, true, {"aaa": 123}, "[1,2]", "\"str\""] | 8 | 8 | 1 | true | true | t | | true | true | true | true | true | true | true | true | | | f | 0 | | false | true | [true] | true | | |
- [1, 1.23, "2", "aaaaaaa", "foo", null, false, true, {"aaa": 123}, "[1,2]", "\"str\""] | 9 | 9 | | | | | | | {"aaa": 123} | {"aaa": 123} | {"aaa": 123} | {"aa | {"aa | {"aaa": 123} | {"aaa": 123} | 123 | 123 | t | 1 | 1 | true | {"aaa": 123} | [{"aaa": 123}] | {"aaa": 123} | | |
- [1, 1.23, "2", "aaaaaaa", "foo", null, false, true, {"aaa": 123}, "[1,2]", "\"str\""] | 10 | 10 | | [1,2] | [1,2 | | | [1,2] | "[1,2]" | "[1,2]" | "[1,2]" | "[1, | "[1, | "[1,2]" | [1, 2] | | | f | 0 | | false | "[1,2]" | ["[1,2]"] | [1, 2] | | |
- [1, 1.23, "2", "aaaaaaa", "foo", null, false, true, {"aaa": 123}, "[1,2]", "\"str\""] | 11 | 11 | | "str" | "str | | | "str" | "\"str\"" | "\"str\"" | "\"str\"" | "\"s | "\"s | "\"str\"" | "str" | | | f | 0 | | false | "\"str\"" | ["\"str\""] | "str" | | |
+ js | id | id2 | int | text | char(4) | bool | numeric | js | jb | jst | jsc | jsv | jsb | jsbq | aaa | aaa1 | exists1 | exists2 | exists3 | exists4 | js2 | jsb2w | jsb2q
+---------------------------------------------------------------------------------------+----+-----+-----+---------+---------+------+---------+--------------+--------------+--------------+------+------+--------------+--------------+-----+------+---------+---------+---------+---------+--------------+----------------+--------------
+ 1 | 1 | 1 | 1 | 1 | 1 | | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | | | f | 0 | | false | 1 | [1] | 1
+ [] | | | | | | | | | | | | | | | | | | | | | | |
+ {} | 1 | 1 | | | | | | {} | {} | {} | {} | {} | {} | {} | | | f | 0 | | false | {} | [{}] | {}
+ [1, 1.23, "2", "aaaaaaa", "foo", null, false, true, {"aaa": 123}, "[1,2]", "\"str\""] | 1 | 1 | 1 | 1 | 1 | | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | | | f | 0 | | false | 1 | [1] | 1
+ [1, 1.23, "2", "aaaaaaa", "foo", null, false, true, {"aaa": 123}, "[1,2]", "\"str\""] | 2 | 2 | 1 | 1.23 | 1.23 | | 1.23 | 1.23 | 1.23 | 1.23 | 1.23 | 1.23 | 1.23 | 1.23 | | | f | 0 | | false | 1.23 | [1.23] | 1.23
+ [1, 1.23, "2", "aaaaaaa", "foo", null, false, true, {"aaa": 123}, "[1,2]", "\"str\""] | 3 | 3 | 2 | 2 | 2 | | 2 | "2" | "2" | "2" | "2" | "2" | "2" | 2 | | | f | 0 | | false | "2" | ["2"] | 2
+ [1, 1.23, "2", "aaaaaaa", "foo", null, false, true, {"aaa": 123}, "[1,2]", "\"str\""] | 4 | 4 | | aaaaaaa | aaaa | | | "aaaaaaa" | "aaaaaaa" | "aaaaaaa" | "aaa | "aaa | "aaaaaaa" | | | | f | 0 | | false | "aaaaaaa" | ["aaaaaaa"] |
+ [1, 1.23, "2", "aaaaaaa", "foo", null, false, true, {"aaa": 123}, "[1,2]", "\"str\""] | 5 | 5 | | foo | foo | | | "foo" | "foo" | "foo" | "foo | "foo | "foo" | | | | f | 0 | | false | "foo" | ["foo"] |
+ [1, 1.23, "2", "aaaaaaa", "foo", null, false, true, {"aaa": 123}, "[1,2]", "\"str\""] | 6 | 6 | | | | | | null | null | null | null | null | null | null | | | f | 0 | | false | null | [null] | null
+ [1, 1.23, "2", "aaaaaaa", "foo", null, false, true, {"aaa": 123}, "[1,2]", "\"str\""] | 7 | 7 | 0 | false | fals | f | | false | false | false | fals | fals | false | false | | | f | 0 | | false | false | [false] | false
+ [1, 1.23, "2", "aaaaaaa", "foo", null, false, true, {"aaa": 123}, "[1,2]", "\"str\""] | 8 | 8 | 1 | true | true | t | | true | true | true | true | true | true | true | | | f | 0 | | false | true | [true] | true
+ [1, 1.23, "2", "aaaaaaa", "foo", null, false, true, {"aaa": 123}, "[1,2]", "\"str\""] | 9 | 9 | | | | | | {"aaa": 123} | {"aaa": 123} | {"aaa": 123} | {"aa | {"aa | {"aaa": 123} | {"aaa": 123} | 123 | 123 | t | 1 | 1 | true | {"aaa": 123} | [{"aaa": 123}] | {"aaa": 123}
+ [1, 1.23, "2", "aaaaaaa", "foo", null, false, true, {"aaa": 123}, "[1,2]", "\"str\""] | 10 | 10 | | [1,2] | [1,2 | | | "[1,2]" | "[1,2]" | "[1,2]" | "[1, | "[1, | "[1,2]" | [1, 2] | | | f | 0 | | false | "[1,2]" | ["[1,2]"] | [1, 2]
+ [1, 1.23, "2", "aaaaaaa", "foo", null, false, true, {"aaa": 123}, "[1,2]", "\"str\""] | 11 | 11 | | "str" | "str | | | "\"str\"" | "\"str\"" | "\"str\"" | "\"s | "\"s | "\"str\"" | "str" | | | f | 0 | | false | "\"str\"" | ["\"str\""] | "str"
(14 rows)
-- JSON_TABLE: Test backward parsing
@@ -1123,7 +1268,6 @@ SELECT * FROM
"char(4)" char(4) PATH '$',
"bool" bool PATH '$',
"numeric" numeric PATH '$',
- "domain" jsonb_test_domain PATH '$',
js json PATH '$',
jb jsonb PATH '$',
jst text FORMAT JSON PATH '$',
@@ -1139,9 +1283,6 @@ SELECT * FROM
js2 json PATH '$',
jsb2w jsonb PATH '$' WITH WRAPPER,
jsb2q jsonb PATH '$' OMIT QUOTES,
- ia int[] PATH '$',
- ta text[] PATH '$',
- jba jsonb[] PATH '$',
NESTED PATH '$[1]' AS p1 COLUMNS (
a1 int,
NESTED PATH '$[*]' AS "p1 1" COLUMNS (
@@ -1168,7 +1309,6 @@ CREATE OR REPLACE VIEW public.jsonb_table_view AS
"json_table"."char(4)",
"json_table".bool,
"json_table"."numeric",
- "json_table".domain,
"json_table".js,
"json_table".jb,
"json_table".jst,
@@ -1184,9 +1324,6 @@ CREATE OR REPLACE VIEW public.jsonb_table_view AS
"json_table".js2,
"json_table".jsb2w,
"json_table".jsb2q,
- "json_table".ia,
- "json_table".ta,
- "json_table".jba,
"json_table".a1,
"json_table".b1,
"json_table".a11,
@@ -1205,7 +1342,6 @@ CREATE OR REPLACE VIEW public.jsonb_table_view AS
"char(4)" character(4) PATH '$',
bool boolean PATH '$',
"numeric" numeric PATH '$',
- domain jsonb_test_domain PATH '$',
js json PATH '$',
jb jsonb PATH '$',
jst text FORMAT JSON PATH '$',
@@ -1221,9 +1357,6 @@ CREATE OR REPLACE VIEW public.jsonb_table_view AS
js2 json PATH '$',
jsb2w jsonb PATH '$' WITH UNCONDITIONAL WRAPPER,
jsb2q jsonb PATH '$' OMIT QUOTES,
- ia integer[] PATH '$',
- ta text[] PATH '$',
- jba jsonb[] PATH '$',
NESTED PATH '$[1]' AS p1
COLUMNS (
a1 integer PATH '$."a1"',
@@ -1248,15 +1381,14 @@ CREATE OR REPLACE VIEW public.jsonb_table_view AS
PLAN (json_table_path_1 OUTER ((p1 OUTER "p1 1") UNION (p2 OUTER ("p2:1" UNION p22))))
)
EXPLAIN (COSTS OFF, VERBOSE) SELECT * FROM jsonb_table_view;
- QUERY PLAN
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ QUERY PLAN
+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Table Function Scan on "json_table"
- Output: "json_table".id, "json_table".id2, "json_table"."int", "json_table".text, "json_table"."char(4)", "json_table".bool, "json_table"."numeric", "json_table".domain, "json_table".js, "json_table".jb, "json_table".jst, "json_table".jsc, "json_table".jsv, "json_table".jsb, "json_table".jsbq, "json_table".aaa, "json_table".aaa1, "json_table".exists1, "json_table".exists2, "json_table".exists3, "json_table".js2, "json_table".jsb2w, "json_table".jsb2q, "json_table".ia, "json_table".ta, "json_table".jba, "json_table".a1, "json_table".b1, "json_table".a11, "json_table".a21, "json_table".a22
- Table Function Call: JSON_TABLE('null'::jsonb, '$[*]' AS json_table_path_1 PASSING 3 AS a, '"foo"'::jsonb AS "b c" COLUMNS (id FOR ORDINALITY, id2 FOR ORDINALITY, "int" integer PATH '$', text text PATH '$', "char(4)" character(4) PATH '$', bool boolean PATH '$', "numeric" numeric PATH '$', domain jsonb_test_domain PATH '$', js json PATH '$', jb jsonb PATH '$', jst text FORMAT JSON PATH '$', jsc character(4) FORMAT JSON PATH '$', jsv character varying(4) FORMAT JSON PATH '$', jsb jsonb PATH '$', jsbq jsonb PATH '$' OMIT QUOTES, aaa integer PATH '$."aaa"', aaa1 integer PATH '$."aaa"', exists1 boolean EXISTS PATH '$."aaa"', exists2 integer EXISTS PATH '$."aaa"' TRUE ON ERROR, exists3 text EXISTS PATH 'strict $."aaa"' UNKNOWN ON ERROR, js2 json PATH '$', jsb2w jsonb PATH '$' WITH UNCONDITIONAL WRAPPER, jsb2q jsonb PATH '$' OMIT QUOTES, ia integer[] PATH '$', ta text[] PATH '$', jba jsonb[] PATH '$', NESTED PATH '$[1]' AS p1 COLUMNS (a1 integer PATH '$."a1"', b1 text PATH '$."b1"', NESTED PATH '$[*]' AS "p1 1" COLUMNS (a11 text PATH '$."a11"')), NESTED PATH '$[2]' AS p2 COLUMNS ( NESTED PATH '$[*]' AS "p2:1" COLUMNS (a21 text PATH '$."a21"'), NESTED PATH '$[*]' AS p22 COLUMNS (a22 text PATH '$."a22"'))) PLAN (json_table_path_1 OUTER ((p1 OUTER "p1 1") UNION (p2 OUTER ("p2:1" UNION p22)))))
+ Output: "json_table".id, "json_table".id2, "json_table"."int", "json_table".text, "json_table"."char(4)", "json_table".bool, "json_table"."numeric", "json_table".js, "json_table".jb, "json_table".jst, "json_table".jsc, "json_table".jsv, "json_table".jsb, "json_table".jsbq, "json_table".aaa, "json_table".aaa1, "json_table".exists1, "json_table".exists2, "json_table".exists3, "json_table".js2, "json_table".jsb2w, "json_table".jsb2q, "json_table".a1, "json_table".b1, "json_table".a11, "json_table".a21, "json_table".a22
+ Table Function Call: JSON_TABLE('null'::jsonb, '$[*]' AS json_table_path_1 PASSING 3 AS a, '"foo"'::jsonb AS "b c" COLUMNS (id FOR ORDINALITY, id2 FOR ORDINALITY, "int" integer PATH '$', text text PATH '$', "char(4)" character(4) PATH '$', bool boolean PATH '$', "numeric" numeric PATH '$', js json PATH '$', jb jsonb PATH '$', jst text FORMAT JSON PATH '$', jsc character(4) FORMAT JSON PATH '$', jsv character varying(4) FORMAT JSON PATH '$', jsb jsonb PATH '$', jsbq jsonb PATH '$' OMIT QUOTES, aaa integer PATH '$."aaa"', aaa1 integer PATH '$."aaa"', exists1 boolean EXISTS PATH '$."aaa"', exists2 integer EXISTS PATH '$."aaa"' TRUE ON ERROR, exists3 text EXISTS PATH 'strict $."aaa"' UNKNOWN ON ERROR, js2 json PATH '$', jsb2w jsonb PATH '$' WITH UNCONDITIONAL WRAPPER, jsb2q jsonb PATH '$' OMIT QUOTES, NESTED PATH '$[1]' AS p1 COLUMNS (a1 integer PATH '$."a1"', b1 text PATH '$."b1"', NESTED PATH '$[*]' AS "p1 1" COLUMNS (a11 text PATH '$."a11"')), NESTED PATH '$[2]' AS p2 COLUMNS ( NESTED PATH '$[*]' AS "p2:1" COLUMNS (a21 text PATH '$."a21"'), NESTED PATH '$[*]' AS p22 COLUMNS (a22 text PATH '$."a22"'))) PLAN (json_table_path_1 OUTER ((p1 OUTER "p1 1") UNION (p2 OUTER ("p2:1" UNION p22)))))
(3 rows)
DROP VIEW jsonb_table_view;
-DROP DOMAIN jsonb_test_domain;
-- JSON_TABLE: ON EMPTY/ON ERROR behavior
SELECT *
FROM
@@ -1318,15 +1450,15 @@ SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a int4 EXISTS PATH '$.a'));
(1 row)
SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a int2 EXISTS PATH '$.a'));
-ERROR: cannot cast type boolean to smallint
+ERROR: returning type smallint is not supported in JSON_EXISTS()
LINE 1: ...ELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a int2 EXI...
^
SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a int8 EXISTS PATH '$.a'));
-ERROR: cannot cast type boolean to bigint
+ERROR: returning type bigint is not supported in JSON_EXISTS()
LINE 1: ...ELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a int8 EXI...
^
SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a float4 EXISTS PATH '$.a'));
-ERROR: cannot cast type boolean to real
+ERROR: returning type real is not supported in JSON_EXISTS()
LINE 1: ...ELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a float4 E...
^
SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a char(3) EXISTS PATH '$.a'));
@@ -1336,11 +1468,11 @@ SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a char(3) EXISTS PATH '$.a'))
(1 row)
SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a json EXISTS PATH '$.a'));
-ERROR: cannot cast type boolean to json
+ERROR: returning type json is not supported in JSON_EXISTS()
LINE 1: ...ELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a json EXI...
^
SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a jsonb EXISTS PATH '$.a'));
-ERROR: cannot cast type boolean to jsonb
+ERROR: returning type jsonb is not supported in JSON_EXISTS()
LINE 1: ...ELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a jsonb EX...
^
-- JSON_TABLE: nested paths and plans
@@ -2102,11 +2234,14 @@ set parallel_leader_participation = off;
-- Should be non-parallel due to subtransactions
EXPLAIN (COSTS OFF)
SELECT sum(JSON_VALUE(js, '$' RETURNING numeric)) FROM test_parallel_jsonb_value;
- QUERY PLAN
----------------------------------------------
- Aggregate
- -> Seq Scan on test_parallel_jsonb_value
-(2 rows)
+ QUERY PLAN
+------------------------------------------------------------------
+ Finalize Aggregate
+ -> Gather
+ Workers Planned: 4
+ -> Partial Aggregate
+ -> Parallel Seq Scan on test_parallel_jsonb_value
+(5 rows)
SELECT sum(JSON_VALUE(js, '$' RETURNING numeric)) FROM test_parallel_jsonb_value;
sum
diff --git a/src/test/regress/sql/jsonb_sqljson.sql b/src/test/regress/sql/jsonb_sqljson.sql
index fff25374808..43a194ad09f 100644
--- a/src/test/regress/sql/jsonb_sqljson.sql
+++ b/src/test/regress/sql/jsonb_sqljson.sql
@@ -82,12 +82,43 @@ 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
+SELECT JSON_VALUE(jsonb '1.23', '$' RETURNING float4);
+SELECT JSON_VALUE(jsonb '"1.23"', '$' RETURNING float4);
+SELECT JSON_VALUE(jsonb '"nan"', '$' RETURNING float4);
+SELECT JSON_VALUE(jsonb '"-inf"', '$' RETURNING float4);
+SELECT JSON_VALUE(jsonb '"err"', '$' RETURNING float4 DEFAULT -1 ON ERROR);
+SELECT JSON_VALUE(jsonb '"err"', '$' RETURNING float4 ERROR ON ERROR);
+
+SELECT JSON_VALUE(jsonb '1.23', '$' RETURNING float8);
+SELECT JSON_VALUE(jsonb '"1.23"', '$' RETURNING float8);
+SELECT JSON_VALUE(jsonb '"nan"', '$' RETURNING float8);
+SELECT JSON_VALUE(jsonb '"-inf"', '$' RETURNING float8);
+SELECT JSON_VALUE(jsonb '"err"', '$' RETURNING float8 DEFAULT -1 ON ERROR);
+SELECT JSON_VALUE(jsonb '"err"', '$' RETURNING float8 ERROR ON ERROR);
+
+SELECT JSON_VALUE(jsonb '1.23', '$' RETURNING numeric);
+SELECT JSON_VALUE(jsonb '"1.23"', '$' RETURNING numeric);
+SELECT JSON_VALUE(jsonb '"nan"', '$' RETURNING numeric);
+SELECT JSON_VALUE(jsonb '"-inf"', '$' RETURNING numeric);
+SELECT JSON_VALUE(jsonb '"err"', '$' RETURNING numeric DEFAULT -1 ON ERROR);
+SELECT JSON_VALUE(jsonb '"err"', '$' RETURNING numeric ERROR ON ERROR);
+
+-- 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);
+
+-- 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 NULL ON ERROR);
SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_int_not_null DEFAULT NULL ON ERROR);
+
+-- Test returning of non-scalar items
SELECT JSON_VALUE(jsonb '[]', '$');
SELECT JSON_VALUE(jsonb '[]', '$' ERROR ON ERROR);
SELECT JSON_VALUE(jsonb '{}', '$');
@@ -133,6 +164,17 @@ 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);
+
+SELECT JSON_VALUE('1'::jsonb, '$.x' RETURNING int DEFAULT -1 ON EMPTY);
+SELECT JSON_VALUE('1'::jsonb, '$.x' RETURNING int DEFAULT '-1' ON EMPTY);
+SELECT JSON_VALUE('1'::jsonb, '$.x' RETURNING int DEFAULT 'err' ON EMPTY);
+SELECT JSON_VALUE('1'::jsonb, '$.x' RETURNING int DEFAULT -1::float ON EMPTY);
+
-- JSON_QUERY
SELECT
@@ -318,6 +360,21 @@ CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.datetime("YY-MM-DD") ?
CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$[1, $.a ? (@.datetime() == $x)]' PASSING '12:34'::time AS x));
CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$[1, 0 to $.a ? (@.datetime() == $x)]' PASSING '12:34'::time AS x));
CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$[1, $.a ? (@.datetime("HH:MI") == $x)]' PASSING '12:34'::time AS x));
+
+CREATE INDEX ON test_jsonb_mutability (JSON_VALUE(js, '$' RETURNING int)); -- immutable
+CREATE INDEX ON test_jsonb_mutability (JSON_VALUE(js, '$' RETURNING date));
+CREATE INDEX ON test_jsonb_mutability (JSON_VALUE(js, '$' RETURNING time));
+CREATE INDEX ON test_jsonb_mutability (JSON_VALUE(js, '$' RETURNING timetz));
+CREATE INDEX ON test_jsonb_mutability (JSON_VALUE(js, '$' RETURNING timestamp));
+CREATE INDEX ON test_jsonb_mutability (JSON_VALUE(js, '$' RETURNING timestamptz));
+
+CREATE INDEX ON test_jsonb_mutability (JSON_VALUE(js, '$.datetime()'));
+CREATE INDEX ON test_jsonb_mutability (JSON_VALUE(js, '$.datetime()[*][1,2].**'));
+CREATE INDEX ON test_jsonb_mutability (JSON_VALUE(js, '$x' PASSING '2022-08-25'::date AS x));
+CREATE INDEX ON test_jsonb_mutability (JSON_VALUE(js, '$.datetime()' RETURNING int)); -- immutable
+
+CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.datetime()')); -- immutable
+
DROP TABLE test_jsonb_mutability;
-- JSON_TABLE
@@ -338,8 +395,6 @@ SELECT * FROM JSON_TABLE(jsonb '123', '$'
COLUMNS (item int PATH '$', foo int)) bar;
-- JSON_TABLE: basic functionality
-CREATE DOMAIN jsonb_test_domain AS text CHECK (value <> 'foo');
-
SELECT *
FROM
(VALUES
@@ -360,7 +415,6 @@ FROM
"char(4)" char(4) PATH '$',
"bool" bool PATH '$',
"numeric" numeric PATH '$',
- "domain" jsonb_test_domain PATH '$',
js json PATH '$',
jb jsonb PATH '$',
jst text FORMAT JSON PATH '$',
@@ -377,10 +431,7 @@ FROM
js2 json PATH '$',
jsb2w jsonb PATH '$' WITH WRAPPER,
- jsb2q jsonb PATH '$' OMIT QUOTES,
- ia int[] PATH '$',
- ta text[] PATH '$',
- jba jsonb[] PATH '$'
+ jsb2q jsonb PATH '$' OMIT QUOTES
)
) jt
ON true;
@@ -399,7 +450,6 @@ SELECT * FROM
"char(4)" char(4) PATH '$',
"bool" bool PATH '$',
"numeric" numeric PATH '$',
- "domain" jsonb_test_domain PATH '$',
js json PATH '$',
jb jsonb PATH '$',
jst text FORMAT JSON PATH '$',
@@ -416,9 +466,6 @@ SELECT * FROM
js2 json PATH '$',
jsb2w jsonb PATH '$' WITH WRAPPER,
jsb2q jsonb PATH '$' OMIT QUOTES,
- ia int[] PATH '$',
- ta text[] PATH '$',
- jba jsonb[] PATH '$',
NESTED PATH '$[1]' AS p1 COLUMNS (
a1 int,
@@ -443,7 +490,6 @@ SELECT * FROM
EXPLAIN (COSTS OFF, VERBOSE) SELECT * FROM jsonb_table_view;
DROP VIEW jsonb_table_view;
-DROP DOMAIN jsonb_test_domain;
-- JSON_TABLE: ON EMPTY/ON ERROR behavior
SELECT *
--
2.17.1
On 2022-08-26 Fr 16:11, Nikita Glukhov wrote:
Hi,
On 26.08.2022 22:25, Andrew Dunstan wrote:
On 2022-08-24 We 20:05, Nikita Glukhov wrote:
v8 - is a highly WIP patch, which I failed to finish today.
Even some test cases fail now, and they simply show unfinished
things like casts to bytea (they can be simply removed) and missing
safe input functions.Thanks for your work, please keep going.
I have completed in v9 all the things I previously planned:
- Added missing safe I/O and type conversion functions for
datetime, float4, varchar, bpchar. This introduces a lot
of boilerplate code for returning errors and also maybe
adds some overhead.- Added JSON_QUERY coercion to UTF8 bytea using pg_convert_to().
- Added immutability checks that were missed with elimination
of coercion expressions.
Coercions text::datetime, datetime1::datetime2 and even
datetime::text for some datetime types are mutable.
datetime::text can be made immutable by passing ISO date
style into output functions (like in jsonpath).- Disabled non-Const expressions in DEFAULT ON EMPTY in non
ERROR ON ERROR case. Non-constant expressions are tried to
evaluate into Const directly inside transformExpr().
Maybe it would be better to simply remove DEFAULT ON EMPTY.
Yes, I think that's what I suggested upthread. I don't think DEFAULT ON
EMPTY matters that much, and we can revisit it for release 16. If it's
simpler please do it that way.
It is possible to easily split this patch into several subpatches,
I will do it if needed.
Thanks, probably a good idea but I will start reviewing what you have
now. Andres and others please chime in if you can.
cheers
andrew
--
Andrew Dunstan
EDB: https://www.enterprisedb.com
On 8/26/22 4:36 PM, Andrew Dunstan wrote:
On 2022-08-26 Fr 16:11, Nikita Glukhov wrote:
Hi,
On 26.08.2022 22:25, Andrew Dunstan wrote:
On 2022-08-24 We 20:05, Nikita Glukhov wrote:
v8 - is a highly WIP patch, which I failed to finish today.
Even some test cases fail now, and they simply show unfinished
things like casts to bytea (they can be simply removed) and missing
safe input functions.Thanks for your work, please keep going.
I have completed in v9 all the things I previously planned:
- Added missing safe I/O and type conversion functions for
datetime, float4, varchar, bpchar. This introduces a lot
of boilerplate code for returning errors and also maybe
adds some overhead.- Added JSON_QUERY coercion to UTF8 bytea using pg_convert_to().
- Added immutability checks that were missed with elimination
of coercion expressions.
Coercions text::datetime, datetime1::datetime2 and even
datetime::text for some datetime types are mutable.
datetime::text can be made immutable by passing ISO date
style into output functions (like in jsonpath).- Disabled non-Const expressions in DEFAULT ON EMPTY in non
ERROR ON ERROR case. Non-constant expressions are tried to
evaluate into Const directly inside transformExpr().
Maybe it would be better to simply remove DEFAULT ON EMPTY.Yes, I think that's what I suggested upthread. I don't think DEFAULT ON
EMPTY matters that much, and we can revisit it for release 16. If it's
simpler please do it that way.It is possible to easily split this patch into several subpatches,
I will do it if needed.Thanks, probably a good idea but I will start reviewing what you have
now. Andres and others please chime in if you can.
Thanks Nikita!
I looked through the tests to see if we would need any doc changes, e.g.
in [1]https://www.postgresql.org/docs/15/functions-json.html#FUNCTIONS-SQLJSON. I noticed that this hint:
"HINT: Use ERROR ON ERROR clause or try to simplify expression into
constant-like form"
lacks a period on the end, which is convention.
I don't know if the SQL/JSON standard calls out if domains should be
castable, but if it does, we should document in [1]https://www.postgresql.org/docs/15/functions-json.html#FUNCTIONS-SQLJSON that we are not
currently supporting them as return types, so that we're only supporting
"constant-like" expressions with examples.
Looking forward to hearing other feedback.
Thanks,
Jonathan
[1]: https://www.postgresql.org/docs/15/functions-json.html#FUNCTIONS-SQLJSON
On Sat, Aug 27, 2022 at 5:11 AM Nikita Glukhov <n.gluhov@postgrespro.ru> wrote:
On 26.08.2022 22:25, Andrew Dunstan wrote:
On 2022-08-24 We 20:05, Nikita Glukhov wrote:
v8 - is a highly WIP patch, which I failed to finish today.
Even some test cases fail now, and they simply show unfinished
things like casts to bytea (they can be simply removed) and missing
safe input functions.Thanks for your work, please keep going.
I have completed in v9 all the things I previously planned:
- Added missing safe I/O and type conversion functions for
datetime, float4, varchar, bpchar. This introduces a lot
of boilerplate code for returning errors and also maybe
adds some overhead.
Didn't know that we have done similar things in the past for jsonpath, as in:
commit 16d489b0fe058e527619f5e9d92fd7ca3c6c2994
Author: Alexander Korotkov <akorotkov@postgresql.org>
Date: Sat Mar 16 12:21:19 2019 +0300
Numeric error suppression in jsonpath
BTW, maybe the following hunk in boolin_opt_error() is unnecessary?
- len = strlen(str);
+ len -= str - in_str;
- Added JSON_QUERY coercion to UTF8 bytea using pg_convert_to().
- Added immutability checks that were missed with elimination
of coercion expressions.
Coercions text::datetime, datetime1::datetime2 and even
datetime::text for some datetime types are mutable.
datetime::text can be made immutable by passing ISO date
style into output functions (like in jsonpath).- Disabled non-Const expressions in DEFAULT ON EMPTY in non
ERROR ON ERROR case. Non-constant expressions are tried to
evaluate into Const directly inside transformExpr().
I am not sure if it's OK to eval_const_expressions() on a Query
sub-expression during parse-analysis. IIUC, it is only correct to
apply it to after the rewriting phase.
Maybe it would be better to simply remove DEFAULT ON EMPTY.
So +1 to this for now.
It is possible to easily split this patch into several subpatches,
I will do it if needed.
That would be nice indeed.
I'm wondering if you're going to change the PASSING values
initialization to add the steps into the parent JsonExpr's ExprState,
like the previous patch was doing?
--
Thanks, Amit Langote
EDB: http://www.enterprisedb.com
On 8/29/22 8:56 AM, Amit Langote wrote:
On Sat, Aug 27, 2022 at 5:11 AM Nikita Glukhov <n.gluhov@postgrespro.ru> wrote:
I am not sure if it's OK to eval_const_expressions() on a Query
sub-expression during parse-analysis. IIUC, it is only correct to
apply it to after the rewriting phase.Maybe it would be better to simply remove DEFAULT ON EMPTY.
So +1 to this for now.
+1, if this simplifies the patch and makes it acceptable for v15
It is possible to easily split this patch into several subpatches,
I will do it if needed.That would be nice indeed.
With RMT hat on, the RMT has its weekly meetings on Tuesdays. Based on
the timing of the Beta 4 commit freeze[1]/messages/by-id/9d251aec-cea2-bc1a-5ed8-46ef0bcf6c69@postgresql.org and how both
including/reverting are nontrivial operations (e.g. we should ensure
we're confident in both and that they pass through the buildfarm), we
are going to have to make a decision on how to proceed at the next meeting.
Can folks please chime in on what they think of the current patchset and
if this is acceptable for v15?
Thanks,
Jonathan
[1]: /messages/by-id/9d251aec-cea2-bc1a-5ed8-46ef0bcf6c69@postgresql.org
/messages/by-id/9d251aec-cea2-bc1a-5ed8-46ef0bcf6c69@postgresql.org
On 2022-08-29 Mo 09:35, Jonathan S. Katz wrote:
On 8/29/22 8:56 AM, Amit Langote wrote:
On Sat, Aug 27, 2022 at 5:11 AM Nikita Glukhov
<n.gluhov@postgrespro.ru> wrote:I am not sure if it's OK to eval_const_expressions() on a Query
sub-expression during parse-analysis. IIUC, it is only correct to
apply it to after the rewriting phase.Maybe it would be better to simply remove DEFAULT ON EMPTY.
So +1 to this for now.
+1, if this simplifies the patch and makes it acceptable for v15
It is possible to easily split this patch into several subpatches,
I will do it if needed.That would be nice indeed.
With RMT hat on, the RMT has its weekly meetings on Tuesdays. Based on
the timing of the Beta 4 commit freeze[1] and how both
including/reverting are nontrivial operations (e.g. we should ensure
we're confident in both and that they pass through the buildfarm), we
are going to have to make a decision on how to proceed at the next
meeting.Can folks please chime in on what they think of the current patchset
and if this is acceptable for v15?
I think at a pinch we could probably go with it, but it's a close call.
I think it deals with the most pressing issues that have been raised. If
people are still worried I think it would be trivial to add in calls
that error out of the DEFAULT clauses are used at all.
cheers
andrew
--
Andrew Dunstan
EDB: https://www.enterprisedb.com
Hi,
On 29.08.2022 15:56, Amit Langote wrote:
On Sat, Aug 27, 2022 at 5:11 AM Nikita Glukhov<n.gluhov@postgrespro.ru> wrote:
On 26.08.2022 22:25, Andrew Dunstan wrote:
On 2022-08-24 We 20:05, Nikita Glukhov wrote:
I have completed in v9 all the things I previously planned:
- Added missing safe I/O and type conversion functions for
datetime, float4, varchar, bpchar. This introduces a lot
of boilerplate code for returning errors and also maybe
adds some overhead.Didn't know that we have done similar things in the past for jsonpath, as in:
commit 16d489b0fe058e527619f5e9d92fd7ca3c6c2994
Author: Alexander Korotkov<akorotkov@postgresql.org>
Date: Sat Mar 16 12:21:19 2019 +0300Numeric error suppression in jsonpath
This was necessary for handling errors in arithmetic operations.
BTW, maybe the following hunk in boolin_opt_error() is unnecessary?
- len = strlen(str); + len -= str - in_str;
This is really not necessary, but helps to avoid extra strlen() call.
I have replaced it with more intuitive
+ {
str++;
+ len--;
+ }
- len = strlen(str);
- Added JSON_QUERY coercion to UTF8 bytea using pg_convert_to().
- Added immutability checks that were missed with elimination
of coercion expressions.
Coercions text::datetime, datetime1::datetime2 and even
datetime::text for some datetime types are mutable.
datetime::text can be made immutable by passing ISO date
style into output functions (like in jsonpath).- Disabled non-Const expressions in DEFAULT ON EMPTY in non
ERROR ON ERROR case. Non-constant expressions are tried to
evaluate into Const directly inside transformExpr().I am not sure if it's OK to eval_const_expressions() on a Query
sub-expression during parse-analysis. IIUC, it is only correct to
apply it to after the rewriting phase.
I also was not sure. Maybe it can be moved to rewriting phase or
even to execution phase.
Maybe it would be better to simply remove DEFAULT ON EMPTY.
So +1 to this for now.
See last patch #9.
It is possible to easily split this patch into several subpatches,
I will do it if needed.That would be nice indeed.
I have extracted patches #1-6 with numerous safe input and type conversion
functions.
I'm wondering if you're going to change the PASSING values
initialization to add the steps into the parent JsonExpr's ExprState,
like the previous patch was doing?
I forget to incorporate your changes for subsidary ExprStates elimination.
See patch #8.
--
Nikita Glukhov
Postgres Professional:http://www.postgrespro.com
The Russian Postgres Company
Attachments:
v9-0001-Add-safe-input-function-for-bool.patchtext/x-patch; charset=UTF-8; name=v9-0001-Add-safe-input-function-for-bool.patchDownload
From ffeadb09e8ab55b5a677716c6f7ebdafba778d38 Mon Sep 17 00:00:00 2001
From: Nikita Glukhov <n.gluhov@postgrespro.ru>
Date: Mon, 29 Aug 2022 23:53:06 +0300
Subject: [PATCH v9 1/9] Add safe input function for bool
---
src/backend/utils/adt/bool.c | 33 +++++++++++++++++++++------------
src/include/utils/builtins.h | 1 +
2 files changed, 22 insertions(+), 12 deletions(-)
diff --git a/src/backend/utils/adt/bool.c b/src/backend/utils/adt/bool.c
index cd7335287f9..2494f441285 100644
--- a/src/backend/utils/adt/bool.c
+++ b/src/backend/utils/adt/bool.c
@@ -126,12 +126,10 @@ parse_bool_with_len(const char *value, size_t len, bool *result)
*
* In the switch statement, check the most-used possibilities first.
*/
-Datum
-boolin(PG_FUNCTION_ARGS)
+bool
+boolin_opt_error(const char *in_str, size_t len, bool *error)
{
- const char *in_str = PG_GETARG_CSTRING(0);
const char *str;
- size_t len;
bool result;
/*
@@ -139,22 +137,33 @@ boolin(PG_FUNCTION_ARGS)
*/
str = in_str;
while (isspace((unsigned char) *str))
+ {
str++;
+ len--;
+ }
- len = strlen(str);
while (len > 0 && isspace((unsigned char) str[len - 1]))
len--;
if (parse_bool_with_len(str, len, &result))
- PG_RETURN_BOOL(result);
+ return result;
+
+ if (!error)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+ errmsg("invalid input syntax for type %s: \"%s\"",
+ "boolean", in_str)));
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
- errmsg("invalid input syntax for type %s: \"%s\"",
- "boolean", in_str)));
+ *error = true;
+ return false;
+}
+
+Datum
+boolin(PG_FUNCTION_ARGS)
+{
+ char *str = PG_GETARG_CSTRING(0);
- /* not reached */
- PG_RETURN_BOOL(false);
+ PG_RETURN_BOOL(boolin_opt_error(str, strlen(str), NULL));
}
/*
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index 221c3e6c3de..2c03ce4fe12 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -24,6 +24,7 @@
/* bool.c */
extern bool parse_bool(const char *value, bool *result);
extern bool parse_bool_with_len(const char *value, size_t len, bool *result);
+extern bool boolin_opt_error(const char *value, size_t len, bool *error);
/* domains.c */
extern void domain_check(Datum value, bool isnull, Oid domainType,
--
2.17.1
v9-0002-Add-safe-input-function-for-int8.patchtext/x-patch; charset=UTF-8; name=v9-0002-Add-safe-input-function-for-int8.patchDownload
From 7a0f71e430136a3bcec95919149b2a1e1d6e86f5 Mon Sep 17 00:00:00 2001
From: Nikita Glukhov <n.gluhov@postgrespro.ru>
Date: Tue, 30 Aug 2022 00:13:54 +0300
Subject: [PATCH v9 2/9] Add safe input function for int8
---
src/backend/utils/adt/numutils.c | 20 +++++++++++++++++++-
src/include/utils/builtins.h | 1 +
2 files changed, 20 insertions(+), 1 deletion(-)
diff --git a/src/backend/utils/adt/numutils.c b/src/backend/utils/adt/numutils.c
index cc3f95d3990..b82b30f9013 100644
--- a/src/backend/utils/adt/numutils.c
+++ b/src/backend/utils/adt/numutils.c
@@ -248,7 +248,7 @@ invalid_syntax:
* positive number.
*/
int64
-pg_strtoint64(const char *s)
+pg_strtoint64_opt_error(const char *s, bool *error)
{
const char *ptr = s;
int64 tmp = 0;
@@ -307,12 +307,24 @@ pg_strtoint64(const char *s)
return tmp;
out_of_range:
+ if (error)
+ {
+ *error = true;
+ return 0;
+ }
+
ereport(ERROR,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("value \"%s\" is out of range for type %s",
s, "bigint")));
invalid_syntax:
+ if (error)
+ {
+ *error = true;
+ return 0;
+ }
+
ereport(ERROR,
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
errmsg("invalid input syntax for type %s: \"%s\"",
@@ -321,6 +333,12 @@ invalid_syntax:
return 0; /* keep compiler quiet */
}
+int64
+pg_strtoint64(const char *s)
+{
+ return pg_strtoint64_opt_error(s, NULL);
+}
+
/*
* pg_itoa: converts a signed 16-bit integer to its string representation
* and returns strlen(a).
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index 2c03ce4fe12..5e1aa37c1f5 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -47,6 +47,7 @@ extern int namestrcmp(Name name, const char *str);
extern int16 pg_strtoint16(const char *s);
extern int32 pg_strtoint32(const char *s);
extern int64 pg_strtoint64(const char *s);
+extern int64 pg_strtoint64_opt_error(const char *s, bool *error);
extern int pg_itoa(int16 i, char *a);
extern int pg_ultoa_n(uint32 l, char *a);
extern int pg_ulltoa_n(uint64 l, char *a);
--
2.17.1
v9-0003-Add-safe-input-function-for-float4.patchtext/x-patch; charset=UTF-8; name=v9-0003-Add-safe-input-function-for-float4.patchDownload
From 9f4179494ef4612eadd8c2c6787391d85347fa4d Mon Sep 17 00:00:00 2001
From: Nikita Glukhov <n.gluhov@postgrespro.ru>
Date: Tue, 30 Aug 2022 00:13:36 +0300
Subject: [PATCH v9 3/9] Add safe input function for float4
---
src/backend/utils/adt/float.c | 70 +++++++++++++++++++----------------
src/include/utils/float.h | 1 +
2 files changed, 40 insertions(+), 31 deletions(-)
diff --git a/src/backend/utils/adt/float.c b/src/backend/utils/adt/float.c
index fc8f39a7a98..d9da8c8466d 100644
--- a/src/backend/utils/adt/float.c
+++ b/src/backend/utils/adt/float.c
@@ -73,6 +73,16 @@ static double sind_q1(double x);
static double cosd_q1(double x);
static void init_degree_constants(void);
+/* Convenience macro: set *have_error flag (if provided) or throw error */
+#define RETURN_ERROR(throw_error, have_error) \
+do { \
+ if (have_error) { \
+ *have_error = true; \
+ return 0.0; \
+ } else { \
+ throw_error; \
+ } \
+} while (0)
/*
* We use these out-of-line ereport() calls to report float overflow,
@@ -159,10 +169,9 @@ is_infinite(double val)
* result of 0xAE43FEp-107.
*
*/
-Datum
-float4in(PG_FUNCTION_ARGS)
+float
+float4in_opt_error(char *num, bool *have_error)
{
- char *num = PG_GETARG_CSTRING(0);
char *orig_num;
float val;
char *endptr;
@@ -183,10 +192,11 @@ float4in(PG_FUNCTION_ARGS)
* strtod() on different platforms.
*/
if (*num == '\0')
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
- errmsg("invalid input syntax for type %s: \"%s\"",
- "real", orig_num)));
+ RETURN_ERROR(ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+ errmsg("invalid input syntax for type %s: \"%s\"",
+ "real", orig_num))),
+ have_error);
errno = 0;
val = strtof(num, &endptr);
@@ -257,16 +267,18 @@ float4in(PG_FUNCTION_ARGS)
(val >= HUGE_VALF || val <= -HUGE_VALF)
#endif
)
- ereport(ERROR,
- (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
- errmsg("\"%s\" is out of range for type real",
- orig_num)));
+ RETURN_ERROR(ereport(ERROR,
+ (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+ errmsg("\"%s\" is out of range for type real",
+ orig_num))),
+ have_error);
}
else
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
- errmsg("invalid input syntax for type %s: \"%s\"",
- "real", orig_num)));
+ RETURN_ERROR(ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+ errmsg("invalid input syntax for type %s: \"%s\"",
+ "real", orig_num))),
+ have_error);
}
/* skip trailing whitespace */
@@ -275,12 +287,19 @@ float4in(PG_FUNCTION_ARGS)
/* if there is any junk left at the end of the string, bail out */
if (*endptr != '\0')
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
- errmsg("invalid input syntax for type %s: \"%s\"",
- "real", orig_num)));
+ RETURN_ERROR(ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+ errmsg("invalid input syntax for type %s: \"%s\"",
+ "real", orig_num))),
+ have_error);
+
+ return val;
+}
- PG_RETURN_FLOAT4(val);
+Datum
+float4in(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_FLOAT4(float4in_opt_error(PG_GETARG_CSTRING(0), NULL));
}
/*
@@ -340,17 +359,6 @@ float8in(PG_FUNCTION_ARGS)
PG_RETURN_FLOAT8(float8in_internal(num, NULL, "double precision", num));
}
-/* Convenience macro: set *have_error flag (if provided) or throw error */
-#define RETURN_ERROR(throw_error, have_error) \
-do { \
- if (have_error) { \
- *have_error = true; \
- return 0.0; \
- } else { \
- throw_error; \
- } \
-} while (0)
-
/*
* float8in_internal_opt_error - guts of float8in()
*
diff --git a/src/include/utils/float.h b/src/include/utils/float.h
index 4bf0e3ac07a..0ad4d9f3973 100644
--- a/src/include/utils/float.h
+++ b/src/include/utils/float.h
@@ -46,6 +46,7 @@ extern float8 float8in_internal(char *num, char **endptr_p,
extern float8 float8in_internal_opt_error(char *num, char **endptr_p,
const char *type_name, const char *orig_string,
bool *have_error);
+extern float float4in_opt_error(char *num, bool *have_error);
extern char *float8out_internal(float8 num);
extern int float4_cmp_internal(float4 a, float4 b);
extern int float8_cmp_internal(float8 a, float8 b);
--
2.17.1
v9-0004-Add-safe-input-and-type-conversion-functions-for-.patchtext/x-patch; charset=UTF-8; name=v9-0004-Add-safe-input-and-type-conversion-functions-for-.patchDownload
From 8365b41223945a3cfd9615fa33488d774f00e0f8 Mon Sep 17 00:00:00 2001
From: Nikita Glukhov <n.gluhov@postgrespro.ru>
Date: Mon, 29 Aug 2022 23:55:52 +0300
Subject: [PATCH v9 4/9] Add safe input and type conversion functions for
numeric
---
src/backend/utils/adt/numeric.c | 230 ++++++++++++++++++++++----------
src/include/utils/numeric.h | 6 +-
2 files changed, 161 insertions(+), 75 deletions(-)
diff --git a/src/backend/utils/adt/numeric.c b/src/backend/utils/adt/numeric.c
index 920a63b0081..951b2f43ab1 100644
--- a/src/backend/utils/adt/numeric.c
+++ b/src/backend/utils/adt/numeric.c
@@ -498,7 +498,7 @@ static void free_var(NumericVar *var);
static void zero_var(NumericVar *var);
static const char *set_var_from_str(const char *str, const char *cp,
- NumericVar *dest);
+ NumericVar *dest, bool *have_error);
static void set_var_from_num(Numeric value, NumericVar *dest);
static void init_var_from_num(Numeric num, NumericVar *dest);
static void set_var_from_var(const NumericVar *value, NumericVar *dest);
@@ -512,8 +512,8 @@ static Numeric duplicate_numeric(Numeric num);
static Numeric make_result(const NumericVar *var);
static Numeric make_result_opt_error(const NumericVar *var, bool *error);
-static void apply_typmod(NumericVar *var, int32 typmod);
-static void apply_typmod_special(Numeric num, int32 typmod);
+static bool apply_typmod(NumericVar *var, int32 typmod, bool *error);
+static void apply_typmod_special(Numeric num, int32 typmod, bool *error);
static bool numericvar_to_int32(const NumericVar *var, int32 *result);
static bool numericvar_to_int64(const NumericVar *var, int64 *result);
@@ -607,21 +607,25 @@ static void accum_sum_combine(NumericSumAccum *accum, NumericSumAccum *accum2);
* ----------------------------------------------------------------------
*/
+/* Convenience macro: set *have_error flag (if provided) or throw error */
+#define RETURN_ERROR(throw_error, have_error) \
+do { \
+ if (have_error) { \
+ *(have_error) = true; \
+ return NULL; \
+ } else { \
+ throw_error; \
+ } \
+} while (0)
/*
* numeric_in() -
*
* Input function for numeric data type
*/
-Datum
-numeric_in(PG_FUNCTION_ARGS)
+Numeric
+numeric_in_opt_error(char *str, int32 typmod, bool *have_error)
{
- char *str = PG_GETARG_CSTRING(0);
-
-#ifdef NOT_USED
- Oid typelem = PG_GETARG_OID(1);
-#endif
- int32 typmod = PG_GETARG_INT32(2);
Numeric res;
const char *cp;
@@ -682,7 +686,10 @@ numeric_in(PG_FUNCTION_ARGS)
init_var(&value);
- cp = set_var_from_str(str, cp, &value);
+ cp = set_var_from_str(str, cp, &value, have_error);
+
+ if (!cp)
+ return NULL; /* error */
/*
* We duplicate a few lines of code here because we would like to
@@ -693,38 +700,59 @@ numeric_in(PG_FUNCTION_ARGS)
while (*cp)
{
if (!isspace((unsigned char) *cp))
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
- errmsg("invalid input syntax for type %s: \"%s\"",
- "numeric", str)));
+ RETURN_ERROR(ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+ errmsg("invalid input syntax for type %s: \"%s\"",
+ "numeric", str))),
+ have_error);
cp++;
}
- apply_typmod(&value, typmod);
+ if (!apply_typmod(&value, typmod, have_error))
+ return NULL; /* error */
- res = make_result(&value);
+ res = make_result_opt_error(&value, have_error);
free_var(&value);
- PG_RETURN_NUMERIC(res);
+ return res;
}
/* Should be nothing left but spaces */
while (*cp)
{
if (!isspace((unsigned char) *cp))
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
- errmsg("invalid input syntax for type %s: \"%s\"",
- "numeric", str)));
+ RETURN_ERROR(ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+ errmsg("invalid input syntax for type %s: \"%s\"",
+ "numeric", str))),
+ have_error);
+
cp++;
}
/* As above, throw any typmod error after finishing syntax check */
- apply_typmod_special(res, typmod);
+ apply_typmod_special(res, typmod, have_error);
- PG_RETURN_NUMERIC(res);
+ return res;
}
+/*
+ * numeric_in() -
+ *
+ * Input function for numeric data type
+ */
+Datum
+numeric_in(PG_FUNCTION_ARGS)
+{
+ char *str = PG_GETARG_CSTRING(0);
+
+#ifdef NOT_USED
+ Oid typelem = PG_GETARG_OID(1);
+#endif
+ int32 typmod = PG_GETARG_INT32(2);
+
+ PG_RETURN_NUMERIC(numeric_in_opt_error(str, typmod, NULL));
+}
/*
* numeric_out() -
@@ -1058,7 +1086,7 @@ numeric_recv(PG_FUNCTION_ARGS)
{
trunc_var(&value, value.dscale);
- apply_typmod(&value, typmod);
+ apply_typmod(&value, typmod, NULL);
res = make_result(&value);
}
@@ -1067,7 +1095,7 @@ numeric_recv(PG_FUNCTION_ARGS)
/* apply_typmod_special wants us to make the Numeric first */
res = make_result(&value);
- apply_typmod_special(res, typmod);
+ apply_typmod_special(res, typmod, NULL);
}
free_var(&value);
@@ -1180,7 +1208,7 @@ numeric (PG_FUNCTION_ARGS)
*/
if (NUMERIC_IS_SPECIAL(num))
{
- apply_typmod_special(num, typmod);
+ apply_typmod_special(num, typmod, NULL);
PG_RETURN_NUMERIC(duplicate_numeric(num));
}
@@ -1231,7 +1259,7 @@ numeric (PG_FUNCTION_ARGS)
init_var(&var);
set_var_from_num(num, &var);
- apply_typmod(&var, typmod);
+ apply_typmod(&var, typmod, NULL);
new = make_result(&var);
free_var(&var);
@@ -4308,15 +4336,20 @@ int8_numeric(PG_FUNCTION_ARGS)
}
-Datum
-numeric_int8(PG_FUNCTION_ARGS)
+int64
+numeric_int8_opt_error(Numeric num, bool *error)
{
- Numeric num = PG_GETARG_NUMERIC(0);
NumericVar x;
int64 result;
if (NUMERIC_IS_SPECIAL(num))
{
+ if (error)
+ {
+ *error = true;
+ return 0;
+ }
+
if (NUMERIC_IS_NAN(num))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
@@ -4331,13 +4364,26 @@ numeric_int8(PG_FUNCTION_ARGS)
init_var_from_num(num, &x);
if (!numericvar_to_int64(&x, &result))
+ {
+ if (error)
+ {
+ *error = true;
+ return 0;
+ }
+
ereport(ERROR,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("bigint out of range")));
+ }
- PG_RETURN_INT64(result);
+ return result;
}
+Datum
+numeric_int8(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_INT64(numeric_int8_opt_error(PG_GETARG_NUMERIC(0), NULL));
+}
Datum
int2_numeric(PG_FUNCTION_ARGS)
@@ -4347,17 +4393,20 @@ int2_numeric(PG_FUNCTION_ARGS)
PG_RETURN_NUMERIC(int64_to_numeric(val));
}
-
-Datum
-numeric_int2(PG_FUNCTION_ARGS)
+int16
+numeric_int2_opt_error(Numeric num, bool *error)
{
- Numeric num = PG_GETARG_NUMERIC(0);
NumericVar x;
int64 val;
- int16 result;
if (NUMERIC_IS_SPECIAL(num))
{
+ if (error)
+ {
+ *error = true;
+ return 0;
+ }
+
if (NUMERIC_IS_NAN(num))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
@@ -4372,21 +4421,40 @@ numeric_int2(PG_FUNCTION_ARGS)
init_var_from_num(num, &x);
if (!numericvar_to_int64(&x, &val))
+ {
+ if (error)
+ {
+ *error = true;
+ return 0;
+ }
+
ereport(ERROR,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("smallint out of range")));
+ }
if (unlikely(val < PG_INT16_MIN) || unlikely(val > PG_INT16_MAX))
+ {
+ if (error)
+ {
+ *error = true;
+ return 0;
+ }
+
ereport(ERROR,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("smallint out of range")));
+ }
/* Down-convert to int2 */
- result = (int16) val;
-
- PG_RETURN_INT16(result);
+ return (int16) val;
}
+Datum
+numeric_int2(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_INT16(numeric_int2_opt_error(PG_GETARG_NUMERIC(0), NULL));
+}
Datum
float8_numeric(PG_FUNCTION_ARGS)
@@ -4412,7 +4480,7 @@ float8_numeric(PG_FUNCTION_ARGS)
init_var(&result);
/* Assume we need not worry about leading/trailing spaces */
- (void) set_var_from_str(buf, buf, &result);
+ (void) set_var_from_str(buf, buf, &result, NULL);
res = make_result(&result);
@@ -4505,7 +4573,7 @@ float4_numeric(PG_FUNCTION_ARGS)
init_var(&result);
/* Assume we need not worry about leading/trailing spaces */
- (void) set_var_from_str(buf, buf, &result);
+ (void) set_var_from_str(buf, buf, &result, NULL);
res = make_result(&result);
@@ -6811,7 +6879,8 @@ zero_var(NumericVar *var)
* reports. (Typically cp would be the same except advanced over spaces.)
*/
static const char *
-set_var_from_str(const char *str, const char *cp, NumericVar *dest)
+set_var_from_str(const char *str, const char *cp, NumericVar *dest,
+ bool *have_error)
{
bool have_dp = false;
int i;
@@ -6849,10 +6918,11 @@ set_var_from_str(const char *str, const char *cp, NumericVar *dest)
}
if (!isdigit((unsigned char) *cp))
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
- errmsg("invalid input syntax for type %s: \"%s\"",
- "numeric", str)));
+ RETURN_ERROR(ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+ errmsg("invalid input syntax for type %s: \"%s\"",
+ "numeric", str))),
+ have_error);
decdigits = (unsigned char *) palloc(strlen(cp) + DEC_DIGITS * 2);
@@ -6873,10 +6943,11 @@ set_var_from_str(const char *str, const char *cp, NumericVar *dest)
else if (*cp == '.')
{
if (have_dp)
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
- errmsg("invalid input syntax for type %s: \"%s\"",
- "numeric", str)));
+ RETURN_ERROR(ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+ errmsg("invalid input syntax for type %s: \"%s\"",
+ "numeric", str))),
+ have_error);
have_dp = true;
cp++;
}
@@ -6897,10 +6968,11 @@ set_var_from_str(const char *str, const char *cp, NumericVar *dest)
cp++;
exponent = strtol(cp, &endptr, 10);
if (endptr == cp)
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
- errmsg("invalid input syntax for type %s: \"%s\"",
- "numeric", str)));
+ RETURN_ERROR(ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+ errmsg("invalid input syntax for type %s: \"%s\"",
+ "numeric", str))),
+ have_error);
cp = endptr;
/*
@@ -6912,9 +6984,10 @@ set_var_from_str(const char *str, const char *cp, NumericVar *dest)
* for consistency use the same ereport errcode/text as make_result().
*/
if (exponent >= INT_MAX / 2 || exponent <= -(INT_MAX / 2))
- ereport(ERROR,
- (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
- errmsg("value overflows numeric format")));
+ RETURN_ERROR(ereport(ERROR,
+ (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+ errmsg("value overflows numeric format"))),
+ have_error);
dweight += (int) exponent;
dscale -= (int) exponent;
if (dscale < 0)
@@ -7420,17 +7493,10 @@ make_result_opt_error(const NumericVar *var, bool *have_error)
if (NUMERIC_WEIGHT(result) != weight ||
NUMERIC_DSCALE(result) != var->dscale)
{
- if (have_error)
- {
- *have_error = true;
- return NULL;
- }
- else
- {
- ereport(ERROR,
- (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
- errmsg("value overflows numeric format")));
- }
+ RETURN_ERROR(ereport(ERROR,
+ (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+ errmsg("value overflows numeric format"))),
+ have_error);
}
dump_numeric("make_result()", result);
@@ -7456,8 +7522,8 @@ make_result(const NumericVar *var)
* Do bounds checking and rounding according to the specified typmod.
* Note that this is only applied to normal finite values.
*/
-static void
-apply_typmod(NumericVar *var, int32 typmod)
+static bool
+apply_typmod(NumericVar *var, int32 typmod, bool *have_error)
{
int precision;
int scale;
@@ -7467,7 +7533,7 @@ apply_typmod(NumericVar *var, int32 typmod)
/* Do nothing if we have an invalid typmod */
if (!is_valid_numeric_typmod(typmod))
- return;
+ return true;
precision = numeric_typmod_precision(typmod);
scale = numeric_typmod_scale(typmod);
@@ -7514,6 +7580,13 @@ apply_typmod(NumericVar *var, int32 typmod)
#error unsupported NBASE
#endif
if (ddigits > maxdigits)
+ {
+ if (have_error)
+ {
+ *have_error = true;
+ return false;
+ }
+
ereport(ERROR,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("numeric field overflow"),
@@ -7523,11 +7596,14 @@ apply_typmod(NumericVar *var, int32 typmod)
maxdigits ? "10^" : "",
maxdigits ? maxdigits : 1
)));
+ }
break;
}
ddigits -= DEC_DIGITS;
}
}
+
+ return true;
}
/*
@@ -7537,7 +7613,7 @@ apply_typmod(NumericVar *var, int32 typmod)
* For convenience of most callers, the value is presented in packed form.
*/
static void
-apply_typmod_special(Numeric num, int32 typmod)
+apply_typmod_special(Numeric num, int32 typmod, bool *have_error)
{
int precision;
int scale;
@@ -7560,6 +7636,12 @@ apply_typmod_special(Numeric num, int32 typmod)
precision = numeric_typmod_precision(typmod);
scale = numeric_typmod_scale(typmod);
+ if (have_error)
+ {
+ *have_error = true;
+ return;
+ }
+
ereport(ERROR,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("numeric field overflow"),
diff --git a/src/include/utils/numeric.h b/src/include/utils/numeric.h
index 3caa74dfe7a..f32807cfdaa 100644
--- a/src/include/utils/numeric.h
+++ b/src/include/utils/numeric.h
@@ -85,6 +85,10 @@ extern Numeric numeric_div_opt_error(Numeric num1, Numeric num2,
bool *have_error);
extern Numeric numeric_mod_opt_error(Numeric num1, Numeric num2,
bool *have_error);
-extern int32 numeric_int4_opt_error(Numeric num, bool *error);
+extern int16 numeric_int2_opt_error(Numeric num, bool *have_error);
+extern int32 numeric_int4_opt_error(Numeric num, bool *have_error);
+extern int64 numeric_int8_opt_error(Numeric num, bool *have_error);
+extern Numeric numeric_in_opt_error(char *str, int32 typmod,
+ bool *have_error);
#endif /* _PG_NUMERIC_H_ */
--
2.17.1
v9-0005-Add-safe-input-and-type-conversion-functions-for-.patchtext/x-patch; charset=UTF-8; name=v9-0005-Add-safe-input-and-type-conversion-functions-for-.patchDownload
From 73dd55775b4ed2609b6b052046423db6241e6a54 Mon Sep 17 00:00:00 2001
From: Nikita Glukhov <n.gluhov@postgrespro.ru>
Date: Mon, 29 Aug 2022 23:54:45 +0300
Subject: [PATCH v9 5/9] Add safe input and type conversion functions for
datetime types
---
contrib/adminpack/adminpack.c | 2 +-
src/backend/utils/adt/date.c | 277 ++++++++++++++++++++++++------
src/backend/utils/adt/datetime.c | 16 +-
src/backend/utils/adt/timestamp.c | 126 +++++++++++---
src/backend/utils/misc/guc.c | 2 +-
src/include/utils/date.h | 13 ++
src/include/utils/datetime.h | 6 +-
src/include/utils/timestamp.h | 6 +
8 files changed, 363 insertions(+), 85 deletions(-)
diff --git a/contrib/adminpack/adminpack.c b/contrib/adminpack/adminpack.c
index 03addf1dc5f..4bf795d42a4 100644
--- a/contrib/adminpack/adminpack.c
+++ b/contrib/adminpack/adminpack.c
@@ -571,7 +571,7 @@ pg_logdir_ls_internal(FunctionCallInfo fcinfo)
if (ParseDateTime(timestampbuf, lowstr, MAXDATELEN, field, ftype, MAXDATEFIELDS, &nf))
continue;
- if (DecodeDateTime(field, ftype, nf, &dtype, &date, &fsec, &tz))
+ if (DecodeDateTime(field, ftype, nf, &dtype, &date, &fsec, &tz, true))
continue;
/* Seems the timestamp is OK; prepare and return tuple */
diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index 081dfa2450f..43a96fc5142 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -108,10 +108,9 @@ anytime_typmodout(bool istz, int32 typmod)
/* date_in()
* Given date text string, convert to internal date format.
*/
-Datum
-date_in(PG_FUNCTION_ARGS)
+DateADT
+date_in_opt_error(char *str, bool *error)
{
- char *str = PG_GETARG_CSTRING(0);
DateADT date;
fsec_t fsec;
struct pg_tm tt,
@@ -127,9 +126,18 @@ date_in(PG_FUNCTION_ARGS)
dterr = ParseDateTime(str, workbuf, sizeof(workbuf),
field, ftype, MAXDATEFIELDS, &nf);
if (dterr == 0)
- dterr = DecodeDateTime(field, ftype, nf, &dtype, tm, &fsec, &tzp);
+ dterr = DecodeDateTime(field, ftype, nf, &dtype, tm, &fsec, &tzp,
+ !error);
if (dterr != 0)
+ {
+ if (error)
+ {
+ *error = true;
+ return 0;
+ }
+
DateTimeParseError(dterr, str, "date");
+ }
switch (dtype)
{
@@ -149,25 +157,56 @@ date_in(PG_FUNCTION_ARGS)
PG_RETURN_DATEADT(date);
default:
+ if (*error)
+ {
+ *error = true;
+ return 0;
+ }
+
DateTimeParseError(DTERR_BAD_FORMAT, str, "date");
break;
}
/* Prevent overflow in Julian-day routines */
if (!IS_VALID_JULIAN(tm->tm_year, tm->tm_mon, tm->tm_mday))
+ {
+ if (error)
+ {
+ *error = true;
+ return 0;
+ }
+
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("date out of range: \"%s\"", str)));
+ }
date = date2j(tm->tm_year, tm->tm_mon, tm->tm_mday) - POSTGRES_EPOCH_JDATE;
/* Now check for just-out-of-range dates */
if (!IS_VALID_DATE(date))
+ {
+ if (error)
+ {
+ *error = true;
+ return 0;
+ }
+
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("date out of range: \"%s\"", str)));
+ }
- PG_RETURN_DATEADT(date);
+ return date;
+}
+
+/* date_in()
+ * Given date text string, convert to internal date format.
+ */
+Datum
+date_in(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_DATEADT(date_in_opt_error(PG_GETARG_CSTRING(0), NULL));
}
/* date_out()
@@ -1286,10 +1325,9 @@ date_timestamp(PG_FUNCTION_ARGS)
/* timestamp_date()
* Convert timestamp to date data type.
*/
-Datum
-timestamp_date(PG_FUNCTION_ARGS)
+DateADT
+timestamp_date_opt_error(Timestamp timestamp, bool *error)
{
- Timestamp timestamp = PG_GETARG_TIMESTAMP(0);
DateADT result;
struct pg_tm tt,
*tm = &tt;
@@ -1302,16 +1340,32 @@ timestamp_date(PG_FUNCTION_ARGS)
else
{
if (timestamp2tm(timestamp, NULL, tm, &fsec, NULL, NULL) != 0)
+ {
+ if (error)
+ {
+ *error = true;
+ return 0;
+ }
+
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("timestamp out of range")));
+ }
result = date2j(tm->tm_year, tm->tm_mon, tm->tm_mday) - POSTGRES_EPOCH_JDATE;
}
- PG_RETURN_DATEADT(result);
+ return result;
}
+/* timestamp_date()
+ * Convert timestamp to date data type.
+ */
+Datum
+timestamp_date(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_DATEADT(timestamp_date_opt_error(PG_GETARG_TIMESTAMP(0), NULL));
+}
/* date_timestamptz()
* Convert date to timestamp with time zone data type.
@@ -1327,14 +1381,9 @@ date_timestamptz(PG_FUNCTION_ARGS)
PG_RETURN_TIMESTAMP(result);
}
-
-/* timestamptz_date()
- * Convert timestamp with time zone to date data type.
- */
-Datum
-timestamptz_date(PG_FUNCTION_ARGS)
+DateADT
+timestamptz_date_opt_error(TimestampTz timestamp, bool *error)
{
- TimestampTz timestamp = PG_GETARG_TIMESTAMP(0);
DateADT result;
struct pg_tm tt,
*tm = &tt;
@@ -1348,14 +1397,31 @@ timestamptz_date(PG_FUNCTION_ARGS)
else
{
if (timestamp2tm(timestamp, &tz, tm, &fsec, NULL, NULL) != 0)
+ {
+ if (error)
+ {
+ *error = true;
+ return 0;
+ }
+
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("timestamp out of range")));
+ }
result = date2j(tm->tm_year, tm->tm_mon, tm->tm_mday) - POSTGRES_EPOCH_JDATE;
}
- PG_RETURN_DATEADT(result);
+ return result;
+}
+
+/* timestamptz_date()
+ * Convert timestamp with time zone to date data type.
+ */
+Datum
+timestamptz_date(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_DATEADT(timestamptz_date_opt_error(PG_GETARG_TIMESTAMP(0), NULL));
}
@@ -1363,15 +1429,9 @@ timestamptz_date(PG_FUNCTION_ARGS)
* Time ADT
*****************************************************************************/
-Datum
-time_in(PG_FUNCTION_ARGS)
+TimeADT
+time_in_opt_error(char *str, int32 typmod, bool *error)
{
- char *str = PG_GETARG_CSTRING(0);
-
-#ifdef NOT_USED
- Oid typelem = PG_GETARG_OID(1);
-#endif
- int32 typmod = PG_GETARG_INT32(2);
TimeADT result;
fsec_t fsec;
struct pg_tm tt,
@@ -1387,14 +1447,35 @@ time_in(PG_FUNCTION_ARGS)
dterr = ParseDateTime(str, workbuf, sizeof(workbuf),
field, ftype, MAXDATEFIELDS, &nf);
if (dterr == 0)
- dterr = DecodeTimeOnly(field, ftype, nf, &dtype, tm, &fsec, &tz);
+ dterr = DecodeTimeOnly(field, ftype, nf, &dtype, tm, &fsec, &tz,
+ !error);
if (dterr != 0)
+ {
+ if (error)
+ {
+ *error = true;
+ return 0;
+ }
+
DateTimeParseError(dterr, str, "time");
+ }
tm2time(tm, fsec, &result);
AdjustTimeForTypmod(&result, typmod);
- PG_RETURN_TIMEADT(result);
+ return result;
+}
+
+Datum
+time_in(PG_FUNCTION_ARGS)
+{
+ char *str = PG_GETARG_CSTRING(0);
+#ifdef NOT_USED
+ Oid typelem = PG_GETARG_OID(1);
+#endif
+ int32 typmod = PG_GETARG_INT32(2);
+
+ PG_RETURN_TIMEADT(time_in_opt_error(str, typmod, NULL));
}
/* tm2time()
@@ -1889,22 +1970,32 @@ overlaps_time(PG_FUNCTION_ARGS)
/* timestamp_time()
* Convert timestamp to time data type.
*/
-Datum
-timestamp_time(PG_FUNCTION_ARGS)
+TimeADT
+timestamp_time_opt_error(Timestamp timestamp, bool *isnull, bool *error)
{
- Timestamp timestamp = PG_GETARG_TIMESTAMP(0);
TimeADT result;
struct pg_tm tt,
*tm = &tt;
fsec_t fsec;
if (TIMESTAMP_NOT_FINITE(timestamp))
- PG_RETURN_NULL();
+ {
+ *isnull = true;
+ return 0;
+ }
if (timestamp2tm(timestamp, NULL, tm, &fsec, NULL, NULL) != 0)
+ {
+ if (error)
+ {
+ *error = *isnull = true;
+ return (Datum) 0;
+ }
+
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("timestamp out of range")));
+ }
/*
* Could also do this with time = (timestamp / USECS_PER_DAY *
@@ -1912,17 +2003,33 @@ timestamp_time(PG_FUNCTION_ARGS)
*/
result = ((((tm->tm_hour * MINS_PER_HOUR + tm->tm_min) * SECS_PER_MINUTE) + tm->tm_sec) *
USECS_PER_SEC) + fsec;
+ *isnull = false;
- PG_RETURN_TIMEADT(result);
+ return result;
}
-/* timestamptz_time()
- * Convert timestamptz to time data type.
+/* timestamp_time()
+ * Convert timestamp to time data type.
*/
Datum
-timestamptz_time(PG_FUNCTION_ARGS)
+timestamp_time(PG_FUNCTION_ARGS)
+{
+ bool isnull;
+ TimeADT time =
+ timestamp_time_opt_error(PG_GETARG_TIMESTAMP(0), &isnull, NULL);
+
+ if (isnull)
+ PG_RETURN_NULL();
+ else
+ PG_RETURN_TIMEADT(time);
+}
+
+/* timestamptz_time_opt_error()
+ * Convert timestamptz to time data type.
+ */
+TimeADT
+timestamptz_time_opt_error(TimestampTz timestamp, bool *isnull, bool *error)
{
- TimestampTz timestamp = PG_GETARG_TIMESTAMP(0);
TimeADT result;
struct pg_tm tt,
*tm = &tt;
@@ -1930,12 +2037,23 @@ timestamptz_time(PG_FUNCTION_ARGS)
fsec_t fsec;
if (TIMESTAMP_NOT_FINITE(timestamp))
- PG_RETURN_NULL();
+ {
+ *isnull = true;
+ return 0;
+ }
if (timestamp2tm(timestamp, &tz, tm, &fsec, NULL, NULL) != 0)
+ {
+ if (error)
+ {
+ *error = *isnull = true;
+ return 0;
+ }
+
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("timestamp out of range")));
+ }
/*
* Could also do this with time = (timestamp / USECS_PER_DAY *
@@ -1943,8 +2061,25 @@ timestamptz_time(PG_FUNCTION_ARGS)
*/
result = ((((tm->tm_hour * MINS_PER_HOUR + tm->tm_min) * SECS_PER_MINUTE) + tm->tm_sec) *
USECS_PER_SEC) + fsec;
+ *isnull = false;
- PG_RETURN_TIMEADT(result);
+ return result;
+}
+
+/* timestamptz_time()
+ * Convert timestamptz to time data type.
+ */
+Datum
+timestamptz_time(PG_FUNCTION_ARGS)
+{
+ bool isnull;
+ TimeADT result =
+ timestamptz_time_opt_error(PG_GETARG_TIMESTAMP(0), &isnull, NULL);
+
+ if (isnull)
+ PG_RETURN_NULL();
+ else
+ PG_RETURN_TIMEADT(result);
}
/* datetime_timestamp()
@@ -2249,15 +2384,9 @@ tm2timetz(struct pg_tm *tm, fsec_t fsec, int tz, TimeTzADT *result)
return 0;
}
-Datum
-timetz_in(PG_FUNCTION_ARGS)
+TimeTzADT *
+timetz_in_opt_error(char *str, int32 typmod, bool *error)
{
- char *str = PG_GETARG_CSTRING(0);
-
-#ifdef NOT_USED
- Oid typelem = PG_GETARG_OID(1);
-#endif
- int32 typmod = PG_GETARG_INT32(2);
TimeTzADT *result;
fsec_t fsec;
struct pg_tm tt,
@@ -2273,17 +2402,38 @@ timetz_in(PG_FUNCTION_ARGS)
dterr = ParseDateTime(str, workbuf, sizeof(workbuf),
field, ftype, MAXDATEFIELDS, &nf);
if (dterr == 0)
- dterr = DecodeTimeOnly(field, ftype, nf, &dtype, tm, &fsec, &tz);
+ dterr = DecodeTimeOnly(field, ftype, nf, &dtype, tm, &fsec, &tz,
+ !error);
if (dterr != 0)
+ {
+ if (error)
+ {
+ *error = true;
+ return NULL;
+ }
+
DateTimeParseError(dterr, str, "time with time zone");
+ }
result = (TimeTzADT *) palloc(sizeof(TimeTzADT));
tm2timetz(tm, fsec, tz, result);
AdjustTimeForTypmod(&(result->time), typmod);
- PG_RETURN_TIMETZADT_P(result);
+ return result;
}
+Datum
+timetz_in(PG_FUNCTION_ARGS)
+{
+ char *str = PG_GETARG_CSTRING(0);
+
+#ifdef NOT_USED
+ Oid typelem = PG_GETARG_OID(1);
+#endif
+ int32 typmod = PG_GETARG_INT32(2);
+
+ PG_RETURN_TIMETZADT_P(timetz_in_opt_error(str, typmod, NULL));
+}
Datum
timetz_out(PG_FUNCTION_ARGS)
{
@@ -2812,10 +2962,9 @@ time_timetz(PG_FUNCTION_ARGS)
/* timestamptz_timetz()
* Convert timestamp to timetz data type.
*/
-Datum
-timestamptz_timetz(PG_FUNCTION_ARGS)
+TimeTzADT *
+timestamptz_timetz_opt_error(TimestampTz timestamp, bool *error)
{
- TimestampTz timestamp = PG_GETARG_TIMESTAMP(0);
TimeTzADT *result;
struct pg_tm tt,
*tm = &tt;
@@ -2823,20 +2972,42 @@ timestamptz_timetz(PG_FUNCTION_ARGS)
fsec_t fsec;
if (TIMESTAMP_NOT_FINITE(timestamp))
- PG_RETURN_NULL();
+ return NULL;
if (timestamp2tm(timestamp, &tz, tm, &fsec, NULL, NULL) != 0)
+ {
+ if (error)
+ {
+ *error = true;
+ return NULL;
+ }
+
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("timestamp out of range")));
+ }
result = (TimeTzADT *) palloc(sizeof(TimeTzADT));
tm2timetz(tm, fsec, tz, result);
- PG_RETURN_TIMETZADT_P(result);
+ return result;
}
+/* timestamptz_timetz()
+ * Convert timestamp to timetz data type.
+ */
+Datum
+timestamptz_timetz(PG_FUNCTION_ARGS)
+{
+ TimeTzADT *result =
+ timestamptz_timetz_opt_error(PG_GETARG_TIMESTAMP(0), NULL);
+
+ if (result)
+ PG_RETURN_TIMETZADT_P(result);
+ else
+ PG_RETURN_NULL();
+}
/* datetimetz_timestamptz()
* Convert date and timetz to timestamp with time zone data type.
diff --git a/src/backend/utils/adt/datetime.c b/src/backend/utils/adt/datetime.c
index 43fff50d490..3275e5fd948 100644
--- a/src/backend/utils/adt/datetime.c
+++ b/src/backend/utils/adt/datetime.c
@@ -971,7 +971,8 @@ ParseDateTime(const char *timestr, char *workbuf, size_t buflen,
*/
int
DecodeDateTime(char **field, int *ftype, int nf,
- int *dtype, struct pg_tm *tm, fsec_t *fsec, int *tzp)
+ int *dtype, struct pg_tm *tm, fsec_t *fsec, int *tzp,
+ bool throw_errors)
{
int fmask = 0,
tmask,
@@ -1114,8 +1115,13 @@ DecodeDateTime(char **field, int *ftype, int nf,
/*
* We should return an error code instead of
* ereport'ing directly, but then there is no way
- * to report the bad time zone name.
+ * to report the bad time zone name. But
+ * throwing errors is disallowed, simply
+ * return error code.
*/
+ if (!throw_errors)
+ return DTERR_BAD_FORMAT;
+
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("time zone \"%s\" not recognized",
@@ -1921,7 +1927,8 @@ DetermineTimeZoneAbbrevOffsetInternal(pg_time_t t, const char *abbr, pg_tz *tzp,
*/
int
DecodeTimeOnly(char **field, int *ftype, int nf,
- int *dtype, struct pg_tm *tm, fsec_t *fsec, int *tzp)
+ int *dtype, struct pg_tm *tm, fsec_t *fsec, int *tzp,
+ bool throw_errors)
{
int fmask = 0,
tmask,
@@ -2022,6 +2029,9 @@ DecodeTimeOnly(char **field, int *ftype, int nf,
* ereport'ing directly, but then there is no way
* to report the bad time zone name.
*/
+ if (!throw_errors)
+ return DTERR_BAD_FORMAT;
+
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("time zone \"%s\" not recognized",
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 49cdb290ac2..04a0e3eb063 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -139,18 +139,12 @@ anytimestamp_typmodout(bool istz, int32 typmod)
* USER I/O ROUTINES *
*****************************************************************************/
-/* timestamp_in()
- * Convert a string to internal form.
+/* timestamp_in_opt_error()
+ * Convert a string to internal form, returning error instead of throwing if needed.
*/
-Datum
-timestamp_in(PG_FUNCTION_ARGS)
+Timestamp
+timestamp_in_opt_error(char *str, int32 typmod, bool *error)
{
- char *str = PG_GETARG_CSTRING(0);
-
-#ifdef NOT_USED
- Oid typelem = PG_GETARG_OID(1);
-#endif
- int32 typmod = PG_GETARG_INT32(2);
Timestamp result;
fsec_t fsec;
struct pg_tm tt,
@@ -166,17 +160,34 @@ timestamp_in(PG_FUNCTION_ARGS)
dterr = ParseDateTime(str, workbuf, sizeof(workbuf),
field, ftype, MAXDATEFIELDS, &nf);
if (dterr == 0)
- dterr = DecodeDateTime(field, ftype, nf, &dtype, tm, &fsec, &tz);
+ dterr = DecodeDateTime(field, ftype, nf, &dtype, tm, &fsec, &tz,
+ !error);
if (dterr != 0)
+ {
+ if (error)
+ {
+ *error = true;
+ return 0;
+ }
+
DateTimeParseError(dterr, str, "timestamp");
+ }
switch (dtype)
{
case DTK_DATE:
if (tm2timestamp(tm, fsec, NULL, &result) != 0)
+ {
+ if (error)
+ {
+ *error = true;
+ return 0;
+ }
+
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("timestamp out of range: \"%s\"", str)));
+ }
break;
case DTK_EPOCH:
@@ -197,11 +208,27 @@ timestamp_in(PG_FUNCTION_ARGS)
TIMESTAMP_NOEND(result);
}
- AdjustTimestampForTypmod(&result, typmod);
+ AdjustTimestampForTypmodError(&result, typmod, error);
PG_RETURN_TIMESTAMP(result);
}
+/* timestamp_in()
+ * Convert a string to internal form.
+ */
+Datum
+timestamp_in(PG_FUNCTION_ARGS)
+{
+ char *str = PG_GETARG_CSTRING(0);
+
+#ifdef NOT_USED
+ Oid typelem = PG_GETARG_OID(1);
+#endif
+ int32 typmod = PG_GETARG_INT32(2);
+
+ PG_RETURN_TIMESTAMP(timestamp_in_opt_error(str, typmod, NULL));
+}
+
/* timestamp_out()
* Convert a timestamp to external form.
*/
@@ -400,15 +427,9 @@ AdjustTimestampForTypmod(Timestamp *time, int32 typmod)
/* timestamptz_in()
* Convert a string to internal form.
*/
-Datum
-timestamptz_in(PG_FUNCTION_ARGS)
+TimestampTz
+timestamptz_in_opt_error(char *str, int32 typmod, bool *error)
{
- char *str = PG_GETARG_CSTRING(0);
-
-#ifdef NOT_USED
- Oid typelem = PG_GETARG_OID(1);
-#endif
- int32 typmod = PG_GETARG_INT32(2);
TimestampTz result;
fsec_t fsec;
struct pg_tm tt,
@@ -424,17 +445,34 @@ timestamptz_in(PG_FUNCTION_ARGS)
dterr = ParseDateTime(str, workbuf, sizeof(workbuf),
field, ftype, MAXDATEFIELDS, &nf);
if (dterr == 0)
- dterr = DecodeDateTime(field, ftype, nf, &dtype, tm, &fsec, &tz);
+ dterr = DecodeDateTime(field, ftype, nf, &dtype, tm, &fsec, &tz,
+ !error);
if (dterr != 0)
+ {
+ if (error)
+ {
+ *error = true;
+ return 0;
+ }
+
DateTimeParseError(dterr, str, "timestamp with time zone");
+ }
switch (dtype)
{
case DTK_DATE:
if (tm2timestamp(tm, fsec, &tz, &result) != 0)
+ {
+ if (error)
+ {
+ *error = true;
+ return 0;
+ }
+
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("timestamp out of range: \"%s\"", str)));
+ }
break;
case DTK_EPOCH:
@@ -455,9 +493,24 @@ timestamptz_in(PG_FUNCTION_ARGS)
TIMESTAMP_NOEND(result);
}
- AdjustTimestampForTypmod(&result, typmod);
+ AdjustTimestampForTypmodError(&result, typmod, error);
- PG_RETURN_TIMESTAMPTZ(result);
+ return result;
+}
+
+/* timestamptz_in()
+ * Convert a string to internal form.
+ */
+Datum
+timestamptz_in(PG_FUNCTION_ARGS)
+{
+ char *str = PG_GETARG_CSTRING(0);
+#ifdef NOT_USED
+ Oid typelem = PG_GETARG_OID(1);
+#endif
+ int32 typmod = PG_GETARG_INT32(2);
+
+ PG_RETURN_TIMESTAMPTZ(timestamptz_in_opt_error(str, typmod, NULL));
}
/*
@@ -5614,8 +5667,8 @@ timestamptz_timestamp(PG_FUNCTION_ARGS)
PG_RETURN_TIMESTAMP(timestamptz2timestamp(timestamp));
}
-static Timestamp
-timestamptz2timestamp(TimestampTz timestamp)
+Timestamp
+timestamptz2timestamp_opt_error(TimestampTz timestamp, bool *error)
{
Timestamp result;
struct pg_tm tt,
@@ -5628,17 +5681,40 @@ timestamptz2timestamp(TimestampTz timestamp)
else
{
if (timestamp2tm(timestamp, &tz, tm, &fsec, NULL, NULL) != 0)
+ {
+ if (error)
+ {
+ *error = true;
+ return 0;
+ }
+
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("timestamp out of range")));
+ }
+
if (tm2timestamp(tm, fsec, NULL, &result) != 0)
+ {
+ if (error)
+ {
+ *error = true;
+ return 0;
+ }
+
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("timestamp out of range")));
+ }
}
return result;
}
+static Timestamp
+timestamptz2timestamp(TimestampTz timestamp)
+{
+ return timestamptz2timestamp_opt_error(timestamp, NULL);
+}
+
/* timestamptz_zone()
* Evaluate timestamp with time zone type at the specified time zone.
* Returns a timestamp without time zone.
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 9fbbfb1be54..7877bce6043 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -12866,7 +12866,7 @@ check_recovery_target_time(char **newval, void **extra, GucSource source)
dterr = ParseDateTime(str, workbuf, sizeof(workbuf),
field, ftype, MAXDATEFIELDS, &nf);
if (dterr == 0)
- dterr = DecodeDateTime(field, ftype, nf, &dtype, tm, &fsec, &tz);
+ dterr = DecodeDateTime(field, ftype, nf, &dtype, tm, &fsec, &tz, true);
if (dterr != 0)
return false;
if (dtype != DTK_DATE)
diff --git a/src/include/utils/date.h b/src/include/utils/date.h
index 91ae24254df..f29168a025b 100644
--- a/src/include/utils/date.h
+++ b/src/include/utils/date.h
@@ -87,4 +87,17 @@ extern bool time_overflows(int hour, int min, int sec, fsec_t fsec);
extern bool float_time_overflows(int hour, int min, double sec);
extern void AdjustTimeForTypmod(TimeADT *time, int32 typmod);
+extern DateADT timestamp_date_opt_error(Timestamp timestamp, bool *error);
+extern TimeADT timestamp_time_opt_error(Timestamp timestamp,
+ bool *isnull, bool *error);
+
+extern DateADT timestamptz_date_opt_error(Timestamp timestamp, bool *error);
+extern TimeADT timestamptz_time_opt_error(TimestampTz timestamp,
+ bool *isnull, bool *error);
+extern TimeTzADT *timestamptz_timetz_opt_error(TimestampTz timestamp,
+ bool *error);
+extern DateADT date_in_opt_error(char *str, bool *error);
+extern TimeADT time_in_opt_error(char *str, int32 typmod, bool *error);
+extern TimeTzADT *timetz_in_opt_error(char *str, int32 typmod, bool *error);
+
#endif /* DATE_H */
diff --git a/src/include/utils/datetime.h b/src/include/utils/datetime.h
index 4527e825177..68241d7c4cc 100644
--- a/src/include/utils/datetime.h
+++ b/src/include/utils/datetime.h
@@ -295,11 +295,13 @@ extern int ParseDateTime(const char *timestr, char *workbuf, size_t buflen,
int maxfields, int *numfields);
extern int DecodeDateTime(char **field, int *ftype,
int nf, int *dtype,
- struct pg_tm *tm, fsec_t *fsec, int *tzp);
+ struct pg_tm *tm, fsec_t *fsec, int *tzp,
+ bool throw_errors);
extern int DecodeTimezone(char *str, int *tzp);
extern int DecodeTimeOnly(char **field, int *ftype,
int nf, int *dtype,
- struct pg_tm *tm, fsec_t *fsec, int *tzp);
+ struct pg_tm *tm, fsec_t *fsec, int *tzp,
+ bool throw_errors);
extern int DecodeInterval(char **field, int *ftype, int nf, int range,
int *dtype, struct pg_itm_in *itm_in);
extern int DecodeISO8601Interval(char *str,
diff --git a/src/include/utils/timestamp.h b/src/include/utils/timestamp.h
index edf3a973186..2685d14342d 100644
--- a/src/include/utils/timestamp.h
+++ b/src/include/utils/timestamp.h
@@ -102,8 +102,14 @@ extern int timestamp_cmp_internal(Timestamp dt1, Timestamp dt2);
extern TimestampTz timestamp2timestamptz_opt_overflow(Timestamp timestamp,
int *overflow);
+extern Timestamp timestamptz2timestamp_opt_error(TimestampTz timestamp,
+ bool *error);
extern int32 timestamp_cmp_timestamptz_internal(Timestamp timestampVal,
TimestampTz dt2);
+extern Timestamp timestamp_in_opt_error(char *str, int32 typmod,
+ bool *error);
+extern TimestampTz timestamptz_in_opt_error(char *str, int32 typmod,
+ bool *error);
extern int isoweek2j(int year, int week);
extern void isoweek2date(int woy, int *year, int *mon, int *mday);
--
2.17.1
v9-0006-Add-safe-input-function-for-jsonb.patchtext/x-patch; charset=UTF-8; name=v9-0006-Add-safe-input-function-for-jsonb.patchDownload
From 62a27e0b243cebc5e3eb8628cbb222533a02338f Mon Sep 17 00:00:00 2001
From: Nikita Glukhov <n.gluhov@postgrespro.ru>
Date: Mon, 29 Aug 2022 23:56:45 +0300
Subject: [PATCH v9 6/9] Add safe input function for jsonb
---
src/backend/utils/adt/jsonb.c | 26 ++++++++++++++++++--------
src/include/utils/jsonb.h | 2 ++
2 files changed, 20 insertions(+), 8 deletions(-)
diff --git a/src/backend/utils/adt/jsonb.c b/src/backend/utils/adt/jsonb.c
index f700c5b4c93..1da519daaef 100644
--- a/src/backend/utils/adt/jsonb.c
+++ b/src/backend/utils/adt/jsonb.c
@@ -46,7 +46,6 @@ typedef struct JsonbAggState
Oid val_output_func;
} JsonbAggState;
-static inline Datum jsonb_from_cstring(char *json, int len, bool unique_keys);
static size_t checkStringLen(size_t len);
static void jsonb_in_object_start(void *pstate);
static void jsonb_in_object_end(void *pstate);
@@ -77,7 +76,7 @@ jsonb_in(PG_FUNCTION_ARGS)
{
char *json = PG_GETARG_CSTRING(0);
- return jsonb_from_cstring(json, strlen(json), false);
+ PG_RETURN_DATUM(jsonb_from_cstring(json, strlen(json), false, NULL));
}
/*
@@ -101,7 +100,7 @@ jsonb_recv(PG_FUNCTION_ARGS)
else
elog(ERROR, "unsupported jsonb version number %d", version);
- return jsonb_from_cstring(str, nbytes, false);
+ return jsonb_from_cstring(str, nbytes, false, NULL);
}
/*
@@ -147,7 +146,7 @@ jsonb_from_text(text *js, bool unique_keys)
{
return jsonb_from_cstring(VARDATA_ANY(js),
VARSIZE_ANY_EXHDR(js),
- unique_keys);
+ unique_keys, NULL);
}
/*
@@ -239,12 +238,13 @@ jsonb_typeof(PG_FUNCTION_ARGS)
*
* Uses the json parser (with hooks) to construct a jsonb.
*/
-static inline Datum
-jsonb_from_cstring(char *json, int len, bool unique_keys)
+Datum
+jsonb_from_cstring(char *json, int len, bool unique_keys, bool *error)
{
JsonLexContext *lex;
JsonbInState state;
JsonSemAction sem;
+ JsonParseErrorType result;
memset(&state, 0, sizeof(state));
memset(&sem, 0, sizeof(sem));
@@ -261,10 +261,20 @@ jsonb_from_cstring(char *json, int len, bool unique_keys)
sem.scalar = jsonb_in_scalar;
sem.object_field_start = jsonb_in_object_field_start;
- pg_parse_json_or_ereport(lex, &sem);
+ result = pg_parse_json(lex, &sem);
+ if (result != JSON_SUCCESS)
+ {
+ if (error)
+ {
+ *error = true;
+ return (Datum) 0;
+ }
+
+ json_ereport_error(result, lex);
+ }
/* after parsing, the item member has the composed jsonb structure */
- PG_RETURN_POINTER(JsonbValueToJsonb(state.res));
+ return JsonbPGetDatum(JsonbValueToJsonb(state.res));
}
static size_t
diff --git a/src/include/utils/jsonb.h b/src/include/utils/jsonb.h
index bae466b5234..d5574faa79e 100644
--- a/src/include/utils/jsonb.h
+++ b/src/include/utils/jsonb.h
@@ -420,6 +420,8 @@ extern void JsonbHashScalarValueExtended(const JsonbValue *scalarVal,
/* jsonb.c support functions */
extern Datum jsonb_from_text(text *js, bool unique_keys);
+extern Datum jsonb_from_cstring(char *json, int len, bool unique_keys,
+ bool *error);
extern char *JsonbToCString(StringInfo out, JsonbContainer *in,
int estimated_len);
extern char *JsonbToCStringIndent(StringInfo out, JsonbContainer *in,
--
2.17.1
v9-0007-Remove-subtransaction-in-SQL-JSON-execution.patchtext/x-patch; charset=UTF-8; name=v9-0007-Remove-subtransaction-in-SQL-JSON-execution.patchDownload
From 9154ff35f22fe7e8ce65ffdad757ff94abd79440 Mon Sep 17 00:00:00 2001
From: Nikita Glukhov <n.gluhov@postgrespro.ru>
Date: Tue, 30 Aug 2022 00:10:44 +0300
Subject: [PATCH v9 7/9] Remove subtransaction in SQL/JSON execution
---
src/backend/executor/execExpr.c | 180 ++--
src/backend/executor/execExprInterp.c | 946 ++++++++++++--------
src/backend/jit/llvm/llvmjit_expr.c | 2 +-
src/backend/jit/llvm/llvmjit_types.c | 2 +-
src/backend/nodes/nodeFuncs.c | 91 +-
src/backend/optimizer/util/clauses.c | 73 +-
src/backend/parser/parse_expr.c | 226 ++---
src/backend/utils/adt/jsonpath.c | 8 +-
src/include/executor/execExpr.h | 49 +-
src/include/nodes/primnodes.h | 36 +-
src/include/optimizer/clauses.h | 2 +
src/include/utils/jsonpath.h | 3 +-
src/test/regress/expected/jsonb_sqljson.out | 327 +++++--
src/test/regress/sql/jsonb_sqljson.sql | 72 +-
14 files changed, 1131 insertions(+), 886 deletions(-)
diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
index d0a57c7aaee..f50e0d703f8 100644
--- a/src/backend/executor/execExpr.c
+++ b/src/backend/executor/execExpr.c
@@ -86,7 +86,9 @@ static void ExecBuildAggTransCall(ExprState *state, AggState *aggstate,
FunctionCallInfo fcinfo, AggStatePerTrans pertrans,
int transno, int setno, int setoff, bool ishash,
bool nullcheck);
-
+static void ExecInitJsonExpr(JsonExpr *jexpr, ExprState *state,
+ ExprEvalStep *scratch,
+ Datum *resv, bool *resnull);
static ExprState *
ExecInitExprInternal(Expr *node, PlanState *parent, ParamListInfo ext_params,
@@ -2559,114 +2561,9 @@ 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;
-
- if (jexpr->coercions)
- {
- JsonCoercion **coercion;
- struct JsonCoercionState *cstate;
- Datum *caseval;
- bool *casenull;
-
- 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;
- }
- }
-
- ExprEvalPushStep(state, &scratch);
- break;
- }
+ ExecInitJsonExpr(castNode(JsonExpr, node), state, &scratch,
+ resv, resnull);
+ break;
default:
elog(ERROR, "unrecognized node type: %d",
@@ -4257,3 +4154,68 @@ ExecBuildParamSetEqual(TupleDesc desc,
return state;
}
+
+static void
+ExecInitJsonExpr(JsonExpr *jexpr, ExprState *state, ExprEvalStep *scratch,
+ Datum *resv, bool *resnull)
+{
+ JsonExprState *jsestate;
+ ListCell *argexprlc;
+ ListCell *argnamelc;
+
+ /* JSON_TABLE preudo-function returns context item as a result */
+ if (jexpr->op == JSON_TABLE_OP)
+ {
+ ExecInitExprRec((Expr *) jexpr->formatted_expr, state,
+ resv, resnull);
+ return;
+ }
+
+ jsestate = palloc0(sizeof(JsonExprState));
+ jsestate->jsexpr = jexpr;
+
+ scratch->opcode = EEOP_JSONEXPR;
+ scratch->d.jsonexpr.jsestate = jsestate;
+
+ ExecInitExprRec((Expr *) jexpr->formatted_expr, state,
+ &jsestate->formatted_expr.value,
+ &jsestate->formatted_expr.isnull);
+
+ ExecInitExprRec((Expr *) jexpr->path_spec, state,
+ &jsestate->pathspec.value,
+ &jsestate->pathspec.isnull);
+
+ if (jexpr->on_empty)
+ jsestate->default_on_empty =
+ ExecInitExpr((Expr *) jexpr->on_empty->default_expr,
+ state->parent);
+
+ jsestate->default_on_error =
+ ExecInitExpr((Expr *) jexpr->on_error->default_expr,
+ state->parent);
+
+ 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);
+ }
+
+ ExprEvalPushStep(state, scratch);
+}
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index 636794ca6f1..45c648817ba 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -75,6 +75,7 @@
#include "utils/date.h"
#include "utils/datum.h"
#include "utils/expandedrecord.h"
+#include "utils/float.h"
#include "utils/json.h"
#include "utils/jsonb.h"
#include "utils/jsonfuncs.h"
@@ -1844,7 +1845,7 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
EEO_CASE(EEOP_JSONEXPR)
{
/* too complex for an inline implementation */
- ExecEvalJson(state, op, econtext);
+ ExecEvalJsonExpr(state, op, econtext);
EEO_NEXT();
}
@@ -4700,96 +4701,6 @@ ExecEvalJsonConstructor(ExprState *state, ExprEvalStep *op,
*op->resnull = isnull;
}
-/*
- * Evaluate a JSON error/empty behavior result.
- */
-static Datum
-ExecEvalJsonBehavior(ExprContext *econtext, JsonBehavior *behavior,
- ExprState *default_estate, 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:
- 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.
*/
@@ -4844,84 +4755,438 @@ EvalJsonPathVar(void *cxt, char *varName, int varNameLen,
}
/*
- * Prepare SQL/JSON item coercion to the output type. Returned a datum of the
- * corresponding SQL type and a pointer to the coercion state.
+ * Check whether we need to override default coercion in
+ * JSON_QUERY(OMIT QUOTES) case.
*/
-Datum
-ExecPrepareJsonItemCoercion(JsonbValue *item,
- JsonReturning *returning,
- struct JsonCoercionsState *coercions,
- struct JsonCoercionState **pcoercion)
+static bool
+ExecJsonQueryNeedsIOCoercion(JsonExpr *jsexpr, Datum res, bool isnull)
{
- struct JsonCoercionState *coercion;
- Datum res;
- JsonbValue buf;
+ if (jsexpr->omit_quotes && !isnull)
+ {
+ Jsonb *jb = DatumGetJsonbP(res);
+ JsonbValue jbv;
+
+ /* Coerce string items via I/O in OMIT QUOTES case */
+ return JsonbExtractScalar(&jb->root, &jbv) &&
+ jbv.type == jbvString;
+ }
+
+ return false;
+}
- if (item->type == jbvBinary &&
- JsonContainerIsScalar(item->val.binary.data))
+/* Coerce C string to text, varchar(N), or bpchar(N) */
+static Datum
+ExecJsonStringCoercion(const char *str, int32 len, Oid typid, int32 typmod)
+{
+ if (typid == BYTEAOID)
{
- bool res PG_USED_FOR_ASSERTS_ONLY;
+ text *txt = cstring_to_text_with_len(str, len);
+ NameData encoding = {0};
+
+ /* UTF8 is the only one supported encoding */
+ strcpy(NameStr(encoding), "UTF8");
- res = JsonbExtractScalar(item->val.binary.data, &buf);
- item = &buf;
- Assert(res);
+ return DirectFunctionCall2(pg_convert_to,
+ PointerGetDatum(txt),
+ PointerGetDatum(&encoding));
}
+ else if (typid == TEXTOID || typid == JSONOID || typmod <= VARHDRSZ)
+ return PointerGetDatum(cstring_to_text_with_len(str, len));
+ else
+ {
+ char *txt;
+ int32 size;
+
+ len = VARHDRSZ + len;
+
+ if (typid == BPCHAROID)
+ size = typmod;
+ else
+ size = Min(len, typmod);
+
+ txt = palloc(size);
+ SET_VARSIZE(txt, size);
+
+ memcpy(VARDATA(txt), str, Min(size, len) - VARHDRSZ);
+
+ if (len < size)
+ memset(txt + len, ' ', size - len);
- /* get coercion state reference and datum of the corresponding SQL type */
+ return PointerGetDatum(txt);
+ }
+}
+
+/* Coerce SQL/JSON item to text */
+static Datum
+ExecJsonCoercionToText(PGFunction outfunc, Datum value, Oid typid, int32 typmod)
+{
+ char *str = DatumGetCString(DirectFunctionCall1(outfunc, value));
+
+ return ExecJsonStringCoercion(str, strlen(str), typid, typmod);
+}
+
+/* Coerce datetime SQL/JSON item to the output typid */
+static Datum
+ExecJsonDatetimeCoercion(Datum val, Oid val_typid, Oid typid, int32 typmod,
+ bool *isnull, bool *error)
+{
+ if (val_typid == typid)
+ return val;
+
+ /*
+ * XXX coercion to text is done using output functions, and they
+ * are mutable for non-time[tz] types due to using of DateStyle.
+ * We can pass USE_ISO_DATES, which is used inside jsonpath, to
+ * make these coercions and JSON_VALUE(RETURNING text) immutable.
+ *
+ * XXX Also timestamp[tz] output functions can throw "out of range"
+ * error, but this error seem to be not possible.
+ */
+ switch (val_typid)
+ {
+ case DATEOID:
+ if (typid == TEXTOID || typid == VARCHAROID || typid == BPCHAROID)
+ return ExecJsonCoercionToText(date_out, val, typid, typmod);
+ else if (typid == DATEOID)
+ return val;
+ else if (typid == TIMESTAMPOID)
+ {
+ int overflow = 0;
+ Timestamp ts =
+ date2timestamp_opt_overflow(DatumGetDateADT(val),
+ error ? &overflow : NULL);
+
+ if (overflow)
+ {
+ *error = true;
+ return (Datum) 0;
+ }
+
+ return TimestampGetDatum(ts);
+ }
+ else if (typid == TIMESTAMPTZOID)
+ {
+ int overflow = 0;
+ TimestampTz res =
+ date2timestamptz_opt_overflow(DatumGetDateADT(val),
+ error ? &overflow : NULL);
+
+ if (overflow)
+ {
+ *error = true;
+ return (Datum) 0;
+ }
+
+ return TimestampTzGetDatum(res);
+ }
+ else
+ break; /* No cast */
+
+ case TIMEOID:
+ if (typid == TEXTOID || typid == VARCHAROID || typid == BPCHAROID)
+ return ExecJsonCoercionToText(time_out, val, typid, typmod);
+ else if (typid == TIMEOID)
+ return val;
+ else if (typid == TIMETZOID)
+ return DirectFunctionCall1(time_timetz, val);
+ else
+ break; /* No cast */
+
+ case TIMETZOID:
+ if (typid == TEXTOID || typid == VARCHAROID || typid == BPCHAROID)
+ return ExecJsonCoercionToText(timetz_out, val, typid, typmod);
+ else if (typid == TIMETZOID)
+ return val;
+ else if (typid == TIMEOID)
+ return DirectFunctionCall1(timetz_time, val);
+ else
+ break;
+
+ case TIMESTAMPOID:
+ if (typid == TEXTOID || typid == VARCHAROID || typid == BPCHAROID)
+ return ExecJsonCoercionToText(timestamp_out, val, typid, typmod);
+ else if (typid == TIMESTAMPOID)
+ return val;
+ else if (typid == DATEOID)
+ return timestamp_date_opt_error(DatumGetTimestamp(val), error);
+ else if (typid == TIMEOID)
+ {
+ TimeADT time =
+ timestamp_time_opt_error(DatumGetTimestamp(val),
+ isnull, error);
+
+ if ((error && *error) || *isnull)
+ return (Datum) 0;
+ else
+ return TimeADTGetDatum(time);
+ }
+ else if (typid == TIMESTAMPTZOID)
+ {
+ int overflow = 0;
+ TimestampTz tstz =
+ timestamp2timestamptz_opt_overflow(DatumGetTimestamp(val),
+ error ? &overflow : NULL);
+
+ if (overflow)
+ {
+ *error = true;
+ return (Datum) 0;
+ }
+
+ return TimestampTzGetDatum(tstz);
+ }
+ else
+ break; /* No cast */
+
+ case TIMESTAMPTZOID:
+ if (typid == TEXTOID || typid == VARCHAROID || typid == BPCHAROID)
+ return ExecJsonCoercionToText(timestamptz_out, val, typid, typmod);
+ else if (typid == TIMESTAMPTZOID)
+ return val;
+ else if (typid == DATEOID)
+ return timestamptz_date_opt_error(DatumGetTimestampTz(val), error);
+ else if (typid == TIMEOID)
+ {
+ TimeADT time =
+ timestamptz_time_opt_error(DatumGetTimestampTz(val),
+ isnull, error);
+
+ if ((error && *error) || *isnull)
+ return (Datum) 0;
+ else
+ return TimeADTGetDatum(time);
+ }
+ else if (typid == TIMETZOID)
+ {
+ TimeTzADT *result =
+ timestamptz_timetz_opt_error(DatumGetTimestampTz(val), error);
+
+ if ((error && *error) || !result)
+ {
+ *isnull = true;
+ return (Datum) 0;
+ }
+
+ return TimeTzADTPGetDatum(result);
+ }
+ else if (typid == TIMESTAMPOID)
+ {
+ Timestamp ts =
+ timestamptz2timestamp_opt_error(DatumGetTimestampTz(val),
+ error);
+
+ if (error && *error)
+ return (Datum) 0;
+
+ return TimestampGetDatum(ts);
+ }
+ else
+ break; /* No cast */
+
+ default:
+ elog(ERROR, "unexpected jsonb datetime type oid %u", val_typid);
+ break;
+ }
+
+ if (!error)
+ ereport(ERROR,
+ (errcode(ERRCODE_SQL_JSON_ITEM_CANNOT_BE_CAST_TO_TARGET_TYPE),
+ errmsg("SQL/JSON item cannot be cast to target type")));
+
+ *error = true;
+ *isnull = true;
+
+ return (Datum) 0;
+}
+
+/* Coerce boolean SQL/JSON item or JSON_EXISTS result to the output type */
+static bool
+ExecJsonBoolCoercion(bool val, Oid typid, int32 typmod, Datum *res)
+{
+ if (typid == BOOLOID)
+ *res = BoolGetDatum(val);
+ else if (typid == INT4OID)
+ /* We only have cast bool::int4 in the catalog. */
+ *res = Int32GetDatum(val ? 1 : 0);
+ else if (typid == TEXTOID || typid == VARCHAROID || typid == BPCHAROID)
+ /*
+ * bool::text returns 'true' / 'false',
+ * boolout() returns 't' / 'f'.
+ */
+ *res = ExecJsonStringCoercion(val ? "true" : "false", val ? 4 : 5,
+ typid, typmod);
+ else
+ return false;
+
+ return true;
+}
+
+static Datum
+JsonbPGetTextDatum(Jsonb *jb)
+{
+ char *str = JsonbToCString(NULL, &jb->root, VARSIZE(jb));
+ Datum res = CStringGetTextDatum(str);
+
+ pfree(str);
+ return res;
+}
+
+/* Coerce SQL/JSON item to the output typid */
+static Datum
+ExecJsonValueCoercion(JsonbValue *item, Oid typid, int32 typmod,
+ bool *isnull, bool *error)
+{
+ *isnull = false;
+
+ /* Special case for json and jsonb output types */
+ if (typid == JSONBOID)
+ return JsonbPGetDatum(JsonbValueToJsonb(item));
+
+ if (typid == JSONOID)
+ return JsonbPGetTextDatum(JsonbValueToJsonb(item));
+
+ /*
+ * Coercion method and set of supported output types are determined
+ * by the item type.
+ */
switch (item->type)
{
case jbvNull:
- coercion = &coercions->null;
- res = (Datum) 0;
- break;
+ Assert(0); /* must be handled by the caller */
+ *isnull = true;
+ return (Datum) 0;
case jbvString:
- coercion = &coercions->string;
- res = PointerGetDatum(cstring_to_text_with_len(item->val.string.val,
- item->val.string.len));
- break;
+ if (typid == TEXTOID || typid == VARCHAROID || typid == BPCHAROID)
+ {
+ return ExecJsonStringCoercion(item->val.string.val,
+ item->val.string.len,
+ typid, typmod);
+ }
+ else if (typid == INT2OID || typid == INT4OID || typid == INT8OID)
+ {
+ char *str = pnstrdup(item->val.string.val,
+ item->val.string.len);
+
+ if (error)
+ {
+ int64 val = pg_strtoint64_opt_error(str, error);
+
+ if (*error)
+ return (Datum) 0;
+
+ if (typid == INT2OID)
+ {
+ if (val <= PG_INT16_MAX || val >= PG_INT16_MIN)
+ return Int16GetDatum((int16) val);
+ }
+ else if (typid == INT4OID)
+ {
+ if (val <= PG_INT32_MAX || val >= PG_INT32_MIN)
+ return Int32GetDatum((int32) val);
+ }
+ else
+ return Int64GetDatum(val);
+
+ *error = true;
+ return (Datum) 0;
+ }
+ else if (typid == INT2OID)
+ return Int16GetDatum(pg_strtoint16(str));
+ else if (typid == INT4OID)
+ return Int32GetDatum(pg_strtoint32(str));
+ else
+ return Int64GetDatum(pg_strtoint64(str));
+ }
+ else if (typid == BOOLOID)
+ {
+ return BoolGetDatum(boolin_opt_error(item->val.string.val,
+ item->val.string.len,
+ error));
+ }
+ else
+ {
+ char *str = pnstrdup(item->val.string.val,
+ item->val.string.len);
+
+ if (typid == FLOAT4OID)
+ return Float4GetDatum(float4in_opt_error(str, error));
+ else if (typid == FLOAT8OID)
+ return Float8GetDatum(
+ float8in_internal_opt_error(str, NULL,
+ "double precision",
+ str, error));
+ else if (typid == NUMERICOID)
+ return NumericGetDatum(numeric_in_opt_error(str, typmod, error));
+ else if (typid == DATEOID)
+ return DateADTGetDatum(date_in_opt_error(str, error));
+ else if (typid == TIMEOID)
+ return TimeADTGetDatum(time_in_opt_error(str, typmod, error));
+ else if (typid == TIMETZOID)
+ return TimeTzADTPGetDatum(timetz_in_opt_error(str, typmod, error));
+ else if (typid == TIMESTAMPOID)
+ return TimestampGetDatum(timestamp_in_opt_error(str, typmod, error));
+ else if (typid == TIMESTAMPTZOID)
+ return TimestampTzGetDatum(timestamptz_in_opt_error(str, typmod, error));
+ else
+ break; /* No cast */
+ }
case jbvNumeric:
- coercion = &coercions->numeric;
- res = NumericGetDatum(item->val.numeric);
- break;
+ {
+ Numeric num = item->val.numeric;
+
+ if (typid == NUMERICOID)
+ return NumericGetDatum(num);
+ else if (typid == TEXTOID || typid == VARCHAROID || typid == BPCHAROID)
+ return ExecJsonCoercionToText(numeric_out, NumericGetDatum(num), typid, typmod);
+ else if (typid == INT2OID)
+ return Int16GetDatum(numeric_int2_opt_error(num, error));
+ else if (typid == INT4OID)
+ return Int32GetDatum(numeric_int4_opt_error(num, error));
+ else if (typid == INT8OID)
+ return Int64GetDatum(numeric_int8_opt_error(num, error));
+ else if (typid == FLOAT4OID || typid == FLOAT8OID)
+ {
+ /*
+ * XXX numeric_float8() also uses I/O coercion, but
+ * it has special handling of Inf and NaN.
+ */
+ Datum cstr = DirectFunctionCall1(numeric_out,
+ NumericGetDatum(num));
+ char *str = DatumGetCString(cstr);
- case jbvBool:
- coercion = &coercions->boolean;
- res = BoolGetDatum(item->val.boolean);
- break;
+ if (typid == FLOAT4OID)
+ return Float4GetDatum(float4in_opt_error(str, error));
+ else
+ return Float8GetDatum(
+ float8in_internal_opt_error(str, NULL,
+ "double precision",
+ str, error));
+ }
+ else
+ break; /* No cast */
+ }
- case jbvDatetime:
- res = item->val.datetime.value;
- switch (item->val.datetime.typid)
+ case jbvBool:
{
- case DATEOID:
- coercion = &coercions->date;
- break;
- case TIMEOID:
- coercion = &coercions->time;
- break;
- case TIMETZOID:
- coercion = &coercions->timetz;
- break;
- case TIMESTAMPOID:
- coercion = &coercions->timestamp;
- break;
- case TIMESTAMPTZOID:
- coercion = &coercions->timestamptz;
- break;
- default:
- elog(ERROR, "unexpected jsonb datetime type oid %u",
- item->val.datetime.typid);
- return (Datum) 0;
+ Datum res;
+
+ if (ExecJsonBoolCoercion(item->val.boolean, typid, typmod, &res))
+ return res;
+
+ break; /* No cast */
}
- break;
+
+ case jbvDatetime:
+ return ExecJsonDatetimeCoercion(item->val.datetime.value,
+ item->val.datetime.typid,
+ typid, typmod, isnull, error);
case jbvArray:
case jbvObject:
case jbvBinary:
- coercion = &coercions->composite;
- res = JsonbPGetDatum(JsonbValueToJsonb(item));
+ Assert(0); /* non-scalars must be rejected by JsonPathValue() */
break;
default:
@@ -4929,99 +5194,100 @@ ExecPrepareJsonItemCoercion(JsonbValue *item,
return (Datum) 0;
}
- *pcoercion = coercion;
+ if (!error)
+ ereport(ERROR,
+ (errcode(ERRCODE_SQL_JSON_ITEM_CANNOT_BE_CAST_TO_TARGET_TYPE),
+ errmsg("SQL/JSON item cannot be cast to target type")));
- return res;
-}
+ *error = true;
+ *isnull = true;
-typedef Datum (*JsonFunc) (ExprEvalStep *op, ExprContext *econtext,
- Datum item, bool *resnull, void *p, bool *error);
+ return (Datum) 0;
+}
+/*
+ * Evaluate a JSON error/empty behavior and coerce result to the output
+ * type.
+ */
static Datum
-ExecEvalJsonExprSubtrans(JsonFunc func, ExprEvalStep *op,
- ExprContext *econtext,
- Datum res, bool *resnull,
- void *p, bool *error, bool subtrans)
+ExecEvalJsonBehavior(ExprContext *econtext, JsonBehavior *behavior,
+ ExprState *default_estate,
+ Oid ret_typid, int32 ret_typmod, bool *is_null)
{
- MemoryContext oldcontext;
- ResourceOwner oldowner;
-
- if (!subtrans)
- /* No need to use subtransactions. */
- return func(op, econtext, res, resnull, p, error);
+ *is_null = false;
- /*
- * We should catch exceptions of category ERRCODE_DATA_EXCEPTION and
- * execute the corresponding ON ERROR behavior then.
- */
- oldcontext = CurrentMemoryContext;
- oldowner = CurrentResourceOwner;
+ switch (behavior->btype)
+ {
+ case JSON_BEHAVIOR_EMPTY_ARRAY:
+ if (ret_typid == JSONBOID)
+ return JsonbPGetDatum(JsonbMakeEmptyArray());
+ else
+ {
+ Assert(ret_typid == JSONOID || ret_typid == TEXTOID ||
+ ret_typid == VARCHAROID || ret_typid == BPCHAROID ||
+ ret_typid == BYTEAOID);
- Assert(error);
+ return ExecJsonStringCoercion("[]", 2, ret_typid, ret_typmod);
+ }
- BeginInternalSubTransaction(NULL);
- /* Want to execute expressions inside function's memory context */
- MemoryContextSwitchTo(oldcontext);
+ case JSON_BEHAVIOR_EMPTY_OBJECT:
+ if (ret_typid == JSONBOID)
+ return JsonbPGetDatum(JsonbMakeEmptyObject());
+ else
+ {
+ Assert(ret_typid == JSONOID || ret_typid == TEXTOID ||
+ ret_typid == VARCHAROID || ret_typid == BPCHAROID ||
+ ret_typid == BYTEAOID);
- PG_TRY();
- {
- res = func(op, econtext, res, resnull, p, error);
+ return ExecJsonStringCoercion("{}", 2, ret_typid, ret_typmod);
+ }
- /* Commit the inner transaction, return to outer xact context */
- ReleaseCurrentSubTransaction();
- MemoryContextSwitchTo(oldcontext);
- CurrentResourceOwner = oldowner;
- }
- PG_CATCH();
- {
- ErrorData *edata;
- int ecategory;
+ case JSON_BEHAVIOR_TRUE:
+ case JSON_BEHAVIOR_FALSE:
+ {
+ Datum res;
+ bool ok =
+ ExecJsonBoolCoercion(behavior->btype == JSON_BEHAVIOR_TRUE,
+ ret_typid, ret_typmod, &res);
- /* Save error info in oldcontext */
- MemoryContextSwitchTo(oldcontext);
- edata = CopyErrorData();
- FlushErrorState();
+ Assert(ok); /* returning type must be checked in parser */
- /* Abort the inner transaction */
- RollbackAndReleaseCurrentSubTransaction();
- MemoryContextSwitchTo(oldcontext);
- CurrentResourceOwner = oldowner;
+ return res;
+ }
- ecategory = ERRCODE_TO_CATEGORY(edata->sqlerrcode);
+ case JSON_BEHAVIOR_NULL:
+ case JSON_BEHAVIOR_UNKNOWN:
+ case JSON_BEHAVIOR_EMPTY:
+ *is_null = true;
+ return (Datum) 0;
- if (ecategory != ERRCODE_DATA_EXCEPTION && /* jsonpath and other data
- * errors */
- ecategory != ERRCODE_INTEGRITY_CONSTRAINT_VIOLATION) /* domain errors */
- ReThrowError(edata);
+ case JSON_BEHAVIOR_DEFAULT:
+ /*
+ * Execute DEFAULT expression.
+ * Coercion is not needed here, because expression is
+ * already coerced to the target type by the parser.
+ */
+ return ExecEvalExpr(default_estate, econtext, is_null);
- res = (Datum) 0;
- *error = true;
+ default:
+ elog(ERROR, "unrecognized SQL/JSON behavior %d", behavior->btype);
+ return (Datum) 0;
}
- 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)
+ExecEvalJsonExprInternal(ExprState *state,
+ JsonExprState *jsestate, ExprContext *econtext,
+ JsonPath *path, Datum item, bool *resnull,
+ bool *error)
{
- ExecEvalJsonExprContext *cxt = pcxt;
- JsonPath *path = cxt->path;
- JsonExprState *jsestate = op->d.jsonexpr.jsestate;
JsonExpr *jexpr = jsestate->jsexpr;
- ExprState *estate = NULL;
bool empty = false;
Datum res = (Datum) 0;
+ Oid ret_typid = jexpr->returning->typid;
+ int32 ret_typmod = jexpr->returning->typmod;
+
+ *resnull = true;
switch (jexpr->op)
{
@@ -5029,70 +5295,96 @@ ExecEvalJsonExpr(ExprEvalStep *op, ExprContext *econtext,
res = JsonPathQuery(item, path, jexpr->wrapper, &empty, error,
jsestate->args);
if (error && *error)
- {
- *resnull = true;
return (Datum) 0;
- }
+
+ if (empty)
+ break;
+
*resnull = !DatumGetPointer(res);
- break;
+
+ /* Override default coercion in OMIT QUOTES case */
+ if (ExecJsonQueryNeedsIOCoercion(jexpr, res, *resnull))
+ {
+ char *str = JsonbUnquote(DatumGetJsonbP(res));
+
+ if (ret_typid == JSONBOID)
+ {
+ res = jsonb_from_cstring(str, strlen(str), false, error);
+
+ if (error && *error)
+ return (Datum) 0;
+
+ return res;
+ }
+ else if (ret_typid == JSONOID)
+ {
+ text *json = cstring_to_text(str);
+ JsonLexContext *lex;
+ JsonParseErrorType result;
+
+ /* validate it */
+ lex = makeJsonLexContext(json, false);
+ result = pg_parse_json(lex, &nullSemAction);
+
+ if (result != JSON_SUCCESS)
+ {
+ if (error)
+ {
+ *error = true;
+ return (Datum) 0;
+ }
+
+ json_ereport_error(result, lex);
+ }
+
+ return PointerGetDatum(json);
+ }
+ else if (ret_typid == TEXTOID ||
+ ret_typid == VARCHAROID ||
+ ret_typid == BPCHAROID ||
+ ret_typid == BYTEAOID)
+ return ExecJsonStringCoercion(str, strlen(str), ret_typid, ret_typmod);
+ }
+ else if (ret_typid == JSONBOID)
+ return res;
+ else if (ret_typid == JSONOID || ret_typid == TEXTOID)
+ return JsonbPGetTextDatum(DatumGetJsonbP(res));
+ else if (ret_typid == VARCHAROID || ret_typid == BPCHAROID ||
+ ret_typid == BYTEAOID)
+ {
+ Jsonb *jb = DatumGetJsonbP(res);
+ char *str = JsonbToCString(NULL, &jb->root, VARSIZE(jb));
+
+ return ExecJsonStringCoercion(str, strlen(str), ret_typid, ret_typmod);
+ }
+
+ Assert(0); /* unsupported output type */
+ *error = *resnull = true;
+ return (Datum) 0;
case JSON_VALUE_OP:
{
- struct JsonCoercionState *jcstate;
JsonbValue *jbv = JsonPathValue(item, path, &empty, error,
jsestate->args);
if (error && *error)
return (Datum) 0;
- if (!jbv) /* NULL or empty */
+ if (empty)
break;
- Assert(!empty);
-
- *resnull = false;
+ if (!jbv)
+ return (Datum) 0; /* NULL */
- /* coerce scalar item to the output type */
- if (jexpr->returning->typid == JSONOID ||
- jexpr->returning->typid == JSONBOID)
- {
- /* Use result coercion from json[b] to the output type */
- res = JsonbPGetDatum(JsonbValueToJsonb(jbv));
- break;
- }
+ Assert(jbv->type != jbvNull);
- /* Use coercion from SQL/JSON item type to the output type */
- res = ExecPrepareJsonItemCoercion(jbv,
- jsestate->jsexpr->returning,
- &jsestate->coercions,
- &jcstate);
+ res = ExecJsonValueCoercion(jbv, ret_typid, ret_typmod,
+ resnull, error);
- if (jcstate->coercion &&
- (jcstate->coercion->via_io ||
- jcstate->coercion->via_populate))
- {
- if (error)
- {
- *error = true;
- return (Datum) 0;
- }
-
- /*
- * 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 */
+ if (error && *error)
+ return (Datum) 0;
- /* coerce using specific expression */
- estate = jcstate->estate;
- jsestate->coercion_expr->value = res;
- jsestate->coercion_expr->isnull = *resnull;
- break;
+ return res;
}
case JSON_EXISTS_OP:
@@ -5101,113 +5393,68 @@ ExecEvalJsonExpr(ExprEvalStep *op, ExprContext *econtext,
jsestate->args,
error);
- *resnull = error && *error;
- res = BoolGetDatum(exists);
-
- if (!jsestate->result_expr)
- return res;
+ if (!error || !*error)
+ {
+ /* Should succeed, output type is checked by parser */
+ (void) ExecJsonBoolCoercion(exists, ret_typid, ret_typmod, &res);
+ *resnull = false;
+ }
- /* coerce using result expression */
- estate = jsestate->result_expr;
- jsestate->res_expr->value = res;
- jsestate->res_expr->isnull = *resnull;
- break;
+ return res;
}
- case JSON_TABLE_OP:
- *resnull = false;
- return item;
-
default:
elog(ERROR, "unrecognized SQL/JSON expression op %d", jexpr->op);
return (Datum) 0;
}
- if (empty)
- {
- Assert(jexpr->on_empty); /* it is not JSON_EXISTS */
+ Assert(empty);
+ Assert(jexpr->on_empty); /* it is not JSON_EXISTS */
- if (jexpr->on_empty->btype == JSON_BEHAVIOR_ERROR)
+ if (jexpr->on_empty->btype == JSON_BEHAVIOR_ERROR)
+ {
+ if (error)
{
- if (error)
- {
- *error = true;
- return (Datum) 0;
- }
-
- ereport(ERROR,
- (errcode(ERRCODE_NO_SQL_JSON_ITEM),
- errmsg("no SQL/JSON item")));
+ *error = true;
+ return (Datum) 0;
}
- 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);
+ ereport(ERROR,
+ (errcode(ERRCODE_NO_SQL_JSON_ITEM),
+ errmsg("no SQL/JSON item")));
}
- return ExecEvalJsonExprSubtrans(ExecEvalJsonExprCoercion, op, econtext,
- res, resnull, estate, error,
- cxt->coercionInSubtrans);
-}
-
-bool
-ExecEvalJsonNeedsSubTransaction(JsonExpr *jsexpr,
- struct JsonCoercionsState *coercions)
-{
- if (jsexpr->on_error->btype == JSON_BEHAVIOR_ERROR)
- return false;
-
- if (jsexpr->op == JSON_EXISTS_OP && !jsexpr->result_coercion)
- return false;
-
- if (!coercions)
- return true;
-
- return false;
+ /* Execute ON EMPTY behavior */
+ return ExecEvalJsonBehavior(econtext, jexpr->on_empty,
+ jsestate->default_on_empty,
+ ret_typid, ret_typmod, resnull);
}
/* ----------------------------------------------------------------
- * ExecEvalJson
+ * ExecEvalJsonExpr
* ----------------------------------------------------------------
*/
void
-ExecEvalJson(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
+ExecEvalJsonExpr(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
{
- ExecEvalJsonExprContext cxt;
JsonExprState *jsestate = op->d.jsonexpr.jsestate;
JsonExpr *jexpr = jsestate->jsexpr;
Datum item;
- Datum res = (Datum) 0;
+ Datum res;
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)
+ if (jsestate->formatted_expr.isnull || jsestate->pathspec.isnull)
{
- /* execute domain checks for NULLs */
- (void) ExecEvalJsonExprCoercion(op, econtext, res, op->resnull,
- NULL, NULL);
-
- Assert(*op->resnull);
+ *op->resnull = true;
+ *op->resvalue = (Datum) 0;
return;
}
- item = jsestate->formatted_expr->value;
- path = DatumGetJsonPathP(jsestate->pathspec->value);
+ item = jsestate->formatted_expr.value;
+ path = DatumGetJsonPathP(jsestate->pathspec.value);
/* reset JSON path variable contexts */
foreach(lc, jsestate->args)
@@ -5218,29 +5465,20 @@ ExecEvalJson(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
var->evaluated = false;
}
- needSubtrans = ExecEvalJsonNeedsSubTransaction(jexpr, &jsestate->coercions);
-
- 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);
+ res = ExecEvalJsonExprInternal(state, jsestate, econtext,
+ path, item, op->resnull,
+ throwErrors ? NULL : &error);
if (error)
{
+ Assert(!throwErrors);
+
/* Execute ON ERROR behavior */
res = ExecEvalJsonBehavior(econtext, jexpr->on_error,
jsestate->default_on_error,
+ jexpr->returning->typid,
+ jexpr->returning->typmod,
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 = res;
diff --git a/src/backend/jit/llvm/llvmjit_expr.c b/src/backend/jit/llvm/llvmjit_expr.c
index bd3965143da..fd72630f5e6 100644
--- a/src/backend/jit/llvm/llvmjit_expr.c
+++ b/src/backend/jit/llvm/llvmjit_expr.c
@@ -2408,7 +2408,7 @@ llvm_compile_expr(ExprState *state)
break;
case EEOP_JSONEXPR:
- build_EvalXFunc(b, mod, "ExecEvalJson",
+ build_EvalXFunc(b, mod, "ExecEvalJsonExpr",
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.c
index 373471ad27f..37fe64654b6 100644
--- a/src/backend/jit/llvm/llvmjit_types.c
+++ b/src/backend/jit/llvm/llvmjit_types.c
@@ -135,7 +135,7 @@ void *referenced_functions[] =
ExecEvalXmlExpr,
ExecEvalJsonConstructor,
ExecEvalJsonIsPredicate,
- ExecEvalJson,
+ ExecEvalJsonExpr,
MakeExpandedObjectReadOnlyInternal,
slot_getmissingattrs,
slot_getsomeattrs_int,
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index c334daae392..e0240beeeab 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -266,9 +266,6 @@ exprType(const Node *expr)
case T_JsonExpr:
type = ((const JsonExpr *) expr)->returning->typid;
break;
- case T_JsonCoercion:
- type = exprType(((const JsonCoercion *) expr)->expr);
- break;
default:
elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
type = InvalidOid; /* keep compiler quiet */
@@ -507,8 +504,6 @@ exprTypmod(const Node *expr)
return ((const JsonConstructorExpr *) expr)->returning->typmod;
case T_JsonExpr:
return ((JsonExpr *) expr)->returning->typmod;
- case T_JsonCoercion:
- return exprTypmod(((const JsonCoercion *) expr)->expr);
default:
break;
}
@@ -1010,14 +1005,12 @@ exprCollation(const Node *expr)
case T_JsonExpr:
{
JsonExpr *jexpr = (JsonExpr *) expr;
- JsonCoercion *coercion = jexpr->result_coercion;
- if (!coercion)
- coll = InvalidOid;
- else if (coercion->expr)
- coll = exprCollation(coercion->expr);
- else if (coercion->via_io || coercion->via_populate)
- coll = coercion->collation;
+ if (jexpr->returning->typid == TEXTOID ||
+ jexpr->returning->typid == CHAROID ||
+ jexpr->returning->typid == VARCHAROID ||
+ jexpr->returning->typid == BPCHAROID)
+ coll = jexpr->collation;
else
coll = InvalidOid;
}
@@ -1255,14 +1248,12 @@ exprSetCollation(Node *expr, Oid collation)
case T_JsonExpr:
{
JsonExpr *jexpr = (JsonExpr *) expr;
- JsonCoercion *coercion = jexpr->result_coercion;
- if (!coercion)
- Assert(!OidIsValid(collation));
- else if (coercion->expr)
- exprSetCollation(coercion->expr, collation);
- else if (coercion->via_io || coercion->via_populate)
- coercion->collation = collation;
+ if (jexpr->returning->typid == TEXTOID ||
+ jexpr->returning->typid == CHAROID ||
+ jexpr->returning->typid == VARCHAROID ||
+ jexpr->returning->typid == BPCHAROID)
+ jexpr->collation = collation;
else
Assert(!OidIsValid(collation));
}
@@ -2507,8 +2498,6 @@ expression_tree_walker(Node *node,
if (walker(jexpr->formatted_expr, context))
return true;
- if (walker(jexpr->result_coercion, context))
- return true;
if (walker(jexpr->passing_values, context))
return true;
/* we assume walker doesn't care about passing_names */
@@ -2517,36 +2506,6 @@ expression_tree_walker(Node *node,
return true;
if (walker(jexpr->on_error->default_expr, context))
return true;
- if (walker(jexpr->coercions, context))
- return true;
- }
- break;
- case T_JsonCoercion:
- return walker(((JsonCoercion *) node)->expr, context);
- case T_JsonItemCoercions:
- {
- JsonItemCoercions *coercions = (JsonItemCoercions *) node;
-
- if (walker(coercions->null, context))
- return true;
- if (walker(coercions->string, context))
- return true;
- if (walker(coercions->numeric, context))
- return true;
- if (walker(coercions->boolean, context))
- return true;
- if (walker(coercions->date, context))
- return true;
- if (walker(coercions->time, context))
- return true;
- if (walker(coercions->timetz, context))
- return true;
- if (walker(coercions->timestamp, context))
- return true;
- if (walker(coercions->timestamptz, context))
- return true;
- if (walker(coercions->composite, context))
- return true;
}
break;
default:
@@ -3576,7 +3535,6 @@ expression_tree_mutator(Node *node,
FLATCOPY(newnode, jexpr, JsonExpr);
MUTATE(newnode->path_spec, jexpr->path_spec, Node *);
MUTATE(newnode->formatted_expr, jexpr->formatted_expr, Node *);
- MUTATE(newnode->result_coercion, jexpr->result_coercion, JsonCoercion *);
MUTATE(newnode->passing_values, jexpr->passing_values, List *);
/* assume mutator does not care about passing_names */
if (newnode->on_empty)
@@ -3587,35 +3545,6 @@ expression_tree_mutator(Node *node,
return (Node *) newnode;
}
break;
- case T_JsonCoercion:
- {
- JsonCoercion *coercion = (JsonCoercion *) node;
- JsonCoercion *newnode;
-
- FLATCOPY(newnode, coercion, JsonCoercion);
- MUTATE(newnode->expr, coercion->expr, Node *);
- return (Node *) newnode;
- }
- break;
- case T_JsonItemCoercions:
- {
- JsonItemCoercions *coercions = (JsonItemCoercions *) node;
- JsonItemCoercions *newnode;
-
- FLATCOPY(newnode, coercions, JsonItemCoercions);
- MUTATE(newnode->null, coercions->null, JsonCoercion *);
- MUTATE(newnode->string, coercions->string, JsonCoercion *);
- MUTATE(newnode->numeric, coercions->numeric, JsonCoercion *);
- MUTATE(newnode->boolean, coercions->boolean, JsonCoercion *);
- MUTATE(newnode->date, coercions->date, JsonCoercion *);
- MUTATE(newnode->time, coercions->time, JsonCoercion *);
- MUTATE(newnode->timetz, coercions->timetz, JsonCoercion *);
- MUTATE(newnode->timestamp, coercions->timestamp, JsonCoercion *);
- MUTATE(newnode->timestamptz, coercions->timestamptz, JsonCoercion *);
- MUTATE(newnode->composite, coercions->composite, JsonCoercion *);
- return (Node *) newnode;
- }
- break;
default:
elog(ERROR, "unrecognized node type: %d",
(int) nodeTag(node));
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 533df86ff77..6eb923892f4 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -411,6 +411,26 @@ contain_mutable_functions_walker(Node *node, void *context)
{
JsonExpr *jexpr = castNode(JsonExpr, node);
Const *cnst;
+ bool returns_datetime;
+
+ /*
+ * Input fuctions for datetime types are stable. They can be
+ * called in JSON_VALUE(), when the resulting SQL/JSON is a
+ * string.
+ */
+ if (jexpr->returning->typid == DATEOID ||
+ jexpr->returning->typid == TIMEOID ||
+ jexpr->returning->typid == TIMETZOID ||
+ jexpr->returning->typid == TIMESTAMPOID ||
+ jexpr->returning->typid == TIMESTAMPTZOID)
+ return true;
+
+ /*
+ * pg_convert_to(), which is used for implementation of
+ * JSON_QUERY(RETURNING bytea FORMAT JSON), is stable.
+ */
+ if (jexpr->returning->typid == BYTEAOID)
+ return true;
if (!IsA(jexpr->path_spec, Const))
return true;
@@ -421,8 +441,32 @@ contain_mutable_functions_walker(Node *node, void *context)
if (cnst->constisnull)
return false;
- return jspIsMutable(DatumGetJsonPathP(cnst->constvalue),
- jexpr->passing_names, jexpr->passing_values);
+ if (jspIsMutable(DatumGetJsonPathP(cnst->constvalue),
+ jexpr->passing_names, jexpr->passing_values,
+ &returns_datetime))
+ return true;
+
+ if (returns_datetime && jexpr->op == JSON_VALUE_OP)
+ {
+ /*
+ * Some datetime types have mutable output functions,
+ * so if returning string types whole expression is mutable.
+ * TODO check individual datetime type if it is known.
+ *
+ * Other non-JSON output types have no conversion from
+ * datetime types.
+ *
+ * Datetime returning types were checked above, no need to
+ * check them here. But conversion between them also can
+ * be mutable.
+ */
+ if (jexpr->returning->typid == TEXTOID ||
+ jexpr->returning->typid == VARCHAROID ||
+ jexpr->returning->typid == BPCHAROID)
+ return true;
+ }
+
+ return false;
}
if (IsA(node, SQLValueFunction))
@@ -896,18 +940,6 @@ max_parallel_hazard_walker(Node *node, max_parallel_hazard_context *context)
context, 0);
}
- /* JsonExpr is parallel-unsafe if subtransactions can be used. */
- else if (IsA(node, JsonExpr))
- {
- JsonExpr *jsexpr = (JsonExpr *) node;
-
- if (ExecEvalJsonNeedsSubTransaction(jsexpr, NULL))
- {
- context->max_hazard = PROPARALLEL_UNSAFE;
- return true;
- }
- }
-
/* Recurse to check arguments */
return expression_tree_walker(node,
max_parallel_hazard_walker,
@@ -5323,3 +5355,16 @@ pull_paramids_walker(Node *node, Bitmapset **context)
return expression_tree_walker(node, pull_paramids_walker,
(void *) context);
}
+
+bool
+expr_can_throw_errors(Node *expr)
+{
+ if (!expr)
+ return false;
+
+ if (IsA(expr, Const))
+ return false;
+
+ /* TODO consider more cases */
+ return true;
+}
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index fabb5f72076..c02530a3671 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -22,6 +22,7 @@
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
+#include "optimizer/clauses.h"
#include "optimizer/optimizer.h"
#include "parser/analyze.h"
#include "parser/parse_agg.h"
@@ -4108,7 +4109,6 @@ transformJsonExprCommon(ParseState *pstate, JsonFuncExpr *func)
/* format is determined by context item type */
format = exprType(jsexpr->formatted_expr) == JSONBOID ? JS_FORMAT_JSONB : JS_FORMAT_JSON;
- jsexpr->result_coercion = NULL;
jsexpr->omit_quotes = false;
jsexpr->format = func->common->expr->format;
@@ -4169,40 +4169,6 @@ assignDefaultJsonReturningType(Node *context_item, JsonFormat *context_format,
ret->typmod = -1;
}
-/*
- * Try to coerce expression to the output type or
- * use json_populate_type() for composite, array and domain types or
- * use coercion via I/O.
- */
-static JsonCoercion *
-coerceJsonExpr(ParseState *pstate, Node *expr, const JsonReturning *returning)
-{
- char typtype;
- JsonCoercion *coercion = makeNode(JsonCoercion);
-
- coercion->expr = coerceJsonFuncExpr(pstate, expr, returning, false);
-
- if (coercion->expr)
- {
- if (coercion->expr == expr)
- coercion->expr = NULL;
-
- return coercion;
- }
-
- typtype = get_typtype(returning->typid);
-
- if (returning->typid == RECORDOID ||
- typtype == TYPTYPE_COMPOSITE ||
- typtype == TYPTYPE_DOMAIN ||
- type_is_array(returning->typid))
- coercion->via_populate = true;
- else
- coercion->via_io = true;
-
- return coercion;
-}
-
/*
* Transform a JSON output clause of JSON_VALUE and JSON_QUERY.
*/
@@ -4210,8 +4176,6 @@ static void
transformJsonFuncExprOutput(ParseState *pstate, JsonFuncExpr *func,
JsonExpr *jsexpr)
{
- Node *expr = jsexpr->formatted_expr;
-
jsexpr->returning = transformJsonOutput(pstate, func->output, false);
/* JSON_VALUE returns text by default */
@@ -4225,14 +4189,41 @@ transformJsonFuncExprOutput(ParseState *pstate, JsonFuncExpr *func,
{
JsonReturning ret;
- if (func->op == JSON_VALUE_OP &&
- jsexpr->returning->typid != JSONOID &&
- jsexpr->returning->typid != JSONBOID)
+ if (func->op == JSON_VALUE_OP)
{
- /* Forced coercion via I/O for JSON_VALUE for non-JSON types */
- jsexpr->result_coercion = makeNode(JsonCoercion);
- jsexpr->result_coercion->expr = NULL;
- jsexpr->result_coercion->via_io = true;
+ /*
+ * Only a limited list of output types is supported in
+ * JSON_VALUE() now.
+ */
+ switch (jsexpr->returning->typid)
+ {
+ case JSONBOID:
+ case JSONOID:
+ case TEXTOID:
+ case VARCHAROID:
+ case BPCHAROID:
+ case BOOLOID:
+ case NUMERICOID:
+ case INT2OID:
+ case INT4OID:
+ case INT8OID:
+ case FLOAT4OID:
+ case FLOAT8OID:
+ case DATEOID:
+ case TIMEOID:
+ case TIMETZOID:
+ case TIMESTAMPOID:
+ case TIMESTAMPTZOID:
+ break; /* Ok */
+
+ default:
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("returning type %s is not supported in JSON_VALUE()",
+ format_type_be(jsexpr->returning->typid)),
+ parser_coercion_errposition(pstate, func->location, (Node *) jsexpr)));
+ }
+
return;
}
@@ -4241,13 +4232,23 @@ transformJsonFuncExprOutput(ParseState *pstate, JsonFuncExpr *func,
if (ret.typid != jsexpr->returning->typid ||
ret.typmod != jsexpr->returning->typmod)
{
- Node *placeholder = makeCaseTestExpr(expr);
-
- Assert(((CaseTestExpr *) placeholder)->typeId == ret.typid);
- Assert(((CaseTestExpr *) placeholder)->typeMod == ret.typmod);
+ switch (jsexpr->returning->typid)
+ {
+ case JSONBOID:
+ case JSONOID:
+ case TEXTOID:
+ case VARCHAROID:
+ case BPCHAROID:
+ case BYTEAOID:
+ break;
- jsexpr->result_coercion = coerceJsonExpr(pstate, placeholder,
- jsexpr->returning);
+ default:
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("returning type %s is not supported in JSON_QUERY()",
+ format_type_be(jsexpr->returning->typid)),
+ parser_coercion_errposition(pstate, func->location, (Node *) jsexpr)));
+ }
}
}
else
@@ -4259,8 +4260,10 @@ transformJsonFuncExprOutput(ParseState *pstate, JsonFuncExpr *func,
* Coerce an expression in JSON DEFAULT behavior to the target output type.
*/
static Node *
-coerceDefaultJsonExpr(ParseState *pstate, JsonExpr *jsexpr, Node *defexpr)
+coerceDefaultJsonExpr(ParseState *pstate, JsonExpr *jsexpr,
+ bool is_on_empty, Node *defexpr)
{
+ Node *orig_defexpr = defexpr;
int location;
Oid exprtype;
@@ -4290,63 +4293,31 @@ coerceDefaultJsonExpr(ParseState *pstate, JsonExpr *jsexpr, Node *defexpr)
format_type_be(jsexpr->returning->typid)),
parser_errposition(pstate, location)));
- return defexpr;
-}
+ /* Don't allow unsafe DEFAUL ON EMPTY expressions, unless ERROR ON ERROR */
+ if (!is_on_empty ||
+ jsexpr->on_error->btype == JSON_BEHAVIOR_ERROR)
+ return defexpr;
-/*
- * Initialize SQL/JSON item coercion from the SQL type "typid" to the target
- * "returning" type.
- */
-static JsonCoercion *
-initJsonItemCoercion(ParseState *pstate, Oid typid,
- const JsonReturning *returning)
-{
- Node *expr;
+ if (!expr_can_throw_errors(defexpr))
+ return defexpr;
- if (typid == UNKNOWNOID)
+ /* Try to simplify expression if there is non-empty coercion */
+ if (defexpr != orig_defexpr &&
+ !expr_can_throw_errors(orig_defexpr))
{
- expr = (Node *) makeNullConst(UNKNOWNOID, -1, InvalidOid);
- }
- else
- {
- CaseTestExpr *placeholder = makeNode(CaseTestExpr);
+ defexpr = eval_const_expressions(NULL, defexpr);
- placeholder->typeId = typid;
- placeholder->typeMod = -1;
- placeholder->collation = InvalidOid;
-
- expr = (Node *) placeholder;
+ if (!expr_can_throw_errors(defexpr))
+ return defexpr;
}
- return coerceJsonExpr(pstate, expr, returning);
-}
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("unsafe DEFAULT ON EMPTY expressions are not supported"),
+ errhint("Use ERROR ON ERROR clause or try to simplify expression into constant-like form."),
+ parser_errposition(pstate, location)));
-static void
-initJsonItemCoercions(ParseState *pstate, JsonItemCoercions *coercions,
- const JsonReturning *returning, Oid contextItemTypeId)
-{
- struct
- {
- JsonCoercion **coercion;
- Oid typid;
- } *p,
- coercionTypids[] =
- {
- {&coercions->null, UNKNOWNOID},
- {&coercions->string, TEXTOID},
- {&coercions->numeric, NUMERICOID},
- {&coercions->boolean, BOOLOID},
- {&coercions->date, DATEOID},
- {&coercions->time, TIMEOID},
- {&coercions->timetz, TIMETZOID},
- {&coercions->timestamp, TIMESTAMPOID},
- {&coercions->timestamptz, TIMESTAMPTZOID},
- {&coercions->composite, contextItemTypeId},
- {NULL, InvalidOid}
- };
-
- for (p = coercionTypids; p->coercion; p++)
- *p->coercion = initJsonItemCoercion(pstate, p->typid, returning);
+ return NULL;
}
/*
@@ -4370,17 +4341,13 @@ transformJsonFuncExpr(ParseState *pstate, JsonFuncExpr *func)
jsexpr->returning->format->encoding = JS_ENC_DEFAULT;
jsexpr->on_empty->default_expr =
- coerceDefaultJsonExpr(pstate, jsexpr,
+ coerceDefaultJsonExpr(pstate, jsexpr, true,
jsexpr->on_empty->default_expr);
jsexpr->on_error->default_expr =
- coerceDefaultJsonExpr(pstate, jsexpr,
+ coerceDefaultJsonExpr(pstate, jsexpr, false,
jsexpr->on_error->default_expr);
- jsexpr->coercions = makeNode(JsonItemCoercions);
- initJsonItemCoercions(pstate, jsexpr->coercions, jsexpr->returning,
- exprType(contextItemExpr));
-
break;
case JSON_QUERY_OP:
@@ -4389,11 +4356,11 @@ transformJsonFuncExpr(ParseState *pstate, JsonFuncExpr *func)
transformJsonFuncExprOutput(pstate, func, jsexpr);
jsexpr->on_empty->default_expr =
- coerceDefaultJsonExpr(pstate, jsexpr,
+ coerceDefaultJsonExpr(pstate, jsexpr, true,
jsexpr->on_empty->default_expr);
jsexpr->on_error->default_expr =
- coerceDefaultJsonExpr(pstate, jsexpr,
+ coerceDefaultJsonExpr(pstate, jsexpr, false,
jsexpr->on_error->default_expr);
jsexpr->wrapper = func->wrapper;
@@ -4409,6 +4376,7 @@ transformJsonFuncExpr(ParseState *pstate, JsonFuncExpr *func)
jsexpr->returning->format->format_type = JS_FORMAT_DEFAULT;
jsexpr->returning->format->encoding = JS_ENC_DEFAULT;
+ /* Coerce intermediate boolean result to the output type if needed */
if (!OidIsValid(jsexpr->returning->typid))
{
jsexpr->returning->typid = BOOLOID;
@@ -4416,32 +4384,21 @@ transformJsonFuncExpr(ParseState *pstate, JsonFuncExpr *func)
}
else if (jsexpr->returning->typid != BOOLOID)
{
- CaseTestExpr *placeholder = makeNode(CaseTestExpr);
- int location = exprLocation((Node *) jsexpr);
-
- placeholder->typeId = BOOLOID;
- placeholder->typeMod = -1;
- placeholder->collation = InvalidOid;
-
- jsexpr->result_coercion = makeNode(JsonCoercion);
- jsexpr->result_coercion->expr =
- coerce_to_target_type(pstate, (Node *) placeholder, BOOLOID,
- jsexpr->returning->typid,
- jsexpr->returning->typmod,
- COERCION_EXPLICIT,
- COERCE_IMPLICIT_CAST,
- location);
-
- if (!jsexpr->result_coercion->expr)
- ereport(ERROR,
- (errcode(ERRCODE_CANNOT_COERCE),
- errmsg("cannot cast type %s to %s",
- format_type_be(BOOLOID),
- format_type_be(jsexpr->returning->typid)),
- parser_coercion_errposition(pstate, location, (Node *) jsexpr)));
+ switch (jsexpr->returning->typid)
+ {
+ case INT4OID:
+ case TEXTOID:
+ case VARCHAROID:
+ case BPCHAROID:
+ break;
- if (jsexpr->result_coercion->expr == (Node *) placeholder)
- jsexpr->result_coercion->expr = NULL;
+ default:
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("returning type %s is not supported in JSON_EXISTS()",
+ format_type_be(jsexpr->returning->typid)),
+ parser_coercion_errposition(pstate, func->location, (Node *) jsexpr)));
+ }
}
break;
@@ -4457,7 +4414,6 @@ transformJsonFuncExpr(ParseState *pstate, JsonFuncExpr *func)
errmsg("JSON_TABLE() is not yet implemented for the json type"),
errhint("Try casting the argument to jsonb"),
parser_errposition(pstate, func->location)));
-
break;
}
diff --git a/src/backend/utils/adt/jsonpath.c b/src/backend/utils/adt/jsonpath.c
index da9df4ae766..9c9f9ad703f 100644
--- a/src/backend/utils/adt/jsonpath.c
+++ b/src/backend/utils/adt/jsonpath.c
@@ -1318,9 +1318,11 @@ jspIsMutableWalker(JsonPathItem *jpi, JsonPathMutableContext *cxt)
* Check whether jsonpath expression is immutable or not.
*/
bool
-jspIsMutable(JsonPath *path, List *varnames, List *varexprs)
+jspIsMutable(JsonPath *path, List *varnames, List *varexprs,
+ bool *returns_datetime)
{
JsonPathMutableContext cxt;
+ JsonPathDatatypeStatus status;
JsonPathItem jpi;
cxt.varnames = varnames;
@@ -1330,7 +1332,9 @@ jspIsMutable(JsonPath *path, List *varnames, List *varexprs)
cxt.mutable = false;
jspInit(&jpi, path);
- jspIsMutableWalker(&jpi, &cxt);
+ status = jspIsMutableWalker(&jpi, &cxt);
+
+ *returns_datetime = status != jpdsNonDateTime;
return cxt.mutable;
}
diff --git a/src/include/executor/execExpr.h b/src/include/executor/execExpr.h
index c8ef917ffe0..adabf57c97b 100644
--- a/src/include/executor/execExpr.h
+++ b/src/include/executor/execExpr.h
@@ -762,43 +762,14 @@ 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 */
+ formatted_expr, /* formatted context item value */
+ coercion_expr, /* input for JSON item coercion */
+ pathspec; /* path specification value */
- 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() */
-
- struct JsonCoercionsState
- {
- struct JsonCoercionState
- {
- JsonCoercion *coercion; /* coercion expression */
- ExprState *estate; /* coercion expression state */
- } null,
- string,
- numeric ,
- boolean,
- date,
- time,
- timetz,
- timestamp,
- timestamptz,
- composite;
- } coercions; /* states for coercion from SQL/JSON item
- * types directly to the output type */
} JsonExprState;
/* functions in execExpr.c */
@@ -860,18 +831,8 @@ extern void ExecEvalSysVar(ExprState *state, ExprEvalStep *op,
ExprContext *econtext, TupleTableSlot *slot);
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,
- struct JsonCoercionsState *);
-extern Datum ExecEvalExprPassingCaseValue(ExprState *estate,
- ExprContext *econtext, bool *isnull,
- Datum caseval_datum,
- bool caseval_isnull);
+extern void ExecEvalJsonExpr(ExprState *state, ExprEvalStep *op,
+ ExprContext *econtext);
extern void ExecAggInitGroup(AggState *aggstate, AggStatePerTrans pertrans, AggStatePerGroup pergroup,
ExprContext *aggcontext);
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 3aa96bb6855..14fbb32ce9c 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -1551,39 +1551,6 @@ typedef struct JsonBehavior
Node *default_expr; /* default expression, if any */
} JsonBehavior;
-/*
- * JsonCoercion -
- * coercion from SQL/JSON item types to SQL types
- */
-typedef struct JsonCoercion
-{
- NodeTag type;
- Node *expr; /* resulting expression coerced to target type */
- bool via_populate; /* coerce result using json_populate_type()? */
- bool via_io; /* coerce result using type input function? */
- Oid collation; /* collation for coercion via I/O or populate */
-} JsonCoercion;
-
-/*
- * JsonItemCoercions -
- * expressions for coercion from SQL/JSON item types directly to the
- * output SQL type
- */
-typedef struct JsonItemCoercions
-{
- NodeTag type;
- JsonCoercion *null;
- JsonCoercion *string;
- JsonCoercion *numeric;
- JsonCoercion *boolean;
- JsonCoercion *date;
- JsonCoercion *time;
- JsonCoercion *timetz;
- JsonCoercion *timestamp;
- JsonCoercion *timestamptz;
- JsonCoercion *composite; /* arrays and objects */
-} JsonItemCoercions;
-
/*
* JsonExpr -
* transformed representation of JSON_VALUE(), JSON_QUERY(), JSON_EXISTS()
@@ -1593,7 +1560,6 @@ typedef struct JsonExpr
Expr xpr;
JsonExprOp op; /* json function ID */
Node *formatted_expr; /* formatted context item expression */
- JsonCoercion *result_coercion; /* resulting coercion to RETURNING type */
JsonFormat *format; /* context item format (JSON/JSONB) */
Node *path_spec; /* JSON path specification expression */
List *passing_names; /* PASSING argument names */
@@ -1601,8 +1567,8 @@ typedef struct JsonExpr
JsonReturning *returning; /* RETURNING clause type/format info */
JsonBehavior *on_empty; /* ON EMPTY behavior */
JsonBehavior *on_error; /* ON ERROR behavior */
- JsonItemCoercions *coercions; /* coercions for JSON_VALUE */
JsonWrapper wrapper; /* WRAPPER for JSON_QUERY */
+ Oid collation; /* OID of collation, or InvalidOid if none */
bool omit_quotes; /* KEEP/OMIT QUOTES for JSON_QUERY */
int location; /* token location, or -1 if unknown */
} JsonExpr;
diff --git a/src/include/optimizer/clauses.h b/src/include/optimizer/clauses.h
index 6c5203dc448..4d61b88c161 100644
--- a/src/include/optimizer/clauses.h
+++ b/src/include/optimizer/clauses.h
@@ -55,4 +55,6 @@ extern Query *inline_set_returning_function(PlannerInfo *root,
extern Bitmapset *pull_paramids(Expr *expr);
+extern bool expr_can_throw_errors(Node *expr);
+
#endif /* CLAUSES_H */
diff --git a/src/include/utils/jsonpath.h b/src/include/utils/jsonpath.h
index 8e79b8dc9f0..e9134b7eab4 100644
--- a/src/include/utils/jsonpath.h
+++ b/src/include/utils/jsonpath.h
@@ -177,7 +177,8 @@ extern bool jspGetBool(JsonPathItem *v);
extern char *jspGetString(JsonPathItem *v, int32 *len);
extern bool jspGetArraySubscript(JsonPathItem *v, JsonPathItem *from,
JsonPathItem *to, int i);
-extern bool jspIsMutable(JsonPath *path, List *varnames, List *varexprs);
+extern bool jspIsMutable(JsonPath *path, List *varnames, List *varexprs,
+ bool *returns_datetime);
extern const char *jspOperationName(JsonPathItemType type);
diff --git a/src/test/regress/expected/jsonb_sqljson.out b/src/test/regress/expected/jsonb_sqljson.out
index ef496110af3..0e07bbc858f 100644
--- a/src/test/regress/expected/jsonb_sqljson.out
+++ b/src/test/regress/expected/jsonb_sqljson.out
@@ -184,11 +184,11 @@ SELECT JSON_EXISTS(jsonb '1', 'strict $[1]' RETURNING text FALSE ON ERROR);
(1 row)
SELECT JSON_EXISTS(jsonb '1', '$[0]' RETURNING jsonb);
-ERROR: cannot cast type boolean to jsonb
+ERROR: returning type jsonb is not supported in JSON_EXISTS()
LINE 1: SELECT JSON_EXISTS(jsonb '1', '$[0]' RETURNING jsonb);
^
SELECT JSON_EXISTS(jsonb '1', '$[0]' RETURNING float4);
-ERROR: cannot cast type boolean to real
+ERROR: returning type real is not supported in JSON_EXISTS()
LINE 1: SELECT JSON_EXISTS(jsonb '1', '$[0]' RETURNING float4);
^
-- JSON_VALUE
@@ -242,7 +242,9 @@ SELECT JSON_VALUE(jsonb '123', '$' RETURNING text);
/* jsonb bytea ??? */
SELECT JSON_VALUE(jsonb '123', '$' RETURNING bytea ERROR ON ERROR);
-ERROR: SQL/JSON item cannot be cast to target type
+ERROR: returning type bytea is not supported in JSON_VALUE()
+LINE 2: SELECT JSON_VALUE(jsonb '123', '$' RETURNING bytea ERROR ON ...
+ ^
SELECT JSON_VALUE(jsonb '1.23', '$');
json_value
------------
@@ -349,14 +351,129 @@ SELECT JSON_VALUE(jsonb '"2017-02-20"', '$' RETURNING date) + 9;
03-01-2017
(1 row)
--- Test NULL checks execution in domain types
+SELECT JSON_VALUE(jsonb '1.23', '$' RETURNING float4);
+ json_value
+------------
+ 1.23
+(1 row)
+
+SELECT JSON_VALUE(jsonb '"1.23"', '$' RETURNING float4);
+ json_value
+------------
+ 1.23
+(1 row)
+
+SELECT JSON_VALUE(jsonb '"nan"', '$' RETURNING float4);
+ json_value
+------------
+ NaN
+(1 row)
+
+SELECT JSON_VALUE(jsonb '"-inf"', '$' RETURNING float4);
+ json_value
+------------
+ -Infinity
+(1 row)
+
+SELECT JSON_VALUE(jsonb '"err"', '$' RETURNING float4 DEFAULT -1 ON ERROR);
+ json_value
+------------
+ -1
+(1 row)
+
+SELECT JSON_VALUE(jsonb '"err"', '$' RETURNING float4 ERROR ON ERROR);
+ERROR: invalid input syntax for type real: "err"
+SELECT JSON_VALUE(jsonb '1.23', '$' RETURNING float8);
+ json_value
+------------
+ 1.23
+(1 row)
+
+SELECT JSON_VALUE(jsonb '"1.23"', '$' RETURNING float8);
+ json_value
+------------
+ 1.23
+(1 row)
+
+SELECT JSON_VALUE(jsonb '"nan"', '$' RETURNING float8);
+ json_value
+------------
+ NaN
+(1 row)
+
+SELECT JSON_VALUE(jsonb '"-inf"', '$' RETURNING float8);
+ json_value
+------------
+ -Infinity
+(1 row)
+
+SELECT JSON_VALUE(jsonb '"err"', '$' RETURNING float8 DEFAULT -1 ON ERROR);
+ json_value
+------------
+ -1
+(1 row)
+
+SELECT JSON_VALUE(jsonb '"err"', '$' RETURNING float8 ERROR ON ERROR);
+ERROR: invalid input syntax for type double precision: "err"
+SELECT JSON_VALUE(jsonb '1.23', '$' RETURNING numeric);
+ json_value
+------------
+ 1.23
+(1 row)
+
+SELECT JSON_VALUE(jsonb '"1.23"', '$' RETURNING numeric);
+ json_value
+------------
+ 1.23
+(1 row)
+
+SELECT JSON_VALUE(jsonb '"nan"', '$' RETURNING numeric);
+ json_value
+------------
+ NaN
+(1 row)
+
+SELECT JSON_VALUE(jsonb '"-inf"', '$' RETURNING numeric);
+ json_value
+------------
+ -Infinity
+(1 row)
+
+SELECT JSON_VALUE(jsonb '"err"', '$' RETURNING numeric DEFAULT -1 ON ERROR);
+ json_value
+------------
+ -1
+(1 row)
+
+SELECT JSON_VALUE(jsonb '"err"', '$' RETURNING numeric ERROR ON ERROR);
+ERROR: invalid input syntax for type numeric: "err"
+-- 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);
+ERROR: returning type sqljsonb_json_not_null is not supported in JSON_VALUE()
+LINE 1: SELECT JSON_VALUE(jsonb '"1"', '$' RETURNING sqljsonb_json_n...
+ ^
+SELECT JSON_VALUE(jsonb '"1"', '$' RETURNING sqljsonb_jsonb_not_null);
+ERROR: returning type sqljsonb_jsonb_not_null is not supported in JSON_VALUE()
+LINE 1: SELECT JSON_VALUE(jsonb '"1"', '$' RETURNING sqljsonb_jsonb_...
+ ^
+-- 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
+ERROR: returning type sqljsonb_int_not_null is not supported in JSON_VALUE()
+LINE 1: SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_int_no...
+ ^
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
+ERROR: returning type sqljsonb_int_not_null is not supported in JSON_VALUE()
+LINE 1: SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_int_no...
+ ^
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
+ERROR: returning type sqljsonb_int_not_null is not supported in JSON_VALUE()
+LINE 1: SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_int_no...
+ ^
+-- Test returning of non-scalar items
SELECT JSON_VALUE(jsonb '[]', '$');
json_value
------------
@@ -478,11 +595,9 @@ SELECT JSON_VALUE(jsonb 'null', '$a' PASSING point ' (1, 2 )' AS a);
(1 row)
SELECT JSON_VALUE(jsonb 'null', '$a' PASSING point ' (1, 2 )' AS a RETURNING point);
- json_value
-------------
- (1,2)
-(1 row)
-
+ERROR: returning type point is not supported in JSON_VALUE()
+LINE 1: SELECT JSON_VALUE(jsonb 'null', '$a' PASSING point ' (1, 2 )...
+ ^
-- Test timestamptz passing and output
SELECT JSON_VALUE(jsonb 'null', '$ts' PASSING timestamptz '2018-02-21 12:34:56 +10' AS ts);
json_value
@@ -514,6 +629,36 @@ 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);
+ERROR: unsafe DEFAULT ON EMPTY expressions are not supported
+LINE 2: DEFAULT 1 / x ON EMPTY
+ ^
+HINT: Use ERROR ON ERROR clause or try to simplify expression into constant-like form.
+SELECT JSON_VALUE('1'::jsonb, '$.x' RETURNING int DEFAULT -1 ON EMPTY);
+ json_value
+------------
+ -1
+(1 row)
+
+SELECT JSON_VALUE('1'::jsonb, '$.x' RETURNING int DEFAULT '-1' ON EMPTY);
+ json_value
+------------
+ -1
+(1 row)
+
+SELECT JSON_VALUE('1'::jsonb, '$.x' RETURNING int DEFAULT 'err' ON EMPTY);
+ERROR: invalid input syntax for type integer: "err"
+LINE 1: ...SON_VALUE('1'::jsonb, '$.x' RETURNING int DEFAULT 'err' ON E...
+ ^
+SELECT JSON_VALUE('1'::jsonb, '$.x' RETURNING int DEFAULT -1::float ON EMPTY);
+ERROR: unsafe DEFAULT ON EMPTY expressions are not supported
+LINE 1: ...SON_VALUE('1'::jsonb, '$.x' RETURNING int DEFAULT -1::float ...
+ ^
+HINT: Use ERROR ON ERROR clause or try to simplify expression into constant-like form.
-- JSON_QUERY
SELECT
JSON_QUERY(js, '$'),
@@ -852,49 +997,35 @@ FROM
CREATE TYPE sqljsonb_rec AS (a int, t text, js json, jb jsonb, jsa json[]);
CREATE TYPE sqljsonb_reca AS (reca sqljsonb_rec[]);
SELECT JSON_QUERY(jsonb '[{"a": 1, "b": "foo", "t": "aaa", "js": [1, "2", {}], "jb": {"x": [1, "2", {}]}}, {"a": 2}]', '$[0]' RETURNING sqljsonb_rec);
- json_query
------------------------------------------------------
- (1,aaa,"[1, ""2"", {}]","{""x"": [1, ""2"", {}]}",)
-(1 row)
-
+ERROR: returning type sqljsonb_rec is not supported in JSON_QUERY()
+LINE 1: SELECT JSON_QUERY(jsonb '[{"a": 1, "b": "foo", "t": "aaa", "...
+ ^
SELECT * FROM unnest((JSON_QUERY(jsonb '{"jsa": [{"a": 1, "b": ["foo"]}, {"a": 2, "c": {}}, 123]}', '$' RETURNING sqljsonb_rec)).jsa);
- unnest
-------------------------
- {"a": 1, "b": ["foo"]}
- {"a": 2, "c": {}}
- 123
-(3 rows)
-
+ERROR: returning type sqljsonb_rec is not supported in JSON_QUERY()
+LINE 1: SELECT * FROM unnest((JSON_QUERY(jsonb '{"jsa": [{"a": 1, "...
+ ^
SELECT * FROM unnest((JSON_QUERY(jsonb '{"reca": [{"a": 1, "t": ["foo", []]}, {"a": 2, "jb": [{}, true]}]}', '$' RETURNING sqljsonb_reca)).reca);
- a | t | js | jb | jsa
----+-------------+----+------------+-----
- 1 | ["foo", []] | | |
- 2 | | | [{}, true] |
-(2 rows)
-
+ERROR: returning type sqljsonb_reca is not supported in JSON_QUERY()
+LINE 1: SELECT * FROM unnest((JSON_QUERY(jsonb '{"reca": [{"a": 1, "...
+ ^
-- Extension: array types returning
SELECT JSON_QUERY(jsonb '[1,2,null,"3"]', '$[*]' RETURNING int[] WITH WRAPPER);
- json_query
---------------
- {1,2,NULL,3}
-(1 row)
-
+ERROR: returning type integer[] is not supported in JSON_QUERY()
+LINE 1: SELECT JSON_QUERY(jsonb '[1,2,null,"3"]', '$[*]' RETURNING i...
+ ^
SELECT * FROM unnest(JSON_QUERY(jsonb '[{"a": 1, "t": ["foo", []]}, {"a": 2, "jb": [{}, true]}]', '$' RETURNING sqljsonb_rec[]));
- a | t | js | jb | jsa
----+-------------+----+------------+-----
- 1 | ["foo", []] | | |
- 2 | | | [{}, true] |
-(2 rows)
-
+ERROR: returning type sqljsonb_rec[] is not supported in JSON_QUERY()
+LINE 1: SELECT * FROM unnest(JSON_QUERY(jsonb '[{"a": 1, "t": ["foo"...
+ ^
-- Extension: domain types returning
SELECT JSON_QUERY(jsonb '{"a": 1}', '$.a' RETURNING sqljsonb_int_not_null);
- json_query
-------------
- 1
-(1 row)
-
+ERROR: returning type sqljsonb_int_not_null is not supported in JSON_QUERY()
+LINE 1: SELECT JSON_QUERY(jsonb '{"a": 1}', '$.a' RETURNING sqljsonb...
+ ^
SELECT JSON_QUERY(jsonb '{"a": 1}', '$.b' RETURNING sqljsonb_int_not_null);
-ERROR: domain sqljsonb_int_not_null does not allow null values
+ERROR: returning type sqljsonb_int_not_null is not supported in JSON_QUERY()
+LINE 1: SELECT JSON_QUERY(jsonb '{"a": 1}', '$.b' RETURNING sqljsonb...
+ ^
-- Test timestamptz passing and output
SELECT JSON_QUERY(jsonb 'null', '$ts' PASSING timestamptz '2018-02-21 12:34:56 +10' AS ts);
json_query
@@ -1019,6 +1150,25 @@ ERROR: functions in index expression must be marked IMMUTABLE
CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$[1, 0 to $.a ? (@.datetime() == $x)]' PASSING '12:34'::time AS x));
ERROR: functions in index expression must be marked IMMUTABLE
CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$[1, $.a ? (@.datetime("HH:MI") == $x)]' PASSING '12:34'::time AS x));
+CREATE INDEX ON test_jsonb_mutability (JSON_VALUE(js, '$' RETURNING int)); -- immutable
+CREATE INDEX ON test_jsonb_mutability (JSON_VALUE(js, '$' RETURNING date));
+ERROR: functions in index expression must be marked IMMUTABLE
+CREATE INDEX ON test_jsonb_mutability (JSON_VALUE(js, '$' RETURNING time));
+ERROR: functions in index expression must be marked IMMUTABLE
+CREATE INDEX ON test_jsonb_mutability (JSON_VALUE(js, '$' RETURNING timetz));
+ERROR: functions in index expression must be marked IMMUTABLE
+CREATE INDEX ON test_jsonb_mutability (JSON_VALUE(js, '$' RETURNING timestamp));
+ERROR: functions in index expression must be marked IMMUTABLE
+CREATE INDEX ON test_jsonb_mutability (JSON_VALUE(js, '$' RETURNING timestamptz));
+ERROR: functions in index expression must be marked IMMUTABLE
+CREATE INDEX ON test_jsonb_mutability (JSON_VALUE(js, '$.datetime()'));
+ERROR: functions in index expression must be marked IMMUTABLE
+CREATE INDEX ON test_jsonb_mutability (JSON_VALUE(js, '$.datetime()[*][1,2].**'));
+ERROR: functions in index expression must be marked IMMUTABLE
+CREATE INDEX ON test_jsonb_mutability (JSON_VALUE(js, '$x' PASSING '2022-08-25'::date AS x));
+ERROR: functions in index expression must be marked IMMUTABLE
+CREATE INDEX ON test_jsonb_mutability (JSON_VALUE(js, '$.datetime()' RETURNING int)); -- immutable
+CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.datetime()')); -- immutable
DROP TABLE test_jsonb_mutability;
-- JSON_TABLE
-- Should fail (JSON_TABLE can be used only in FROM clause)
@@ -1048,7 +1198,6 @@ SELECT * FROM JSON_TABLE(jsonb '123', '$'
(1 row)
-- JSON_TABLE: basic functionality
-CREATE DOMAIN jsonb_test_domain AS text CHECK (value <> 'foo');
SELECT *
FROM
(VALUES
@@ -1069,7 +1218,6 @@ FROM
"char(4)" char(4) PATH '$',
"bool" bool PATH '$',
"numeric" numeric PATH '$',
- "domain" jsonb_test_domain PATH '$',
js json PATH '$',
jb jsonb PATH '$',
jst text FORMAT JSON PATH '$',
@@ -1085,29 +1233,26 @@ FROM
exists4 text EXISTS PATH 'strict $.aaa' FALSE ON ERROR,
js2 json PATH '$',
jsb2w jsonb PATH '$' WITH WRAPPER,
- jsb2q jsonb PATH '$' OMIT QUOTES,
- ia int[] PATH '$',
- ta text[] PATH '$',
- jba jsonb[] PATH '$'
+ jsb2q jsonb PATH '$' OMIT QUOTES
)
) jt
ON true;
- js | id | id2 | int | text | char(4) | bool | numeric | domain | js | jb | jst | jsc | jsv | jsb | jsbq | aaa | aaa1 | exists1 | exists2 | exists3 | exists4 | js2 | jsb2w | jsb2q | ia | ta | jba
----------------------------------------------------------------------------------------+----+-----+-----+---------+---------+------+---------+---------+--------------+--------------+--------------+------+------+--------------+--------------+-----+------+---------+---------+---------+---------+--------------+----------------+--------------+----+----+-----
- 1 | 1 | 1 | 1 | 1 | 1 | | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | | | f | 0 | | false | 1 | [1] | 1 | | |
- [] | | | | | | | | | | | | | | | | | | | | | | | | | | |
- {} | 1 | 1 | | | | | | | {} | {} | {} | {} | {} | {} | {} | | | f | 0 | | false | {} | [{}] | {} | | |
- [1, 1.23, "2", "aaaaaaa", "foo", null, false, true, {"aaa": 123}, "[1,2]", "\"str\""] | 1 | 1 | 1 | 1 | 1 | | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | | | f | 0 | | false | 1 | [1] | 1 | | |
- [1, 1.23, "2", "aaaaaaa", "foo", null, false, true, {"aaa": 123}, "[1,2]", "\"str\""] | 2 | 2 | 1 | 1.23 | 1.23 | | 1.23 | 1.23 | 1.23 | 1.23 | 1.23 | 1.23 | 1.23 | 1.23 | 1.23 | | | f | 0 | | false | 1.23 | [1.23] | 1.23 | | |
- [1, 1.23, "2", "aaaaaaa", "foo", null, false, true, {"aaa": 123}, "[1,2]", "\"str\""] | 3 | 3 | 2 | 2 | 2 | | 2 | 2 | "2" | "2" | "2" | "2" | "2" | "2" | 2 | | | f | 0 | | false | "2" | ["2"] | 2 | | |
- [1, 1.23, "2", "aaaaaaa", "foo", null, false, true, {"aaa": 123}, "[1,2]", "\"str\""] | 4 | 4 | | aaaaaaa | aaaa | | | aaaaaaa | "aaaaaaa" | "aaaaaaa" | "aaaaaaa" | "aaa | "aaa | "aaaaaaa" | | | | f | 0 | | false | "aaaaaaa" | ["aaaaaaa"] | | | |
- [1, 1.23, "2", "aaaaaaa", "foo", null, false, true, {"aaa": 123}, "[1,2]", "\"str\""] | 5 | 5 | | foo | foo | | | | "foo" | "foo" | "foo" | "foo | "foo | "foo" | | | | f | 0 | | false | "foo" | ["foo"] | | | |
- [1, 1.23, "2", "aaaaaaa", "foo", null, false, true, {"aaa": 123}, "[1,2]", "\"str\""] | 6 | 6 | | | | | | | null | null | null | null | null | null | null | | | f | 0 | | false | null | [null] | null | | |
- [1, 1.23, "2", "aaaaaaa", "foo", null, false, true, {"aaa": 123}, "[1,2]", "\"str\""] | 7 | 7 | 0 | false | fals | f | | false | false | false | false | fals | fals | false | false | | | f | 0 | | false | false | [false] | false | | |
- [1, 1.23, "2", "aaaaaaa", "foo", null, false, true, {"aaa": 123}, "[1,2]", "\"str\""] | 8 | 8 | 1 | true | true | t | | true | true | true | true | true | true | true | true | | | f | 0 | | false | true | [true] | true | | |
- [1, 1.23, "2", "aaaaaaa", "foo", null, false, true, {"aaa": 123}, "[1,2]", "\"str\""] | 9 | 9 | | | | | | | {"aaa": 123} | {"aaa": 123} | {"aaa": 123} | {"aa | {"aa | {"aaa": 123} | {"aaa": 123} | 123 | 123 | t | 1 | 1 | true | {"aaa": 123} | [{"aaa": 123}] | {"aaa": 123} | | |
- [1, 1.23, "2", "aaaaaaa", "foo", null, false, true, {"aaa": 123}, "[1,2]", "\"str\""] | 10 | 10 | | [1,2] | [1,2 | | | [1,2] | "[1,2]" | "[1,2]" | "[1,2]" | "[1, | "[1, | "[1,2]" | [1, 2] | | | f | 0 | | false | "[1,2]" | ["[1,2]"] | [1, 2] | | |
- [1, 1.23, "2", "aaaaaaa", "foo", null, false, true, {"aaa": 123}, "[1,2]", "\"str\""] | 11 | 11 | | "str" | "str | | | "str" | "\"str\"" | "\"str\"" | "\"str\"" | "\"s | "\"s | "\"str\"" | "str" | | | f | 0 | | false | "\"str\"" | ["\"str\""] | "str" | | |
+ js | id | id2 | int | text | char(4) | bool | numeric | js | jb | jst | jsc | jsv | jsb | jsbq | aaa | aaa1 | exists1 | exists2 | exists3 | exists4 | js2 | jsb2w | jsb2q
+---------------------------------------------------------------------------------------+----+-----+-----+---------+---------+------+---------+--------------+--------------+--------------+------+------+--------------+--------------+-----+------+---------+---------+---------+---------+--------------+----------------+--------------
+ 1 | 1 | 1 | 1 | 1 | 1 | | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | | | f | 0 | | false | 1 | [1] | 1
+ [] | | | | | | | | | | | | | | | | | | | | | | |
+ {} | 1 | 1 | | | | | | {} | {} | {} | {} | {} | {} | {} | | | f | 0 | | false | {} | [{}] | {}
+ [1, 1.23, "2", "aaaaaaa", "foo", null, false, true, {"aaa": 123}, "[1,2]", "\"str\""] | 1 | 1 | 1 | 1 | 1 | | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | | | f | 0 | | false | 1 | [1] | 1
+ [1, 1.23, "2", "aaaaaaa", "foo", null, false, true, {"aaa": 123}, "[1,2]", "\"str\""] | 2 | 2 | 1 | 1.23 | 1.23 | | 1.23 | 1.23 | 1.23 | 1.23 | 1.23 | 1.23 | 1.23 | 1.23 | | | f | 0 | | false | 1.23 | [1.23] | 1.23
+ [1, 1.23, "2", "aaaaaaa", "foo", null, false, true, {"aaa": 123}, "[1,2]", "\"str\""] | 3 | 3 | 2 | 2 | 2 | | 2 | "2" | "2" | "2" | "2" | "2" | "2" | 2 | | | f | 0 | | false | "2" | ["2"] | 2
+ [1, 1.23, "2", "aaaaaaa", "foo", null, false, true, {"aaa": 123}, "[1,2]", "\"str\""] | 4 | 4 | | aaaaaaa | aaaa | | | "aaaaaaa" | "aaaaaaa" | "aaaaaaa" | "aaa | "aaa | "aaaaaaa" | | | | f | 0 | | false | "aaaaaaa" | ["aaaaaaa"] |
+ [1, 1.23, "2", "aaaaaaa", "foo", null, false, true, {"aaa": 123}, "[1,2]", "\"str\""] | 5 | 5 | | foo | foo | | | "foo" | "foo" | "foo" | "foo | "foo | "foo" | | | | f | 0 | | false | "foo" | ["foo"] |
+ [1, 1.23, "2", "aaaaaaa", "foo", null, false, true, {"aaa": 123}, "[1,2]", "\"str\""] | 6 | 6 | | | | | | null | null | null | null | null | null | null | | | f | 0 | | false | null | [null] | null
+ [1, 1.23, "2", "aaaaaaa", "foo", null, false, true, {"aaa": 123}, "[1,2]", "\"str\""] | 7 | 7 | 0 | false | fals | f | | false | false | false | fals | fals | false | false | | | f | 0 | | false | false | [false] | false
+ [1, 1.23, "2", "aaaaaaa", "foo", null, false, true, {"aaa": 123}, "[1,2]", "\"str\""] | 8 | 8 | 1 | true | true | t | | true | true | true | true | true | true | true | | | f | 0 | | false | true | [true] | true
+ [1, 1.23, "2", "aaaaaaa", "foo", null, false, true, {"aaa": 123}, "[1,2]", "\"str\""] | 9 | 9 | | | | | | {"aaa": 123} | {"aaa": 123} | {"aaa": 123} | {"aa | {"aa | {"aaa": 123} | {"aaa": 123} | 123 | 123 | t | 1 | 1 | true | {"aaa": 123} | [{"aaa": 123}] | {"aaa": 123}
+ [1, 1.23, "2", "aaaaaaa", "foo", null, false, true, {"aaa": 123}, "[1,2]", "\"str\""] | 10 | 10 | | [1,2] | [1,2 | | | "[1,2]" | "[1,2]" | "[1,2]" | "[1, | "[1, | "[1,2]" | [1, 2] | | | f | 0 | | false | "[1,2]" | ["[1,2]"] | [1, 2]
+ [1, 1.23, "2", "aaaaaaa", "foo", null, false, true, {"aaa": 123}, "[1,2]", "\"str\""] | 11 | 11 | | "str" | "str | | | "\"str\"" | "\"str\"" | "\"str\"" | "\"s | "\"s | "\"str\"" | "str" | | | f | 0 | | false | "\"str\"" | ["\"str\""] | "str"
(14 rows)
-- JSON_TABLE: Test backward parsing
@@ -1123,7 +1268,6 @@ SELECT * FROM
"char(4)" char(4) PATH '$',
"bool" bool PATH '$',
"numeric" numeric PATH '$',
- "domain" jsonb_test_domain PATH '$',
js json PATH '$',
jb jsonb PATH '$',
jst text FORMAT JSON PATH '$',
@@ -1139,9 +1283,6 @@ SELECT * FROM
js2 json PATH '$',
jsb2w jsonb PATH '$' WITH WRAPPER,
jsb2q jsonb PATH '$' OMIT QUOTES,
- ia int[] PATH '$',
- ta text[] PATH '$',
- jba jsonb[] PATH '$',
NESTED PATH '$[1]' AS p1 COLUMNS (
a1 int,
NESTED PATH '$[*]' AS "p1 1" COLUMNS (
@@ -1168,7 +1309,6 @@ CREATE OR REPLACE VIEW public.jsonb_table_view AS
"json_table"."char(4)",
"json_table".bool,
"json_table"."numeric",
- "json_table".domain,
"json_table".js,
"json_table".jb,
"json_table".jst,
@@ -1184,9 +1324,6 @@ CREATE OR REPLACE VIEW public.jsonb_table_view AS
"json_table".js2,
"json_table".jsb2w,
"json_table".jsb2q,
- "json_table".ia,
- "json_table".ta,
- "json_table".jba,
"json_table".a1,
"json_table".b1,
"json_table".a11,
@@ -1205,7 +1342,6 @@ CREATE OR REPLACE VIEW public.jsonb_table_view AS
"char(4)" character(4) PATH '$',
bool boolean PATH '$',
"numeric" numeric PATH '$',
- domain jsonb_test_domain PATH '$',
js json PATH '$',
jb jsonb PATH '$',
jst text FORMAT JSON PATH '$',
@@ -1221,9 +1357,6 @@ CREATE OR REPLACE VIEW public.jsonb_table_view AS
js2 json PATH '$',
jsb2w jsonb PATH '$' WITH UNCONDITIONAL WRAPPER,
jsb2q jsonb PATH '$' OMIT QUOTES,
- ia integer[] PATH '$',
- ta text[] PATH '$',
- jba jsonb[] PATH '$',
NESTED PATH '$[1]' AS p1
COLUMNS (
a1 integer PATH '$."a1"',
@@ -1248,15 +1381,14 @@ CREATE OR REPLACE VIEW public.jsonb_table_view AS
PLAN (json_table_path_1 OUTER ((p1 OUTER "p1 1") UNION (p2 OUTER ("p2:1" UNION p22))))
)
EXPLAIN (COSTS OFF, VERBOSE) SELECT * FROM jsonb_table_view;
- QUERY PLAN
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ QUERY PLAN
+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Table Function Scan on "json_table"
- Output: "json_table".id, "json_table".id2, "json_table"."int", "json_table".text, "json_table"."char(4)", "json_table".bool, "json_table"."numeric", "json_table".domain, "json_table".js, "json_table".jb, "json_table".jst, "json_table".jsc, "json_table".jsv, "json_table".jsb, "json_table".jsbq, "json_table".aaa, "json_table".aaa1, "json_table".exists1, "json_table".exists2, "json_table".exists3, "json_table".js2, "json_table".jsb2w, "json_table".jsb2q, "json_table".ia, "json_table".ta, "json_table".jba, "json_table".a1, "json_table".b1, "json_table".a11, "json_table".a21, "json_table".a22
- Table Function Call: JSON_TABLE('null'::jsonb, '$[*]' AS json_table_path_1 PASSING 3 AS a, '"foo"'::jsonb AS "b c" COLUMNS (id FOR ORDINALITY, id2 FOR ORDINALITY, "int" integer PATH '$', text text PATH '$', "char(4)" character(4) PATH '$', bool boolean PATH '$', "numeric" numeric PATH '$', domain jsonb_test_domain PATH '$', js json PATH '$', jb jsonb PATH '$', jst text FORMAT JSON PATH '$', jsc character(4) FORMAT JSON PATH '$', jsv character varying(4) FORMAT JSON PATH '$', jsb jsonb PATH '$', jsbq jsonb PATH '$' OMIT QUOTES, aaa integer PATH '$."aaa"', aaa1 integer PATH '$."aaa"', exists1 boolean EXISTS PATH '$."aaa"', exists2 integer EXISTS PATH '$."aaa"' TRUE ON ERROR, exists3 text EXISTS PATH 'strict $."aaa"' UNKNOWN ON ERROR, js2 json PATH '$', jsb2w jsonb PATH '$' WITH UNCONDITIONAL WRAPPER, jsb2q jsonb PATH '$' OMIT QUOTES, ia integer[] PATH '$', ta text[] PATH '$', jba jsonb[] PATH '$', NESTED PATH '$[1]' AS p1 COLUMNS (a1 integer PATH '$."a1"', b1 text PATH '$."b1"', NESTED PATH '$[*]' AS "p1 1" COLUMNS (a11 text PATH '$."a11"')), NESTED PATH '$[2]' AS p2 COLUMNS ( NESTED PATH '$[*]' AS "p2:1" COLUMNS (a21 text PATH '$."a21"'), NESTED PATH '$[*]' AS p22 COLUMNS (a22 text PATH '$."a22"'))) PLAN (json_table_path_1 OUTER ((p1 OUTER "p1 1") UNION (p2 OUTER ("p2:1" UNION p22)))))
+ Output: "json_table".id, "json_table".id2, "json_table"."int", "json_table".text, "json_table"."char(4)", "json_table".bool, "json_table"."numeric", "json_table".js, "json_table".jb, "json_table".jst, "json_table".jsc, "json_table".jsv, "json_table".jsb, "json_table".jsbq, "json_table".aaa, "json_table".aaa1, "json_table".exists1, "json_table".exists2, "json_table".exists3, "json_table".js2, "json_table".jsb2w, "json_table".jsb2q, "json_table".a1, "json_table".b1, "json_table".a11, "json_table".a21, "json_table".a22
+ Table Function Call: JSON_TABLE('null'::jsonb, '$[*]' AS json_table_path_1 PASSING 3 AS a, '"foo"'::jsonb AS "b c" COLUMNS (id FOR ORDINALITY, id2 FOR ORDINALITY, "int" integer PATH '$', text text PATH '$', "char(4)" character(4) PATH '$', bool boolean PATH '$', "numeric" numeric PATH '$', js json PATH '$', jb jsonb PATH '$', jst text FORMAT JSON PATH '$', jsc character(4) FORMAT JSON PATH '$', jsv character varying(4) FORMAT JSON PATH '$', jsb jsonb PATH '$', jsbq jsonb PATH '$' OMIT QUOTES, aaa integer PATH '$."aaa"', aaa1 integer PATH '$."aaa"', exists1 boolean EXISTS PATH '$."aaa"', exists2 integer EXISTS PATH '$."aaa"' TRUE ON ERROR, exists3 text EXISTS PATH 'strict $."aaa"' UNKNOWN ON ERROR, js2 json PATH '$', jsb2w jsonb PATH '$' WITH UNCONDITIONAL WRAPPER, jsb2q jsonb PATH '$' OMIT QUOTES, NESTED PATH '$[1]' AS p1 COLUMNS (a1 integer PATH '$."a1"', b1 text PATH '$."b1"', NESTED PATH '$[*]' AS "p1 1" COLUMNS (a11 text PATH '$."a11"')), NESTED PATH '$[2]' AS p2 COLUMNS ( NESTED PATH '$[*]' AS "p2:1" COLUMNS (a21 text PATH '$."a21"'), NESTED PATH '$[*]' AS p22 COLUMNS (a22 text PATH '$."a22"'))) PLAN (json_table_path_1 OUTER ((p1 OUTER "p1 1") UNION (p2 OUTER ("p2:1" UNION p22)))))
(3 rows)
DROP VIEW jsonb_table_view;
-DROP DOMAIN jsonb_test_domain;
-- JSON_TABLE: ON EMPTY/ON ERROR behavior
SELECT *
FROM
@@ -1318,15 +1450,15 @@ SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a int4 EXISTS PATH '$.a'));
(1 row)
SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a int2 EXISTS PATH '$.a'));
-ERROR: cannot cast type boolean to smallint
+ERROR: returning type smallint is not supported in JSON_EXISTS()
LINE 1: ...ELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a int2 EXI...
^
SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a int8 EXISTS PATH '$.a'));
-ERROR: cannot cast type boolean to bigint
+ERROR: returning type bigint is not supported in JSON_EXISTS()
LINE 1: ...ELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a int8 EXI...
^
SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a float4 EXISTS PATH '$.a'));
-ERROR: cannot cast type boolean to real
+ERROR: returning type real is not supported in JSON_EXISTS()
LINE 1: ...ELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a float4 E...
^
SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a char(3) EXISTS PATH '$.a'));
@@ -1336,11 +1468,11 @@ SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a char(3) EXISTS PATH '$.a'))
(1 row)
SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a json EXISTS PATH '$.a'));
-ERROR: cannot cast type boolean to json
+ERROR: returning type json is not supported in JSON_EXISTS()
LINE 1: ...ELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a json EXI...
^
SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a jsonb EXISTS PATH '$.a'));
-ERROR: cannot cast type boolean to jsonb
+ERROR: returning type jsonb is not supported in JSON_EXISTS()
LINE 1: ...ELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a jsonb EX...
^
-- JSON_TABLE: nested paths and plans
@@ -2102,11 +2234,14 @@ set parallel_leader_participation = off;
-- Should be non-parallel due to subtransactions
EXPLAIN (COSTS OFF)
SELECT sum(JSON_VALUE(js, '$' RETURNING numeric)) FROM test_parallel_jsonb_value;
- QUERY PLAN
----------------------------------------------
- Aggregate
- -> Seq Scan on test_parallel_jsonb_value
-(2 rows)
+ QUERY PLAN
+------------------------------------------------------------------
+ Finalize Aggregate
+ -> Gather
+ Workers Planned: 4
+ -> Partial Aggregate
+ -> Parallel Seq Scan on test_parallel_jsonb_value
+(5 rows)
SELECT sum(JSON_VALUE(js, '$' RETURNING numeric)) FROM test_parallel_jsonb_value;
sum
diff --git a/src/test/regress/sql/jsonb_sqljson.sql b/src/test/regress/sql/jsonb_sqljson.sql
index fff25374808..43a194ad09f 100644
--- a/src/test/regress/sql/jsonb_sqljson.sql
+++ b/src/test/regress/sql/jsonb_sqljson.sql
@@ -82,12 +82,43 @@ 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
+SELECT JSON_VALUE(jsonb '1.23', '$' RETURNING float4);
+SELECT JSON_VALUE(jsonb '"1.23"', '$' RETURNING float4);
+SELECT JSON_VALUE(jsonb '"nan"', '$' RETURNING float4);
+SELECT JSON_VALUE(jsonb '"-inf"', '$' RETURNING float4);
+SELECT JSON_VALUE(jsonb '"err"', '$' RETURNING float4 DEFAULT -1 ON ERROR);
+SELECT JSON_VALUE(jsonb '"err"', '$' RETURNING float4 ERROR ON ERROR);
+
+SELECT JSON_VALUE(jsonb '1.23', '$' RETURNING float8);
+SELECT JSON_VALUE(jsonb '"1.23"', '$' RETURNING float8);
+SELECT JSON_VALUE(jsonb '"nan"', '$' RETURNING float8);
+SELECT JSON_VALUE(jsonb '"-inf"', '$' RETURNING float8);
+SELECT JSON_VALUE(jsonb '"err"', '$' RETURNING float8 DEFAULT -1 ON ERROR);
+SELECT JSON_VALUE(jsonb '"err"', '$' RETURNING float8 ERROR ON ERROR);
+
+SELECT JSON_VALUE(jsonb '1.23', '$' RETURNING numeric);
+SELECT JSON_VALUE(jsonb '"1.23"', '$' RETURNING numeric);
+SELECT JSON_VALUE(jsonb '"nan"', '$' RETURNING numeric);
+SELECT JSON_VALUE(jsonb '"-inf"', '$' RETURNING numeric);
+SELECT JSON_VALUE(jsonb '"err"', '$' RETURNING numeric DEFAULT -1 ON ERROR);
+SELECT JSON_VALUE(jsonb '"err"', '$' RETURNING numeric ERROR ON ERROR);
+
+-- 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);
+
+-- 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 NULL ON ERROR);
SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_int_not_null DEFAULT NULL ON ERROR);
+
+-- Test returning of non-scalar items
SELECT JSON_VALUE(jsonb '[]', '$');
SELECT JSON_VALUE(jsonb '[]', '$' ERROR ON ERROR);
SELECT JSON_VALUE(jsonb '{}', '$');
@@ -133,6 +164,17 @@ 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);
+
+SELECT JSON_VALUE('1'::jsonb, '$.x' RETURNING int DEFAULT -1 ON EMPTY);
+SELECT JSON_VALUE('1'::jsonb, '$.x' RETURNING int DEFAULT '-1' ON EMPTY);
+SELECT JSON_VALUE('1'::jsonb, '$.x' RETURNING int DEFAULT 'err' ON EMPTY);
+SELECT JSON_VALUE('1'::jsonb, '$.x' RETURNING int DEFAULT -1::float ON EMPTY);
+
-- JSON_QUERY
SELECT
@@ -318,6 +360,21 @@ CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.datetime("YY-MM-DD") ?
CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$[1, $.a ? (@.datetime() == $x)]' PASSING '12:34'::time AS x));
CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$[1, 0 to $.a ? (@.datetime() == $x)]' PASSING '12:34'::time AS x));
CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$[1, $.a ? (@.datetime("HH:MI") == $x)]' PASSING '12:34'::time AS x));
+
+CREATE INDEX ON test_jsonb_mutability (JSON_VALUE(js, '$' RETURNING int)); -- immutable
+CREATE INDEX ON test_jsonb_mutability (JSON_VALUE(js, '$' RETURNING date));
+CREATE INDEX ON test_jsonb_mutability (JSON_VALUE(js, '$' RETURNING time));
+CREATE INDEX ON test_jsonb_mutability (JSON_VALUE(js, '$' RETURNING timetz));
+CREATE INDEX ON test_jsonb_mutability (JSON_VALUE(js, '$' RETURNING timestamp));
+CREATE INDEX ON test_jsonb_mutability (JSON_VALUE(js, '$' RETURNING timestamptz));
+
+CREATE INDEX ON test_jsonb_mutability (JSON_VALUE(js, '$.datetime()'));
+CREATE INDEX ON test_jsonb_mutability (JSON_VALUE(js, '$.datetime()[*][1,2].**'));
+CREATE INDEX ON test_jsonb_mutability (JSON_VALUE(js, '$x' PASSING '2022-08-25'::date AS x));
+CREATE INDEX ON test_jsonb_mutability (JSON_VALUE(js, '$.datetime()' RETURNING int)); -- immutable
+
+CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.datetime()')); -- immutable
+
DROP TABLE test_jsonb_mutability;
-- JSON_TABLE
@@ -338,8 +395,6 @@ SELECT * FROM JSON_TABLE(jsonb '123', '$'
COLUMNS (item int PATH '$', foo int)) bar;
-- JSON_TABLE: basic functionality
-CREATE DOMAIN jsonb_test_domain AS text CHECK (value <> 'foo');
-
SELECT *
FROM
(VALUES
@@ -360,7 +415,6 @@ FROM
"char(4)" char(4) PATH '$',
"bool" bool PATH '$',
"numeric" numeric PATH '$',
- "domain" jsonb_test_domain PATH '$',
js json PATH '$',
jb jsonb PATH '$',
jst text FORMAT JSON PATH '$',
@@ -377,10 +431,7 @@ FROM
js2 json PATH '$',
jsb2w jsonb PATH '$' WITH WRAPPER,
- jsb2q jsonb PATH '$' OMIT QUOTES,
- ia int[] PATH '$',
- ta text[] PATH '$',
- jba jsonb[] PATH '$'
+ jsb2q jsonb PATH '$' OMIT QUOTES
)
) jt
ON true;
@@ -399,7 +450,6 @@ SELECT * FROM
"char(4)" char(4) PATH '$',
"bool" bool PATH '$',
"numeric" numeric PATH '$',
- "domain" jsonb_test_domain PATH '$',
js json PATH '$',
jb jsonb PATH '$',
jst text FORMAT JSON PATH '$',
@@ -416,9 +466,6 @@ SELECT * FROM
js2 json PATH '$',
jsb2w jsonb PATH '$' WITH WRAPPER,
jsb2q jsonb PATH '$' OMIT QUOTES,
- ia int[] PATH '$',
- ta text[] PATH '$',
- jba jsonb[] PATH '$',
NESTED PATH '$[1]' AS p1 COLUMNS (
a1 int,
@@ -443,7 +490,6 @@ SELECT * FROM
EXPLAIN (COSTS OFF, VERBOSE) SELECT * FROM jsonb_table_view;
DROP VIEW jsonb_table_view;
-DROP DOMAIN jsonb_test_domain;
-- JSON_TABLE: ON EMPTY/ON ERROR behavior
SELECT *
--
2.17.1
v9-0008-Remove-subsidary-ExprStates-in-SQL-JSON-execution.patchtext/x-patch; charset=UTF-8; name=v9-0008-Remove-subsidary-ExprStates-in-SQL-JSON-execution.patchDownload
From 003bfccffc69824e0fb947d11f3f75a7b70fefb4 Mon Sep 17 00:00:00 2001
From: Nikita Glukhov <n.gluhov@postgrespro.ru>
Date: Mon, 29 Aug 2022 22:41:08 +0300
Subject: [PATCH v9 8/9] Remove subsidary ExprStates in SQL/JSON execution
---
src/backend/executor/execExpr.c | 88 ++++++++---
src/backend/executor/execExprInterp.c | 206 ++++++++++++--------------
src/backend/jit/llvm/llvmjit_expr.c | 70 ++++++++-
src/backend/jit/llvm/llvmjit_types.c | 1 +
src/backend/utils/adt/jsonpath_exec.c | 79 +++++++++-
src/include/executor/execExpr.h | 20 ++-
src/include/utils/jsonpath.h | 4 -
7 files changed, 317 insertions(+), 151 deletions(-)
diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
index f50e0d703f8..68227ebee21 100644
--- a/src/backend/executor/execExpr.c
+++ b/src/backend/executor/execExpr.c
@@ -4159,9 +4159,16 @@ static void
ExecInitJsonExpr(JsonExpr *jexpr, ExprState *state, ExprEvalStep *scratch,
Datum *resv, bool *resnull)
{
+ ExprEvalStep *step;
JsonExprState *jsestate;
ListCell *argexprlc;
ListCell *argnamelc;
+ int skip_step_off;
+ int execpath_step_off;
+ int done_step_off;
+ int default_on_empty_step_off = -1;
+ int default_on_error_step_off = -1;
+ int default_on_empty_jump_off = -1;
/* JSON_TABLE preudo-function returns context item as a result */
if (jexpr->op == JSON_TABLE_OP)
@@ -4174,9 +4181,6 @@ ExecInitJsonExpr(JsonExpr *jexpr, ExprState *state, ExprEvalStep *scratch,
jsestate = palloc0(sizeof(JsonExprState));
jsestate->jsexpr = jexpr;
- scratch->opcode = EEOP_JSONEXPR;
- scratch->d.jsonexpr.jsestate = jsestate;
-
ExecInitExprRec((Expr *) jexpr->formatted_expr, state,
&jsestate->formatted_expr.value,
&jsestate->formatted_expr.isnull);
@@ -4185,15 +4189,12 @@ ExecInitJsonExpr(JsonExpr *jexpr, ExprState *state, ExprEvalStep *scratch,
&jsestate->pathspec.value,
&jsestate->pathspec.isnull);
- if (jexpr->on_empty)
- jsestate->default_on_empty =
- ExecInitExpr((Expr *) jexpr->on_empty->default_expr,
- state->parent);
-
- jsestate->default_on_error =
- ExecInitExpr((Expr *) jexpr->on_error->default_expr,
- state->parent);
+ scratch->opcode = EEOP_JSONEXPR_SKIP;
+ scratch->d.jsonexpr_skip.jsestate = jsestate;
+ skip_step_off = state->steps_len;
+ ExprEvalPushStep(state, scratch);
+ /* PASSING args. */
jsestate->args = NIL;
forboth(argexprlc, jexpr->passing_values,
@@ -4206,16 +4207,69 @@ ExecInitJsonExpr(JsonExpr *jexpr, ExprState *state, ExprEvalStep *scratch,
var->name = pstrdup(argname->sval);
var->typid = exprType((Node *) argexpr);
var->typmod = exprTypmod((Node *) argexpr);
- var->estate = ExecInitExpr(argexpr, state->parent);
+
+ /*
+ * 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;
- var->evaluated = false;
- var->value = (Datum) 0;
- var->isnull = true;
- jsestate->args =
- lappend(jsestate->args, var);
+ /*
+ * 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);
+
+ jsestate->args = lappend(jsestate->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);
+
+ if (jexpr->on_empty && jexpr->on_empty->default_expr)
+ {
+ default_on_empty_step_off = state->steps_len;
+ ExecInitExprRec((Expr *) jexpr->on_empty->default_expr, state, resv, resnull);
+
+ scratch->opcode = EEOP_JUMP;
+ default_on_empty_jump_off = state->steps_len;
+ ExprEvalPushStep(state, scratch);
+ }
+
+ if (jexpr->on_error->default_expr)
+ {
+ default_on_error_step_off = state->steps_len;
+ ExecInitExprRec((Expr *) jexpr->on_error->default_expr, state, resv, resnull);
+ }
+
+ /* Adjust jump locations */
+ done_step_off = state->steps_len;
+
+ /* EEOP_JSONEXPR_SKIP */
+ step = &state->steps[skip_step_off];
+ step->d.jsonexpr_skip.jump_done = done_step_off;
+
+ /* EEOP_JSONEXPR_PATH */
+ step = &state->steps[execpath_step_off];
+ step->d.jsonexpr.jump_done = done_step_off;
+ step->d.jsonexpr.jump_default_on_empty =
+ default_on_empty_step_off > 0 ? default_on_empty_step_off : done_step_off;
+ step->d.jsonexpr.jump_default_on_error =
+ default_on_error_step_off > 0 ? default_on_error_step_off : done_step_off;
+
+ /* EEOP_JUMP */
+ if (default_on_empty_jump_off > 0)
+ {
+ step = &state->steps[default_on_empty_jump_off];
+ step->d.jump.jumpdone = done_step_off;
+ }
}
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index 45c648817ba..d39223e0d15 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -491,7 +491,8 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
&&CASE_EEOP_SUBPLAN,
&&CASE_EEOP_JSON_CONSTRUCTOR,
&&CASE_EEOP_IS_JSON,
- &&CASE_EEOP_JSONEXPR,
+ &&CASE_EEOP_JSONEXPR_SKIP,
+ &&CASE_EEOP_JSONEXPR_PATH,
&&CASE_EEOP_AGG_STRICT_DESERIALIZE,
&&CASE_EEOP_AGG_DESERIALIZE,
&&CASE_EEOP_AGG_STRICT_INPUT_CHECK_ARGS,
@@ -1842,11 +1843,26 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
EEO_NEXT();
}
- EEO_CASE(EEOP_JSONEXPR)
+ EEO_CASE(EEOP_JSONEXPR_SKIP)
{
/* too complex for an inline implementation */
- ExecEvalJsonExpr(state, op, econtext);
- EEO_NEXT();
+ if (ExecEvalJsonExprSkip(state, op))
+ EEO_JUMP(op->d.jsonexpr_skip.jump_done);
+ else
+ EEO_NEXT();
+ }
+
+ EEO_CASE(EEOP_JSONEXPR_PATH)
+ {
+ /* too complex for an inline implementation */
+ int res = ExecEvalJsonExpr(state, op);
+
+ if (res == 0)
+ EEO_JUMP(op->d.jsonexpr.jump_default_on_empty);
+ else if (res == -1)
+ EEO_JUMP(op->d.jsonexpr.jump_default_on_error);
+ else
+ EEO_JUMP(op->d.jsonexpr.jump_done);
}
EEO_CASE(EEOP_LAST)
@@ -4701,59 +4717,6 @@ ExecEvalJsonConstructor(ExprState *state, ExprEvalStep *op,
*op->resnull = isnull;
}
-/*
- * 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;
-
- if (!var->evaluated)
- {
- MemoryContext oldcxt = var->mcxt ?
- MemoryContextSwitchTo(var->mcxt) : NULL;
-
- var->value = ExecEvalExpr(var->estate, var->econtext, &var->isnull);
- var->evaluated = true;
-
- if (oldcxt)
- MemoryContextSwitchTo(oldcxt);
- }
-
- if (var->isnull)
- {
- val->type = jbvNull;
- return 0;
- }
-
- JsonItemFromDatum(var->value, var->typid, var->typmod, val);
-
- *baseObject = *val;
- return id;
-}
-
/*
* Check whether we need to override default coercion in
* JSON_QUERY(OMIT QUOTES) case.
@@ -5210,9 +5173,8 @@ ExecJsonValueCoercion(JsonbValue *item, Oid typid, int32 typmod,
* type.
*/
static Datum
-ExecEvalJsonBehavior(ExprContext *econtext, JsonBehavior *behavior,
- ExprState *default_estate,
- Oid ret_typid, int32 ret_typmod, bool *is_null)
+ExecEvalJsonBehavior(JsonBehavior *behavior, Oid ret_typid,
+ int32 ret_typmod, bool *is_null)
{
*is_null = false;
@@ -5262,12 +5224,8 @@ ExecEvalJsonBehavior(ExprContext *econtext, JsonBehavior *behavior,
return (Datum) 0;
case JSON_BEHAVIOR_DEFAULT:
- /*
- * Execute DEFAULT expression.
- * Coercion is not needed here, because expression is
- * already coerced to the target type by the parser.
- */
- return ExecEvalExpr(default_estate, econtext, is_null);
+ Assert(0);
+ return (Datum) 0; /* must be handled by caller */
default:
elog(ERROR, "unrecognized SQL/JSON behavior %d", behavior->btype);
@@ -5276,28 +5234,27 @@ ExecEvalJsonBehavior(ExprContext *econtext, JsonBehavior *behavior,
}
static Datum
-ExecEvalJsonExprInternal(ExprState *state,
- JsonExprState *jsestate, ExprContext *econtext,
+ExecEvalJsonExprInternal(ExprState *state, JsonExprState *jsestate,
JsonPath *path, Datum item, bool *resnull,
- bool *error)
+ bool *empty, bool *error)
{
JsonExpr *jexpr = jsestate->jsexpr;
- bool empty = false;
Datum res = (Datum) 0;
Oid ret_typid = jexpr->returning->typid;
int32 ret_typmod = jexpr->returning->typmod;
*resnull = true;
+ *empty = false;
switch (jexpr->op)
{
case JSON_QUERY_OP:
- res = JsonPathQuery(item, path, jexpr->wrapper, &empty, error,
+ res = JsonPathQuery(item, path, jexpr->wrapper, empty, error,
jsestate->args);
if (error && *error)
return (Datum) 0;
- if (empty)
+ if (*empty)
break;
*resnull = !DatumGetPointer(res);
@@ -5364,13 +5321,13 @@ ExecEvalJsonExprInternal(ExprState *state,
case JSON_VALUE_OP:
{
- JsonbValue *jbv = JsonPathValue(item, path, &empty, error,
+ JsonbValue *jbv = JsonPathValue(item, path, empty, error,
jsestate->args);
if (error && *error)
return (Datum) 0;
- if (empty)
+ if (*empty)
break;
if (!jbv)
@@ -5408,78 +5365,101 @@ ExecEvalJsonExprInternal(ExprState *state,
return (Datum) 0;
}
- Assert(empty);
- Assert(jexpr->on_empty); /* it is not JSON_EXISTS */
+ Assert(*empty);
- if (jexpr->on_empty->btype == JSON_BEHAVIOR_ERROR)
- {
- if (error)
- {
- *error = true;
- return (Datum) 0;
- }
+ return (Datum) 0;
+}
- ereport(ERROR,
- (errcode(ERRCODE_NO_SQL_JSON_ITEM),
- errmsg("no SQL/JSON item")));
+bool
+ExecEvalJsonExprSkip(ExprState *state, ExprEvalStep *op)
+{
+ JsonExprState *jsestate = op->d.jsonexpr_skip.jsestate;
+
+ /*
+ * 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->formatted_expr.isnull ||
+ jsestate->pathspec.isnull)
+ {
+ *op->resvalue = (Datum) 0;
+ *op->resnull = true;
+ return true;
}
- /* Execute ON EMPTY behavior */
- return ExecEvalJsonBehavior(econtext, jexpr->on_empty,
- jsestate->default_on_empty,
- ret_typid, ret_typmod, resnull);
+ /*
+ * Go evaluate the PASSING args if any and subsequently JSON path
+ * itself.
+ */
+ return false;
}
/* ----------------------------------------------------------------
* ExecEvalJsonExpr
* ----------------------------------------------------------------
+ *
+ * Return value:
+ * 1 - Ok, jump to the end of JsonExpr
+ * 0 - empty result, need to execute DEFAULT ON EMPTY expression
+ * -1 - error occured, need to execute DEFAULT ON ERROR expression
*/
-void
-ExecEvalJsonExpr(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
+int
+ExecEvalJsonExpr(ExprState *state, ExprEvalStep *op)
{
JsonExprState *jsestate = op->d.jsonexpr.jsestate;
JsonExpr *jexpr = jsestate->jsexpr;
Datum item;
Datum res;
JsonPath *path;
- ListCell *lc;
+ bool empty = false;
bool error = false;
- bool throwErrors = jexpr->on_error->btype == JSON_BEHAVIOR_ERROR;
-
- if (jsestate->formatted_expr.isnull || jsestate->pathspec.isnull)
- {
- *op->resnull = true;
- *op->resvalue = (Datum) 0;
- return;
- }
+ bool throw_errors = jexpr->on_error->btype == JSON_BEHAVIOR_ERROR;
item = jsestate->formatted_expr.value;
path = DatumGetJsonPathP(jsestate->pathspec.value);
- /* reset JSON path variable contexts */
- foreach(lc, jsestate->args)
+ res = ExecEvalJsonExprInternal(state, jsestate, path, item,
+ op->resnull, &empty,
+ throw_errors ? NULL : &error);
+
+ if (empty && !error)
{
- JsonPathVariableEvalContext *var = lfirst(lc);
+ Assert(jexpr->on_empty); /* it is not JSON_EXISTS */
- var->econtext = econtext;
- var->evaluated = false;
- }
+ if (jexpr->on_empty->btype == JSON_BEHAVIOR_ERROR)
+ {
+ if (throw_errors)
+ ereport(ERROR,
+ (errcode(ERRCODE_NO_SQL_JSON_ITEM),
+ errmsg("no SQL/JSON item")));
- res = ExecEvalJsonExprInternal(state, jsestate, econtext,
- path, item, op->resnull,
- throwErrors ? NULL : &error);
+ error = true; /* fallback to error behavior */
+ }
+ else if (jexpr->on_empty->btype == JSON_BEHAVIOR_DEFAULT)
+ return 0; /* jump to ON EMPTY expression */
+ else
+ /* Execute ON EMPTY behavior */
+ res = ExecEvalJsonBehavior(jexpr->on_empty,
+ jexpr->returning->typid,
+ jexpr->returning->typmod,
+ op->resnull);
+ }
if (error)
{
- Assert(!throwErrors);
+ Assert(!throw_errors);
+
+ if (jexpr->on_error->btype == JSON_BEHAVIOR_DEFAULT)
+ return -1; /* jump to ON ERROR expression */
/* Execute ON ERROR behavior */
- res = ExecEvalJsonBehavior(econtext, jexpr->on_error,
- jsestate->default_on_error,
+ res = ExecEvalJsonBehavior(jexpr->on_error,
jexpr->returning->typid,
jexpr->returning->typmod,
op->resnull);
}
*op->resvalue = res;
+ return 1; /* jump to the end of expression */
}
diff --git a/src/backend/jit/llvm/llvmjit_expr.c b/src/backend/jit/llvm/llvmjit_expr.c
index fd72630f5e6..7681a77cd63 100644
--- a/src/backend/jit/llvm/llvmjit_expr.c
+++ b/src/backend/jit/llvm/llvmjit_expr.c
@@ -2407,11 +2407,71 @@ llvm_compile_expr(ExprState *state)
LLVMBuildBr(b, opblocks[opno + 1]);
break;
- case EEOP_JSONEXPR:
- build_EvalXFunc(b, mod, "ExecEvalJsonExpr",
- v_state, op, v_econtext);
- LLVMBuildBr(b, opblocks[opno + 1]);
- 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_done]);
+ break;
+ }
+
+ case EEOP_JSONEXPR_PATH:
+ {
+ LLVMValueRef v_ret;
+ LLVMValueRef v_switch;
+ int n_jumps = 0;
+
+ v_ret = build_EvalXFunc(b, mod, "ExecEvalJsonExpr",
+ v_state, op);
+
+ if (op->d.jsonexpr.jump_default_on_empty != op->d.jsonexpr.jump_done)
+ n_jumps++;
+
+ if (op->d.jsonexpr.jump_default_on_error != op->d.jsonexpr.jump_done)
+ n_jumps++;
+
+ if (!n_jumps)
+ LLVMBuildBr(b, opblocks[op->d.jsonexpr.jump_done]);
+ else
+ {
+ v_switch = LLVMBuildSwitch(b, v_ret,
+ opblocks[op->d.jsonexpr.jump_done],
+ n_jumps);
+
+ if (op->d.jsonexpr.jump_default_on_empty != op->d.jsonexpr.jump_done)
+ LLVMAddCase(v_switch, l_int32_const(0),
+ opblocks[op->d.jsonexpr.jump_default_on_empty]);
+
+ if (op->d.jsonexpr.jump_default_on_error != op->d.jsonexpr.jump_done)
+ LLVMAddCase(v_switch, l_int32_const(-1),
+ opblocks[op->d.jsonexpr.jump_default_on_error]);
+ }
+
+ break;
+ }
case EEOP_LAST:
Assert(false);
diff --git a/src/backend/jit/llvm/llvmjit_types.c b/src/backend/jit/llvm/llvmjit_types.c
index 37fe64654b6..b9148ce515c 100644
--- a/src/backend/jit/llvm/llvmjit_types.c
+++ b/src/backend/jit/llvm/llvmjit_types.c
@@ -136,6 +136,7 @@ void *referenced_functions[] =
ExecEvalJsonConstructor,
ExecEvalJsonIsPredicate,
ExecEvalJsonExpr,
+ ExecEvalJsonExprSkip,
MakeExpandedObjectReadOnlyInternal,
slot_getmissingattrs,
slot_getsomeattrs_int,
diff --git a/src/backend/utils/adt/jsonpath_exec.c b/src/backend/utils/adt/jsonpath_exec.c
index 5b6a4805721..b8949789710 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 evalJsonPathVariable(void *cxt, 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,71 @@ getJsonPathItem(JsonPathExecContext *cxt, JsonPathItem *item,
}
}
+/*
+ * Evaluate a JSON path variable caching computed value.
+ */
+static int
+evalJsonPathVariable(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)
+ {
+ Assert(var->evaluated);
+ }
+ else if (!var->evaluated)
+ {
+ MemoryContext oldcxt = var->mcxt ? MemoryContextSwitchTo(var->mcxt) : NULL;
+
+ Assert(var->econtext);
+ var->value = ExecEvalExpr(var->estate, var->econtext, &var->isnull);
+ var->evaluated = true;
+
+ if (oldcxt)
+ MemoryContextSwitchTo(oldcxt);
+ }
+
+ 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
*/
@@ -2903,7 +2970,7 @@ compareDatetime(Datum val1, Oid typid1, Datum val2, Oid typid2,
bool
JsonPathExists(Datum jb, JsonPath *jp, List *vars, bool *error)
{
- JsonPathExecResult res = executeJsonPath(jp, vars, EvalJsonPathVar,
+ JsonPathExecResult res = executeJsonPath(jp, vars, evalJsonPathVariable,
DatumGetJsonbP(jb), !error, NULL,
true);
@@ -2925,8 +2992,8 @@ JsonPathQuery(Datum jb, JsonPath *jp, JsonWrapper wrapper, bool *empty,
JsonPathExecResult res PG_USED_FOR_ASSERTS_ONLY;
int count;
- res = executeJsonPath(jp, vars, EvalJsonPathVar, DatumGetJsonbP(jb), !error,
- &found, true);
+ res = executeJsonPath(jp, vars, evalJsonPathVariable,
+ DatumGetJsonbP(jb), !error, &found, true);
Assert(error || !jperIsError(res));
@@ -2992,8 +3059,8 @@ JsonPathValue(Datum jb, JsonPath *jp, bool *empty, bool *error, List *vars)
JsonPathExecResult jper PG_USED_FOR_ASSERTS_ONLY;
int count;
- jper = executeJsonPath(jp, vars, EvalJsonPathVar, DatumGetJsonbP(jb), !error,
- &found, true);
+ jper = executeJsonPath(jp, vars, evalJsonPathVariable,
+ DatumGetJsonbP(jb), !error, &found, true);
Assert(error || !jperIsError(jper));
@@ -3306,7 +3373,7 @@ JsonTableResetContextItem(JsonTableScanState *scan, Datum item)
oldcxt = MemoryContextSwitchTo(scan->mcxt);
- res = executeJsonPath(scan->path, scan->args, EvalJsonPathVar, js,
+ res = executeJsonPath(scan->path, scan->args, evalJsonPathVariable, js,
scan->errorOnError, &scan->found, false /* FIXME */ );
MemoryContextSwitchTo(oldcxt);
diff --git a/src/include/executor/execExpr.h b/src/include/executor/execExpr.h
index adabf57c97b..b005bf440dd 100644
--- a/src/include/executor/execExpr.h
+++ b/src/include/executor/execExpr.h
@@ -244,7 +244,8 @@ typedef enum ExprEvalOp
EEOP_SUBPLAN,
EEOP_JSON_CONSTRUCTOR,
EEOP_IS_JSON,
- EEOP_JSONEXPR,
+ EEOP_JSONEXPR_SKIP,
+ EEOP_JSONEXPR_PATH,
/* aggregation related nodes */
EEOP_AGG_STRICT_DESERIALIZE,
@@ -696,8 +697,18 @@ typedef struct ExprEvalStep
struct
{
struct JsonExprState *jsestate;
+ int jump_done;
+ int jump_default_on_empty; /* ON EMPTY DEFAULT expression */
+ int jump_default_on_error; /* ON ERROR DEFAULT expression */
} jsonexpr;
+ /* for EEOP_JSONEXPR_SKIP */
+ struct
+ {
+ struct JsonExprState *jsestate;
+ int jump_done;
+ } jsonexpr_skip;
+
} d;
} ExprEvalStep;
@@ -764,11 +775,8 @@ typedef struct JsonExprState
NullableDatum
formatted_expr, /* formatted context item value */
- coercion_expr, /* input for JSON item coercion */
pathspec; /* path specification value */
- ExprState *default_on_empty; /* ON EMPTY DEFAULT expression */
- ExprState *default_on_error; /* ON ERROR DEFAULT expression */
List *args; /* passing arguments */
} JsonExprState;
@@ -831,8 +839,8 @@ extern void ExecEvalSysVar(ExprState *state, ExprEvalStep *op,
ExprContext *econtext, TupleTableSlot *slot);
extern void ExecEvalJsonConstructor(ExprState *state, ExprEvalStep *op,
ExprContext *econtext);
-extern void ExecEvalJsonExpr(ExprState *state, ExprEvalStep *op,
- ExprContext *econtext);
+extern int ExecEvalJsonExpr(ExprState *state, ExprEvalStep *op);
+extern bool ExecEvalJsonExprSkip(ExprState *state, ExprEvalStep *op);
extern void ExecAggInitGroup(AggState *aggstate, AggStatePerTrans pertrans, AggStatePerGroup pergroup,
ExprContext *aggcontext);
diff --git a/src/include/utils/jsonpath.h b/src/include/utils/jsonpath.h
index e9134b7eab4..a33a770f7aa 100644
--- a/src/include/utils/jsonpath.h
+++ b/src/include/utils/jsonpath.h
@@ -280,10 +280,6 @@ extern Datum JsonPathQuery(Datum jb, JsonPath *jp, JsonWrapper wrapper,
bool *empty, bool *error, List *vars);
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
--
2.17.1
v9-0009-Remove-support-of-DEFAULT-ON-EMPTY-in-SQL-JSON-fu.patchtext/x-patch; charset=UTF-8; name=v9-0009-Remove-support-of-DEFAULT-ON-EMPTY-in-SQL-JSON-fu.patchDownload
From d7e694a3006dbca17fd156724c8b1ac9b511ad11 Mon Sep 17 00:00:00 2001
From: Nikita Glukhov <n.gluhov@postgrespro.ru>
Date: Mon, 29 Aug 2022 23:23:02 +0300
Subject: [PATCH v9 9/9] Remove support of DEFAULT ON EMPTY in SQL/JSON
functions
---
doc/src/sgml/func.sgml | 4 +-
src/backend/executor/execExpr.c | 21 ------
src/backend/executor/execExprInterp.c | 27 ++++----
src/backend/jit/llvm/llvmjit_expr.c | 32 +++------
src/backend/parser/parse_expr.c | 33 ++-------
src/include/executor/execExpr.h | 3 +-
src/test/regress/expected/jsonb_sqljson.out | 74 +++++++++------------
src/test/regress/sql/jsonb_sqljson.sql | 34 +++++-----
8 files changed, 82 insertions(+), 146 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 8dd63c04556..8e03a1831ab 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -17976,7 +17976,7 @@ FROM
<parameter>context_item</parameter>, <parameter>path_expression</parameter>
<optional> <literal>PASSING</literal> { <parameter>value</parameter> <literal>AS</literal> <parameter>varname</parameter> } <optional>, ...</optional></optional>
<optional> <literal>RETURNING</literal> <parameter>data_type</parameter> </optional>
- <optional> { <literal>ERROR</literal> | <literal>NULL</literal> | <literal>DEFAULT</literal> <parameter>expression</parameter> } <literal>ON EMPTY</literal> </optional>
+ <optional> { <literal>ERROR</literal> | <literal>NULL</literal> } <literal>ON EMPTY</literal> </optional>
<optional> { <literal>ERROR</literal> | <literal>NULL</literal> | <literal>DEFAULT</literal> <parameter>expression</parameter> } <literal>ON ERROR</literal> </optional>)
</para>
<para>
@@ -18017,7 +18017,7 @@ FROM
<optional> <literal>RETURNING</literal> <parameter>data_type</parameter> <optional> <literal>FORMAT JSON</literal> <optional> <literal>ENCODING UTF8</literal> </optional> </optional> </optional>
<optional> { <literal>WITHOUT</literal> | <literal>WITH</literal> { <literal>CONDITIONAL</literal> | <optional><literal>UNCONDITIONAL</literal></optional> } } <optional> <literal>ARRAY</literal> </optional> <literal>WRAPPER</literal> </optional>
<optional> { <literal>KEEP</literal> | <literal>OMIT</literal> } <literal>QUOTES</literal> <optional> <literal>ON SCALAR STRING</literal> </optional> </optional>
- <optional> { <literal>ERROR</literal> | <literal>NULL</literal> | <literal>EMPTY</literal> { <optional> <literal>ARRAY</literal> </optional> | <literal>OBJECT</literal> } | <literal>DEFAULT</literal> <parameter>expression</parameter> } <literal>ON EMPTY</literal> </optional>
+ <optional> { <literal>ERROR</literal> | <literal>NULL</literal> | <literal>EMPTY</literal> { <optional> <literal>ARRAY</literal> </optional> | <literal>OBJECT</literal> } } <literal>ON EMPTY</literal> </optional>
<optional> { <literal>ERROR</literal> | <literal>NULL</literal> | <literal>EMPTY</literal> { <optional> <literal>ARRAY</literal> </optional> | <literal>OBJECT</literal> } | <literal>DEFAULT</literal> <parameter>expression</parameter> } <literal>ON ERROR</literal> </optional>)
</para>
<para>
diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
index 68227ebee21..5ea7e6b421a 100644
--- a/src/backend/executor/execExpr.c
+++ b/src/backend/executor/execExpr.c
@@ -4166,9 +4166,7 @@ ExecInitJsonExpr(JsonExpr *jexpr, ExprState *state, ExprEvalStep *scratch,
int skip_step_off;
int execpath_step_off;
int done_step_off;
- int default_on_empty_step_off = -1;
int default_on_error_step_off = -1;
- int default_on_empty_jump_off = -1;
/* JSON_TABLE preudo-function returns context item as a result */
if (jexpr->op == JSON_TABLE_OP)
@@ -4235,16 +4233,6 @@ ExecInitJsonExpr(JsonExpr *jexpr, ExprState *state, ExprEvalStep *scratch,
execpath_step_off = state->steps_len;
ExprEvalPushStep(state, scratch);
- if (jexpr->on_empty && jexpr->on_empty->default_expr)
- {
- default_on_empty_step_off = state->steps_len;
- ExecInitExprRec((Expr *) jexpr->on_empty->default_expr, state, resv, resnull);
-
- scratch->opcode = EEOP_JUMP;
- default_on_empty_jump_off = state->steps_len;
- ExprEvalPushStep(state, scratch);
- }
-
if (jexpr->on_error->default_expr)
{
default_on_error_step_off = state->steps_len;
@@ -4261,15 +4249,6 @@ ExecInitJsonExpr(JsonExpr *jexpr, ExprState *state, ExprEvalStep *scratch,
/* EEOP_JSONEXPR_PATH */
step = &state->steps[execpath_step_off];
step->d.jsonexpr.jump_done = done_step_off;
- step->d.jsonexpr.jump_default_on_empty =
- default_on_empty_step_off > 0 ? default_on_empty_step_off : done_step_off;
step->d.jsonexpr.jump_default_on_error =
default_on_error_step_off > 0 ? default_on_error_step_off : done_step_off;
-
- /* EEOP_JUMP */
- if (default_on_empty_jump_off > 0)
- {
- step = &state->steps[default_on_empty_jump_off];
- step->d.jump.jumpdone = done_step_off;
- }
}
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index d39223e0d15..9a18413c6e8 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -1855,14 +1855,10 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
EEO_CASE(EEOP_JSONEXPR_PATH)
{
/* too complex for an inline implementation */
- int res = ExecEvalJsonExpr(state, op);
-
- if (res == 0)
- EEO_JUMP(op->d.jsonexpr.jump_default_on_empty);
- else if (res == -1)
- EEO_JUMP(op->d.jsonexpr.jump_default_on_error);
- else
+ if (ExecEvalJsonExpr(state, op))
EEO_JUMP(op->d.jsonexpr.jump_done);
+ else
+ EEO_JUMP(op->d.jsonexpr.jump_default_on_error);
}
EEO_CASE(EEOP_LAST)
@@ -5400,11 +5396,10 @@ ExecEvalJsonExprSkip(ExprState *state, ExprEvalStep *op)
* ----------------------------------------------------------------
*
* Return value:
- * 1 - Ok, jump to the end of JsonExpr
- * 0 - empty result, need to execute DEFAULT ON EMPTY expression
- * -1 - error occured, need to execute DEFAULT ON ERROR expression
+ * true - Ok, jump to the end of JsonExpr
+ * false - error occured, need to execute DEFAULT ON ERROR expression
*/
-int
+bool
ExecEvalJsonExpr(ExprState *state, ExprEvalStep *op)
{
JsonExprState *jsestate = op->d.jsonexpr.jsestate;
@@ -5436,14 +5431,16 @@ ExecEvalJsonExpr(ExprState *state, ExprEvalStep *op)
error = true; /* fallback to error behavior */
}
- else if (jexpr->on_empty->btype == JSON_BEHAVIOR_DEFAULT)
- return 0; /* jump to ON EMPTY expression */
else
+ {
+ Assert(jexpr->on_empty->btype != JSON_BEHAVIOR_DEFAULT); /* not supported */
+
/* Execute ON EMPTY behavior */
res = ExecEvalJsonBehavior(jexpr->on_empty,
jexpr->returning->typid,
jexpr->returning->typmod,
op->resnull);
+ }
}
if (error)
@@ -5451,7 +5448,7 @@ ExecEvalJsonExpr(ExprState *state, ExprEvalStep *op)
Assert(!throw_errors);
if (jexpr->on_error->btype == JSON_BEHAVIOR_DEFAULT)
- return -1; /* jump to ON ERROR expression */
+ return false; /* jump to ON ERROR expression */
/* Execute ON ERROR behavior */
res = ExecEvalJsonBehavior(jexpr->on_error,
@@ -5461,5 +5458,5 @@ ExecEvalJsonExpr(ExprState *state, ExprEvalStep *op)
}
*op->resvalue = res;
- return 1; /* jump to the end of expression */
+ return true; /* jump to the end of expression */
}
diff --git a/src/backend/jit/llvm/llvmjit_expr.c b/src/backend/jit/llvm/llvmjit_expr.c
index 7681a77cd63..e0890fb63f1 100644
--- a/src/backend/jit/llvm/llvmjit_expr.c
+++ b/src/backend/jit/llvm/llvmjit_expr.c
@@ -2441,34 +2441,22 @@ llvm_compile_expr(ExprState *state)
case EEOP_JSONEXPR_PATH:
{
LLVMValueRef v_ret;
- LLVMValueRef v_switch;
- int n_jumps = 0;
v_ret = build_EvalXFunc(b, mod, "ExecEvalJsonExpr",
v_state, op);
-
- if (op->d.jsonexpr.jump_default_on_empty != op->d.jsonexpr.jump_done)
- n_jumps++;
+ v_ret = LLVMBuildZExt(b, v_ret, TypeStorageBool, "");
if (op->d.jsonexpr.jump_default_on_error != op->d.jsonexpr.jump_done)
- n_jumps++;
-
- if (!n_jumps)
- LLVMBuildBr(b, opblocks[op->d.jsonexpr.jump_done]);
+ LLVMBuildCondBr(b,
+ LLVMBuildICmp(b,
+ LLVMIntEQ,
+ v_ret,
+ l_sbool_const(0),
+ ""),
+ opblocks[op->d.jsonexpr.jump_default_on_error],
+ opblocks[op->d.jsonexpr.jump_done]);
else
- {
- v_switch = LLVMBuildSwitch(b, v_ret,
- opblocks[op->d.jsonexpr.jump_done],
- n_jumps);
-
- if (op->d.jsonexpr.jump_default_on_empty != op->d.jsonexpr.jump_done)
- LLVMAddCase(v_switch, l_int32_const(0),
- opblocks[op->d.jsonexpr.jump_default_on_empty]);
-
- if (op->d.jsonexpr.jump_default_on_error != op->d.jsonexpr.jump_done)
- LLVMAddCase(v_switch, l_int32_const(-1),
- opblocks[op->d.jsonexpr.jump_default_on_error]);
- }
+ LLVMBuildBr(b, opblocks[op->d.jsonexpr.jump_done]);
break;
}
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index c02530a3671..f96334ce088 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -4263,7 +4263,6 @@ static Node *
coerceDefaultJsonExpr(ParseState *pstate, JsonExpr *jsexpr,
bool is_on_empty, Node *defexpr)
{
- Node *orig_defexpr = defexpr;
int location;
Oid exprtype;
@@ -4276,6 +4275,12 @@ coerceDefaultJsonExpr(ParseState *pstate, JsonExpr *jsexpr,
if (location < 0)
location = jsexpr->location;
+ if (is_on_empty)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("DEFAULT ON EMPTY expressions are not supported"),
+ parser_errposition(pstate, location)));
+
defexpr = coerce_to_target_type(pstate,
defexpr,
exprtype,
@@ -4293,31 +4298,7 @@ coerceDefaultJsonExpr(ParseState *pstate, JsonExpr *jsexpr,
format_type_be(jsexpr->returning->typid)),
parser_errposition(pstate, location)));
- /* Don't allow unsafe DEFAUL ON EMPTY expressions, unless ERROR ON ERROR */
- if (!is_on_empty ||
- jsexpr->on_error->btype == JSON_BEHAVIOR_ERROR)
- return defexpr;
-
- if (!expr_can_throw_errors(defexpr))
- return defexpr;
-
- /* Try to simplify expression if there is non-empty coercion */
- if (defexpr != orig_defexpr &&
- !expr_can_throw_errors(orig_defexpr))
- {
- defexpr = eval_const_expressions(NULL, defexpr);
-
- if (!expr_can_throw_errors(defexpr))
- return defexpr;
- }
-
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("unsafe DEFAULT ON EMPTY expressions are not supported"),
- errhint("Use ERROR ON ERROR clause or try to simplify expression into constant-like form."),
- parser_errposition(pstate, location)));
-
- return NULL;
+ return defexpr;
}
/*
diff --git a/src/include/executor/execExpr.h b/src/include/executor/execExpr.h
index b005bf440dd..d67419c66a9 100644
--- a/src/include/executor/execExpr.h
+++ b/src/include/executor/execExpr.h
@@ -698,7 +698,6 @@ typedef struct ExprEvalStep
{
struct JsonExprState *jsestate;
int jump_done;
- int jump_default_on_empty; /* ON EMPTY DEFAULT expression */
int jump_default_on_error; /* ON ERROR DEFAULT expression */
} jsonexpr;
@@ -839,7 +838,7 @@ extern void ExecEvalSysVar(ExprState *state, ExprEvalStep *op,
ExprContext *econtext, TupleTableSlot *slot);
extern void ExecEvalJsonConstructor(ExprState *state, ExprEvalStep *op,
ExprContext *econtext);
-extern int ExecEvalJsonExpr(ExprState *state, ExprEvalStep *op);
+extern bool ExecEvalJsonExpr(ExprState *state, ExprEvalStep *op);
extern bool ExecEvalJsonExprSkip(ExprState *state, ExprEvalStep *op);
extern void ExecAggInitGroup(AggState *aggstate, AggStatePerTrans pertrans, AggStatePerGroup pergroup,
diff --git a/src/test/regress/expected/jsonb_sqljson.out b/src/test/regress/expected/jsonb_sqljson.out
index 0e07bbc858f..fcd921130f5 100644
--- a/src/test/regress/expected/jsonb_sqljson.out
+++ b/src/test/regress/expected/jsonb_sqljson.out
@@ -537,11 +537,9 @@ SELECT JSON_VALUE(jsonb '1', 'lax $.a' NULL ON EMPTY DEFAULT '2' ON ERROR);
(1 row)
SELECT JSON_VALUE(jsonb '1', 'lax $.a' DEFAULT '2' ON EMPTY DEFAULT '3' ON ERROR);
- json_value
-------------
- 2
-(1 row)
-
+ERROR: DEFAULT ON EMPTY expressions are not supported
+LINE 1: SELECT JSON_VALUE(jsonb '1', 'lax $.a' DEFAULT '2' ON EMPTY ...
+ ^
SELECT JSON_VALUE(jsonb '1', 'lax $.a' ERROR ON EMPTY DEFAULT '3' ON ERROR);
json_value
------------
@@ -576,7 +574,7 @@ SELECT
jsonb '{"a": 1, "b": 2}',
'$.* ? (@ > $x)' PASSING x AS x
RETURNING int
- DEFAULT -1 ON EMPTY
+ NULL ON EMPTY
DEFAULT -2 ON ERROR
) y
FROM
@@ -585,7 +583,7 @@ FROM
---+----
0 | -2
1 | 2
- 2 | -1
+ 2 |
(3 rows)
SELECT JSON_VALUE(jsonb 'null', '$a' PASSING point ' (1, 2 )' AS a);
@@ -634,31 +632,25 @@ SELECT JSON_VALUE('1'::jsonb, '$.x' RETURNING int
DEFAULT 1 / x ON EMPTY
DEFAULT 2 ON ERROR)
FROM (VALUES (1::int), (0)) x(x);
-ERROR: unsafe DEFAULT ON EMPTY expressions are not supported
+ERROR: DEFAULT ON EMPTY expressions are not supported
LINE 2: DEFAULT 1 / x ON EMPTY
^
-HINT: Use ERROR ON ERROR clause or try to simplify expression into constant-like form.
SELECT JSON_VALUE('1'::jsonb, '$.x' RETURNING int DEFAULT -1 ON EMPTY);
- json_value
-------------
- -1
-(1 row)
-
+ERROR: DEFAULT ON EMPTY expressions are not supported
+LINE 1: ...SON_VALUE('1'::jsonb, '$.x' RETURNING int DEFAULT -1 ON EMPT...
+ ^
SELECT JSON_VALUE('1'::jsonb, '$.x' RETURNING int DEFAULT '-1' ON EMPTY);
- json_value
-------------
- -1
-(1 row)
-
+ERROR: DEFAULT ON EMPTY expressions are not supported
+LINE 1: ...SON_VALUE('1'::jsonb, '$.x' RETURNING int DEFAULT '-1' ON EM...
+ ^
SELECT JSON_VALUE('1'::jsonb, '$.x' RETURNING int DEFAULT 'err' ON EMPTY);
-ERROR: invalid input syntax for type integer: "err"
+ERROR: DEFAULT ON EMPTY expressions are not supported
LINE 1: ...SON_VALUE('1'::jsonb, '$.x' RETURNING int DEFAULT 'err' ON E...
^
SELECT JSON_VALUE('1'::jsonb, '$.x' RETURNING int DEFAULT -1::float ON EMPTY);
-ERROR: unsafe DEFAULT ON EMPTY expressions are not supported
+ERROR: DEFAULT ON EMPTY expressions are not supported
LINE 1: ...SON_VALUE('1'::jsonb, '$.x' RETURNING int DEFAULT -1::float ...
^
-HINT: Use ERROR ON ERROR clause or try to simplify expression into constant-like form.
-- JSON_QUERY
SELECT
JSON_QUERY(js, '$'),
@@ -827,7 +819,7 @@ SELECT JSON_QUERY(jsonb '[]', '$[*]' ERROR ON EMPTY);
(1 row)
-SELECT JSON_QUERY(jsonb '[]', '$[*]' DEFAULT '"empty"' ON EMPTY);
+SELECT JSON_QUERY(jsonb '[]', '$[*]' ERROR ON EMPTY DEFAULT '"empty"' ON ERROR);
json_query
------------
"empty"
@@ -1055,7 +1047,7 @@ CREATE TABLE test_jsonb_constraints (
CONSTRAINT test_jsonb_constraint2
CHECK (JSON_EXISTS(js::jsonb, '$.a' PASSING i + 5 AS int, i::text AS txt, array[1,2,3] as arr))
CONSTRAINT test_jsonb_constraint3
- CHECK (JSON_VALUE(js::jsonb, '$.a' RETURNING int DEFAULT ('12' || i)::int ON EMPTY ERROR ON ERROR) > i)
+ CHECK (JSON_VALUE(js::jsonb, '$.a' RETURNING int NULL ON EMPTY ERROR ON ERROR) > i)
CONSTRAINT test_jsonb_constraint4
CHECK (JSON_QUERY(js::jsonb, '$.a' WITH CONDITIONAL WRAPPER EMPTY OBJECT ON ERROR) < jsonb '[10]')
CONSTRAINT test_jsonb_constraint5
@@ -1073,7 +1065,7 @@ CREATE TABLE test_jsonb_constraints (
Check constraints:
"test_jsonb_constraint1" CHECK (js IS JSON)
"test_jsonb_constraint2" CHECK (JSON_EXISTS(js::jsonb, '$."a"' PASSING i + 5 AS int, i::text AS txt, ARRAY[1, 2, 3] AS arr))
- "test_jsonb_constraint3" CHECK (JSON_VALUE(js::jsonb, '$."a"' RETURNING integer DEFAULT ('12'::text || i)::integer ON EMPTY ERROR ON ERROR) > i)
+ "test_jsonb_constraint3" CHECK (JSON_VALUE(js::jsonb, '$."a"' RETURNING integer ERROR ON ERROR) > i)
"test_jsonb_constraint4" CHECK (JSON_QUERY(js::jsonb, '$."a"' RETURNING jsonb WITH CONDITIONAL WRAPPER EMPTY OBJECT ON ERROR) < '[10]'::jsonb)
"test_jsonb_constraint5" CHECK (JSON_QUERY(js::jsonb, '$."a"' RETURNING character(5) OMIT QUOTES EMPTY ARRAY ON EMPTY) > ('a'::bpchar COLLATE "C"))
"test_jsonb_constraint6" CHECK (JSON_EXISTS(js::jsonb, 'strict $."a"' RETURNING integer TRUE ON ERROR) < 2)
@@ -1087,7 +1079,7 @@ ORDER BY 1;
((JSON_EXISTS((js)::jsonb, 'strict $."a"' RETURNING integer TRUE ON ERROR) < 2))
((JSON_QUERY((js)::jsonb, '$."a"' RETURNING character(5) OMIT QUOTES EMPTY ARRAY ON EMPTY) > ('a'::bpchar COLLATE "C")))
((JSON_QUERY((js)::jsonb, '$."a"' RETURNING jsonb WITH CONDITIONAL WRAPPER EMPTY OBJECT ON ERROR) < '[10]'::jsonb))
- ((JSON_VALUE((js)::jsonb, '$."a"' RETURNING integer DEFAULT (('12'::text || i))::integer ON EMPTY ERROR ON ERROR) > i))
+ ((JSON_VALUE((js)::jsonb, '$."a"' RETURNING integer ERROR ON ERROR) > i))
((js IS JSON))
(JSON_EXISTS((js)::jsonb, '$."a"' PASSING (i + 5) AS int, (i)::text AS txt, ARRAY[1, 2, 3] AS arr))
(6 rows)
@@ -1424,22 +1416,22 @@ SELECT * FROM JSON_TABLE(jsonb '1', '$' COLUMNS (a int PATH 'strict $.a' ERROR O
ERROR: jsonpath member accessor can only be applied to an object
SELECT * FROM JSON_TABLE(jsonb '1', '$' COLUMNS (a int PATH 'lax $.a' ERROR ON EMPTY) ERROR ON ERROR) jt;
ERROR: no SQL/JSON item
-SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a int PATH '$' DEFAULT 1 ON EMPTY DEFAULT 2 ON ERROR)) jt;
+SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a int PATH '$' NULL ON EMPTY DEFAULT 2 ON ERROR)) jt;
a
---
2
(1 row)
-SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a int PATH 'strict $.a' DEFAULT 1 ON EMPTY DEFAULT 2 ON ERROR)) jt;
+SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a int PATH 'strict $.a' NULL ON EMPTY DEFAULT 2 ON ERROR)) jt;
a
---
2
(1 row)
-SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a int PATH 'lax $.a' DEFAULT 1 ON EMPTY DEFAULT 2 ON ERROR)) jt;
+SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a int PATH 'lax $.a' NULL ON EMPTY DEFAULT 2 ON ERROR)) jt;
a
---
- 1
+
(1 row)
-- JSON_TABLE: EXISTS PATH types
@@ -1761,7 +1753,7 @@ from
jtt.js,'strict $[*]' as p
columns (
n for ordinality,
- a int path 'lax $.a' default -1 on empty,
+ a int path 'lax $.a' error on empty default -1 on error,
nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
nested path 'strict $.c[*]' as pc columns ( c int path '$' )
)
@@ -1790,7 +1782,7 @@ from
jtt.js,'strict $[*]' as p
columns (
n for ordinality,
- a int path 'lax $.a' default -1 on empty,
+ a int path 'lax $.a' error on empty default -1 on error,
nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
nested path 'strict $.c[*]' as pc columns ( c int path '$' )
)
@@ -1820,7 +1812,7 @@ from
jtt.js,'strict $[*]' as p
columns (
n for ordinality,
- a int path 'lax $.a' default -1 on empty,
+ a int path 'lax $.a' error on empty default -1 on error,
nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
nested path 'strict $.c[*]' as pc columns ( c int path '$' )
)
@@ -1850,7 +1842,7 @@ from
jtt.js,'strict $[*]' as p
columns (
n for ordinality,
- a int path 'lax $.a' default -1 on empty,
+ a int path 'lax $.a' error on empty default -1 on error,
nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
nested path 'strict $.c[*]' as pc columns ( c int path '$' )
)
@@ -1880,7 +1872,7 @@ from
jtt.js,'strict $[*]' as p
columns (
n for ordinality,
- a int path 'lax $.a' default -1 on empty,
+ a int path 'lax $.a' error on empty default -1 on error,
nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
nested path 'strict $.c[*]' as pc columns ( c int path '$' )
)
@@ -1909,7 +1901,7 @@ from
jtt.js,'strict $[*]' as p
columns (
n for ordinality,
- a int path 'lax $.a' default -1 on empty,
+ a int path 'lax $.a' error on empty default -1 on error,
nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
nested path 'strict $.c[*]' as pc columns ( c int path '$' )
)
@@ -1938,7 +1930,7 @@ from
jtt.js,'strict $[*]' as p
columns (
n for ordinality,
- a int path 'lax $.a' default -1 on empty,
+ a int path 'lax $.a' error on empty default -1 on error,
nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
nested path 'strict $.c[*]' as pc columns ( c int path '$' )
)
@@ -1966,7 +1958,7 @@ from
jtt.js,'strict $[*]' as p
columns (
n for ordinality,
- a int path 'lax $.a' default -1 on empty,
+ a int path 'lax $.a' error on empty default -1 on error,
nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
nested path 'strict $.c[*]' as pc columns ( c int path '$' )
)
@@ -1994,7 +1986,7 @@ from
jtt.js,'strict $[*]' as p
columns (
n for ordinality,
- a int path 'lax $.a' default -1 on empty,
+ a int path 'lax $.a' error on empty default -1 on error,
nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
nested path 'strict $.c[*]' as pc columns ( c int path '$' )
)
@@ -2025,7 +2017,7 @@ from
jtt.js,'strict $[*]' as p
columns (
n for ordinality,
- a int path 'lax $.a' default -1 on empty,
+ a int path 'lax $.a' error on empty default -1 on error,
nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
nested path 'strict $.c[*]' as pc columns ( c int path '$' )
)
@@ -2195,7 +2187,7 @@ SELECT JSON_VALUE(jsonb '{"a": 123}', '$' || '.' || 'a');
123
(1 row)
-SELECT JSON_VALUE(jsonb '{"a": 123}', '$' || '.' || 'b' DEFAULT 'foo' ON EMPTY);
+SELECT JSON_VALUE(jsonb '{"a": 123}', '$' || '.' || 'b' ERROR ON EMPTY DEFAULT 'foo' ON ERROR);
json_value
------------
foo
diff --git a/src/test/regress/sql/jsonb_sqljson.sql b/src/test/regress/sql/jsonb_sqljson.sql
index 43a194ad09f..76d4c9cc14e 100644
--- a/src/test/regress/sql/jsonb_sqljson.sql
+++ b/src/test/regress/sql/jsonb_sqljson.sql
@@ -148,7 +148,7 @@ SELECT
jsonb '{"a": 1, "b": 2}',
'$.* ? (@ > $x)' PASSING x AS x
RETURNING int
- DEFAULT -1 ON EMPTY
+ NULL ON EMPTY
DEFAULT -2 ON ERROR
) y
FROM
@@ -237,7 +237,7 @@ SELECT JSON_QUERY(jsonb '[]', '$[*]' EMPTY ON EMPTY);
SELECT JSON_QUERY(jsonb '[]', '$[*]' EMPTY ARRAY ON EMPTY);
SELECT JSON_QUERY(jsonb '[]', '$[*]' EMPTY OBJECT ON EMPTY);
SELECT JSON_QUERY(jsonb '[]', '$[*]' ERROR ON EMPTY);
-SELECT JSON_QUERY(jsonb '[]', '$[*]' DEFAULT '"empty"' ON EMPTY);
+SELECT JSON_QUERY(jsonb '[]', '$[*]' ERROR ON EMPTY DEFAULT '"empty"' ON ERROR);
SELECT JSON_QUERY(jsonb '[]', '$[*]' ERROR ON EMPTY NULL ON ERROR);
SELECT JSON_QUERY(jsonb '[]', '$[*]' ERROR ON EMPTY EMPTY ARRAY ON ERROR);
@@ -309,7 +309,7 @@ CREATE TABLE test_jsonb_constraints (
CONSTRAINT test_jsonb_constraint2
CHECK (JSON_EXISTS(js::jsonb, '$.a' PASSING i + 5 AS int, i::text AS txt, array[1,2,3] as arr))
CONSTRAINT test_jsonb_constraint3
- CHECK (JSON_VALUE(js::jsonb, '$.a' RETURNING int DEFAULT ('12' || i)::int ON EMPTY ERROR ON ERROR) > i)
+ CHECK (JSON_VALUE(js::jsonb, '$.a' RETURNING int NULL ON EMPTY ERROR ON ERROR) > i)
CONSTRAINT test_jsonb_constraint4
CHECK (JSON_QUERY(js::jsonb, '$.a' WITH CONDITIONAL WRAPPER EMPTY OBJECT ON ERROR) < jsonb '[10]')
CONSTRAINT test_jsonb_constraint5
@@ -515,9 +515,9 @@ SELECT * FROM JSON_TABLE(jsonb '1', '$' COLUMNS (a int PATH '$.a' ERROR ON EMPTY
SELECT * FROM JSON_TABLE(jsonb '1', '$' COLUMNS (a int PATH 'strict $.a' ERROR ON EMPTY) ERROR ON ERROR) jt;
SELECT * FROM JSON_TABLE(jsonb '1', '$' COLUMNS (a int PATH 'lax $.a' ERROR ON EMPTY) ERROR ON ERROR) jt;
-SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a int PATH '$' DEFAULT 1 ON EMPTY DEFAULT 2 ON ERROR)) jt;
-SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a int PATH 'strict $.a' DEFAULT 1 ON EMPTY DEFAULT 2 ON ERROR)) jt;
-SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a int PATH 'lax $.a' DEFAULT 1 ON EMPTY DEFAULT 2 ON ERROR)) jt;
+SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a int PATH '$' NULL ON EMPTY DEFAULT 2 ON ERROR)) jt;
+SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a int PATH 'strict $.a' NULL ON EMPTY DEFAULT 2 ON ERROR)) jt;
+SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a int PATH 'lax $.a' NULL ON EMPTY DEFAULT 2 ON ERROR)) jt;
-- JSON_TABLE: EXISTS PATH types
SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a int4 EXISTS PATH '$.a'));
@@ -776,7 +776,7 @@ from
jtt.js,'strict $[*]' as p
columns (
n for ordinality,
- a int path 'lax $.a' default -1 on empty,
+ a int path 'lax $.a' error on empty default -1 on error,
nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
nested path 'strict $.c[*]' as pc columns ( c int path '$' )
)
@@ -791,7 +791,7 @@ from
jtt.js,'strict $[*]' as p
columns (
n for ordinality,
- a int path 'lax $.a' default -1 on empty,
+ a int path 'lax $.a' error on empty default -1 on error,
nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
nested path 'strict $.c[*]' as pc columns ( c int path '$' )
)
@@ -807,7 +807,7 @@ from
jtt.js,'strict $[*]' as p
columns (
n for ordinality,
- a int path 'lax $.a' default -1 on empty,
+ a int path 'lax $.a' error on empty default -1 on error,
nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
nested path 'strict $.c[*]' as pc columns ( c int path '$' )
)
@@ -823,7 +823,7 @@ from
jtt.js,'strict $[*]' as p
columns (
n for ordinality,
- a int path 'lax $.a' default -1 on empty,
+ a int path 'lax $.a' error on empty default -1 on error,
nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
nested path 'strict $.c[*]' as pc columns ( c int path '$' )
)
@@ -839,7 +839,7 @@ from
jtt.js,'strict $[*]' as p
columns (
n for ordinality,
- a int path 'lax $.a' default -1 on empty,
+ a int path 'lax $.a' error on empty default -1 on error,
nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
nested path 'strict $.c[*]' as pc columns ( c int path '$' )
)
@@ -855,7 +855,7 @@ from
jtt.js,'strict $[*]' as p
columns (
n for ordinality,
- a int path 'lax $.a' default -1 on empty,
+ a int path 'lax $.a' error on empty default -1 on error,
nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
nested path 'strict $.c[*]' as pc columns ( c int path '$' )
)
@@ -871,7 +871,7 @@ from
jtt.js,'strict $[*]' as p
columns (
n for ordinality,
- a int path 'lax $.a' default -1 on empty,
+ a int path 'lax $.a' error on empty default -1 on error,
nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
nested path 'strict $.c[*]' as pc columns ( c int path '$' )
)
@@ -887,7 +887,7 @@ from
jtt.js,'strict $[*]' as p
columns (
n for ordinality,
- a int path 'lax $.a' default -1 on empty,
+ a int path 'lax $.a' error on empty default -1 on error,
nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
nested path 'strict $.c[*]' as pc columns ( c int path '$' )
)
@@ -903,7 +903,7 @@ from
jtt.js,'strict $[*]' as p
columns (
n for ordinality,
- a int path 'lax $.a' default -1 on empty,
+ a int path 'lax $.a' error on empty default -1 on error,
nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
nested path 'strict $.c[*]' as pc columns ( c int path '$' )
)
@@ -919,7 +919,7 @@ from
jtt.js,'strict $[*]' as p
columns (
n for ordinality,
- a int path 'lax $.a' default -1 on empty,
+ a int path 'lax $.a' error on empty default -1 on error,
nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
nested path 'strict $.c[*]' as pc columns ( c int path '$' )
)
@@ -987,7 +987,7 @@ FROM JSON_TABLE(
-- Extension: non-constant JSON path
SELECT JSON_EXISTS(jsonb '{"a": 123}', '$' || '.' || 'a');
SELECT JSON_VALUE(jsonb '{"a": 123}', '$' || '.' || 'a');
-SELECT JSON_VALUE(jsonb '{"a": 123}', '$' || '.' || 'b' DEFAULT 'foo' ON EMPTY);
+SELECT JSON_VALUE(jsonb '{"a": 123}', '$' || '.' || 'b' ERROR ON EMPTY DEFAULT 'foo' ON ERROR);
SELECT JSON_QUERY(jsonb '{"a": 123}', '$' || '.' || 'a');
SELECT JSON_QUERY(jsonb '{"a": 123}', '$' || '.' || 'a' WITH WRAPPER);
-- Should fail (invalid path)
--
2.17.1
On Tue, Aug 30, 2022 at 6:49 AM Nikita Glukhov <n.gluhov@postgrespro.ru> wrote:
On 29.08.2022 15:56, Amit Langote wrote:
On Sat, Aug 27, 2022 at 5:11 AM Nikita Glukhov <n.gluhov@postgrespro.ru> wrote:
I have completed in v9 all the things I previously planned:BTW, maybe the following hunk in boolin_opt_error() is unnecessary?
- len = strlen(str); + len -= str - in_str;This is really not necessary, but helps to avoid extra strlen() call.
I have replaced it with more intuitive+ {
str++; + len--; + }- len = strlen(str);
+1
- Added JSON_QUERY coercion to UTF8 bytea using pg_convert_to().
- Added immutability checks that were missed with elimination
of coercion expressions.
Coercions text::datetime, datetime1::datetime2 and even
datetime::text for some datetime types are mutable.
datetime::text can be made immutable by passing ISO date
style into output functions (like in jsonpath).- Disabled non-Const expressions in DEFAULT ON EMPTY in non
ERROR ON ERROR case. Non-constant expressions are tried to
evaluate into Const directly inside transformExpr().I am not sure if it's OK to eval_const_expressions() on a Query
sub-expression during parse-analysis. IIUC, it is only correct to
apply it to after the rewriting phase.I also was not sure. Maybe it can be moved to rewriting phase or
even to execution phase.
I suppose we wouldn't need to bother with doing this when we
eventually move to supporting the DEFAULT expressions.
Maybe it would be better to simply remove DEFAULT ON EMPTY.
So +1 to this for now.
See last patch #9.
It is possible to easily split this patch into several subpatches,
I will do it if needed.That would be nice indeed.
I have extracted patches #1-6 with numerous safe input and type conversion
functions.I'm wondering if you're going to change the PASSING values
initialization to add the steps into the parent JsonExpr's ExprState,
like the previous patch was doing?I forget to incorporate your changes for subsidary ExprStates elimination.
See patch #8.
Thanks. Here are some comments.
First of all, regarding 0009, my understanding was that we should
disallow DEFAULT expression ON ERROR too for now, so something like
the following does not occur:
SELECT JSON_VALUE(jsonb '"err"', '$' RETURNING numeric DEFAULT ('{"'
|| -1+a || '"}')::text ON ERROR) from foo;
ERROR: invalid input syntax for type numeric: "{"0"}"
Patches 0001-0006:
Yeah, these add the overhead of an extra function call (typin() ->
typin_opt_error()) in possibly very common paths. Other than
refactoring *all* places that call typin() to use the new API, the
only other option seems to be to leave the typin() functions alone and
duplicate their code in typin_opt_error() versions for all the types
that this patch cares about. Though maybe, that's not necessarily a
better compromise than accepting the extra function call overhead.
Patch 0007:
+
+ /* Override default coercion in OMIT QUOTES case */
+ if (ExecJsonQueryNeedsIOCoercion(jexpr, res, *resnull))
+ {
+ char *str = JsonbUnquote(DatumGetJsonbP(res));
...
+ else if (ret_typid == VARCHAROID || ret_typid == BPCHAROID ||
+ ret_typid == BYTEAOID)
+ {
+ Jsonb *jb = DatumGetJsonbP(res);
+ char *str = JsonbToCString(NULL, &jb->root, VARSIZE(jb));
+
+ return ExecJsonStringCoercion(str, strlen(str),
ret_typid, ret_typmod);
+ }
I think it might be better to create ExecJsonQueryCoercion() similar
to ExecJsonValueCoercion() and put the above block in that function
rather than inlining it in ExecEvalJsonExprInternal().
+ ExecJsonStringCoercion(const char *str, int32 len, Oid typid, int32 typmod)
I'd suggest renaming this one to ExecJsonConvertCStringToText().
+ ExecJsonCoercionToText(PGFunction outfunc, Datum value, Oid typid,
int32 typmod)
+ ExecJsonDatetimeCoercion(Datum val, Oid val_typid, Oid typid, int32 typmod,
+ ExecJsonBoolCoercion(bool val, Oid typid, int32 typmod, Datum *res)
And also rename these to sound like verbs:
ExecJsonCoerceToText
ExecJsonCoerceDatetime[ToType]
ExecJsonCoerceBool[ToType]
+ /*
+ * XXX coercion to text is done using output functions, and they
+ * are mutable for non-time[tz] types due to using of DateStyle.
+ * We can pass USE_ISO_DATES, which is used inside jsonpath, to
+ * make these coercions and JSON_VALUE(RETURNING text) immutable.
+ *
+ * XXX Also timestamp[tz] output functions can throw "out of range"
+ * error, but this error seem to be not possible.
+ */
Are we planning to fix these before committing?
+static Datum
+JsonbPGetTextDatum(Jsonb *jb)
Maybe static inline?
- coercion = &coercions->composite;
- res = JsonbPGetDatum(JsonbValueToJsonb(item));
+ Assert(0); /* non-scalars must be rejected by JsonPathValue() */
I didn't notice any changes to JsonPathValue(). Is the new comment
referring to an existing behavior of JsonPathValue() or something that
must be done by the patch?
@@ -411,6 +411,26 @@ contain_mutable_functions_walker(Node *node, void *context)
{
JsonExpr *jexpr = castNode(JsonExpr, node);
Const *cnst;
+ bool returns_datetime;
+
+ /*
+ * Input fuctions for datetime types are stable. They can be
+ * called in JSON_VALUE(), when the resulting SQL/JSON is a
+ * string.
+ */
...
Sorry if you've mentioned it before, but are these hunks changing
contain_mutable_functions_walker() fixing a bug? That is, did the
original SQL/JSON patch miss doing this?
+ Oid collation; /* OID of collation, or InvalidOid if none */
I think the comment should rather say: /* Collation of <what>, ... */
+
+bool
+expr_can_throw_errors(Node *expr)
+{
+ if (!expr)
+ return false;
+
+ if (IsA(expr, Const))
+ return false;
+
+ /* TODO consider more cases */
+ return true;
+}
+extern bool expr_can_throw_errors(Node *expr);
+
Not used anymore.
Patch 0008:
Thanks for re-introducing this.
+bool
+ExecEvalJsonExprSkip(ExprState *state, ExprEvalStep *op)
+{
+ JsonExprState *jsestate = op->d.jsonexpr_skip.jsestate;
+
+ /*
+ * 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.
+ */
I think the part starting with ", though" is no longer necessary.
+ * Return value:
+ * 1 - Ok, jump to the end of JsonExpr
+ * 0 - empty result, need to execute DEFAULT ON EMPTY expression
+ * -1 - error occured, need to execute DEFAULT ON ERROR expression
...need to execute ON EMPTY/ERROR behavior
+ return 0; /* jump to ON EMPTY expression */
...
+ return -1; /* jump to ON ERROR expression */
Likewise:
/* jump to handle ON EMPTY/ERROR behavior */
+ * Jump to coercion step if true was returned,
+ * which signifies skipping of JSON path evaluation,
...
Jump to "end" if true was returned.
--
Thanks, Amit Langote
EDB: http://www.enterprisedb.com
On 2022-Aug-30, Amit Langote wrote:
Patches 0001-0006:
Yeah, these add the overhead of an extra function call (typin() ->
typin_opt_error()) in possibly very common paths. Other than
refactoring *all* places that call typin() to use the new API, the
only other option seems to be to leave the typin() functions alone and
duplicate their code in typin_opt_error() versions for all the types
that this patch cares about. Though maybe, that's not necessarily a
better compromise than accepting the extra function call overhead.
I think another possibility is to create a static inline function in the
corresponding .c module (say boolin_impl() in bool.c), which is called
by both the opt_error variant as well as the regular one. This would
avoid the duplicate code as well as the added function-call overhead.
--
Álvaro Herrera PostgreSQL Developer — https://www.EnterpriseDB.com/
Syntax error: function hell() needs an argument.
Please choose what hell you want to involve.
On Tue, Aug 30, 2022 at 6:19 PM Alvaro Herrera <alvherre@alvh.no-ip.org> wrote:
On 2022-Aug-30, Amit Langote wrote:
Patches 0001-0006:
Yeah, these add the overhead of an extra function call (typin() ->
typin_opt_error()) in possibly very common paths. Other than
refactoring *all* places that call typin() to use the new API, the
only other option seems to be to leave the typin() functions alone and
duplicate their code in typin_opt_error() versions for all the types
that this patch cares about. Though maybe, that's not necessarily a
better compromise than accepting the extra function call overhead.I think another possibility is to create a static inline function in the
corresponding .c module (say boolin_impl() in bool.c), which is called
by both the opt_error variant as well as the regular one. This would
avoid the duplicate code as well as the added function-call overhead.
+1
--
Thanks, Amit Langote
EDB: http://www.enterprisedb.com
On 2022-08-30 Tu 06:29, Amit Langote wrote:
On Tue, Aug 30, 2022 at 6:19 PM Alvaro Herrera <alvherre@alvh.no-ip.org> wrote:
On 2022-Aug-30, Amit Langote wrote:
Patches 0001-0006:
Yeah, these add the overhead of an extra function call (typin() ->
typin_opt_error()) in possibly very common paths. Other than
refactoring *all* places that call typin() to use the new API, the
only other option seems to be to leave the typin() functions alone and
duplicate their code in typin_opt_error() versions for all the types
that this patch cares about. Though maybe, that's not necessarily a
better compromise than accepting the extra function call overhead.I think another possibility is to create a static inline function in the
corresponding .c module (say boolin_impl() in bool.c), which is called
by both the opt_error variant as well as the regular one. This would
avoid the duplicate code as well as the added function-call overhead.+1
Makes plenty of sense, I'll try to come up with replacements for these
forthwith.
cheers
andrew
--
Andrew Dunstan
EDB: https://www.enterprisedb.com
On 8/30/22 9:16 AM, Andrew Dunstan wrote:
On 2022-08-30 Tu 06:29, Amit Langote wrote:
On Tue, Aug 30, 2022 at 6:19 PM Alvaro Herrera <alvherre@alvh.no-ip.org> wrote:
On 2022-Aug-30, Amit Langote wrote:
Patches 0001-0006:
Yeah, these add the overhead of an extra function call (typin() ->
typin_opt_error()) in possibly very common paths. Other than
refactoring *all* places that call typin() to use the new API, the
only other option seems to be to leave the typin() functions alone and
duplicate their code in typin_opt_error() versions for all the types
that this patch cares about. Though maybe, that's not necessarily a
better compromise than accepting the extra function call overhead.I think another possibility is to create a static inline function in the
corresponding .c module (say boolin_impl() in bool.c), which is called
by both the opt_error variant as well as the regular one. This would
avoid the duplicate code as well as the added function-call overhead.+1
Makes plenty of sense, I'll try to come up with replacements for these
forthwith.
The RMT had its weekly meeting today to discuss open items. As stated
last week, to keep the v15 release within a late Sept / early Oct
timeframe, we need to make a decision about the inclusion of SQL/JSON
this week.
First, we appreciate all of the effort and work that has gone into
incorporating community feedback into the patches. We did note that
folks working on this made a lot of progress over the past week.
The RMT still has a few concerns, summarized as:
1. There is not yet consensus on the current patch proposals as we
approach the end of the major release cycle
2. There is a lack of general feedback from folks who raised concerns
about the implementation
The RMT is still inclined to revert, but will give folks until Sep 1
0:00 AoE[1]https://en.wikipedia.org/wiki/Anywhere_on_Earth to reach consensus on if SQL/JSON can be included in v15.
This matches up to Andrew's availability timeline for a revert, and
gives enough time to get through the buildfarm prior to the Beta 4
release[2]/messages/by-id/9d251aec-cea2-bc1a-5ed8-46ef0bcf6c69@postgresql.org.
After the deadline, if there is no consensus on how to proceed, the RMT
will request that the patches are reverted.
While noting that this RMT has no decision making over v16, in the event
of a revert we do hope this recent work can be the basis of the feature
in v16.
Again, we appreciate the efforts that have gone into addressing the
community feedback.
Sincerely,
John, Jonathan, Michael
[1]: https://en.wikipedia.org/wiki/Anywhere_on_Earth
[2]: /messages/by-id/9d251aec-cea2-bc1a-5ed8-46ef0bcf6c69@postgresql.org
/messages/by-id/9d251aec-cea2-bc1a-5ed8-46ef0bcf6c69@postgresql.org
On 30.08.2022 11:09, Amit Langote wrote:
- Added JSON_QUERY coercion to UTF8 bytea using pg_convert_to().
- Added immutability checks that were missed with elimination
of coercion expressions.
Coercions text::datetime, datetime1::datetime2 and even
datetime::text for some datetime types are mutable.
datetime::text can be made immutable by passing ISO date
style into output functions (like in jsonpath).- Disabled non-Const expressions in DEFAULT ON EMPTY in non
ERROR ON ERROR case. Non-constant expressions are tried to
evaluate into Const directly inside transformExpr().I am not sure if it's OK to eval_const_expressions() on a Query
sub-expression during parse-analysis. IIUC, it is only correct to
apply it to after the rewriting phase.I also was not sure. Maybe it can be moved to rewriting phase or
even to execution phase.I suppose we wouldn't need to bother with doing this when we
eventually move to supporting the DEFAULT expressions.Maybe it would be better to simply remove DEFAULT ON EMPTY.
So +1 to this for now.
See last patch #9.
It is possible to easily split this patch into several subpatches,
I will do it if needed.That would be nice indeed.
I have extracted patches #1-6 with numerous safe input and type conversion
functions.I'm wondering if you're going to change the PASSING values
initialization to add the steps into the parent JsonExpr's ExprState,
like the previous patch was doing?I forget to incorporate your changes for subsidary ExprStates elimination.
See patch #8.Thanks. Here are some comments.
First of all, regarding 0009, my understanding was that we should
disallow DEFAULT expression ON ERROR too for now, so something like
the following does not occur:SELECT JSON_VALUE(jsonb '"err"', '$' RETURNING numeric DEFAULT ('{"'
|| -1+a || '"}')::text ON ERROR) from foo;
ERROR: invalid input syntax for type numeric: "{"0"}"
Personally, I don't like complete removal of DEFAULT behaviors, but
I've done it in patch #10 (JsonBehavior node removed, grammar fixed).
Patches 0001-0006:
On 30.08.2022 13:29, Amit Langote wrote:
On Tue, Aug 30, 2022 at 6:19 PM Alvaro Herrera<alvherre@alvh.no-ip.org> wrote:
On 2022-Aug-30, Amit Langote wrote:
Patches 0001-0006:
Yeah, these add the overhead of an extra function call (typin() ->
typin_opt_error()) in possibly very common paths. Other than
refactoring *all* places that call typin() to use the new API, the
only other option seems to be to leave the typin() functions alone and
duplicate their code in typin_opt_error() versions for all the types
that this patch cares about. Though maybe, that's not necessarily a
better compromise than accepting the extra function call overhead.I think another possibility is to create a static inline function in the
corresponding .c module (say boolin_impl() in bool.c), which is called
by both the opt_error variant as well as the regular one. This would
avoid the duplicate code as well as the added function-call overhead.+1
I always thought about such internal inline functions, I 've added them in v10.
Patch 0007:
+ + /* Override default coercion in OMIT QUOTES case */ + if (ExecJsonQueryNeedsIOCoercion(jexpr, res, *resnull)) + { + char *str = JsonbUnquote(DatumGetJsonbP(res)); ... + else if (ret_typid == VARCHAROID || ret_typid == BPCHAROID || + ret_typid == BYTEAOID) + { + Jsonb *jb = DatumGetJsonbP(res); + char *str = JsonbToCString(NULL, &jb->root, VARSIZE(jb)); + + return ExecJsonStringCoercion(str, strlen(str), ret_typid, ret_typmod); + }I think it might be better to create ExecJsonQueryCoercion() similar
to ExecJsonValueCoercion() and put the above block in that function
rather than inlining it in ExecEvalJsonExprInternal().
Extracted ExecJsonQueryCoercion().
+ ExecJsonStringCoercion(const char *str, int32 len, Oid typid, int32 typmod)
I'd suggest renaming this one to ExecJsonConvertCStringToText().
+ ExecJsonCoercionToText(PGFunction outfunc, Datum value, Oid typid, int32 typmod) + ExecJsonDatetimeCoercion(Datum val, Oid val_typid, Oid typid, int32 typmod, + ExecJsonBoolCoercion(bool val, Oid typid, int32 typmod, Datum *res)And also rename these to sound like verbs:
ExecJsonCoerceToText
ExecJsonCoerceDatetime[ToType]
ExecJsonCoerceBool[ToType]
Fixed.
+ /* + * XXX coercion to text is done using output functions, and they + * are mutable for non-time[tz] types due to using of DateStyle. + * We can pass USE_ISO_DATES, which is used inside jsonpath, to + * make these coercions and JSON_VALUE(RETURNING text) immutable. + * + * XXX Also timestamp[tz] output functions can throw "out of range" + * error, but this error seem to be not possible. + */Are we planning to fix these before committing?
I don't know, but the first issue is critical for building functional indexes
on JSON_VALUE().
+static Datum
+JsonbPGetTextDatum(Jsonb *jb)Maybe static inline?
Fixed.
- coercion = &coercions->composite; - res = JsonbPGetDatum(JsonbValueToJsonb(item)); + Assert(0); /* non-scalars must be rejected by JsonPathValue() */I didn't notice any changes to JsonPathValue(). Is the new comment
referring to an existing behavior of JsonPathValue() or something that
must be done by the patch?
JsonPathValue() has a check for non-scalars items, this is simply a new comment.
@@ -411,6 +411,26 @@ contain_mutable_functions_walker(Node *node, void *context) { JsonExpr *jexpr = castNode(JsonExpr, node); Const *cnst; + bool returns_datetime; + + /* + * Input fuctions for datetime types are stable. They can be + * called in JSON_VALUE(), when the resulting SQL/JSON is a + * string. + */ ...Sorry if you've mentioned it before, but are these hunks changing
contain_mutable_functions_walker() fixing a bug? That is, did the
original SQL/JSON patch miss doing this?
In the original patch there were checks for mutability of expressions contained
in JsonCoercion nodes. After their removal, we need to use hardcoded checks.
+ Oid collation; /* OID of collation, or InvalidOid if none */
I think the comment should rather say: /* Collation of <what>, ... */
Fixed.
+ +bool +expr_can_throw_errors(Node *expr) +{ + if (!expr) + return false; + + if (IsA(expr, Const)) + return false; + + /* TODO consider more cases */ + return true; +}+extern bool expr_can_throw_errors(Node *expr); +Not used anymore.
expr_can_throw_errors() removed.
Patch 0008:
Thanks for re-introducing this.
+bool +ExecEvalJsonExprSkip(ExprState *state, ExprEvalStep *op) +{ + JsonExprState *jsestate = op->d.jsonexpr_skip.jsestate; + + /* + * 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. + */I think the part starting with ", though" is no longer necessary.
Fixed.
+ * Return value: + * 1 - Ok, jump to the end of JsonExpr + * 0 - empty result, need to execute DEFAULT ON EMPTY expression + * -1 - error occured, need to execute DEFAULT ON ERROR expression...need to execute ON EMPTY/ERROR behavior
+ return 0; /* jump to ON EMPTY expression */ ... + return -1; /* jump to ON ERROR expression */Likewise:
/* jump to handle ON EMPTY/ERROR behavior */
+ * Jump to coercion step if true was returned, + * which signifies skipping of JSON path evaluation, ...Jump to "end" if true was returned.
Fixed, but I leaved "expression" instead of "behavior" because
these jumps are needed only for execution of DEFAULT expressions.
--
Nikita Glukhov
Postgres Professional:http://www.postgrespro.com
The Russian Postgres Company
Attachments:
v10-0006-Add-safe-input-function-for-jsonb.patchtext/x-patch; charset=UTF-8; name=v10-0006-Add-safe-input-function-for-jsonb.patchDownload
From cfdbf4a7f6da17096e8ff1a2534452647de282f1 Mon Sep 17 00:00:00 2001
From: Nikita Glukhov <n.gluhov@postgrespro.ru>
Date: Mon, 29 Aug 2022 23:56:45 +0300
Subject: [PATCH v10 06/10] Add safe input function for jsonb
---
src/backend/utils/adt/jsonb.c | 26 ++++++++++++++++++--------
src/include/utils/jsonb.h | 2 ++
2 files changed, 20 insertions(+), 8 deletions(-)
diff --git a/src/backend/utils/adt/jsonb.c b/src/backend/utils/adt/jsonb.c
index f700c5b4c93..1da519daaef 100644
--- a/src/backend/utils/adt/jsonb.c
+++ b/src/backend/utils/adt/jsonb.c
@@ -46,7 +46,6 @@ typedef struct JsonbAggState
Oid val_output_func;
} JsonbAggState;
-static inline Datum jsonb_from_cstring(char *json, int len, bool unique_keys);
static size_t checkStringLen(size_t len);
static void jsonb_in_object_start(void *pstate);
static void jsonb_in_object_end(void *pstate);
@@ -77,7 +76,7 @@ jsonb_in(PG_FUNCTION_ARGS)
{
char *json = PG_GETARG_CSTRING(0);
- return jsonb_from_cstring(json, strlen(json), false);
+ PG_RETURN_DATUM(jsonb_from_cstring(json, strlen(json), false, NULL));
}
/*
@@ -101,7 +100,7 @@ jsonb_recv(PG_FUNCTION_ARGS)
else
elog(ERROR, "unsupported jsonb version number %d", version);
- return jsonb_from_cstring(str, nbytes, false);
+ return jsonb_from_cstring(str, nbytes, false, NULL);
}
/*
@@ -147,7 +146,7 @@ jsonb_from_text(text *js, bool unique_keys)
{
return jsonb_from_cstring(VARDATA_ANY(js),
VARSIZE_ANY_EXHDR(js),
- unique_keys);
+ unique_keys, NULL);
}
/*
@@ -239,12 +238,13 @@ jsonb_typeof(PG_FUNCTION_ARGS)
*
* Uses the json parser (with hooks) to construct a jsonb.
*/
-static inline Datum
-jsonb_from_cstring(char *json, int len, bool unique_keys)
+Datum
+jsonb_from_cstring(char *json, int len, bool unique_keys, bool *error)
{
JsonLexContext *lex;
JsonbInState state;
JsonSemAction sem;
+ JsonParseErrorType result;
memset(&state, 0, sizeof(state));
memset(&sem, 0, sizeof(sem));
@@ -261,10 +261,20 @@ jsonb_from_cstring(char *json, int len, bool unique_keys)
sem.scalar = jsonb_in_scalar;
sem.object_field_start = jsonb_in_object_field_start;
- pg_parse_json_or_ereport(lex, &sem);
+ result = pg_parse_json(lex, &sem);
+ if (result != JSON_SUCCESS)
+ {
+ if (error)
+ {
+ *error = true;
+ return (Datum) 0;
+ }
+
+ json_ereport_error(result, lex);
+ }
/* after parsing, the item member has the composed jsonb structure */
- PG_RETURN_POINTER(JsonbValueToJsonb(state.res));
+ return JsonbPGetDatum(JsonbValueToJsonb(state.res));
}
static size_t
diff --git a/src/include/utils/jsonb.h b/src/include/utils/jsonb.h
index bae466b5234..d5574faa79e 100644
--- a/src/include/utils/jsonb.h
+++ b/src/include/utils/jsonb.h
@@ -420,6 +420,8 @@ extern void JsonbHashScalarValueExtended(const JsonbValue *scalarVal,
/* jsonb.c support functions */
extern Datum jsonb_from_text(text *js, bool unique_keys);
+extern Datum jsonb_from_cstring(char *json, int len, bool unique_keys,
+ bool *error);
extern char *JsonbToCString(StringInfo out, JsonbContainer *in,
int estimated_len);
extern char *JsonbToCStringIndent(StringInfo out, JsonbContainer *in,
--
2.17.1
v10-0007-Remove-subtransactions-in-SQL-JSON-execution.patchtext/x-patch; charset=UTF-8; name=v10-0007-Remove-subtransactions-in-SQL-JSON-execution.patchDownload
From 2722882ff0ed476afe5a6319e16d04392232b421 Mon Sep 17 00:00:00 2001
From: Nikita Glukhov <n.gluhov@postgrespro.ru>
Date: Tue, 30 Aug 2022 23:01:30 +0300
Subject: [PATCH v10 07/10] Remove subtransactions in SQL/JSON execution
---
src/backend/executor/execExpr.c | 180 ++--
src/backend/executor/execExprInterp.c | 948 ++++++++++++--------
src/backend/jit/llvm/llvmjit_expr.c | 2 +-
src/backend/jit/llvm/llvmjit_types.c | 2 +-
src/backend/nodes/nodeFuncs.c | 91 +-
src/backend/optimizer/util/clauses.c | 73 +-
src/backend/parser/parse_expr.c | 226 ++---
src/backend/utils/adt/jsonpath.c | 8 +-
src/include/executor/execExpr.h | 49 +-
src/include/nodes/primnodes.h | 36 +-
src/include/optimizer/clauses.h | 2 +
src/include/utils/jsonpath.h | 3 +-
src/test/regress/expected/jsonb_sqljson.out | 327 +++++--
src/test/regress/sql/jsonb_sqljson.sql | 72 +-
src/tools/pgindent/typedefs.list | 1 -
15 files changed, 1134 insertions(+), 886 deletions(-)
diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
index d0a57c7aaee..f50e0d703f8 100644
--- a/src/backend/executor/execExpr.c
+++ b/src/backend/executor/execExpr.c
@@ -86,7 +86,9 @@ static void ExecBuildAggTransCall(ExprState *state, AggState *aggstate,
FunctionCallInfo fcinfo, AggStatePerTrans pertrans,
int transno, int setno, int setoff, bool ishash,
bool nullcheck);
-
+static void ExecInitJsonExpr(JsonExpr *jexpr, ExprState *state,
+ ExprEvalStep *scratch,
+ Datum *resv, bool *resnull);
static ExprState *
ExecInitExprInternal(Expr *node, PlanState *parent, ParamListInfo ext_params,
@@ -2559,114 +2561,9 @@ 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;
-
- if (jexpr->coercions)
- {
- JsonCoercion **coercion;
- struct JsonCoercionState *cstate;
- Datum *caseval;
- bool *casenull;
-
- 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;
- }
- }
-
- ExprEvalPushStep(state, &scratch);
- break;
- }
+ ExecInitJsonExpr(castNode(JsonExpr, node), state, &scratch,
+ resv, resnull);
+ break;
default:
elog(ERROR, "unrecognized node type: %d",
@@ -4257,3 +4154,68 @@ ExecBuildParamSetEqual(TupleDesc desc,
return state;
}
+
+static void
+ExecInitJsonExpr(JsonExpr *jexpr, ExprState *state, ExprEvalStep *scratch,
+ Datum *resv, bool *resnull)
+{
+ JsonExprState *jsestate;
+ ListCell *argexprlc;
+ ListCell *argnamelc;
+
+ /* JSON_TABLE preudo-function returns context item as a result */
+ if (jexpr->op == JSON_TABLE_OP)
+ {
+ ExecInitExprRec((Expr *) jexpr->formatted_expr, state,
+ resv, resnull);
+ return;
+ }
+
+ jsestate = palloc0(sizeof(JsonExprState));
+ jsestate->jsexpr = jexpr;
+
+ scratch->opcode = EEOP_JSONEXPR;
+ scratch->d.jsonexpr.jsestate = jsestate;
+
+ ExecInitExprRec((Expr *) jexpr->formatted_expr, state,
+ &jsestate->formatted_expr.value,
+ &jsestate->formatted_expr.isnull);
+
+ ExecInitExprRec((Expr *) jexpr->path_spec, state,
+ &jsestate->pathspec.value,
+ &jsestate->pathspec.isnull);
+
+ if (jexpr->on_empty)
+ jsestate->default_on_empty =
+ ExecInitExpr((Expr *) jexpr->on_empty->default_expr,
+ state->parent);
+
+ jsestate->default_on_error =
+ ExecInitExpr((Expr *) jexpr->on_error->default_expr,
+ state->parent);
+
+ 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);
+ }
+
+ ExprEvalPushStep(state, scratch);
+}
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index 636794ca6f1..56f7bd2a5ac 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -75,6 +75,7 @@
#include "utils/date.h"
#include "utils/datum.h"
#include "utils/expandedrecord.h"
+#include "utils/float.h"
#include "utils/json.h"
#include "utils/jsonb.h"
#include "utils/jsonfuncs.h"
@@ -1844,7 +1845,7 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
EEO_CASE(EEOP_JSONEXPR)
{
/* too complex for an inline implementation */
- ExecEvalJson(state, op, econtext);
+ ExecEvalJsonExpr(state, op, econtext);
EEO_NEXT();
}
@@ -4700,96 +4701,6 @@ ExecEvalJsonConstructor(ExprState *state, ExprEvalStep *op,
*op->resnull = isnull;
}
-/*
- * Evaluate a JSON error/empty behavior result.
- */
-static Datum
-ExecEvalJsonBehavior(ExprContext *econtext, JsonBehavior *behavior,
- ExprState *default_estate, 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:
- 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.
*/
@@ -4843,85 +4754,420 @@ EvalJsonPathVar(void *cxt, char *varName, int varNameLen,
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)
+/* Coerce C string to text, varchar(N), or bpchar(N) */
+static Datum
+ExecJsonCoerceCStringToText(const char *str, int32 len,
+ Oid typid, int32 typmod)
{
- struct JsonCoercionState *coercion;
- Datum res;
- JsonbValue buf;
+ if (typid == BYTEAOID)
+ {
+ text *txt = cstring_to_text_with_len(str, len);
+ NameData encoding = {0};
- if (item->type == jbvBinary &&
- JsonContainerIsScalar(item->val.binary.data))
+ /* UTF8 is the only one supported encoding */
+ strcpy(NameStr(encoding), "UTF8");
+
+ return DirectFunctionCall2(pg_convert_to,
+ PointerGetDatum(txt),
+ PointerGetDatum(&encoding));
+ }
+ else if (typid == TEXTOID || typid == JSONOID || typmod <= VARHDRSZ)
+ return PointerGetDatum(cstring_to_text_with_len(str, len));
+ else
{
- bool res PG_USED_FOR_ASSERTS_ONLY;
+ char *txt;
+ int32 size;
- res = JsonbExtractScalar(item->val.binary.data, &buf);
- item = &buf;
- Assert(res);
+ len = VARHDRSZ + len;
+
+ if (typid == BPCHAROID)
+ size = typmod;
+ else
+ size = Min(len, typmod);
+
+ txt = palloc(size);
+ SET_VARSIZE(txt, size);
+
+ memcpy(VARDATA(txt), str, Min(size, len) - VARHDRSZ);
+
+ if (len < size)
+ memset(txt + len, ' ', size - len);
+
+ return PointerGetDatum(txt);
}
+}
- /* get coercion state reference and datum of the corresponding SQL type */
+/* Coerce SQL/JSON item to text */
+static Datum
+ExecJsonCoerceToText(PGFunction outfunc, Datum value, Oid typid, int32 typmod)
+{
+ char *str = DatumGetCString(DirectFunctionCall1(outfunc, value));
+
+ return ExecJsonCoerceCStringToText(str, strlen(str), typid, typmod);
+}
+
+/* Coerce datetime SQL/JSON item to the output typid */
+static Datum
+ExecJsonCoerceDatetime(Datum val, Oid val_typid, Oid typid, int32 typmod,
+ bool *isnull, bool *error)
+{
+ if (val_typid == typid)
+ return val;
+
+ /*
+ * XXX coercion to text is done using output functions, and they
+ * are mutable for non-time[tz] types due to using of DateStyle.
+ * We can pass USE_ISO_DATES, which is used inside jsonpath, to
+ * make these coercions and JSON_VALUE(RETURNING text) immutable.
+ *
+ * XXX Also timestamp[tz] output functions can throw "out of range"
+ * error, but this error seem to be not possible.
+ */
+ switch (val_typid)
+ {
+ case DATEOID:
+ if (typid == TEXTOID || typid == VARCHAROID || typid == BPCHAROID)
+ return ExecJsonCoerceToText(date_out, val, typid, typmod);
+ else if (typid == DATEOID)
+ return val;
+ else if (typid == TIMESTAMPOID)
+ {
+ int overflow = 0;
+ Timestamp ts =
+ date2timestamp_opt_overflow(DatumGetDateADT(val),
+ error ? &overflow : NULL);
+
+ if (overflow)
+ {
+ *error = true;
+ return (Datum) 0;
+ }
+
+ return TimestampGetDatum(ts);
+ }
+ else if (typid == TIMESTAMPTZOID)
+ {
+ int overflow = 0;
+ TimestampTz res =
+ date2timestamptz_opt_overflow(DatumGetDateADT(val),
+ error ? &overflow : NULL);
+
+ if (overflow)
+ {
+ *error = true;
+ return (Datum) 0;
+ }
+
+ return TimestampTzGetDatum(res);
+ }
+ else
+ break; /* No cast */
+
+ case TIMEOID:
+ if (typid == TEXTOID || typid == VARCHAROID || typid == BPCHAROID)
+ return ExecJsonCoerceToText(time_out, val, typid, typmod);
+ else if (typid == TIMEOID)
+ return val;
+ else if (typid == TIMETZOID)
+ return DirectFunctionCall1(time_timetz, val);
+ else
+ break; /* No cast */
+
+ case TIMETZOID:
+ if (typid == TEXTOID || typid == VARCHAROID || typid == BPCHAROID)
+ return ExecJsonCoerceToText(timetz_out, val, typid, typmod);
+ else if (typid == TIMETZOID)
+ return val;
+ else if (typid == TIMEOID)
+ return DirectFunctionCall1(timetz_time, val);
+ else
+ break;
+
+ case TIMESTAMPOID:
+ if (typid == TEXTOID || typid == VARCHAROID || typid == BPCHAROID)
+ return ExecJsonCoerceToText(timestamp_out, val, typid, typmod);
+ else if (typid == TIMESTAMPOID)
+ return val;
+ else if (typid == DATEOID)
+ return timestamp_date_opt_error(DatumGetTimestamp(val), error);
+ else if (typid == TIMEOID)
+ {
+ TimeADT time =
+ timestamp_time_opt_error(DatumGetTimestamp(val),
+ isnull, error);
+
+ if ((error && *error) || *isnull)
+ return (Datum) 0;
+ else
+ return TimeADTGetDatum(time);
+ }
+ else if (typid == TIMESTAMPTZOID)
+ {
+ int overflow = 0;
+ TimestampTz tstz =
+ timestamp2timestamptz_opt_overflow(DatumGetTimestamp(val),
+ error ? &overflow : NULL);
+
+ if (overflow)
+ {
+ *error = true;
+ return (Datum) 0;
+ }
+
+ return TimestampTzGetDatum(tstz);
+ }
+ else
+ break; /* No cast */
+
+ case TIMESTAMPTZOID:
+ if (typid == TEXTOID || typid == VARCHAROID || typid == BPCHAROID)
+ return ExecJsonCoerceToText(timestamptz_out, val, typid, typmod);
+ else if (typid == TIMESTAMPTZOID)
+ return val;
+ else if (typid == DATEOID)
+ return timestamptz_date_opt_error(DatumGetTimestampTz(val), error);
+ else if (typid == TIMEOID)
+ {
+ TimeADT time =
+ timestamptz_time_opt_error(DatumGetTimestampTz(val),
+ isnull, error);
+
+ if ((error && *error) || *isnull)
+ return (Datum) 0;
+ else
+ return TimeADTGetDatum(time);
+ }
+ else if (typid == TIMETZOID)
+ {
+ TimeTzADT *result =
+ timestamptz_timetz_opt_error(DatumGetTimestampTz(val), error);
+
+ if ((error && *error) || !result)
+ {
+ *isnull = true;
+ return (Datum) 0;
+ }
+
+ return TimeTzADTPGetDatum(result);
+ }
+ else if (typid == TIMESTAMPOID)
+ {
+ Timestamp ts =
+ timestamptz2timestamp_opt_error(DatumGetTimestampTz(val),
+ error);
+
+ if (error && *error)
+ return (Datum) 0;
+
+ return TimestampGetDatum(ts);
+ }
+ else
+ break; /* No cast */
+
+ default:
+ elog(ERROR, "unexpected jsonb datetime type oid %u", val_typid);
+ break;
+ }
+
+ if (!error)
+ ereport(ERROR,
+ (errcode(ERRCODE_SQL_JSON_ITEM_CANNOT_BE_CAST_TO_TARGET_TYPE),
+ errmsg("SQL/JSON item cannot be cast to target type")));
+
+ *error = true;
+ *isnull = true;
+
+ return (Datum) 0;
+}
+
+/* Coerce boolean SQL/JSON item or JSON_EXISTS result to the output type */
+static bool
+ExecJsonCoerceBool(bool val, Oid typid, int32 typmod, Datum *res)
+{
+ if (typid == BOOLOID)
+ *res = BoolGetDatum(val);
+ else if (typid == INT4OID)
+ /* We only have cast bool::int4 in the catalog. */
+ *res = Int32GetDatum(val ? 1 : 0);
+ else if (typid == TEXTOID || typid == VARCHAROID || typid == BPCHAROID)
+ /*
+ * bool::text returns 'true' / 'false',
+ * boolout() returns 't' / 'f'.
+ */
+ *res = ExecJsonCoerceCStringToText(val ? "true" : "false",
+ val ? 4 : 5, typid, typmod);
+ else
+ return false;
+
+ return true;
+}
+
+static inline Datum
+JsonbPGetTextDatum(Jsonb *jb)
+{
+ char *str = JsonbToCString(NULL, &jb->root, VARSIZE(jb));
+ Datum res = CStringGetTextDatum(str);
+
+ pfree(str);
+ return res;
+}
+
+/* Coerce SQL/JSON item to the output typid */
+static Datum
+ExecJsonValueCoercion(JsonbValue *item, Oid typid, int32 typmod,
+ bool *isnull, bool *error)
+{
+ *isnull = false;
+
+ /* Special case for json and jsonb output types */
+ if (typid == JSONBOID)
+ return JsonbPGetDatum(JsonbValueToJsonb(item));
+
+ if (typid == JSONOID)
+ return JsonbPGetTextDatum(JsonbValueToJsonb(item));
+
+ /*
+ * Coercion method and set of supported output types are determined
+ * by the item type.
+ */
switch (item->type)
{
case jbvNull:
- coercion = &coercions->null;
- res = (Datum) 0;
- break;
+ Assert(0); /* must be handled by the caller */
+ *isnull = true;
+ return (Datum) 0;
case jbvString:
- coercion = &coercions->string;
- res = PointerGetDatum(cstring_to_text_with_len(item->val.string.val,
- item->val.string.len));
- break;
+ if (typid == TEXTOID || typid == VARCHAROID || typid == BPCHAROID)
+ {
+ return ExecJsonCoerceCStringToText(item->val.string.val,
+ item->val.string.len,
+ typid, typmod);
+ }
+ else if (typid == INT2OID || typid == INT4OID || typid == INT8OID)
+ {
+ char *str = pnstrdup(item->val.string.val,
+ item->val.string.len);
+
+ if (error)
+ {
+ int64 val = pg_strtoint64_opt_error(str, error);
+
+ if (*error)
+ return (Datum) 0;
+
+ if (typid == INT2OID)
+ {
+ if (val <= PG_INT16_MAX || val >= PG_INT16_MIN)
+ return Int16GetDatum((int16) val);
+ }
+ else if (typid == INT4OID)
+ {
+ if (val <= PG_INT32_MAX || val >= PG_INT32_MIN)
+ return Int32GetDatum((int32) val);
+ }
+ else
+ return Int64GetDatum(val);
+
+ *error = true;
+ return (Datum) 0;
+ }
+ else if (typid == INT2OID)
+ return Int16GetDatum(pg_strtoint16(str));
+ else if (typid == INT4OID)
+ return Int32GetDatum(pg_strtoint32(str));
+ else
+ return Int64GetDatum(pg_strtoint64(str));
+ }
+ else if (typid == BOOLOID)
+ {
+ return BoolGetDatum(boolin_opt_error(item->val.string.val,
+ item->val.string.len,
+ error));
+ }
+ else
+ {
+ char *str = pnstrdup(item->val.string.val,
+ item->val.string.len);
+
+ if (typid == FLOAT4OID)
+ return Float4GetDatum(float4in_opt_error(str, error));
+ else if (typid == FLOAT8OID)
+ return Float8GetDatum(
+ float8in_internal_opt_error(str, NULL,
+ "double precision",
+ str, error));
+ else if (typid == NUMERICOID)
+ return NumericGetDatum(numeric_in_opt_error(str, typmod, error));
+ else if (typid == DATEOID)
+ return DateADTGetDatum(date_in_opt_error(str, error));
+ else if (typid == TIMEOID)
+ return TimeADTGetDatum(time_in_opt_error(str, typmod, error));
+ else if (typid == TIMETZOID)
+ return TimeTzADTPGetDatum(timetz_in_opt_error(str, typmod, error));
+ else if (typid == TIMESTAMPOID)
+ return TimestampGetDatum(timestamp_in_opt_error(str, typmod, error));
+ else if (typid == TIMESTAMPTZOID)
+ return TimestampTzGetDatum(timestamptz_in_opt_error(str, typmod, error));
+ else
+ break; /* No cast */
+ }
case jbvNumeric:
- coercion = &coercions->numeric;
- res = NumericGetDatum(item->val.numeric);
- break;
+ {
+ Numeric num = item->val.numeric;
+
+ if (typid == NUMERICOID)
+ return NumericGetDatum(num);
+ else if (typid == TEXTOID || typid == VARCHAROID || typid == BPCHAROID)
+ return ExecJsonCoerceToText(numeric_out, NumericGetDatum(num), typid, typmod);
+ else if (typid == INT2OID)
+ return Int16GetDatum(numeric_int2_opt_error(num, error));
+ else if (typid == INT4OID)
+ return Int32GetDatum(numeric_int4_opt_error(num, error));
+ else if (typid == INT8OID)
+ return Int64GetDatum(numeric_int8_opt_error(num, error));
+ else if (typid == FLOAT4OID || typid == FLOAT8OID)
+ {
+ /*
+ * XXX numeric_float8() also uses I/O coercion, but
+ * it has special handling of Inf and NaN.
+ */
+ Datum cstr = DirectFunctionCall1(numeric_out,
+ NumericGetDatum(num));
+ char *str = DatumGetCString(cstr);
- case jbvBool:
- coercion = &coercions->boolean;
- res = BoolGetDatum(item->val.boolean);
- break;
+ if (typid == FLOAT4OID)
+ return Float4GetDatum(float4in_opt_error(str, error));
+ else
+ return Float8GetDatum(
+ float8in_internal_opt_error(str, NULL,
+ "double precision",
+ str, error));
+ }
+ else
+ break; /* No cast */
+ }
- case jbvDatetime:
- res = item->val.datetime.value;
- switch (item->val.datetime.typid)
+ case jbvBool:
{
- case DATEOID:
- coercion = &coercions->date;
- break;
- case TIMEOID:
- coercion = &coercions->time;
- break;
- case TIMETZOID:
- coercion = &coercions->timetz;
- break;
- case TIMESTAMPOID:
- coercion = &coercions->timestamp;
- break;
- case TIMESTAMPTZOID:
- coercion = &coercions->timestamptz;
- break;
- default:
- elog(ERROR, "unexpected jsonb datetime type oid %u",
- item->val.datetime.typid);
- return (Datum) 0;
+ Datum res;
+
+ if (ExecJsonCoerceBool(item->val.boolean, typid, typmod, &res))
+ return res;
+
+ break; /* No cast */
}
- break;
+
+ case jbvDatetime:
+ return ExecJsonCoerceDatetime(item->val.datetime.value,
+ item->val.datetime.typid,
+ typid, typmod, isnull, error);
case jbvArray:
case jbvObject:
case jbvBinary:
- coercion = &coercions->composite;
- res = JsonbPGetDatum(JsonbValueToJsonb(item));
+ Assert(0); /* non-scalars must be rejected by JsonPathValue() */
break;
default:
@@ -4929,99 +5175,184 @@ ExecPrepareJsonItemCoercion(JsonbValue *item,
return (Datum) 0;
}
- *pcoercion = coercion;
+ if (!error)
+ ereport(ERROR,
+ (errcode(ERRCODE_SQL_JSON_ITEM_CANNOT_BE_CAST_TO_TARGET_TYPE),
+ errmsg("SQL/JSON item cannot be cast to target type")));
- return res;
+ *error = true;
+ *isnull = true;
+
+ return (Datum) 0;
}
-typedef Datum (*JsonFunc) (ExprEvalStep *op, ExprContext *econtext,
- Datum item, bool *resnull, void *p, bool *error);
+/*
+ * Check whether we need to override default coercion in
+ * JSON_QUERY(OMIT QUOTES) case.
+ */
+static bool
+ExecJsonQueryNeedsIOCoercion(JsonExpr *jsexpr, Datum res)
+{
+ if (jsexpr->omit_quotes)
+ {
+ Jsonb *jb = DatumGetJsonbP(res);
+ JsonbValue jbv;
+
+ /* Coerce string items via I/O in OMIT QUOTES case */
+ return JsonbExtractScalar(&jb->root, &jbv) &&
+ jbv.type == jbvString;
+ }
+
+ return false;
+}
+/* Coerce JSONB datum to the output typid(typmod) */
static Datum
-ExecEvalJsonExprSubtrans(JsonFunc func, ExprEvalStep *op,
- ExprContext *econtext,
- Datum res, bool *resnull,
- void *p, bool *error, bool subtrans)
+ExecJsonQueryCoercion(JsonExpr *jexpr, Oid typid, int32 typmod,
+ Datum jb, bool *error)
{
- MemoryContext oldcontext;
- ResourceOwner oldowner;
+ /* Override default coercion in OMIT QUOTES case */
+ if (ExecJsonQueryNeedsIOCoercion(jexpr, jb))
+ {
+ char *str = JsonbUnquote(DatumGetJsonbP(jb));
- if (!subtrans)
- /* No need to use subtransactions. */
- return func(op, econtext, res, resnull, p, error);
+ if (typid == JSONBOID)
+ {
+ Datum res =
+ jsonb_from_cstring(str, strlen(str), false, error);
- /*
- * We should catch exceptions of category ERRCODE_DATA_EXCEPTION and
- * execute the corresponding ON ERROR behavior then.
- */
- oldcontext = CurrentMemoryContext;
- oldowner = CurrentResourceOwner;
+ if (error && *error)
+ return (Datum) 0;
- Assert(error);
+ return res;
+ }
+ else if (typid == JSONOID)
+ {
+ text *json = cstring_to_text(str);
+ JsonLexContext *lex;
+ JsonParseErrorType result;
- BeginInternalSubTransaction(NULL);
- /* Want to execute expressions inside function's memory context */
- MemoryContextSwitchTo(oldcontext);
+ /* validate json */
+ lex = makeJsonLexContext(json, false);
+ result = pg_parse_json(lex, &nullSemAction);
- PG_TRY();
+ if (result != JSON_SUCCESS)
+ {
+ if (error)
+ {
+ *error = true;
+ return (Datum) 0;
+ }
+
+ json_ereport_error(result, lex);
+ }
+
+ return PointerGetDatum(json);
+ }
+ else if (typid == TEXTOID || typid == VARCHAROID ||
+ typid == BPCHAROID || typid == BYTEAOID)
+ return ExecJsonCoerceCStringToText(str, strlen(str), typid, typmod);
+ }
+ else if (typid == JSONBOID)
+ return jb;
+ else if (typid == JSONOID || typid == TEXTOID)
+ return JsonbPGetTextDatum(DatumGetJsonbP(jb));
+ else if (typid == VARCHAROID || typid == BPCHAROID || typid == BYTEAOID)
{
- res = func(op, econtext, res, resnull, p, error);
+ Jsonb *jsonb = DatumGetJsonbP(jb);
+ char *str = JsonbToCString(NULL, &jsonb->root, VARSIZE(jsonb));
- /* Commit the inner transaction, return to outer xact context */
- ReleaseCurrentSubTransaction();
- MemoryContextSwitchTo(oldcontext);
- CurrentResourceOwner = oldowner;
+ return ExecJsonCoerceCStringToText(str, strlen(str), typid, typmod);
}
- PG_CATCH();
+
+ Assert(0); /* unsupported output type */
+ *error = true;
+ return (Datum) 0;
+}
+
+/*
+ * Evaluate a JSON error/empty behavior and coerce result to the output
+ * type.
+ */
+static Datum
+ExecEvalJsonBehavior(ExprContext *econtext, JsonBehavior *behavior,
+ ExprState *default_estate,
+ Oid ret_typid, int32 ret_typmod, bool *is_null)
+{
+ *is_null = false;
+
+ switch (behavior->btype)
{
- ErrorData *edata;
- int ecategory;
+ case JSON_BEHAVIOR_EMPTY_ARRAY:
+ if (ret_typid == JSONBOID)
+ return JsonbPGetDatum(JsonbMakeEmptyArray());
+ else
+ {
+ Assert(ret_typid == JSONOID || ret_typid == TEXTOID ||
+ ret_typid == VARCHAROID || ret_typid == BPCHAROID ||
+ ret_typid == BYTEAOID);
- /* Save error info in oldcontext */
- MemoryContextSwitchTo(oldcontext);
- edata = CopyErrorData();
- FlushErrorState();
+ return ExecJsonCoerceCStringToText("[]", 2, ret_typid, ret_typmod);
+ }
- /* Abort the inner transaction */
- RollbackAndReleaseCurrentSubTransaction();
- MemoryContextSwitchTo(oldcontext);
- CurrentResourceOwner = oldowner;
+ case JSON_BEHAVIOR_EMPTY_OBJECT:
+ if (ret_typid == JSONBOID)
+ return JsonbPGetDatum(JsonbMakeEmptyObject());
+ else
+ {
+ Assert(ret_typid == JSONOID || ret_typid == TEXTOID ||
+ ret_typid == VARCHAROID || ret_typid == BPCHAROID ||
+ ret_typid == BYTEAOID);
+
+ return ExecJsonCoerceCStringToText("{}", 2, ret_typid, ret_typmod);
+ }
- ecategory = ERRCODE_TO_CATEGORY(edata->sqlerrcode);
+ case JSON_BEHAVIOR_TRUE:
+ case JSON_BEHAVIOR_FALSE:
+ {
+ Datum res;
+ bool ok PG_USED_FOR_ASSERTS_ONLY =
+ ExecJsonCoerceBool(behavior->btype == JSON_BEHAVIOR_TRUE,
+ ret_typid, ret_typmod, &res);
- if (ecategory != ERRCODE_DATA_EXCEPTION && /* jsonpath and other data
- * errors */
- ecategory != ERRCODE_INTEGRITY_CONSTRAINT_VIOLATION) /* domain errors */
- ReThrowError(edata);
+ Assert(ok); /* returning type must be checked in parser */
- res = (Datum) 0;
- *error = true;
- }
- PG_END_TRY();
+ return res;
+ }
- return res;
-}
+ case JSON_BEHAVIOR_NULL:
+ case JSON_BEHAVIOR_UNKNOWN:
+ case JSON_BEHAVIOR_EMPTY:
+ *is_null = true;
+ return (Datum) 0;
+ case JSON_BEHAVIOR_DEFAULT:
+ /*
+ * Execute DEFAULT expression.
+ * Coercion is not needed here, because expression is
+ * already coerced to the target type by the parser.
+ */
+ return ExecEvalExpr(default_estate, econtext, is_null);
-typedef struct
-{
- JsonPath *path;
- bool *error;
- bool coercionInSubtrans;
-} ExecEvalJsonExprContext;
+ default:
+ elog(ERROR, "unrecognized SQL/JSON behavior %d", behavior->btype);
+ return (Datum) 0;
+ }
+}
static Datum
-ExecEvalJsonExpr(ExprEvalStep *op, ExprContext *econtext,
- Datum item, bool *resnull, void *pcxt,
- bool *error)
+ExecEvalJsonExprInternal(ExprState *state,
+ JsonExprState *jsestate, ExprContext *econtext,
+ JsonPath *path, Datum item, bool *resnull,
+ bool *error)
{
- ExecEvalJsonExprContext *cxt = pcxt;
- JsonPath *path = cxt->path;
- JsonExprState *jsestate = op->d.jsonexpr.jsestate;
JsonExpr *jexpr = jsestate->jsexpr;
- ExprState *estate = NULL;
bool empty = false;
Datum res = (Datum) 0;
+ Oid ret_typid = jexpr->returning->typid;
+ int32 ret_typmod = jexpr->returning->typmod;
+
+ *resnull = true;
switch (jexpr->op)
{
@@ -5029,70 +5360,35 @@ ExecEvalJsonExpr(ExprEvalStep *op, ExprContext *econtext,
res = JsonPathQuery(item, path, jexpr->wrapper, &empty, error,
jsestate->args);
if (error && *error)
- {
- *resnull = true;
return (Datum) 0;
- }
- *resnull = !DatumGetPointer(res);
- break;
+
+ if (empty)
+ break;
+
+ Assert(DatumGetPointer(res));
+ *resnull = false;
+
+ return ExecJsonQueryCoercion(jexpr, ret_typid, ret_typmod,
+ res, error);
case JSON_VALUE_OP:
{
- struct JsonCoercionState *jcstate;
JsonbValue *jbv = JsonPathValue(item, path, &empty, error,
jsestate->args);
if (error && *error)
return (Datum) 0;
- if (!jbv) /* NULL or empty */
- break;
-
- Assert(!empty);
-
- *resnull = false;
-
- /* coerce scalar item to the output type */
- if (jexpr->returning->typid == JSONOID ||
- jexpr->returning->typid == JSONBOID)
- {
- /* Use result coercion from json[b] to the output type */
- res = JsonbPGetDatum(JsonbValueToJsonb(jbv));
+ if (empty)
break;
- }
- /* Use coercion from SQL/JSON item type to the output type */
- res = ExecPrepareJsonItemCoercion(jbv,
- jsestate->jsexpr->returning,
- &jsestate->coercions,
- &jcstate);
+ if (!jbv)
+ return (Datum) 0; /* NULL */
- if (jcstate->coercion &&
- (jcstate->coercion->via_io ||
- jcstate->coercion->via_populate))
- {
- if (error)
- {
- *error = true;
- return (Datum) 0;
- }
-
- /*
- * 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 */
+ Assert(jbv->type != jbvNull);
- /* coerce using specific expression */
- estate = jcstate->estate;
- jsestate->coercion_expr->value = res;
- jsestate->coercion_expr->isnull = *resnull;
- break;
+ return ExecJsonValueCoercion(jbv, ret_typid, ret_typmod,
+ resnull, error);
}
case JSON_EXISTS_OP:
@@ -5101,113 +5397,68 @@ ExecEvalJsonExpr(ExprEvalStep *op, ExprContext *econtext,
jsestate->args,
error);
- *resnull = error && *error;
- res = BoolGetDatum(exists);
-
- if (!jsestate->result_expr)
- return res;
+ if (!error || !*error)
+ {
+ /* Should succeed, output type is checked by parser */
+ (void) ExecJsonCoerceBool(exists, ret_typid, ret_typmod, &res);
+ *resnull = false;
+ }
- /* coerce using result expression */
- estate = jsestate->result_expr;
- jsestate->res_expr->value = res;
- jsestate->res_expr->isnull = *resnull;
- break;
+ return res;
}
- case JSON_TABLE_OP:
- *resnull = false;
- return item;
-
default:
elog(ERROR, "unrecognized SQL/JSON expression op %d", jexpr->op);
return (Datum) 0;
}
- if (empty)
- {
- Assert(jexpr->on_empty); /* it is not JSON_EXISTS */
+ Assert(empty);
+ Assert(jexpr->on_empty); /* it is not JSON_EXISTS */
- if (jexpr->on_empty->btype == JSON_BEHAVIOR_ERROR)
+ if (jexpr->on_empty->btype == JSON_BEHAVIOR_ERROR)
+ {
+ if (error)
{
- if (error)
- {
- *error = true;
- return (Datum) 0;
- }
-
- ereport(ERROR,
- (errcode(ERRCODE_NO_SQL_JSON_ITEM),
- errmsg("no SQL/JSON item")));
+ *error = true;
+ return (Datum) 0;
}
- 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);
+ ereport(ERROR,
+ (errcode(ERRCODE_NO_SQL_JSON_ITEM),
+ errmsg("no SQL/JSON item")));
}
- return ExecEvalJsonExprSubtrans(ExecEvalJsonExprCoercion, op, econtext,
- res, resnull, estate, error,
- cxt->coercionInSubtrans);
-}
-
-bool
-ExecEvalJsonNeedsSubTransaction(JsonExpr *jsexpr,
- struct JsonCoercionsState *coercions)
-{
- if (jsexpr->on_error->btype == JSON_BEHAVIOR_ERROR)
- return false;
-
- if (jsexpr->op == JSON_EXISTS_OP && !jsexpr->result_coercion)
- return false;
-
- if (!coercions)
- return true;
-
- return false;
+ /* Execute ON EMPTY behavior */
+ return ExecEvalJsonBehavior(econtext, jexpr->on_empty,
+ jsestate->default_on_empty,
+ ret_typid, ret_typmod, resnull);
}
/* ----------------------------------------------------------------
- * ExecEvalJson
+ * ExecEvalJsonExpr
* ----------------------------------------------------------------
*/
void
-ExecEvalJson(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
+ExecEvalJsonExpr(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
{
- ExecEvalJsonExprContext cxt;
JsonExprState *jsestate = op->d.jsonexpr.jsestate;
JsonExpr *jexpr = jsestate->jsexpr;
Datum item;
- Datum res = (Datum) 0;
+ Datum res;
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)
+ if (jsestate->formatted_expr.isnull || jsestate->pathspec.isnull)
{
- /* execute domain checks for NULLs */
- (void) ExecEvalJsonExprCoercion(op, econtext, res, op->resnull,
- NULL, NULL);
-
- Assert(*op->resnull);
+ *op->resnull = true;
+ *op->resvalue = (Datum) 0;
return;
}
- item = jsestate->formatted_expr->value;
- path = DatumGetJsonPathP(jsestate->pathspec->value);
+ item = jsestate->formatted_expr.value;
+ path = DatumGetJsonPathP(jsestate->pathspec.value);
/* reset JSON path variable contexts */
foreach(lc, jsestate->args)
@@ -5218,29 +5469,20 @@ ExecEvalJson(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
var->evaluated = false;
}
- needSubtrans = ExecEvalJsonNeedsSubTransaction(jexpr, &jsestate->coercions);
-
- 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);
+ res = ExecEvalJsonExprInternal(state, jsestate, econtext,
+ path, item, op->resnull,
+ throwErrors ? NULL : &error);
if (error)
{
+ Assert(!throwErrors);
+
/* Execute ON ERROR behavior */
res = ExecEvalJsonBehavior(econtext, jexpr->on_error,
jsestate->default_on_error,
+ jexpr->returning->typid,
+ jexpr->returning->typmod,
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 = res;
diff --git a/src/backend/jit/llvm/llvmjit_expr.c b/src/backend/jit/llvm/llvmjit_expr.c
index bd3965143da..fd72630f5e6 100644
--- a/src/backend/jit/llvm/llvmjit_expr.c
+++ b/src/backend/jit/llvm/llvmjit_expr.c
@@ -2408,7 +2408,7 @@ llvm_compile_expr(ExprState *state)
break;
case EEOP_JSONEXPR:
- build_EvalXFunc(b, mod, "ExecEvalJson",
+ build_EvalXFunc(b, mod, "ExecEvalJsonExpr",
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.c
index 373471ad27f..37fe64654b6 100644
--- a/src/backend/jit/llvm/llvmjit_types.c
+++ b/src/backend/jit/llvm/llvmjit_types.c
@@ -135,7 +135,7 @@ void *referenced_functions[] =
ExecEvalXmlExpr,
ExecEvalJsonConstructor,
ExecEvalJsonIsPredicate,
- ExecEvalJson,
+ ExecEvalJsonExpr,
MakeExpandedObjectReadOnlyInternal,
slot_getmissingattrs,
slot_getsomeattrs_int,
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index c334daae392..e0240beeeab 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -266,9 +266,6 @@ exprType(const Node *expr)
case T_JsonExpr:
type = ((const JsonExpr *) expr)->returning->typid;
break;
- case T_JsonCoercion:
- type = exprType(((const JsonCoercion *) expr)->expr);
- break;
default:
elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
type = InvalidOid; /* keep compiler quiet */
@@ -507,8 +504,6 @@ exprTypmod(const Node *expr)
return ((const JsonConstructorExpr *) expr)->returning->typmod;
case T_JsonExpr:
return ((JsonExpr *) expr)->returning->typmod;
- case T_JsonCoercion:
- return exprTypmod(((const JsonCoercion *) expr)->expr);
default:
break;
}
@@ -1010,14 +1005,12 @@ exprCollation(const Node *expr)
case T_JsonExpr:
{
JsonExpr *jexpr = (JsonExpr *) expr;
- JsonCoercion *coercion = jexpr->result_coercion;
- if (!coercion)
- coll = InvalidOid;
- else if (coercion->expr)
- coll = exprCollation(coercion->expr);
- else if (coercion->via_io || coercion->via_populate)
- coll = coercion->collation;
+ if (jexpr->returning->typid == TEXTOID ||
+ jexpr->returning->typid == CHAROID ||
+ jexpr->returning->typid == VARCHAROID ||
+ jexpr->returning->typid == BPCHAROID)
+ coll = jexpr->collation;
else
coll = InvalidOid;
}
@@ -1255,14 +1248,12 @@ exprSetCollation(Node *expr, Oid collation)
case T_JsonExpr:
{
JsonExpr *jexpr = (JsonExpr *) expr;
- JsonCoercion *coercion = jexpr->result_coercion;
- if (!coercion)
- Assert(!OidIsValid(collation));
- else if (coercion->expr)
- exprSetCollation(coercion->expr, collation);
- else if (coercion->via_io || coercion->via_populate)
- coercion->collation = collation;
+ if (jexpr->returning->typid == TEXTOID ||
+ jexpr->returning->typid == CHAROID ||
+ jexpr->returning->typid == VARCHAROID ||
+ jexpr->returning->typid == BPCHAROID)
+ jexpr->collation = collation;
else
Assert(!OidIsValid(collation));
}
@@ -2507,8 +2498,6 @@ expression_tree_walker(Node *node,
if (walker(jexpr->formatted_expr, context))
return true;
- if (walker(jexpr->result_coercion, context))
- return true;
if (walker(jexpr->passing_values, context))
return true;
/* we assume walker doesn't care about passing_names */
@@ -2517,36 +2506,6 @@ expression_tree_walker(Node *node,
return true;
if (walker(jexpr->on_error->default_expr, context))
return true;
- if (walker(jexpr->coercions, context))
- return true;
- }
- break;
- case T_JsonCoercion:
- return walker(((JsonCoercion *) node)->expr, context);
- case T_JsonItemCoercions:
- {
- JsonItemCoercions *coercions = (JsonItemCoercions *) node;
-
- if (walker(coercions->null, context))
- return true;
- if (walker(coercions->string, context))
- return true;
- if (walker(coercions->numeric, context))
- return true;
- if (walker(coercions->boolean, context))
- return true;
- if (walker(coercions->date, context))
- return true;
- if (walker(coercions->time, context))
- return true;
- if (walker(coercions->timetz, context))
- return true;
- if (walker(coercions->timestamp, context))
- return true;
- if (walker(coercions->timestamptz, context))
- return true;
- if (walker(coercions->composite, context))
- return true;
}
break;
default:
@@ -3576,7 +3535,6 @@ expression_tree_mutator(Node *node,
FLATCOPY(newnode, jexpr, JsonExpr);
MUTATE(newnode->path_spec, jexpr->path_spec, Node *);
MUTATE(newnode->formatted_expr, jexpr->formatted_expr, Node *);
- MUTATE(newnode->result_coercion, jexpr->result_coercion, JsonCoercion *);
MUTATE(newnode->passing_values, jexpr->passing_values, List *);
/* assume mutator does not care about passing_names */
if (newnode->on_empty)
@@ -3587,35 +3545,6 @@ expression_tree_mutator(Node *node,
return (Node *) newnode;
}
break;
- case T_JsonCoercion:
- {
- JsonCoercion *coercion = (JsonCoercion *) node;
- JsonCoercion *newnode;
-
- FLATCOPY(newnode, coercion, JsonCoercion);
- MUTATE(newnode->expr, coercion->expr, Node *);
- return (Node *) newnode;
- }
- break;
- case T_JsonItemCoercions:
- {
- JsonItemCoercions *coercions = (JsonItemCoercions *) node;
- JsonItemCoercions *newnode;
-
- FLATCOPY(newnode, coercions, JsonItemCoercions);
- MUTATE(newnode->null, coercions->null, JsonCoercion *);
- MUTATE(newnode->string, coercions->string, JsonCoercion *);
- MUTATE(newnode->numeric, coercions->numeric, JsonCoercion *);
- MUTATE(newnode->boolean, coercions->boolean, JsonCoercion *);
- MUTATE(newnode->date, coercions->date, JsonCoercion *);
- MUTATE(newnode->time, coercions->time, JsonCoercion *);
- MUTATE(newnode->timetz, coercions->timetz, JsonCoercion *);
- MUTATE(newnode->timestamp, coercions->timestamp, JsonCoercion *);
- MUTATE(newnode->timestamptz, coercions->timestamptz, JsonCoercion *);
- MUTATE(newnode->composite, coercions->composite, JsonCoercion *);
- return (Node *) newnode;
- }
- break;
default:
elog(ERROR, "unrecognized node type: %d",
(int) nodeTag(node));
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 533df86ff77..6eb923892f4 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -411,6 +411,26 @@ contain_mutable_functions_walker(Node *node, void *context)
{
JsonExpr *jexpr = castNode(JsonExpr, node);
Const *cnst;
+ bool returns_datetime;
+
+ /*
+ * Input fuctions for datetime types are stable. They can be
+ * called in JSON_VALUE(), when the resulting SQL/JSON is a
+ * string.
+ */
+ if (jexpr->returning->typid == DATEOID ||
+ jexpr->returning->typid == TIMEOID ||
+ jexpr->returning->typid == TIMETZOID ||
+ jexpr->returning->typid == TIMESTAMPOID ||
+ jexpr->returning->typid == TIMESTAMPTZOID)
+ return true;
+
+ /*
+ * pg_convert_to(), which is used for implementation of
+ * JSON_QUERY(RETURNING bytea FORMAT JSON), is stable.
+ */
+ if (jexpr->returning->typid == BYTEAOID)
+ return true;
if (!IsA(jexpr->path_spec, Const))
return true;
@@ -421,8 +441,32 @@ contain_mutable_functions_walker(Node *node, void *context)
if (cnst->constisnull)
return false;
- return jspIsMutable(DatumGetJsonPathP(cnst->constvalue),
- jexpr->passing_names, jexpr->passing_values);
+ if (jspIsMutable(DatumGetJsonPathP(cnst->constvalue),
+ jexpr->passing_names, jexpr->passing_values,
+ &returns_datetime))
+ return true;
+
+ if (returns_datetime && jexpr->op == JSON_VALUE_OP)
+ {
+ /*
+ * Some datetime types have mutable output functions,
+ * so if returning string types whole expression is mutable.
+ * TODO check individual datetime type if it is known.
+ *
+ * Other non-JSON output types have no conversion from
+ * datetime types.
+ *
+ * Datetime returning types were checked above, no need to
+ * check them here. But conversion between them also can
+ * be mutable.
+ */
+ if (jexpr->returning->typid == TEXTOID ||
+ jexpr->returning->typid == VARCHAROID ||
+ jexpr->returning->typid == BPCHAROID)
+ return true;
+ }
+
+ return false;
}
if (IsA(node, SQLValueFunction))
@@ -896,18 +940,6 @@ max_parallel_hazard_walker(Node *node, max_parallel_hazard_context *context)
context, 0);
}
- /* JsonExpr is parallel-unsafe if subtransactions can be used. */
- else if (IsA(node, JsonExpr))
- {
- JsonExpr *jsexpr = (JsonExpr *) node;
-
- if (ExecEvalJsonNeedsSubTransaction(jsexpr, NULL))
- {
- context->max_hazard = PROPARALLEL_UNSAFE;
- return true;
- }
- }
-
/* Recurse to check arguments */
return expression_tree_walker(node,
max_parallel_hazard_walker,
@@ -5323,3 +5355,16 @@ pull_paramids_walker(Node *node, Bitmapset **context)
return expression_tree_walker(node, pull_paramids_walker,
(void *) context);
}
+
+bool
+expr_can_throw_errors(Node *expr)
+{
+ if (!expr)
+ return false;
+
+ if (IsA(expr, Const))
+ return false;
+
+ /* TODO consider more cases */
+ return true;
+}
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index fabb5f72076..c02530a3671 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -22,6 +22,7 @@
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
+#include "optimizer/clauses.h"
#include "optimizer/optimizer.h"
#include "parser/analyze.h"
#include "parser/parse_agg.h"
@@ -4108,7 +4109,6 @@ transformJsonExprCommon(ParseState *pstate, JsonFuncExpr *func)
/* format is determined by context item type */
format = exprType(jsexpr->formatted_expr) == JSONBOID ? JS_FORMAT_JSONB : JS_FORMAT_JSON;
- jsexpr->result_coercion = NULL;
jsexpr->omit_quotes = false;
jsexpr->format = func->common->expr->format;
@@ -4169,40 +4169,6 @@ assignDefaultJsonReturningType(Node *context_item, JsonFormat *context_format,
ret->typmod = -1;
}
-/*
- * Try to coerce expression to the output type or
- * use json_populate_type() for composite, array and domain types or
- * use coercion via I/O.
- */
-static JsonCoercion *
-coerceJsonExpr(ParseState *pstate, Node *expr, const JsonReturning *returning)
-{
- char typtype;
- JsonCoercion *coercion = makeNode(JsonCoercion);
-
- coercion->expr = coerceJsonFuncExpr(pstate, expr, returning, false);
-
- if (coercion->expr)
- {
- if (coercion->expr == expr)
- coercion->expr = NULL;
-
- return coercion;
- }
-
- typtype = get_typtype(returning->typid);
-
- if (returning->typid == RECORDOID ||
- typtype == TYPTYPE_COMPOSITE ||
- typtype == TYPTYPE_DOMAIN ||
- type_is_array(returning->typid))
- coercion->via_populate = true;
- else
- coercion->via_io = true;
-
- return coercion;
-}
-
/*
* Transform a JSON output clause of JSON_VALUE and JSON_QUERY.
*/
@@ -4210,8 +4176,6 @@ static void
transformJsonFuncExprOutput(ParseState *pstate, JsonFuncExpr *func,
JsonExpr *jsexpr)
{
- Node *expr = jsexpr->formatted_expr;
-
jsexpr->returning = transformJsonOutput(pstate, func->output, false);
/* JSON_VALUE returns text by default */
@@ -4225,14 +4189,41 @@ transformJsonFuncExprOutput(ParseState *pstate, JsonFuncExpr *func,
{
JsonReturning ret;
- if (func->op == JSON_VALUE_OP &&
- jsexpr->returning->typid != JSONOID &&
- jsexpr->returning->typid != JSONBOID)
+ if (func->op == JSON_VALUE_OP)
{
- /* Forced coercion via I/O for JSON_VALUE for non-JSON types */
- jsexpr->result_coercion = makeNode(JsonCoercion);
- jsexpr->result_coercion->expr = NULL;
- jsexpr->result_coercion->via_io = true;
+ /*
+ * Only a limited list of output types is supported in
+ * JSON_VALUE() now.
+ */
+ switch (jsexpr->returning->typid)
+ {
+ case JSONBOID:
+ case JSONOID:
+ case TEXTOID:
+ case VARCHAROID:
+ case BPCHAROID:
+ case BOOLOID:
+ case NUMERICOID:
+ case INT2OID:
+ case INT4OID:
+ case INT8OID:
+ case FLOAT4OID:
+ case FLOAT8OID:
+ case DATEOID:
+ case TIMEOID:
+ case TIMETZOID:
+ case TIMESTAMPOID:
+ case TIMESTAMPTZOID:
+ break; /* Ok */
+
+ default:
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("returning type %s is not supported in JSON_VALUE()",
+ format_type_be(jsexpr->returning->typid)),
+ parser_coercion_errposition(pstate, func->location, (Node *) jsexpr)));
+ }
+
return;
}
@@ -4241,13 +4232,23 @@ transformJsonFuncExprOutput(ParseState *pstate, JsonFuncExpr *func,
if (ret.typid != jsexpr->returning->typid ||
ret.typmod != jsexpr->returning->typmod)
{
- Node *placeholder = makeCaseTestExpr(expr);
-
- Assert(((CaseTestExpr *) placeholder)->typeId == ret.typid);
- Assert(((CaseTestExpr *) placeholder)->typeMod == ret.typmod);
+ switch (jsexpr->returning->typid)
+ {
+ case JSONBOID:
+ case JSONOID:
+ case TEXTOID:
+ case VARCHAROID:
+ case BPCHAROID:
+ case BYTEAOID:
+ break;
- jsexpr->result_coercion = coerceJsonExpr(pstate, placeholder,
- jsexpr->returning);
+ default:
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("returning type %s is not supported in JSON_QUERY()",
+ format_type_be(jsexpr->returning->typid)),
+ parser_coercion_errposition(pstate, func->location, (Node *) jsexpr)));
+ }
}
}
else
@@ -4259,8 +4260,10 @@ transformJsonFuncExprOutput(ParseState *pstate, JsonFuncExpr *func,
* Coerce an expression in JSON DEFAULT behavior to the target output type.
*/
static Node *
-coerceDefaultJsonExpr(ParseState *pstate, JsonExpr *jsexpr, Node *defexpr)
+coerceDefaultJsonExpr(ParseState *pstate, JsonExpr *jsexpr,
+ bool is_on_empty, Node *defexpr)
{
+ Node *orig_defexpr = defexpr;
int location;
Oid exprtype;
@@ -4290,63 +4293,31 @@ coerceDefaultJsonExpr(ParseState *pstate, JsonExpr *jsexpr, Node *defexpr)
format_type_be(jsexpr->returning->typid)),
parser_errposition(pstate, location)));
- return defexpr;
-}
+ /* Don't allow unsafe DEFAUL ON EMPTY expressions, unless ERROR ON ERROR */
+ if (!is_on_empty ||
+ jsexpr->on_error->btype == JSON_BEHAVIOR_ERROR)
+ return defexpr;
-/*
- * Initialize SQL/JSON item coercion from the SQL type "typid" to the target
- * "returning" type.
- */
-static JsonCoercion *
-initJsonItemCoercion(ParseState *pstate, Oid typid,
- const JsonReturning *returning)
-{
- Node *expr;
+ if (!expr_can_throw_errors(defexpr))
+ return defexpr;
- if (typid == UNKNOWNOID)
+ /* Try to simplify expression if there is non-empty coercion */
+ if (defexpr != orig_defexpr &&
+ !expr_can_throw_errors(orig_defexpr))
{
- expr = (Node *) makeNullConst(UNKNOWNOID, -1, InvalidOid);
- }
- else
- {
- CaseTestExpr *placeholder = makeNode(CaseTestExpr);
+ defexpr = eval_const_expressions(NULL, defexpr);
- placeholder->typeId = typid;
- placeholder->typeMod = -1;
- placeholder->collation = InvalidOid;
-
- expr = (Node *) placeholder;
+ if (!expr_can_throw_errors(defexpr))
+ return defexpr;
}
- return coerceJsonExpr(pstate, expr, returning);
-}
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("unsafe DEFAULT ON EMPTY expressions are not supported"),
+ errhint("Use ERROR ON ERROR clause or try to simplify expression into constant-like form."),
+ parser_errposition(pstate, location)));
-static void
-initJsonItemCoercions(ParseState *pstate, JsonItemCoercions *coercions,
- const JsonReturning *returning, Oid contextItemTypeId)
-{
- struct
- {
- JsonCoercion **coercion;
- Oid typid;
- } *p,
- coercionTypids[] =
- {
- {&coercions->null, UNKNOWNOID},
- {&coercions->string, TEXTOID},
- {&coercions->numeric, NUMERICOID},
- {&coercions->boolean, BOOLOID},
- {&coercions->date, DATEOID},
- {&coercions->time, TIMEOID},
- {&coercions->timetz, TIMETZOID},
- {&coercions->timestamp, TIMESTAMPOID},
- {&coercions->timestamptz, TIMESTAMPTZOID},
- {&coercions->composite, contextItemTypeId},
- {NULL, InvalidOid}
- };
-
- for (p = coercionTypids; p->coercion; p++)
- *p->coercion = initJsonItemCoercion(pstate, p->typid, returning);
+ return NULL;
}
/*
@@ -4370,17 +4341,13 @@ transformJsonFuncExpr(ParseState *pstate, JsonFuncExpr *func)
jsexpr->returning->format->encoding = JS_ENC_DEFAULT;
jsexpr->on_empty->default_expr =
- coerceDefaultJsonExpr(pstate, jsexpr,
+ coerceDefaultJsonExpr(pstate, jsexpr, true,
jsexpr->on_empty->default_expr);
jsexpr->on_error->default_expr =
- coerceDefaultJsonExpr(pstate, jsexpr,
+ coerceDefaultJsonExpr(pstate, jsexpr, false,
jsexpr->on_error->default_expr);
- jsexpr->coercions = makeNode(JsonItemCoercions);
- initJsonItemCoercions(pstate, jsexpr->coercions, jsexpr->returning,
- exprType(contextItemExpr));
-
break;
case JSON_QUERY_OP:
@@ -4389,11 +4356,11 @@ transformJsonFuncExpr(ParseState *pstate, JsonFuncExpr *func)
transformJsonFuncExprOutput(pstate, func, jsexpr);
jsexpr->on_empty->default_expr =
- coerceDefaultJsonExpr(pstate, jsexpr,
+ coerceDefaultJsonExpr(pstate, jsexpr, true,
jsexpr->on_empty->default_expr);
jsexpr->on_error->default_expr =
- coerceDefaultJsonExpr(pstate, jsexpr,
+ coerceDefaultJsonExpr(pstate, jsexpr, false,
jsexpr->on_error->default_expr);
jsexpr->wrapper = func->wrapper;
@@ -4409,6 +4376,7 @@ transformJsonFuncExpr(ParseState *pstate, JsonFuncExpr *func)
jsexpr->returning->format->format_type = JS_FORMAT_DEFAULT;
jsexpr->returning->format->encoding = JS_ENC_DEFAULT;
+ /* Coerce intermediate boolean result to the output type if needed */
if (!OidIsValid(jsexpr->returning->typid))
{
jsexpr->returning->typid = BOOLOID;
@@ -4416,32 +4384,21 @@ transformJsonFuncExpr(ParseState *pstate, JsonFuncExpr *func)
}
else if (jsexpr->returning->typid != BOOLOID)
{
- CaseTestExpr *placeholder = makeNode(CaseTestExpr);
- int location = exprLocation((Node *) jsexpr);
-
- placeholder->typeId = BOOLOID;
- placeholder->typeMod = -1;
- placeholder->collation = InvalidOid;
-
- jsexpr->result_coercion = makeNode(JsonCoercion);
- jsexpr->result_coercion->expr =
- coerce_to_target_type(pstate, (Node *) placeholder, BOOLOID,
- jsexpr->returning->typid,
- jsexpr->returning->typmod,
- COERCION_EXPLICIT,
- COERCE_IMPLICIT_CAST,
- location);
-
- if (!jsexpr->result_coercion->expr)
- ereport(ERROR,
- (errcode(ERRCODE_CANNOT_COERCE),
- errmsg("cannot cast type %s to %s",
- format_type_be(BOOLOID),
- format_type_be(jsexpr->returning->typid)),
- parser_coercion_errposition(pstate, location, (Node *) jsexpr)));
+ switch (jsexpr->returning->typid)
+ {
+ case INT4OID:
+ case TEXTOID:
+ case VARCHAROID:
+ case BPCHAROID:
+ break;
- if (jsexpr->result_coercion->expr == (Node *) placeholder)
- jsexpr->result_coercion->expr = NULL;
+ default:
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("returning type %s is not supported in JSON_EXISTS()",
+ format_type_be(jsexpr->returning->typid)),
+ parser_coercion_errposition(pstate, func->location, (Node *) jsexpr)));
+ }
}
break;
@@ -4457,7 +4414,6 @@ transformJsonFuncExpr(ParseState *pstate, JsonFuncExpr *func)
errmsg("JSON_TABLE() is not yet implemented for the json type"),
errhint("Try casting the argument to jsonb"),
parser_errposition(pstate, func->location)));
-
break;
}
diff --git a/src/backend/utils/adt/jsonpath.c b/src/backend/utils/adt/jsonpath.c
index da9df4ae766..9c9f9ad703f 100644
--- a/src/backend/utils/adt/jsonpath.c
+++ b/src/backend/utils/adt/jsonpath.c
@@ -1318,9 +1318,11 @@ jspIsMutableWalker(JsonPathItem *jpi, JsonPathMutableContext *cxt)
* Check whether jsonpath expression is immutable or not.
*/
bool
-jspIsMutable(JsonPath *path, List *varnames, List *varexprs)
+jspIsMutable(JsonPath *path, List *varnames, List *varexprs,
+ bool *returns_datetime)
{
JsonPathMutableContext cxt;
+ JsonPathDatatypeStatus status;
JsonPathItem jpi;
cxt.varnames = varnames;
@@ -1330,7 +1332,9 @@ jspIsMutable(JsonPath *path, List *varnames, List *varexprs)
cxt.mutable = false;
jspInit(&jpi, path);
- jspIsMutableWalker(&jpi, &cxt);
+ status = jspIsMutableWalker(&jpi, &cxt);
+
+ *returns_datetime = status != jpdsNonDateTime;
return cxt.mutable;
}
diff --git a/src/include/executor/execExpr.h b/src/include/executor/execExpr.h
index c8ef917ffe0..adabf57c97b 100644
--- a/src/include/executor/execExpr.h
+++ b/src/include/executor/execExpr.h
@@ -762,43 +762,14 @@ 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 */
+ formatted_expr, /* formatted context item value */
+ coercion_expr, /* input for JSON item coercion */
+ pathspec; /* path specification value */
- 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() */
-
- struct JsonCoercionsState
- {
- struct JsonCoercionState
- {
- JsonCoercion *coercion; /* coercion expression */
- ExprState *estate; /* coercion expression state */
- } null,
- string,
- numeric ,
- boolean,
- date,
- time,
- timetz,
- timestamp,
- timestamptz,
- composite;
- } coercions; /* states for coercion from SQL/JSON item
- * types directly to the output type */
} JsonExprState;
/* functions in execExpr.c */
@@ -860,18 +831,8 @@ extern void ExecEvalSysVar(ExprState *state, ExprEvalStep *op,
ExprContext *econtext, TupleTableSlot *slot);
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,
- struct JsonCoercionsState *);
-extern Datum ExecEvalExprPassingCaseValue(ExprState *estate,
- ExprContext *econtext, bool *isnull,
- Datum caseval_datum,
- bool caseval_isnull);
+extern void ExecEvalJsonExpr(ExprState *state, ExprEvalStep *op,
+ ExprContext *econtext);
extern void ExecAggInitGroup(AggState *aggstate, AggStatePerTrans pertrans, AggStatePerGroup pergroup,
ExprContext *aggcontext);
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 3aa96bb6855..25a0fb8a445 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -1551,39 +1551,6 @@ typedef struct JsonBehavior
Node *default_expr; /* default expression, if any */
} JsonBehavior;
-/*
- * JsonCoercion -
- * coercion from SQL/JSON item types to SQL types
- */
-typedef struct JsonCoercion
-{
- NodeTag type;
- Node *expr; /* resulting expression coerced to target type */
- bool via_populate; /* coerce result using json_populate_type()? */
- bool via_io; /* coerce result using type input function? */
- Oid collation; /* collation for coercion via I/O or populate */
-} JsonCoercion;
-
-/*
- * JsonItemCoercions -
- * expressions for coercion from SQL/JSON item types directly to the
- * output SQL type
- */
-typedef struct JsonItemCoercions
-{
- NodeTag type;
- JsonCoercion *null;
- JsonCoercion *string;
- JsonCoercion *numeric;
- JsonCoercion *boolean;
- JsonCoercion *date;
- JsonCoercion *time;
- JsonCoercion *timetz;
- JsonCoercion *timestamp;
- JsonCoercion *timestamptz;
- JsonCoercion *composite; /* arrays and objects */
-} JsonItemCoercions;
-
/*
* JsonExpr -
* transformed representation of JSON_VALUE(), JSON_QUERY(), JSON_EXISTS()
@@ -1593,7 +1560,6 @@ typedef struct JsonExpr
Expr xpr;
JsonExprOp op; /* json function ID */
Node *formatted_expr; /* formatted context item expression */
- JsonCoercion *result_coercion; /* resulting coercion to RETURNING type */
JsonFormat *format; /* context item format (JSON/JSONB) */
Node *path_spec; /* JSON path specification expression */
List *passing_names; /* PASSING argument names */
@@ -1601,8 +1567,8 @@ typedef struct JsonExpr
JsonReturning *returning; /* RETURNING clause type/format info */
JsonBehavior *on_empty; /* ON EMPTY behavior */
JsonBehavior *on_error; /* ON ERROR behavior */
- JsonItemCoercions *coercions; /* coercions for JSON_VALUE */
JsonWrapper wrapper; /* WRAPPER for JSON_QUERY */
+ Oid collation; /* Collation of result, or InvalidOid if none */
bool omit_quotes; /* KEEP/OMIT QUOTES for JSON_QUERY */
int location; /* token location, or -1 if unknown */
} JsonExpr;
diff --git a/src/include/optimizer/clauses.h b/src/include/optimizer/clauses.h
index 6c5203dc448..4d61b88c161 100644
--- a/src/include/optimizer/clauses.h
+++ b/src/include/optimizer/clauses.h
@@ -55,4 +55,6 @@ extern Query *inline_set_returning_function(PlannerInfo *root,
extern Bitmapset *pull_paramids(Expr *expr);
+extern bool expr_can_throw_errors(Node *expr);
+
#endif /* CLAUSES_H */
diff --git a/src/include/utils/jsonpath.h b/src/include/utils/jsonpath.h
index 8e79b8dc9f0..e9134b7eab4 100644
--- a/src/include/utils/jsonpath.h
+++ b/src/include/utils/jsonpath.h
@@ -177,7 +177,8 @@ extern bool jspGetBool(JsonPathItem *v);
extern char *jspGetString(JsonPathItem *v, int32 *len);
extern bool jspGetArraySubscript(JsonPathItem *v, JsonPathItem *from,
JsonPathItem *to, int i);
-extern bool jspIsMutable(JsonPath *path, List *varnames, List *varexprs);
+extern bool jspIsMutable(JsonPath *path, List *varnames, List *varexprs,
+ bool *returns_datetime);
extern const char *jspOperationName(JsonPathItemType type);
diff --git a/src/test/regress/expected/jsonb_sqljson.out b/src/test/regress/expected/jsonb_sqljson.out
index ef496110af3..0e07bbc858f 100644
--- a/src/test/regress/expected/jsonb_sqljson.out
+++ b/src/test/regress/expected/jsonb_sqljson.out
@@ -184,11 +184,11 @@ SELECT JSON_EXISTS(jsonb '1', 'strict $[1]' RETURNING text FALSE ON ERROR);
(1 row)
SELECT JSON_EXISTS(jsonb '1', '$[0]' RETURNING jsonb);
-ERROR: cannot cast type boolean to jsonb
+ERROR: returning type jsonb is not supported in JSON_EXISTS()
LINE 1: SELECT JSON_EXISTS(jsonb '1', '$[0]' RETURNING jsonb);
^
SELECT JSON_EXISTS(jsonb '1', '$[0]' RETURNING float4);
-ERROR: cannot cast type boolean to real
+ERROR: returning type real is not supported in JSON_EXISTS()
LINE 1: SELECT JSON_EXISTS(jsonb '1', '$[0]' RETURNING float4);
^
-- JSON_VALUE
@@ -242,7 +242,9 @@ SELECT JSON_VALUE(jsonb '123', '$' RETURNING text);
/* jsonb bytea ??? */
SELECT JSON_VALUE(jsonb '123', '$' RETURNING bytea ERROR ON ERROR);
-ERROR: SQL/JSON item cannot be cast to target type
+ERROR: returning type bytea is not supported in JSON_VALUE()
+LINE 2: SELECT JSON_VALUE(jsonb '123', '$' RETURNING bytea ERROR ON ...
+ ^
SELECT JSON_VALUE(jsonb '1.23', '$');
json_value
------------
@@ -349,14 +351,129 @@ SELECT JSON_VALUE(jsonb '"2017-02-20"', '$' RETURNING date) + 9;
03-01-2017
(1 row)
--- Test NULL checks execution in domain types
+SELECT JSON_VALUE(jsonb '1.23', '$' RETURNING float4);
+ json_value
+------------
+ 1.23
+(1 row)
+
+SELECT JSON_VALUE(jsonb '"1.23"', '$' RETURNING float4);
+ json_value
+------------
+ 1.23
+(1 row)
+
+SELECT JSON_VALUE(jsonb '"nan"', '$' RETURNING float4);
+ json_value
+------------
+ NaN
+(1 row)
+
+SELECT JSON_VALUE(jsonb '"-inf"', '$' RETURNING float4);
+ json_value
+------------
+ -Infinity
+(1 row)
+
+SELECT JSON_VALUE(jsonb '"err"', '$' RETURNING float4 DEFAULT -1 ON ERROR);
+ json_value
+------------
+ -1
+(1 row)
+
+SELECT JSON_VALUE(jsonb '"err"', '$' RETURNING float4 ERROR ON ERROR);
+ERROR: invalid input syntax for type real: "err"
+SELECT JSON_VALUE(jsonb '1.23', '$' RETURNING float8);
+ json_value
+------------
+ 1.23
+(1 row)
+
+SELECT JSON_VALUE(jsonb '"1.23"', '$' RETURNING float8);
+ json_value
+------------
+ 1.23
+(1 row)
+
+SELECT JSON_VALUE(jsonb '"nan"', '$' RETURNING float8);
+ json_value
+------------
+ NaN
+(1 row)
+
+SELECT JSON_VALUE(jsonb '"-inf"', '$' RETURNING float8);
+ json_value
+------------
+ -Infinity
+(1 row)
+
+SELECT JSON_VALUE(jsonb '"err"', '$' RETURNING float8 DEFAULT -1 ON ERROR);
+ json_value
+------------
+ -1
+(1 row)
+
+SELECT JSON_VALUE(jsonb '"err"', '$' RETURNING float8 ERROR ON ERROR);
+ERROR: invalid input syntax for type double precision: "err"
+SELECT JSON_VALUE(jsonb '1.23', '$' RETURNING numeric);
+ json_value
+------------
+ 1.23
+(1 row)
+
+SELECT JSON_VALUE(jsonb '"1.23"', '$' RETURNING numeric);
+ json_value
+------------
+ 1.23
+(1 row)
+
+SELECT JSON_VALUE(jsonb '"nan"', '$' RETURNING numeric);
+ json_value
+------------
+ NaN
+(1 row)
+
+SELECT JSON_VALUE(jsonb '"-inf"', '$' RETURNING numeric);
+ json_value
+------------
+ -Infinity
+(1 row)
+
+SELECT JSON_VALUE(jsonb '"err"', '$' RETURNING numeric DEFAULT -1 ON ERROR);
+ json_value
+------------
+ -1
+(1 row)
+
+SELECT JSON_VALUE(jsonb '"err"', '$' RETURNING numeric ERROR ON ERROR);
+ERROR: invalid input syntax for type numeric: "err"
+-- 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);
+ERROR: returning type sqljsonb_json_not_null is not supported in JSON_VALUE()
+LINE 1: SELECT JSON_VALUE(jsonb '"1"', '$' RETURNING sqljsonb_json_n...
+ ^
+SELECT JSON_VALUE(jsonb '"1"', '$' RETURNING sqljsonb_jsonb_not_null);
+ERROR: returning type sqljsonb_jsonb_not_null is not supported in JSON_VALUE()
+LINE 1: SELECT JSON_VALUE(jsonb '"1"', '$' RETURNING sqljsonb_jsonb_...
+ ^
+-- 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
+ERROR: returning type sqljsonb_int_not_null is not supported in JSON_VALUE()
+LINE 1: SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_int_no...
+ ^
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
+ERROR: returning type sqljsonb_int_not_null is not supported in JSON_VALUE()
+LINE 1: SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_int_no...
+ ^
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
+ERROR: returning type sqljsonb_int_not_null is not supported in JSON_VALUE()
+LINE 1: SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_int_no...
+ ^
+-- Test returning of non-scalar items
SELECT JSON_VALUE(jsonb '[]', '$');
json_value
------------
@@ -478,11 +595,9 @@ SELECT JSON_VALUE(jsonb 'null', '$a' PASSING point ' (1, 2 )' AS a);
(1 row)
SELECT JSON_VALUE(jsonb 'null', '$a' PASSING point ' (1, 2 )' AS a RETURNING point);
- json_value
-------------
- (1,2)
-(1 row)
-
+ERROR: returning type point is not supported in JSON_VALUE()
+LINE 1: SELECT JSON_VALUE(jsonb 'null', '$a' PASSING point ' (1, 2 )...
+ ^
-- Test timestamptz passing and output
SELECT JSON_VALUE(jsonb 'null', '$ts' PASSING timestamptz '2018-02-21 12:34:56 +10' AS ts);
json_value
@@ -514,6 +629,36 @@ 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);
+ERROR: unsafe DEFAULT ON EMPTY expressions are not supported
+LINE 2: DEFAULT 1 / x ON EMPTY
+ ^
+HINT: Use ERROR ON ERROR clause or try to simplify expression into constant-like form.
+SELECT JSON_VALUE('1'::jsonb, '$.x' RETURNING int DEFAULT -1 ON EMPTY);
+ json_value
+------------
+ -1
+(1 row)
+
+SELECT JSON_VALUE('1'::jsonb, '$.x' RETURNING int DEFAULT '-1' ON EMPTY);
+ json_value
+------------
+ -1
+(1 row)
+
+SELECT JSON_VALUE('1'::jsonb, '$.x' RETURNING int DEFAULT 'err' ON EMPTY);
+ERROR: invalid input syntax for type integer: "err"
+LINE 1: ...SON_VALUE('1'::jsonb, '$.x' RETURNING int DEFAULT 'err' ON E...
+ ^
+SELECT JSON_VALUE('1'::jsonb, '$.x' RETURNING int DEFAULT -1::float ON EMPTY);
+ERROR: unsafe DEFAULT ON EMPTY expressions are not supported
+LINE 1: ...SON_VALUE('1'::jsonb, '$.x' RETURNING int DEFAULT -1::float ...
+ ^
+HINT: Use ERROR ON ERROR clause or try to simplify expression into constant-like form.
-- JSON_QUERY
SELECT
JSON_QUERY(js, '$'),
@@ -852,49 +997,35 @@ FROM
CREATE TYPE sqljsonb_rec AS (a int, t text, js json, jb jsonb, jsa json[]);
CREATE TYPE sqljsonb_reca AS (reca sqljsonb_rec[]);
SELECT JSON_QUERY(jsonb '[{"a": 1, "b": "foo", "t": "aaa", "js": [1, "2", {}], "jb": {"x": [1, "2", {}]}}, {"a": 2}]', '$[0]' RETURNING sqljsonb_rec);
- json_query
------------------------------------------------------
- (1,aaa,"[1, ""2"", {}]","{""x"": [1, ""2"", {}]}",)
-(1 row)
-
+ERROR: returning type sqljsonb_rec is not supported in JSON_QUERY()
+LINE 1: SELECT JSON_QUERY(jsonb '[{"a": 1, "b": "foo", "t": "aaa", "...
+ ^
SELECT * FROM unnest((JSON_QUERY(jsonb '{"jsa": [{"a": 1, "b": ["foo"]}, {"a": 2, "c": {}}, 123]}', '$' RETURNING sqljsonb_rec)).jsa);
- unnest
-------------------------
- {"a": 1, "b": ["foo"]}
- {"a": 2, "c": {}}
- 123
-(3 rows)
-
+ERROR: returning type sqljsonb_rec is not supported in JSON_QUERY()
+LINE 1: SELECT * FROM unnest((JSON_QUERY(jsonb '{"jsa": [{"a": 1, "...
+ ^
SELECT * FROM unnest((JSON_QUERY(jsonb '{"reca": [{"a": 1, "t": ["foo", []]}, {"a": 2, "jb": [{}, true]}]}', '$' RETURNING sqljsonb_reca)).reca);
- a | t | js | jb | jsa
----+-------------+----+------------+-----
- 1 | ["foo", []] | | |
- 2 | | | [{}, true] |
-(2 rows)
-
+ERROR: returning type sqljsonb_reca is not supported in JSON_QUERY()
+LINE 1: SELECT * FROM unnest((JSON_QUERY(jsonb '{"reca": [{"a": 1, "...
+ ^
-- Extension: array types returning
SELECT JSON_QUERY(jsonb '[1,2,null,"3"]', '$[*]' RETURNING int[] WITH WRAPPER);
- json_query
---------------
- {1,2,NULL,3}
-(1 row)
-
+ERROR: returning type integer[] is not supported in JSON_QUERY()
+LINE 1: SELECT JSON_QUERY(jsonb '[1,2,null,"3"]', '$[*]' RETURNING i...
+ ^
SELECT * FROM unnest(JSON_QUERY(jsonb '[{"a": 1, "t": ["foo", []]}, {"a": 2, "jb": [{}, true]}]', '$' RETURNING sqljsonb_rec[]));
- a | t | js | jb | jsa
----+-------------+----+------------+-----
- 1 | ["foo", []] | | |
- 2 | | | [{}, true] |
-(2 rows)
-
+ERROR: returning type sqljsonb_rec[] is not supported in JSON_QUERY()
+LINE 1: SELECT * FROM unnest(JSON_QUERY(jsonb '[{"a": 1, "t": ["foo"...
+ ^
-- Extension: domain types returning
SELECT JSON_QUERY(jsonb '{"a": 1}', '$.a' RETURNING sqljsonb_int_not_null);
- json_query
-------------
- 1
-(1 row)
-
+ERROR: returning type sqljsonb_int_not_null is not supported in JSON_QUERY()
+LINE 1: SELECT JSON_QUERY(jsonb '{"a": 1}', '$.a' RETURNING sqljsonb...
+ ^
SELECT JSON_QUERY(jsonb '{"a": 1}', '$.b' RETURNING sqljsonb_int_not_null);
-ERROR: domain sqljsonb_int_not_null does not allow null values
+ERROR: returning type sqljsonb_int_not_null is not supported in JSON_QUERY()
+LINE 1: SELECT JSON_QUERY(jsonb '{"a": 1}', '$.b' RETURNING sqljsonb...
+ ^
-- Test timestamptz passing and output
SELECT JSON_QUERY(jsonb 'null', '$ts' PASSING timestamptz '2018-02-21 12:34:56 +10' AS ts);
json_query
@@ -1019,6 +1150,25 @@ ERROR: functions in index expression must be marked IMMUTABLE
CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$[1, 0 to $.a ? (@.datetime() == $x)]' PASSING '12:34'::time AS x));
ERROR: functions in index expression must be marked IMMUTABLE
CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$[1, $.a ? (@.datetime("HH:MI") == $x)]' PASSING '12:34'::time AS x));
+CREATE INDEX ON test_jsonb_mutability (JSON_VALUE(js, '$' RETURNING int)); -- immutable
+CREATE INDEX ON test_jsonb_mutability (JSON_VALUE(js, '$' RETURNING date));
+ERROR: functions in index expression must be marked IMMUTABLE
+CREATE INDEX ON test_jsonb_mutability (JSON_VALUE(js, '$' RETURNING time));
+ERROR: functions in index expression must be marked IMMUTABLE
+CREATE INDEX ON test_jsonb_mutability (JSON_VALUE(js, '$' RETURNING timetz));
+ERROR: functions in index expression must be marked IMMUTABLE
+CREATE INDEX ON test_jsonb_mutability (JSON_VALUE(js, '$' RETURNING timestamp));
+ERROR: functions in index expression must be marked IMMUTABLE
+CREATE INDEX ON test_jsonb_mutability (JSON_VALUE(js, '$' RETURNING timestamptz));
+ERROR: functions in index expression must be marked IMMUTABLE
+CREATE INDEX ON test_jsonb_mutability (JSON_VALUE(js, '$.datetime()'));
+ERROR: functions in index expression must be marked IMMUTABLE
+CREATE INDEX ON test_jsonb_mutability (JSON_VALUE(js, '$.datetime()[*][1,2].**'));
+ERROR: functions in index expression must be marked IMMUTABLE
+CREATE INDEX ON test_jsonb_mutability (JSON_VALUE(js, '$x' PASSING '2022-08-25'::date AS x));
+ERROR: functions in index expression must be marked IMMUTABLE
+CREATE INDEX ON test_jsonb_mutability (JSON_VALUE(js, '$.datetime()' RETURNING int)); -- immutable
+CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.datetime()')); -- immutable
DROP TABLE test_jsonb_mutability;
-- JSON_TABLE
-- Should fail (JSON_TABLE can be used only in FROM clause)
@@ -1048,7 +1198,6 @@ SELECT * FROM JSON_TABLE(jsonb '123', '$'
(1 row)
-- JSON_TABLE: basic functionality
-CREATE DOMAIN jsonb_test_domain AS text CHECK (value <> 'foo');
SELECT *
FROM
(VALUES
@@ -1069,7 +1218,6 @@ FROM
"char(4)" char(4) PATH '$',
"bool" bool PATH '$',
"numeric" numeric PATH '$',
- "domain" jsonb_test_domain PATH '$',
js json PATH '$',
jb jsonb PATH '$',
jst text FORMAT JSON PATH '$',
@@ -1085,29 +1233,26 @@ FROM
exists4 text EXISTS PATH 'strict $.aaa' FALSE ON ERROR,
js2 json PATH '$',
jsb2w jsonb PATH '$' WITH WRAPPER,
- jsb2q jsonb PATH '$' OMIT QUOTES,
- ia int[] PATH '$',
- ta text[] PATH '$',
- jba jsonb[] PATH '$'
+ jsb2q jsonb PATH '$' OMIT QUOTES
)
) jt
ON true;
- js | id | id2 | int | text | char(4) | bool | numeric | domain | js | jb | jst | jsc | jsv | jsb | jsbq | aaa | aaa1 | exists1 | exists2 | exists3 | exists4 | js2 | jsb2w | jsb2q | ia | ta | jba
----------------------------------------------------------------------------------------+----+-----+-----+---------+---------+------+---------+---------+--------------+--------------+--------------+------+------+--------------+--------------+-----+------+---------+---------+---------+---------+--------------+----------------+--------------+----+----+-----
- 1 | 1 | 1 | 1 | 1 | 1 | | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | | | f | 0 | | false | 1 | [1] | 1 | | |
- [] | | | | | | | | | | | | | | | | | | | | | | | | | | |
- {} | 1 | 1 | | | | | | | {} | {} | {} | {} | {} | {} | {} | | | f | 0 | | false | {} | [{}] | {} | | |
- [1, 1.23, "2", "aaaaaaa", "foo", null, false, true, {"aaa": 123}, "[1,2]", "\"str\""] | 1 | 1 | 1 | 1 | 1 | | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | | | f | 0 | | false | 1 | [1] | 1 | | |
- [1, 1.23, "2", "aaaaaaa", "foo", null, false, true, {"aaa": 123}, "[1,2]", "\"str\""] | 2 | 2 | 1 | 1.23 | 1.23 | | 1.23 | 1.23 | 1.23 | 1.23 | 1.23 | 1.23 | 1.23 | 1.23 | 1.23 | | | f | 0 | | false | 1.23 | [1.23] | 1.23 | | |
- [1, 1.23, "2", "aaaaaaa", "foo", null, false, true, {"aaa": 123}, "[1,2]", "\"str\""] | 3 | 3 | 2 | 2 | 2 | | 2 | 2 | "2" | "2" | "2" | "2" | "2" | "2" | 2 | | | f | 0 | | false | "2" | ["2"] | 2 | | |
- [1, 1.23, "2", "aaaaaaa", "foo", null, false, true, {"aaa": 123}, "[1,2]", "\"str\""] | 4 | 4 | | aaaaaaa | aaaa | | | aaaaaaa | "aaaaaaa" | "aaaaaaa" | "aaaaaaa" | "aaa | "aaa | "aaaaaaa" | | | | f | 0 | | false | "aaaaaaa" | ["aaaaaaa"] | | | |
- [1, 1.23, "2", "aaaaaaa", "foo", null, false, true, {"aaa": 123}, "[1,2]", "\"str\""] | 5 | 5 | | foo | foo | | | | "foo" | "foo" | "foo" | "foo | "foo | "foo" | | | | f | 0 | | false | "foo" | ["foo"] | | | |
- [1, 1.23, "2", "aaaaaaa", "foo", null, false, true, {"aaa": 123}, "[1,2]", "\"str\""] | 6 | 6 | | | | | | | null | null | null | null | null | null | null | | | f | 0 | | false | null | [null] | null | | |
- [1, 1.23, "2", "aaaaaaa", "foo", null, false, true, {"aaa": 123}, "[1,2]", "\"str\""] | 7 | 7 | 0 | false | fals | f | | false | false | false | false | fals | fals | false | false | | | f | 0 | | false | false | [false] | false | | |
- [1, 1.23, "2", "aaaaaaa", "foo", null, false, true, {"aaa": 123}, "[1,2]", "\"str\""] | 8 | 8 | 1 | true | true | t | | true | true | true | true | true | true | true | true | | | f | 0 | | false | true | [true] | true | | |
- [1, 1.23, "2", "aaaaaaa", "foo", null, false, true, {"aaa": 123}, "[1,2]", "\"str\""] | 9 | 9 | | | | | | | {"aaa": 123} | {"aaa": 123} | {"aaa": 123} | {"aa | {"aa | {"aaa": 123} | {"aaa": 123} | 123 | 123 | t | 1 | 1 | true | {"aaa": 123} | [{"aaa": 123}] | {"aaa": 123} | | |
- [1, 1.23, "2", "aaaaaaa", "foo", null, false, true, {"aaa": 123}, "[1,2]", "\"str\""] | 10 | 10 | | [1,2] | [1,2 | | | [1,2] | "[1,2]" | "[1,2]" | "[1,2]" | "[1, | "[1, | "[1,2]" | [1, 2] | | | f | 0 | | false | "[1,2]" | ["[1,2]"] | [1, 2] | | |
- [1, 1.23, "2", "aaaaaaa", "foo", null, false, true, {"aaa": 123}, "[1,2]", "\"str\""] | 11 | 11 | | "str" | "str | | | "str" | "\"str\"" | "\"str\"" | "\"str\"" | "\"s | "\"s | "\"str\"" | "str" | | | f | 0 | | false | "\"str\"" | ["\"str\""] | "str" | | |
+ js | id | id2 | int | text | char(4) | bool | numeric | js | jb | jst | jsc | jsv | jsb | jsbq | aaa | aaa1 | exists1 | exists2 | exists3 | exists4 | js2 | jsb2w | jsb2q
+---------------------------------------------------------------------------------------+----+-----+-----+---------+---------+------+---------+--------------+--------------+--------------+------+------+--------------+--------------+-----+------+---------+---------+---------+---------+--------------+----------------+--------------
+ 1 | 1 | 1 | 1 | 1 | 1 | | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | | | f | 0 | | false | 1 | [1] | 1
+ [] | | | | | | | | | | | | | | | | | | | | | | |
+ {} | 1 | 1 | | | | | | {} | {} | {} | {} | {} | {} | {} | | | f | 0 | | false | {} | [{}] | {}
+ [1, 1.23, "2", "aaaaaaa", "foo", null, false, true, {"aaa": 123}, "[1,2]", "\"str\""] | 1 | 1 | 1 | 1 | 1 | | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | | | f | 0 | | false | 1 | [1] | 1
+ [1, 1.23, "2", "aaaaaaa", "foo", null, false, true, {"aaa": 123}, "[1,2]", "\"str\""] | 2 | 2 | 1 | 1.23 | 1.23 | | 1.23 | 1.23 | 1.23 | 1.23 | 1.23 | 1.23 | 1.23 | 1.23 | | | f | 0 | | false | 1.23 | [1.23] | 1.23
+ [1, 1.23, "2", "aaaaaaa", "foo", null, false, true, {"aaa": 123}, "[1,2]", "\"str\""] | 3 | 3 | 2 | 2 | 2 | | 2 | "2" | "2" | "2" | "2" | "2" | "2" | 2 | | | f | 0 | | false | "2" | ["2"] | 2
+ [1, 1.23, "2", "aaaaaaa", "foo", null, false, true, {"aaa": 123}, "[1,2]", "\"str\""] | 4 | 4 | | aaaaaaa | aaaa | | | "aaaaaaa" | "aaaaaaa" | "aaaaaaa" | "aaa | "aaa | "aaaaaaa" | | | | f | 0 | | false | "aaaaaaa" | ["aaaaaaa"] |
+ [1, 1.23, "2", "aaaaaaa", "foo", null, false, true, {"aaa": 123}, "[1,2]", "\"str\""] | 5 | 5 | | foo | foo | | | "foo" | "foo" | "foo" | "foo | "foo | "foo" | | | | f | 0 | | false | "foo" | ["foo"] |
+ [1, 1.23, "2", "aaaaaaa", "foo", null, false, true, {"aaa": 123}, "[1,2]", "\"str\""] | 6 | 6 | | | | | | null | null | null | null | null | null | null | | | f | 0 | | false | null | [null] | null
+ [1, 1.23, "2", "aaaaaaa", "foo", null, false, true, {"aaa": 123}, "[1,2]", "\"str\""] | 7 | 7 | 0 | false | fals | f | | false | false | false | fals | fals | false | false | | | f | 0 | | false | false | [false] | false
+ [1, 1.23, "2", "aaaaaaa", "foo", null, false, true, {"aaa": 123}, "[1,2]", "\"str\""] | 8 | 8 | 1 | true | true | t | | true | true | true | true | true | true | true | | | f | 0 | | false | true | [true] | true
+ [1, 1.23, "2", "aaaaaaa", "foo", null, false, true, {"aaa": 123}, "[1,2]", "\"str\""] | 9 | 9 | | | | | | {"aaa": 123} | {"aaa": 123} | {"aaa": 123} | {"aa | {"aa | {"aaa": 123} | {"aaa": 123} | 123 | 123 | t | 1 | 1 | true | {"aaa": 123} | [{"aaa": 123}] | {"aaa": 123}
+ [1, 1.23, "2", "aaaaaaa", "foo", null, false, true, {"aaa": 123}, "[1,2]", "\"str\""] | 10 | 10 | | [1,2] | [1,2 | | | "[1,2]" | "[1,2]" | "[1,2]" | "[1, | "[1, | "[1,2]" | [1, 2] | | | f | 0 | | false | "[1,2]" | ["[1,2]"] | [1, 2]
+ [1, 1.23, "2", "aaaaaaa", "foo", null, false, true, {"aaa": 123}, "[1,2]", "\"str\""] | 11 | 11 | | "str" | "str | | | "\"str\"" | "\"str\"" | "\"str\"" | "\"s | "\"s | "\"str\"" | "str" | | | f | 0 | | false | "\"str\"" | ["\"str\""] | "str"
(14 rows)
-- JSON_TABLE: Test backward parsing
@@ -1123,7 +1268,6 @@ SELECT * FROM
"char(4)" char(4) PATH '$',
"bool" bool PATH '$',
"numeric" numeric PATH '$',
- "domain" jsonb_test_domain PATH '$',
js json PATH '$',
jb jsonb PATH '$',
jst text FORMAT JSON PATH '$',
@@ -1139,9 +1283,6 @@ SELECT * FROM
js2 json PATH '$',
jsb2w jsonb PATH '$' WITH WRAPPER,
jsb2q jsonb PATH '$' OMIT QUOTES,
- ia int[] PATH '$',
- ta text[] PATH '$',
- jba jsonb[] PATH '$',
NESTED PATH '$[1]' AS p1 COLUMNS (
a1 int,
NESTED PATH '$[*]' AS "p1 1" COLUMNS (
@@ -1168,7 +1309,6 @@ CREATE OR REPLACE VIEW public.jsonb_table_view AS
"json_table"."char(4)",
"json_table".bool,
"json_table"."numeric",
- "json_table".domain,
"json_table".js,
"json_table".jb,
"json_table".jst,
@@ -1184,9 +1324,6 @@ CREATE OR REPLACE VIEW public.jsonb_table_view AS
"json_table".js2,
"json_table".jsb2w,
"json_table".jsb2q,
- "json_table".ia,
- "json_table".ta,
- "json_table".jba,
"json_table".a1,
"json_table".b1,
"json_table".a11,
@@ -1205,7 +1342,6 @@ CREATE OR REPLACE VIEW public.jsonb_table_view AS
"char(4)" character(4) PATH '$',
bool boolean PATH '$',
"numeric" numeric PATH '$',
- domain jsonb_test_domain PATH '$',
js json PATH '$',
jb jsonb PATH '$',
jst text FORMAT JSON PATH '$',
@@ -1221,9 +1357,6 @@ CREATE OR REPLACE VIEW public.jsonb_table_view AS
js2 json PATH '$',
jsb2w jsonb PATH '$' WITH UNCONDITIONAL WRAPPER,
jsb2q jsonb PATH '$' OMIT QUOTES,
- ia integer[] PATH '$',
- ta text[] PATH '$',
- jba jsonb[] PATH '$',
NESTED PATH '$[1]' AS p1
COLUMNS (
a1 integer PATH '$."a1"',
@@ -1248,15 +1381,14 @@ CREATE OR REPLACE VIEW public.jsonb_table_view AS
PLAN (json_table_path_1 OUTER ((p1 OUTER "p1 1") UNION (p2 OUTER ("p2:1" UNION p22))))
)
EXPLAIN (COSTS OFF, VERBOSE) SELECT * FROM jsonb_table_view;
- QUERY PLAN
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ QUERY PLAN
+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Table Function Scan on "json_table"
- Output: "json_table".id, "json_table".id2, "json_table"."int", "json_table".text, "json_table"."char(4)", "json_table".bool, "json_table"."numeric", "json_table".domain, "json_table".js, "json_table".jb, "json_table".jst, "json_table".jsc, "json_table".jsv, "json_table".jsb, "json_table".jsbq, "json_table".aaa, "json_table".aaa1, "json_table".exists1, "json_table".exists2, "json_table".exists3, "json_table".js2, "json_table".jsb2w, "json_table".jsb2q, "json_table".ia, "json_table".ta, "json_table".jba, "json_table".a1, "json_table".b1, "json_table".a11, "json_table".a21, "json_table".a22
- Table Function Call: JSON_TABLE('null'::jsonb, '$[*]' AS json_table_path_1 PASSING 3 AS a, '"foo"'::jsonb AS "b c" COLUMNS (id FOR ORDINALITY, id2 FOR ORDINALITY, "int" integer PATH '$', text text PATH '$', "char(4)" character(4) PATH '$', bool boolean PATH '$', "numeric" numeric PATH '$', domain jsonb_test_domain PATH '$', js json PATH '$', jb jsonb PATH '$', jst text FORMAT JSON PATH '$', jsc character(4) FORMAT JSON PATH '$', jsv character varying(4) FORMAT JSON PATH '$', jsb jsonb PATH '$', jsbq jsonb PATH '$' OMIT QUOTES, aaa integer PATH '$."aaa"', aaa1 integer PATH '$."aaa"', exists1 boolean EXISTS PATH '$."aaa"', exists2 integer EXISTS PATH '$."aaa"' TRUE ON ERROR, exists3 text EXISTS PATH 'strict $."aaa"' UNKNOWN ON ERROR, js2 json PATH '$', jsb2w jsonb PATH '$' WITH UNCONDITIONAL WRAPPER, jsb2q jsonb PATH '$' OMIT QUOTES, ia integer[] PATH '$', ta text[] PATH '$', jba jsonb[] PATH '$', NESTED PATH '$[1]' AS p1 COLUMNS (a1 integer PATH '$."a1"', b1 text PATH '$."b1"', NESTED PATH '$[*]' AS "p1 1" COLUMNS (a11 text PATH '$."a11"')), NESTED PATH '$[2]' AS p2 COLUMNS ( NESTED PATH '$[*]' AS "p2:1" COLUMNS (a21 text PATH '$."a21"'), NESTED PATH '$[*]' AS p22 COLUMNS (a22 text PATH '$."a22"'))) PLAN (json_table_path_1 OUTER ((p1 OUTER "p1 1") UNION (p2 OUTER ("p2:1" UNION p22)))))
+ Output: "json_table".id, "json_table".id2, "json_table"."int", "json_table".text, "json_table"."char(4)", "json_table".bool, "json_table"."numeric", "json_table".js, "json_table".jb, "json_table".jst, "json_table".jsc, "json_table".jsv, "json_table".jsb, "json_table".jsbq, "json_table".aaa, "json_table".aaa1, "json_table".exists1, "json_table".exists2, "json_table".exists3, "json_table".js2, "json_table".jsb2w, "json_table".jsb2q, "json_table".a1, "json_table".b1, "json_table".a11, "json_table".a21, "json_table".a22
+ Table Function Call: JSON_TABLE('null'::jsonb, '$[*]' AS json_table_path_1 PASSING 3 AS a, '"foo"'::jsonb AS "b c" COLUMNS (id FOR ORDINALITY, id2 FOR ORDINALITY, "int" integer PATH '$', text text PATH '$', "char(4)" character(4) PATH '$', bool boolean PATH '$', "numeric" numeric PATH '$', js json PATH '$', jb jsonb PATH '$', jst text FORMAT JSON PATH '$', jsc character(4) FORMAT JSON PATH '$', jsv character varying(4) FORMAT JSON PATH '$', jsb jsonb PATH '$', jsbq jsonb PATH '$' OMIT QUOTES, aaa integer PATH '$."aaa"', aaa1 integer PATH '$."aaa"', exists1 boolean EXISTS PATH '$."aaa"', exists2 integer EXISTS PATH '$."aaa"' TRUE ON ERROR, exists3 text EXISTS PATH 'strict $."aaa"' UNKNOWN ON ERROR, js2 json PATH '$', jsb2w jsonb PATH '$' WITH UNCONDITIONAL WRAPPER, jsb2q jsonb PATH '$' OMIT QUOTES, NESTED PATH '$[1]' AS p1 COLUMNS (a1 integer PATH '$."a1"', b1 text PATH '$."b1"', NESTED PATH '$[*]' AS "p1 1" COLUMNS (a11 text PATH '$."a11"')), NESTED PATH '$[2]' AS p2 COLUMNS ( NESTED PATH '$[*]' AS "p2:1" COLUMNS (a21 text PATH '$."a21"'), NESTED PATH '$[*]' AS p22 COLUMNS (a22 text PATH '$."a22"'))) PLAN (json_table_path_1 OUTER ((p1 OUTER "p1 1") UNION (p2 OUTER ("p2:1" UNION p22)))))
(3 rows)
DROP VIEW jsonb_table_view;
-DROP DOMAIN jsonb_test_domain;
-- JSON_TABLE: ON EMPTY/ON ERROR behavior
SELECT *
FROM
@@ -1318,15 +1450,15 @@ SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a int4 EXISTS PATH '$.a'));
(1 row)
SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a int2 EXISTS PATH '$.a'));
-ERROR: cannot cast type boolean to smallint
+ERROR: returning type smallint is not supported in JSON_EXISTS()
LINE 1: ...ELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a int2 EXI...
^
SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a int8 EXISTS PATH '$.a'));
-ERROR: cannot cast type boolean to bigint
+ERROR: returning type bigint is not supported in JSON_EXISTS()
LINE 1: ...ELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a int8 EXI...
^
SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a float4 EXISTS PATH '$.a'));
-ERROR: cannot cast type boolean to real
+ERROR: returning type real is not supported in JSON_EXISTS()
LINE 1: ...ELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a float4 E...
^
SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a char(3) EXISTS PATH '$.a'));
@@ -1336,11 +1468,11 @@ SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a char(3) EXISTS PATH '$.a'))
(1 row)
SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a json EXISTS PATH '$.a'));
-ERROR: cannot cast type boolean to json
+ERROR: returning type json is not supported in JSON_EXISTS()
LINE 1: ...ELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a json EXI...
^
SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a jsonb EXISTS PATH '$.a'));
-ERROR: cannot cast type boolean to jsonb
+ERROR: returning type jsonb is not supported in JSON_EXISTS()
LINE 1: ...ELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a jsonb EX...
^
-- JSON_TABLE: nested paths and plans
@@ -2102,11 +2234,14 @@ set parallel_leader_participation = off;
-- Should be non-parallel due to subtransactions
EXPLAIN (COSTS OFF)
SELECT sum(JSON_VALUE(js, '$' RETURNING numeric)) FROM test_parallel_jsonb_value;
- QUERY PLAN
----------------------------------------------
- Aggregate
- -> Seq Scan on test_parallel_jsonb_value
-(2 rows)
+ QUERY PLAN
+------------------------------------------------------------------
+ Finalize Aggregate
+ -> Gather
+ Workers Planned: 4
+ -> Partial Aggregate
+ -> Parallel Seq Scan on test_parallel_jsonb_value
+(5 rows)
SELECT sum(JSON_VALUE(js, '$' RETURNING numeric)) FROM test_parallel_jsonb_value;
sum
diff --git a/src/test/regress/sql/jsonb_sqljson.sql b/src/test/regress/sql/jsonb_sqljson.sql
index fff25374808..43a194ad09f 100644
--- a/src/test/regress/sql/jsonb_sqljson.sql
+++ b/src/test/regress/sql/jsonb_sqljson.sql
@@ -82,12 +82,43 @@ 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
+SELECT JSON_VALUE(jsonb '1.23', '$' RETURNING float4);
+SELECT JSON_VALUE(jsonb '"1.23"', '$' RETURNING float4);
+SELECT JSON_VALUE(jsonb '"nan"', '$' RETURNING float4);
+SELECT JSON_VALUE(jsonb '"-inf"', '$' RETURNING float4);
+SELECT JSON_VALUE(jsonb '"err"', '$' RETURNING float4 DEFAULT -1 ON ERROR);
+SELECT JSON_VALUE(jsonb '"err"', '$' RETURNING float4 ERROR ON ERROR);
+
+SELECT JSON_VALUE(jsonb '1.23', '$' RETURNING float8);
+SELECT JSON_VALUE(jsonb '"1.23"', '$' RETURNING float8);
+SELECT JSON_VALUE(jsonb '"nan"', '$' RETURNING float8);
+SELECT JSON_VALUE(jsonb '"-inf"', '$' RETURNING float8);
+SELECT JSON_VALUE(jsonb '"err"', '$' RETURNING float8 DEFAULT -1 ON ERROR);
+SELECT JSON_VALUE(jsonb '"err"', '$' RETURNING float8 ERROR ON ERROR);
+
+SELECT JSON_VALUE(jsonb '1.23', '$' RETURNING numeric);
+SELECT JSON_VALUE(jsonb '"1.23"', '$' RETURNING numeric);
+SELECT JSON_VALUE(jsonb '"nan"', '$' RETURNING numeric);
+SELECT JSON_VALUE(jsonb '"-inf"', '$' RETURNING numeric);
+SELECT JSON_VALUE(jsonb '"err"', '$' RETURNING numeric DEFAULT -1 ON ERROR);
+SELECT JSON_VALUE(jsonb '"err"', '$' RETURNING numeric ERROR ON ERROR);
+
+-- 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);
+
+-- 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 NULL ON ERROR);
SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_int_not_null DEFAULT NULL ON ERROR);
+
+-- Test returning of non-scalar items
SELECT JSON_VALUE(jsonb '[]', '$');
SELECT JSON_VALUE(jsonb '[]', '$' ERROR ON ERROR);
SELECT JSON_VALUE(jsonb '{}', '$');
@@ -133,6 +164,17 @@ 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);
+
+SELECT JSON_VALUE('1'::jsonb, '$.x' RETURNING int DEFAULT -1 ON EMPTY);
+SELECT JSON_VALUE('1'::jsonb, '$.x' RETURNING int DEFAULT '-1' ON EMPTY);
+SELECT JSON_VALUE('1'::jsonb, '$.x' RETURNING int DEFAULT 'err' ON EMPTY);
+SELECT JSON_VALUE('1'::jsonb, '$.x' RETURNING int DEFAULT -1::float ON EMPTY);
+
-- JSON_QUERY
SELECT
@@ -318,6 +360,21 @@ CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.datetime("YY-MM-DD") ?
CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$[1, $.a ? (@.datetime() == $x)]' PASSING '12:34'::time AS x));
CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$[1, 0 to $.a ? (@.datetime() == $x)]' PASSING '12:34'::time AS x));
CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$[1, $.a ? (@.datetime("HH:MI") == $x)]' PASSING '12:34'::time AS x));
+
+CREATE INDEX ON test_jsonb_mutability (JSON_VALUE(js, '$' RETURNING int)); -- immutable
+CREATE INDEX ON test_jsonb_mutability (JSON_VALUE(js, '$' RETURNING date));
+CREATE INDEX ON test_jsonb_mutability (JSON_VALUE(js, '$' RETURNING time));
+CREATE INDEX ON test_jsonb_mutability (JSON_VALUE(js, '$' RETURNING timetz));
+CREATE INDEX ON test_jsonb_mutability (JSON_VALUE(js, '$' RETURNING timestamp));
+CREATE INDEX ON test_jsonb_mutability (JSON_VALUE(js, '$' RETURNING timestamptz));
+
+CREATE INDEX ON test_jsonb_mutability (JSON_VALUE(js, '$.datetime()'));
+CREATE INDEX ON test_jsonb_mutability (JSON_VALUE(js, '$.datetime()[*][1,2].**'));
+CREATE INDEX ON test_jsonb_mutability (JSON_VALUE(js, '$x' PASSING '2022-08-25'::date AS x));
+CREATE INDEX ON test_jsonb_mutability (JSON_VALUE(js, '$.datetime()' RETURNING int)); -- immutable
+
+CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.datetime()')); -- immutable
+
DROP TABLE test_jsonb_mutability;
-- JSON_TABLE
@@ -338,8 +395,6 @@ SELECT * FROM JSON_TABLE(jsonb '123', '$'
COLUMNS (item int PATH '$', foo int)) bar;
-- JSON_TABLE: basic functionality
-CREATE DOMAIN jsonb_test_domain AS text CHECK (value <> 'foo');
-
SELECT *
FROM
(VALUES
@@ -360,7 +415,6 @@ FROM
"char(4)" char(4) PATH '$',
"bool" bool PATH '$',
"numeric" numeric PATH '$',
- "domain" jsonb_test_domain PATH '$',
js json PATH '$',
jb jsonb PATH '$',
jst text FORMAT JSON PATH '$',
@@ -377,10 +431,7 @@ FROM
js2 json PATH '$',
jsb2w jsonb PATH '$' WITH WRAPPER,
- jsb2q jsonb PATH '$' OMIT QUOTES,
- ia int[] PATH '$',
- ta text[] PATH '$',
- jba jsonb[] PATH '$'
+ jsb2q jsonb PATH '$' OMIT QUOTES
)
) jt
ON true;
@@ -399,7 +450,6 @@ SELECT * FROM
"char(4)" char(4) PATH '$',
"bool" bool PATH '$',
"numeric" numeric PATH '$',
- "domain" jsonb_test_domain PATH '$',
js json PATH '$',
jb jsonb PATH '$',
jst text FORMAT JSON PATH '$',
@@ -416,9 +466,6 @@ SELECT * FROM
js2 json PATH '$',
jsb2w jsonb PATH '$' WITH WRAPPER,
jsb2q jsonb PATH '$' OMIT QUOTES,
- ia int[] PATH '$',
- ta text[] PATH '$',
- jba jsonb[] PATH '$',
NESTED PATH '$[1]' AS p1 COLUMNS (
a1 int,
@@ -443,7 +490,6 @@ SELECT * FROM
EXPLAIN (COSTS OFF, VERBOSE) SELECT * FROM jsonb_table_view;
DROP VIEW jsonb_table_view;
-DROP DOMAIN jsonb_test_domain;
-- JSON_TABLE: ON EMPTY/ON ERROR behavior
SELECT *
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 35c9f1efce2..8a0585994ee 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1234,7 +1234,6 @@ JsonArrayQueryConstructor
JsonBaseObjectInfo
JsonBehavior
JsonBehaviorType
-JsonCoercion
JsonCommon
JsonConstructorExpr
JsonConstructorExprState
--
2.17.1
v10-0008-Remove-subsidary-ExprStates-in-SQL-JSON-executio.patchtext/x-patch; charset=UTF-8; name=v10-0008-Remove-subsidary-ExprStates-in-SQL-JSON-executio.patchDownload
From 7a352784fa98d694d5d5d41cf27c18ca2504cf0d Mon Sep 17 00:00:00 2001
From: Nikita Glukhov <n.gluhov@postgrespro.ru>
Date: Mon, 29 Aug 2022 22:41:08 +0300
Subject: [PATCH v10 08/10] Remove subsidary ExprStates in SQL/JSON execution
---
src/backend/executor/execExpr.c | 88 ++++++++---
src/backend/executor/execExprInterp.c | 205 ++++++++++++--------------
src/backend/jit/llvm/llvmjit_expr.c | 70 ++++++++-
src/backend/jit/llvm/llvmjit_types.c | 1 +
src/backend/utils/adt/jsonpath_exec.c | 79 +++++++++-
src/include/executor/execExpr.h | 20 ++-
src/include/utils/jsonpath.h | 4 -
7 files changed, 316 insertions(+), 151 deletions(-)
diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
index f50e0d703f8..68227ebee21 100644
--- a/src/backend/executor/execExpr.c
+++ b/src/backend/executor/execExpr.c
@@ -4159,9 +4159,16 @@ static void
ExecInitJsonExpr(JsonExpr *jexpr, ExprState *state, ExprEvalStep *scratch,
Datum *resv, bool *resnull)
{
+ ExprEvalStep *step;
JsonExprState *jsestate;
ListCell *argexprlc;
ListCell *argnamelc;
+ int skip_step_off;
+ int execpath_step_off;
+ int done_step_off;
+ int default_on_empty_step_off = -1;
+ int default_on_error_step_off = -1;
+ int default_on_empty_jump_off = -1;
/* JSON_TABLE preudo-function returns context item as a result */
if (jexpr->op == JSON_TABLE_OP)
@@ -4174,9 +4181,6 @@ ExecInitJsonExpr(JsonExpr *jexpr, ExprState *state, ExprEvalStep *scratch,
jsestate = palloc0(sizeof(JsonExprState));
jsestate->jsexpr = jexpr;
- scratch->opcode = EEOP_JSONEXPR;
- scratch->d.jsonexpr.jsestate = jsestate;
-
ExecInitExprRec((Expr *) jexpr->formatted_expr, state,
&jsestate->formatted_expr.value,
&jsestate->formatted_expr.isnull);
@@ -4185,15 +4189,12 @@ ExecInitJsonExpr(JsonExpr *jexpr, ExprState *state, ExprEvalStep *scratch,
&jsestate->pathspec.value,
&jsestate->pathspec.isnull);
- if (jexpr->on_empty)
- jsestate->default_on_empty =
- ExecInitExpr((Expr *) jexpr->on_empty->default_expr,
- state->parent);
-
- jsestate->default_on_error =
- ExecInitExpr((Expr *) jexpr->on_error->default_expr,
- state->parent);
+ scratch->opcode = EEOP_JSONEXPR_SKIP;
+ scratch->d.jsonexpr_skip.jsestate = jsestate;
+ skip_step_off = state->steps_len;
+ ExprEvalPushStep(state, scratch);
+ /* PASSING args. */
jsestate->args = NIL;
forboth(argexprlc, jexpr->passing_values,
@@ -4206,16 +4207,69 @@ ExecInitJsonExpr(JsonExpr *jexpr, ExprState *state, ExprEvalStep *scratch,
var->name = pstrdup(argname->sval);
var->typid = exprType((Node *) argexpr);
var->typmod = exprTypmod((Node *) argexpr);
- var->estate = ExecInitExpr(argexpr, state->parent);
+
+ /*
+ * 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;
- var->evaluated = false;
- var->value = (Datum) 0;
- var->isnull = true;
- jsestate->args =
- lappend(jsestate->args, var);
+ /*
+ * 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);
+
+ jsestate->args = lappend(jsestate->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);
+
+ if (jexpr->on_empty && jexpr->on_empty->default_expr)
+ {
+ default_on_empty_step_off = state->steps_len;
+ ExecInitExprRec((Expr *) jexpr->on_empty->default_expr, state, resv, resnull);
+
+ scratch->opcode = EEOP_JUMP;
+ default_on_empty_jump_off = state->steps_len;
+ ExprEvalPushStep(state, scratch);
+ }
+
+ if (jexpr->on_error->default_expr)
+ {
+ default_on_error_step_off = state->steps_len;
+ ExecInitExprRec((Expr *) jexpr->on_error->default_expr, state, resv, resnull);
+ }
+
+ /* Adjust jump locations */
+ done_step_off = state->steps_len;
+
+ /* EEOP_JSONEXPR_SKIP */
+ step = &state->steps[skip_step_off];
+ step->d.jsonexpr_skip.jump_done = done_step_off;
+
+ /* EEOP_JSONEXPR_PATH */
+ step = &state->steps[execpath_step_off];
+ step->d.jsonexpr.jump_done = done_step_off;
+ step->d.jsonexpr.jump_default_on_empty =
+ default_on_empty_step_off > 0 ? default_on_empty_step_off : done_step_off;
+ step->d.jsonexpr.jump_default_on_error =
+ default_on_error_step_off > 0 ? default_on_error_step_off : done_step_off;
+
+ /* EEOP_JUMP */
+ if (default_on_empty_jump_off > 0)
+ {
+ step = &state->steps[default_on_empty_jump_off];
+ step->d.jump.jumpdone = done_step_off;
+ }
}
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index 56f7bd2a5ac..22959ae569c 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -491,7 +491,8 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
&&CASE_EEOP_SUBPLAN,
&&CASE_EEOP_JSON_CONSTRUCTOR,
&&CASE_EEOP_IS_JSON,
- &&CASE_EEOP_JSONEXPR,
+ &&CASE_EEOP_JSONEXPR_SKIP,
+ &&CASE_EEOP_JSONEXPR_PATH,
&&CASE_EEOP_AGG_STRICT_DESERIALIZE,
&&CASE_EEOP_AGG_DESERIALIZE,
&&CASE_EEOP_AGG_STRICT_INPUT_CHECK_ARGS,
@@ -1842,11 +1843,26 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
EEO_NEXT();
}
- EEO_CASE(EEOP_JSONEXPR)
+ EEO_CASE(EEOP_JSONEXPR_SKIP)
{
/* too complex for an inline implementation */
- ExecEvalJsonExpr(state, op, econtext);
- EEO_NEXT();
+ if (ExecEvalJsonExprSkip(state, op))
+ EEO_JUMP(op->d.jsonexpr_skip.jump_done);
+ else
+ EEO_NEXT();
+ }
+
+ EEO_CASE(EEOP_JSONEXPR_PATH)
+ {
+ /* too complex for an inline implementation */
+ int res = ExecEvalJsonExpr(state, op);
+
+ if (res == 0)
+ EEO_JUMP(op->d.jsonexpr.jump_default_on_empty);
+ else if (res == -1)
+ EEO_JUMP(op->d.jsonexpr.jump_default_on_error);
+ else
+ EEO_JUMP(op->d.jsonexpr.jump_done);
}
EEO_CASE(EEOP_LAST)
@@ -4701,59 +4717,6 @@ ExecEvalJsonConstructor(ExprState *state, ExprEvalStep *op,
*op->resnull = isnull;
}
-/*
- * 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;
-
- if (!var->evaluated)
- {
- MemoryContext oldcxt = var->mcxt ?
- MemoryContextSwitchTo(var->mcxt) : NULL;
-
- var->value = ExecEvalExpr(var->estate, var->econtext, &var->isnull);
- var->evaluated = true;
-
- if (oldcxt)
- MemoryContextSwitchTo(oldcxt);
- }
-
- if (var->isnull)
- {
- val->type = jbvNull;
- return 0;
- }
-
- JsonItemFromDatum(var->value, var->typid, var->typmod, val);
-
- *baseObject = *val;
- return id;
-}
-
/* Coerce C string to text, varchar(N), or bpchar(N) */
static Datum
ExecJsonCoerceCStringToText(const char *str, int32 len,
@@ -5275,9 +5238,8 @@ ExecJsonQueryCoercion(JsonExpr *jexpr, Oid typid, int32 typmod,
* type.
*/
static Datum
-ExecEvalJsonBehavior(ExprContext *econtext, JsonBehavior *behavior,
- ExprState *default_estate,
- Oid ret_typid, int32 ret_typmod, bool *is_null)
+ExecEvalJsonBehavior(JsonBehavior *behavior, Oid ret_typid,
+ int32 ret_typmod, bool *is_null)
{
*is_null = false;
@@ -5327,12 +5289,8 @@ ExecEvalJsonBehavior(ExprContext *econtext, JsonBehavior *behavior,
return (Datum) 0;
case JSON_BEHAVIOR_DEFAULT:
- /*
- * Execute DEFAULT expression.
- * Coercion is not needed here, because expression is
- * already coerced to the target type by the parser.
- */
- return ExecEvalExpr(default_estate, econtext, is_null);
+ Assert(0);
+ return (Datum) 0; /* must be handled by caller */
default:
elog(ERROR, "unrecognized SQL/JSON behavior %d", behavior->btype);
@@ -5341,28 +5299,27 @@ ExecEvalJsonBehavior(ExprContext *econtext, JsonBehavior *behavior,
}
static Datum
-ExecEvalJsonExprInternal(ExprState *state,
- JsonExprState *jsestate, ExprContext *econtext,
+ExecEvalJsonExprInternal(ExprState *state, JsonExprState *jsestate,
JsonPath *path, Datum item, bool *resnull,
- bool *error)
+ bool *empty, bool *error)
{
JsonExpr *jexpr = jsestate->jsexpr;
- bool empty = false;
Datum res = (Datum) 0;
Oid ret_typid = jexpr->returning->typid;
int32 ret_typmod = jexpr->returning->typmod;
*resnull = true;
+ *empty = false;
switch (jexpr->op)
{
case JSON_QUERY_OP:
- res = JsonPathQuery(item, path, jexpr->wrapper, &empty, error,
+ res = JsonPathQuery(item, path, jexpr->wrapper, empty, error,
jsestate->args);
if (error && *error)
return (Datum) 0;
- if (empty)
+ if (*empty)
break;
Assert(DatumGetPointer(res));
@@ -5373,13 +5330,13 @@ ExecEvalJsonExprInternal(ExprState *state,
case JSON_VALUE_OP:
{
- JsonbValue *jbv = JsonPathValue(item, path, &empty, error,
+ JsonbValue *jbv = JsonPathValue(item, path, empty, error,
jsestate->args);
if (error && *error)
return (Datum) 0;
- if (empty)
+ if (*empty)
break;
if (!jbv)
@@ -5412,78 +5369,100 @@ ExecEvalJsonExprInternal(ExprState *state,
return (Datum) 0;
}
- Assert(empty);
- Assert(jexpr->on_empty); /* it is not JSON_EXISTS */
+ Assert(*empty);
- if (jexpr->on_empty->btype == JSON_BEHAVIOR_ERROR)
- {
- if (error)
- {
- *error = true;
- return (Datum) 0;
- }
+ return (Datum) 0;
+}
- ereport(ERROR,
- (errcode(ERRCODE_NO_SQL_JSON_ITEM),
- errmsg("no SQL/JSON item")));
+bool
+ExecEvalJsonExprSkip(ExprState *state, ExprEvalStep *op)
+{
+ JsonExprState *jsestate = op->d.jsonexpr_skip.jsestate;
+
+ /*
+ * Skip if either of the input expressions has turned out to be
+ * NULL.
+ */
+ if (jsestate->formatted_expr.isnull ||
+ jsestate->pathspec.isnull)
+ {
+ *op->resvalue = (Datum) 0;
+ *op->resnull = true;
+ return true;
}
- /* Execute ON EMPTY behavior */
- return ExecEvalJsonBehavior(econtext, jexpr->on_empty,
- jsestate->default_on_empty,
- ret_typid, ret_typmod, resnull);
+ /*
+ * Go evaluate the PASSING args if any and subsequently JSON path
+ * itself.
+ */
+ return false;
}
/* ----------------------------------------------------------------
* ExecEvalJsonExpr
* ----------------------------------------------------------------
+ *
+ * Return value:
+ * 1 - Ok, jump to the end of JsonExpr
+ * 0 - empty result, need to execute DEFAULT ON EMPTY expression
+ * -1 - error occured, need to execute DEFAULT ON ERROR expression
*/
-void
-ExecEvalJsonExpr(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
+int
+ExecEvalJsonExpr(ExprState *state, ExprEvalStep *op)
{
JsonExprState *jsestate = op->d.jsonexpr.jsestate;
JsonExpr *jexpr = jsestate->jsexpr;
Datum item;
Datum res;
JsonPath *path;
- ListCell *lc;
+ bool empty = false;
bool error = false;
- bool throwErrors = jexpr->on_error->btype == JSON_BEHAVIOR_ERROR;
-
- if (jsestate->formatted_expr.isnull || jsestate->pathspec.isnull)
- {
- *op->resnull = true;
- *op->resvalue = (Datum) 0;
- return;
- }
+ bool throw_errors = jexpr->on_error->btype == JSON_BEHAVIOR_ERROR;
item = jsestate->formatted_expr.value;
path = DatumGetJsonPathP(jsestate->pathspec.value);
- /* reset JSON path variable contexts */
- foreach(lc, jsestate->args)
+ res = ExecEvalJsonExprInternal(state, jsestate, path, item,
+ op->resnull, &empty,
+ throw_errors ? NULL : &error);
+
+ if (empty && !error)
{
- JsonPathVariableEvalContext *var = lfirst(lc);
+ Assert(jexpr->on_empty); /* it is not JSON_EXISTS */
- var->econtext = econtext;
- var->evaluated = false;
- }
+ if (jexpr->on_empty->btype == JSON_BEHAVIOR_ERROR)
+ {
+ if (throw_errors)
+ ereport(ERROR,
+ (errcode(ERRCODE_NO_SQL_JSON_ITEM),
+ errmsg("no SQL/JSON item")));
- res = ExecEvalJsonExprInternal(state, jsestate, econtext,
- path, item, op->resnull,
- throwErrors ? NULL : &error);
+ error = true; /* fallback to error behavior */
+ }
+ else if (jexpr->on_empty->btype == JSON_BEHAVIOR_DEFAULT)
+ return 0; /* jump to DEFAULT ON EMPTY expression */
+ else
+ /* Execute ON EMPTY behavior */
+ res = ExecEvalJsonBehavior(jexpr->on_empty,
+ jexpr->returning->typid,
+ jexpr->returning->typmod,
+ op->resnull);
+ }
if (error)
{
- Assert(!throwErrors);
+ Assert(!throw_errors);
+
+ if (jexpr->on_error->btype == JSON_BEHAVIOR_DEFAULT)
+ return -1; /* jump to DEFAULT ON ERROR expression */
/* Execute ON ERROR behavior */
- res = ExecEvalJsonBehavior(econtext, jexpr->on_error,
- jsestate->default_on_error,
+ res = ExecEvalJsonBehavior(jexpr->on_error,
jexpr->returning->typid,
jexpr->returning->typmod,
op->resnull);
}
*op->resvalue = res;
+ return 1; /* jump to the end of expression */
}
diff --git a/src/backend/jit/llvm/llvmjit_expr.c b/src/backend/jit/llvm/llvmjit_expr.c
index fd72630f5e6..ed53ad93443 100644
--- a/src/backend/jit/llvm/llvmjit_expr.c
+++ b/src/backend/jit/llvm/llvmjit_expr.c
@@ -2407,11 +2407,71 @@ llvm_compile_expr(ExprState *state)
LLVMBuildBr(b, opblocks[opno + 1]);
break;
- case EEOP_JSONEXPR:
- build_EvalXFunc(b, mod, "ExecEvalJsonExpr",
- v_state, op, v_econtext);
- LLVMBuildBr(b, opblocks[opno + 1]);
- 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 end 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_done]);
+ break;
+ }
+
+ case EEOP_JSONEXPR_PATH:
+ {
+ LLVMValueRef v_ret;
+ LLVMValueRef v_switch;
+ int n_jumps = 0;
+
+ v_ret = build_EvalXFunc(b, mod, "ExecEvalJsonExpr",
+ v_state, op);
+
+ if (op->d.jsonexpr.jump_default_on_empty != op->d.jsonexpr.jump_done)
+ n_jumps++;
+
+ if (op->d.jsonexpr.jump_default_on_error != op->d.jsonexpr.jump_done)
+ n_jumps++;
+
+ if (!n_jumps)
+ LLVMBuildBr(b, opblocks[op->d.jsonexpr.jump_done]);
+ else
+ {
+ v_switch = LLVMBuildSwitch(b, v_ret,
+ opblocks[op->d.jsonexpr.jump_done],
+ n_jumps);
+
+ if (op->d.jsonexpr.jump_default_on_empty != op->d.jsonexpr.jump_done)
+ LLVMAddCase(v_switch, l_int32_const(0),
+ opblocks[op->d.jsonexpr.jump_default_on_empty]);
+
+ if (op->d.jsonexpr.jump_default_on_error != op->d.jsonexpr.jump_done)
+ LLVMAddCase(v_switch, l_int32_const(-1),
+ opblocks[op->d.jsonexpr.jump_default_on_error]);
+ }
+
+ break;
+ }
case EEOP_LAST:
Assert(false);
diff --git a/src/backend/jit/llvm/llvmjit_types.c b/src/backend/jit/llvm/llvmjit_types.c
index 37fe64654b6..b9148ce515c 100644
--- a/src/backend/jit/llvm/llvmjit_types.c
+++ b/src/backend/jit/llvm/llvmjit_types.c
@@ -136,6 +136,7 @@ void *referenced_functions[] =
ExecEvalJsonConstructor,
ExecEvalJsonIsPredicate,
ExecEvalJsonExpr,
+ ExecEvalJsonExprSkip,
MakeExpandedObjectReadOnlyInternal,
slot_getmissingattrs,
slot_getsomeattrs_int,
diff --git a/src/backend/utils/adt/jsonpath_exec.c b/src/backend/utils/adt/jsonpath_exec.c
index 5b6a4805721..b8949789710 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 evalJsonPathVariable(void *cxt, 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,71 @@ getJsonPathItem(JsonPathExecContext *cxt, JsonPathItem *item,
}
}
+/*
+ * Evaluate a JSON path variable caching computed value.
+ */
+static int
+evalJsonPathVariable(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)
+ {
+ Assert(var->evaluated);
+ }
+ else if (!var->evaluated)
+ {
+ MemoryContext oldcxt = var->mcxt ? MemoryContextSwitchTo(var->mcxt) : NULL;
+
+ Assert(var->econtext);
+ var->value = ExecEvalExpr(var->estate, var->econtext, &var->isnull);
+ var->evaluated = true;
+
+ if (oldcxt)
+ MemoryContextSwitchTo(oldcxt);
+ }
+
+ 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
*/
@@ -2903,7 +2970,7 @@ compareDatetime(Datum val1, Oid typid1, Datum val2, Oid typid2,
bool
JsonPathExists(Datum jb, JsonPath *jp, List *vars, bool *error)
{
- JsonPathExecResult res = executeJsonPath(jp, vars, EvalJsonPathVar,
+ JsonPathExecResult res = executeJsonPath(jp, vars, evalJsonPathVariable,
DatumGetJsonbP(jb), !error, NULL,
true);
@@ -2925,8 +2992,8 @@ JsonPathQuery(Datum jb, JsonPath *jp, JsonWrapper wrapper, bool *empty,
JsonPathExecResult res PG_USED_FOR_ASSERTS_ONLY;
int count;
- res = executeJsonPath(jp, vars, EvalJsonPathVar, DatumGetJsonbP(jb), !error,
- &found, true);
+ res = executeJsonPath(jp, vars, evalJsonPathVariable,
+ DatumGetJsonbP(jb), !error, &found, true);
Assert(error || !jperIsError(res));
@@ -2992,8 +3059,8 @@ JsonPathValue(Datum jb, JsonPath *jp, bool *empty, bool *error, List *vars)
JsonPathExecResult jper PG_USED_FOR_ASSERTS_ONLY;
int count;
- jper = executeJsonPath(jp, vars, EvalJsonPathVar, DatumGetJsonbP(jb), !error,
- &found, true);
+ jper = executeJsonPath(jp, vars, evalJsonPathVariable,
+ DatumGetJsonbP(jb), !error, &found, true);
Assert(error || !jperIsError(jper));
@@ -3306,7 +3373,7 @@ JsonTableResetContextItem(JsonTableScanState *scan, Datum item)
oldcxt = MemoryContextSwitchTo(scan->mcxt);
- res = executeJsonPath(scan->path, scan->args, EvalJsonPathVar, js,
+ res = executeJsonPath(scan->path, scan->args, evalJsonPathVariable, js,
scan->errorOnError, &scan->found, false /* FIXME */ );
MemoryContextSwitchTo(oldcxt);
diff --git a/src/include/executor/execExpr.h b/src/include/executor/execExpr.h
index adabf57c97b..b005bf440dd 100644
--- a/src/include/executor/execExpr.h
+++ b/src/include/executor/execExpr.h
@@ -244,7 +244,8 @@ typedef enum ExprEvalOp
EEOP_SUBPLAN,
EEOP_JSON_CONSTRUCTOR,
EEOP_IS_JSON,
- EEOP_JSONEXPR,
+ EEOP_JSONEXPR_SKIP,
+ EEOP_JSONEXPR_PATH,
/* aggregation related nodes */
EEOP_AGG_STRICT_DESERIALIZE,
@@ -696,8 +697,18 @@ typedef struct ExprEvalStep
struct
{
struct JsonExprState *jsestate;
+ int jump_done;
+ int jump_default_on_empty; /* ON EMPTY DEFAULT expression */
+ int jump_default_on_error; /* ON ERROR DEFAULT expression */
} jsonexpr;
+ /* for EEOP_JSONEXPR_SKIP */
+ struct
+ {
+ struct JsonExprState *jsestate;
+ int jump_done;
+ } jsonexpr_skip;
+
} d;
} ExprEvalStep;
@@ -764,11 +775,8 @@ typedef struct JsonExprState
NullableDatum
formatted_expr, /* formatted context item value */
- coercion_expr, /* input for JSON item coercion */
pathspec; /* path specification value */
- ExprState *default_on_empty; /* ON EMPTY DEFAULT expression */
- ExprState *default_on_error; /* ON ERROR DEFAULT expression */
List *args; /* passing arguments */
} JsonExprState;
@@ -831,8 +839,8 @@ extern void ExecEvalSysVar(ExprState *state, ExprEvalStep *op,
ExprContext *econtext, TupleTableSlot *slot);
extern void ExecEvalJsonConstructor(ExprState *state, ExprEvalStep *op,
ExprContext *econtext);
-extern void ExecEvalJsonExpr(ExprState *state, ExprEvalStep *op,
- ExprContext *econtext);
+extern int ExecEvalJsonExpr(ExprState *state, ExprEvalStep *op);
+extern bool ExecEvalJsonExprSkip(ExprState *state, ExprEvalStep *op);
extern void ExecAggInitGroup(AggState *aggstate, AggStatePerTrans pertrans, AggStatePerGroup pergroup,
ExprContext *aggcontext);
diff --git a/src/include/utils/jsonpath.h b/src/include/utils/jsonpath.h
index e9134b7eab4..a33a770f7aa 100644
--- a/src/include/utils/jsonpath.h
+++ b/src/include/utils/jsonpath.h
@@ -280,10 +280,6 @@ extern Datum JsonPathQuery(Datum jb, JsonPath *jp, JsonWrapper wrapper,
bool *empty, bool *error, List *vars);
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
--
2.17.1
v10-0009-Remove-support-of-DEFAULT-ON-EMPTY-in-SQL-JSON-f.patchtext/x-patch; charset=UTF-8; name=v10-0009-Remove-support-of-DEFAULT-ON-EMPTY-in-SQL-JSON-f.patchDownload
From 358e7c7c83cb65f1eb310e2e78627dcc9d1b9bb3 Mon Sep 17 00:00:00 2001
From: Nikita Glukhov <n.gluhov@postgrespro.ru>
Date: Mon, 29 Aug 2022 23:23:02 +0300
Subject: [PATCH v10 09/10] Remove support of DEFAULT ON EMPTY in SQL/JSON
functions
---
doc/src/sgml/func.sgml | 4 +-
src/backend/executor/execExpr.c | 21 ------
src/backend/executor/execExprInterp.c | 27 ++++----
src/backend/jit/llvm/llvmjit_expr.c | 32 +++------
src/backend/optimizer/util/clauses.c | 13 ----
src/backend/parser/parse_expr.c | 33 ++-------
src/include/executor/execExpr.h | 3 +-
src/include/optimizer/clauses.h | 2 -
src/test/regress/expected/jsonb_sqljson.out | 74 +++++++++------------
src/test/regress/sql/jsonb_sqljson.sql | 34 +++++-----
10 files changed, 82 insertions(+), 161 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 8dd63c04556..8e03a1831ab 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -17976,7 +17976,7 @@ FROM
<parameter>context_item</parameter>, <parameter>path_expression</parameter>
<optional> <literal>PASSING</literal> { <parameter>value</parameter> <literal>AS</literal> <parameter>varname</parameter> } <optional>, ...</optional></optional>
<optional> <literal>RETURNING</literal> <parameter>data_type</parameter> </optional>
- <optional> { <literal>ERROR</literal> | <literal>NULL</literal> | <literal>DEFAULT</literal> <parameter>expression</parameter> } <literal>ON EMPTY</literal> </optional>
+ <optional> { <literal>ERROR</literal> | <literal>NULL</literal> } <literal>ON EMPTY</literal> </optional>
<optional> { <literal>ERROR</literal> | <literal>NULL</literal> | <literal>DEFAULT</literal> <parameter>expression</parameter> } <literal>ON ERROR</literal> </optional>)
</para>
<para>
@@ -18017,7 +18017,7 @@ FROM
<optional> <literal>RETURNING</literal> <parameter>data_type</parameter> <optional> <literal>FORMAT JSON</literal> <optional> <literal>ENCODING UTF8</literal> </optional> </optional> </optional>
<optional> { <literal>WITHOUT</literal> | <literal>WITH</literal> { <literal>CONDITIONAL</literal> | <optional><literal>UNCONDITIONAL</literal></optional> } } <optional> <literal>ARRAY</literal> </optional> <literal>WRAPPER</literal> </optional>
<optional> { <literal>KEEP</literal> | <literal>OMIT</literal> } <literal>QUOTES</literal> <optional> <literal>ON SCALAR STRING</literal> </optional> </optional>
- <optional> { <literal>ERROR</literal> | <literal>NULL</literal> | <literal>EMPTY</literal> { <optional> <literal>ARRAY</literal> </optional> | <literal>OBJECT</literal> } | <literal>DEFAULT</literal> <parameter>expression</parameter> } <literal>ON EMPTY</literal> </optional>
+ <optional> { <literal>ERROR</literal> | <literal>NULL</literal> | <literal>EMPTY</literal> { <optional> <literal>ARRAY</literal> </optional> | <literal>OBJECT</literal> } } <literal>ON EMPTY</literal> </optional>
<optional> { <literal>ERROR</literal> | <literal>NULL</literal> | <literal>EMPTY</literal> { <optional> <literal>ARRAY</literal> </optional> | <literal>OBJECT</literal> } | <literal>DEFAULT</literal> <parameter>expression</parameter> } <literal>ON ERROR</literal> </optional>)
</para>
<para>
diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
index 68227ebee21..5ea7e6b421a 100644
--- a/src/backend/executor/execExpr.c
+++ b/src/backend/executor/execExpr.c
@@ -4166,9 +4166,7 @@ ExecInitJsonExpr(JsonExpr *jexpr, ExprState *state, ExprEvalStep *scratch,
int skip_step_off;
int execpath_step_off;
int done_step_off;
- int default_on_empty_step_off = -1;
int default_on_error_step_off = -1;
- int default_on_empty_jump_off = -1;
/* JSON_TABLE preudo-function returns context item as a result */
if (jexpr->op == JSON_TABLE_OP)
@@ -4235,16 +4233,6 @@ ExecInitJsonExpr(JsonExpr *jexpr, ExprState *state, ExprEvalStep *scratch,
execpath_step_off = state->steps_len;
ExprEvalPushStep(state, scratch);
- if (jexpr->on_empty && jexpr->on_empty->default_expr)
- {
- default_on_empty_step_off = state->steps_len;
- ExecInitExprRec((Expr *) jexpr->on_empty->default_expr, state, resv, resnull);
-
- scratch->opcode = EEOP_JUMP;
- default_on_empty_jump_off = state->steps_len;
- ExprEvalPushStep(state, scratch);
- }
-
if (jexpr->on_error->default_expr)
{
default_on_error_step_off = state->steps_len;
@@ -4261,15 +4249,6 @@ ExecInitJsonExpr(JsonExpr *jexpr, ExprState *state, ExprEvalStep *scratch,
/* EEOP_JSONEXPR_PATH */
step = &state->steps[execpath_step_off];
step->d.jsonexpr.jump_done = done_step_off;
- step->d.jsonexpr.jump_default_on_empty =
- default_on_empty_step_off > 0 ? default_on_empty_step_off : done_step_off;
step->d.jsonexpr.jump_default_on_error =
default_on_error_step_off > 0 ? default_on_error_step_off : done_step_off;
-
- /* EEOP_JUMP */
- if (default_on_empty_jump_off > 0)
- {
- step = &state->steps[default_on_empty_jump_off];
- step->d.jump.jumpdone = done_step_off;
- }
}
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index 22959ae569c..35f20d88232 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -1855,14 +1855,10 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
EEO_CASE(EEOP_JSONEXPR_PATH)
{
/* too complex for an inline implementation */
- int res = ExecEvalJsonExpr(state, op);
-
- if (res == 0)
- EEO_JUMP(op->d.jsonexpr.jump_default_on_empty);
- else if (res == -1)
- EEO_JUMP(op->d.jsonexpr.jump_default_on_error);
- else
+ if (ExecEvalJsonExpr(state, op))
EEO_JUMP(op->d.jsonexpr.jump_done);
+ else
+ EEO_JUMP(op->d.jsonexpr.jump_default_on_error);
}
EEO_CASE(EEOP_LAST)
@@ -5403,11 +5399,10 @@ ExecEvalJsonExprSkip(ExprState *state, ExprEvalStep *op)
* ----------------------------------------------------------------
*
* Return value:
- * 1 - Ok, jump to the end of JsonExpr
- * 0 - empty result, need to execute DEFAULT ON EMPTY expression
- * -1 - error occured, need to execute DEFAULT ON ERROR expression
+ * true - Ok, jump to the end of JsonExpr
+ * false - error occured, need to execute DEFAULT ON ERROR expression
*/
-int
+bool
ExecEvalJsonExpr(ExprState *state, ExprEvalStep *op)
{
JsonExprState *jsestate = op->d.jsonexpr.jsestate;
@@ -5439,14 +5434,16 @@ ExecEvalJsonExpr(ExprState *state, ExprEvalStep *op)
error = true; /* fallback to error behavior */
}
- else if (jexpr->on_empty->btype == JSON_BEHAVIOR_DEFAULT)
- return 0; /* jump to DEFAULT ON EMPTY expression */
else
+ {
+ Assert(jexpr->on_empty->btype != JSON_BEHAVIOR_DEFAULT); /* not supported */
+
/* Execute ON EMPTY behavior */
res = ExecEvalJsonBehavior(jexpr->on_empty,
jexpr->returning->typid,
jexpr->returning->typmod,
op->resnull);
+ }
}
if (error)
@@ -5454,7 +5451,7 @@ ExecEvalJsonExpr(ExprState *state, ExprEvalStep *op)
Assert(!throw_errors);
if (jexpr->on_error->btype == JSON_BEHAVIOR_DEFAULT)
- return -1; /* jump to DEFAULT ON ERROR expression */
+ return false; /* jump to DEFAULT ON ERROR expression */
/* Execute ON ERROR behavior */
res = ExecEvalJsonBehavior(jexpr->on_error,
@@ -5464,5 +5461,5 @@ ExecEvalJsonExpr(ExprState *state, ExprEvalStep *op)
}
*op->resvalue = res;
- return 1; /* jump to the end of expression */
+ return true; /* jump to the end of expression */
}
diff --git a/src/backend/jit/llvm/llvmjit_expr.c b/src/backend/jit/llvm/llvmjit_expr.c
index ed53ad93443..69eb86aaf0a 100644
--- a/src/backend/jit/llvm/llvmjit_expr.c
+++ b/src/backend/jit/llvm/llvmjit_expr.c
@@ -2441,34 +2441,22 @@ llvm_compile_expr(ExprState *state)
case EEOP_JSONEXPR_PATH:
{
LLVMValueRef v_ret;
- LLVMValueRef v_switch;
- int n_jumps = 0;
v_ret = build_EvalXFunc(b, mod, "ExecEvalJsonExpr",
v_state, op);
-
- if (op->d.jsonexpr.jump_default_on_empty != op->d.jsonexpr.jump_done)
- n_jumps++;
+ v_ret = LLVMBuildZExt(b, v_ret, TypeStorageBool, "");
if (op->d.jsonexpr.jump_default_on_error != op->d.jsonexpr.jump_done)
- n_jumps++;
-
- if (!n_jumps)
- LLVMBuildBr(b, opblocks[op->d.jsonexpr.jump_done]);
+ LLVMBuildCondBr(b,
+ LLVMBuildICmp(b,
+ LLVMIntEQ,
+ v_ret,
+ l_sbool_const(0),
+ ""),
+ opblocks[op->d.jsonexpr.jump_default_on_error],
+ opblocks[op->d.jsonexpr.jump_done]);
else
- {
- v_switch = LLVMBuildSwitch(b, v_ret,
- opblocks[op->d.jsonexpr.jump_done],
- n_jumps);
-
- if (op->d.jsonexpr.jump_default_on_empty != op->d.jsonexpr.jump_done)
- LLVMAddCase(v_switch, l_int32_const(0),
- opblocks[op->d.jsonexpr.jump_default_on_empty]);
-
- if (op->d.jsonexpr.jump_default_on_error != op->d.jsonexpr.jump_done)
- LLVMAddCase(v_switch, l_int32_const(-1),
- opblocks[op->d.jsonexpr.jump_default_on_error]);
- }
+ LLVMBuildBr(b, opblocks[op->d.jsonexpr.jump_done]);
break;
}
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 6eb923892f4..b133dad10cf 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -5355,16 +5355,3 @@ pull_paramids_walker(Node *node, Bitmapset **context)
return expression_tree_walker(node, pull_paramids_walker,
(void *) context);
}
-
-bool
-expr_can_throw_errors(Node *expr)
-{
- if (!expr)
- return false;
-
- if (IsA(expr, Const))
- return false;
-
- /* TODO consider more cases */
- return true;
-}
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index c02530a3671..f96334ce088 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -4263,7 +4263,6 @@ static Node *
coerceDefaultJsonExpr(ParseState *pstate, JsonExpr *jsexpr,
bool is_on_empty, Node *defexpr)
{
- Node *orig_defexpr = defexpr;
int location;
Oid exprtype;
@@ -4276,6 +4275,12 @@ coerceDefaultJsonExpr(ParseState *pstate, JsonExpr *jsexpr,
if (location < 0)
location = jsexpr->location;
+ if (is_on_empty)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("DEFAULT ON EMPTY expressions are not supported"),
+ parser_errposition(pstate, location)));
+
defexpr = coerce_to_target_type(pstate,
defexpr,
exprtype,
@@ -4293,31 +4298,7 @@ coerceDefaultJsonExpr(ParseState *pstate, JsonExpr *jsexpr,
format_type_be(jsexpr->returning->typid)),
parser_errposition(pstate, location)));
- /* Don't allow unsafe DEFAUL ON EMPTY expressions, unless ERROR ON ERROR */
- if (!is_on_empty ||
- jsexpr->on_error->btype == JSON_BEHAVIOR_ERROR)
- return defexpr;
-
- if (!expr_can_throw_errors(defexpr))
- return defexpr;
-
- /* Try to simplify expression if there is non-empty coercion */
- if (defexpr != orig_defexpr &&
- !expr_can_throw_errors(orig_defexpr))
- {
- defexpr = eval_const_expressions(NULL, defexpr);
-
- if (!expr_can_throw_errors(defexpr))
- return defexpr;
- }
-
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("unsafe DEFAULT ON EMPTY expressions are not supported"),
- errhint("Use ERROR ON ERROR clause or try to simplify expression into constant-like form."),
- parser_errposition(pstate, location)));
-
- return NULL;
+ return defexpr;
}
/*
diff --git a/src/include/executor/execExpr.h b/src/include/executor/execExpr.h
index b005bf440dd..d67419c66a9 100644
--- a/src/include/executor/execExpr.h
+++ b/src/include/executor/execExpr.h
@@ -698,7 +698,6 @@ typedef struct ExprEvalStep
{
struct JsonExprState *jsestate;
int jump_done;
- int jump_default_on_empty; /* ON EMPTY DEFAULT expression */
int jump_default_on_error; /* ON ERROR DEFAULT expression */
} jsonexpr;
@@ -839,7 +838,7 @@ extern void ExecEvalSysVar(ExprState *state, ExprEvalStep *op,
ExprContext *econtext, TupleTableSlot *slot);
extern void ExecEvalJsonConstructor(ExprState *state, ExprEvalStep *op,
ExprContext *econtext);
-extern int ExecEvalJsonExpr(ExprState *state, ExprEvalStep *op);
+extern bool ExecEvalJsonExpr(ExprState *state, ExprEvalStep *op);
extern bool ExecEvalJsonExprSkip(ExprState *state, ExprEvalStep *op);
extern void ExecAggInitGroup(AggState *aggstate, AggStatePerTrans pertrans, AggStatePerGroup pergroup,
diff --git a/src/include/optimizer/clauses.h b/src/include/optimizer/clauses.h
index 4d61b88c161..6c5203dc448 100644
--- a/src/include/optimizer/clauses.h
+++ b/src/include/optimizer/clauses.h
@@ -55,6 +55,4 @@ extern Query *inline_set_returning_function(PlannerInfo *root,
extern Bitmapset *pull_paramids(Expr *expr);
-extern bool expr_can_throw_errors(Node *expr);
-
#endif /* CLAUSES_H */
diff --git a/src/test/regress/expected/jsonb_sqljson.out b/src/test/regress/expected/jsonb_sqljson.out
index 0e07bbc858f..fcd921130f5 100644
--- a/src/test/regress/expected/jsonb_sqljson.out
+++ b/src/test/regress/expected/jsonb_sqljson.out
@@ -537,11 +537,9 @@ SELECT JSON_VALUE(jsonb '1', 'lax $.a' NULL ON EMPTY DEFAULT '2' ON ERROR);
(1 row)
SELECT JSON_VALUE(jsonb '1', 'lax $.a' DEFAULT '2' ON EMPTY DEFAULT '3' ON ERROR);
- json_value
-------------
- 2
-(1 row)
-
+ERROR: DEFAULT ON EMPTY expressions are not supported
+LINE 1: SELECT JSON_VALUE(jsonb '1', 'lax $.a' DEFAULT '2' ON EMPTY ...
+ ^
SELECT JSON_VALUE(jsonb '1', 'lax $.a' ERROR ON EMPTY DEFAULT '3' ON ERROR);
json_value
------------
@@ -576,7 +574,7 @@ SELECT
jsonb '{"a": 1, "b": 2}',
'$.* ? (@ > $x)' PASSING x AS x
RETURNING int
- DEFAULT -1 ON EMPTY
+ NULL ON EMPTY
DEFAULT -2 ON ERROR
) y
FROM
@@ -585,7 +583,7 @@ FROM
---+----
0 | -2
1 | 2
- 2 | -1
+ 2 |
(3 rows)
SELECT JSON_VALUE(jsonb 'null', '$a' PASSING point ' (1, 2 )' AS a);
@@ -634,31 +632,25 @@ SELECT JSON_VALUE('1'::jsonb, '$.x' RETURNING int
DEFAULT 1 / x ON EMPTY
DEFAULT 2 ON ERROR)
FROM (VALUES (1::int), (0)) x(x);
-ERROR: unsafe DEFAULT ON EMPTY expressions are not supported
+ERROR: DEFAULT ON EMPTY expressions are not supported
LINE 2: DEFAULT 1 / x ON EMPTY
^
-HINT: Use ERROR ON ERROR clause or try to simplify expression into constant-like form.
SELECT JSON_VALUE('1'::jsonb, '$.x' RETURNING int DEFAULT -1 ON EMPTY);
- json_value
-------------
- -1
-(1 row)
-
+ERROR: DEFAULT ON EMPTY expressions are not supported
+LINE 1: ...SON_VALUE('1'::jsonb, '$.x' RETURNING int DEFAULT -1 ON EMPT...
+ ^
SELECT JSON_VALUE('1'::jsonb, '$.x' RETURNING int DEFAULT '-1' ON EMPTY);
- json_value
-------------
- -1
-(1 row)
-
+ERROR: DEFAULT ON EMPTY expressions are not supported
+LINE 1: ...SON_VALUE('1'::jsonb, '$.x' RETURNING int DEFAULT '-1' ON EM...
+ ^
SELECT JSON_VALUE('1'::jsonb, '$.x' RETURNING int DEFAULT 'err' ON EMPTY);
-ERROR: invalid input syntax for type integer: "err"
+ERROR: DEFAULT ON EMPTY expressions are not supported
LINE 1: ...SON_VALUE('1'::jsonb, '$.x' RETURNING int DEFAULT 'err' ON E...
^
SELECT JSON_VALUE('1'::jsonb, '$.x' RETURNING int DEFAULT -1::float ON EMPTY);
-ERROR: unsafe DEFAULT ON EMPTY expressions are not supported
+ERROR: DEFAULT ON EMPTY expressions are not supported
LINE 1: ...SON_VALUE('1'::jsonb, '$.x' RETURNING int DEFAULT -1::float ...
^
-HINT: Use ERROR ON ERROR clause or try to simplify expression into constant-like form.
-- JSON_QUERY
SELECT
JSON_QUERY(js, '$'),
@@ -827,7 +819,7 @@ SELECT JSON_QUERY(jsonb '[]', '$[*]' ERROR ON EMPTY);
(1 row)
-SELECT JSON_QUERY(jsonb '[]', '$[*]' DEFAULT '"empty"' ON EMPTY);
+SELECT JSON_QUERY(jsonb '[]', '$[*]' ERROR ON EMPTY DEFAULT '"empty"' ON ERROR);
json_query
------------
"empty"
@@ -1055,7 +1047,7 @@ CREATE TABLE test_jsonb_constraints (
CONSTRAINT test_jsonb_constraint2
CHECK (JSON_EXISTS(js::jsonb, '$.a' PASSING i + 5 AS int, i::text AS txt, array[1,2,3] as arr))
CONSTRAINT test_jsonb_constraint3
- CHECK (JSON_VALUE(js::jsonb, '$.a' RETURNING int DEFAULT ('12' || i)::int ON EMPTY ERROR ON ERROR) > i)
+ CHECK (JSON_VALUE(js::jsonb, '$.a' RETURNING int NULL ON EMPTY ERROR ON ERROR) > i)
CONSTRAINT test_jsonb_constraint4
CHECK (JSON_QUERY(js::jsonb, '$.a' WITH CONDITIONAL WRAPPER EMPTY OBJECT ON ERROR) < jsonb '[10]')
CONSTRAINT test_jsonb_constraint5
@@ -1073,7 +1065,7 @@ CREATE TABLE test_jsonb_constraints (
Check constraints:
"test_jsonb_constraint1" CHECK (js IS JSON)
"test_jsonb_constraint2" CHECK (JSON_EXISTS(js::jsonb, '$."a"' PASSING i + 5 AS int, i::text AS txt, ARRAY[1, 2, 3] AS arr))
- "test_jsonb_constraint3" CHECK (JSON_VALUE(js::jsonb, '$."a"' RETURNING integer DEFAULT ('12'::text || i)::integer ON EMPTY ERROR ON ERROR) > i)
+ "test_jsonb_constraint3" CHECK (JSON_VALUE(js::jsonb, '$."a"' RETURNING integer ERROR ON ERROR) > i)
"test_jsonb_constraint4" CHECK (JSON_QUERY(js::jsonb, '$."a"' RETURNING jsonb WITH CONDITIONAL WRAPPER EMPTY OBJECT ON ERROR) < '[10]'::jsonb)
"test_jsonb_constraint5" CHECK (JSON_QUERY(js::jsonb, '$."a"' RETURNING character(5) OMIT QUOTES EMPTY ARRAY ON EMPTY) > ('a'::bpchar COLLATE "C"))
"test_jsonb_constraint6" CHECK (JSON_EXISTS(js::jsonb, 'strict $."a"' RETURNING integer TRUE ON ERROR) < 2)
@@ -1087,7 +1079,7 @@ ORDER BY 1;
((JSON_EXISTS((js)::jsonb, 'strict $."a"' RETURNING integer TRUE ON ERROR) < 2))
((JSON_QUERY((js)::jsonb, '$."a"' RETURNING character(5) OMIT QUOTES EMPTY ARRAY ON EMPTY) > ('a'::bpchar COLLATE "C")))
((JSON_QUERY((js)::jsonb, '$."a"' RETURNING jsonb WITH CONDITIONAL WRAPPER EMPTY OBJECT ON ERROR) < '[10]'::jsonb))
- ((JSON_VALUE((js)::jsonb, '$."a"' RETURNING integer DEFAULT (('12'::text || i))::integer ON EMPTY ERROR ON ERROR) > i))
+ ((JSON_VALUE((js)::jsonb, '$."a"' RETURNING integer ERROR ON ERROR) > i))
((js IS JSON))
(JSON_EXISTS((js)::jsonb, '$."a"' PASSING (i + 5) AS int, (i)::text AS txt, ARRAY[1, 2, 3] AS arr))
(6 rows)
@@ -1424,22 +1416,22 @@ SELECT * FROM JSON_TABLE(jsonb '1', '$' COLUMNS (a int PATH 'strict $.a' ERROR O
ERROR: jsonpath member accessor can only be applied to an object
SELECT * FROM JSON_TABLE(jsonb '1', '$' COLUMNS (a int PATH 'lax $.a' ERROR ON EMPTY) ERROR ON ERROR) jt;
ERROR: no SQL/JSON item
-SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a int PATH '$' DEFAULT 1 ON EMPTY DEFAULT 2 ON ERROR)) jt;
+SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a int PATH '$' NULL ON EMPTY DEFAULT 2 ON ERROR)) jt;
a
---
2
(1 row)
-SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a int PATH 'strict $.a' DEFAULT 1 ON EMPTY DEFAULT 2 ON ERROR)) jt;
+SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a int PATH 'strict $.a' NULL ON EMPTY DEFAULT 2 ON ERROR)) jt;
a
---
2
(1 row)
-SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a int PATH 'lax $.a' DEFAULT 1 ON EMPTY DEFAULT 2 ON ERROR)) jt;
+SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a int PATH 'lax $.a' NULL ON EMPTY DEFAULT 2 ON ERROR)) jt;
a
---
- 1
+
(1 row)
-- JSON_TABLE: EXISTS PATH types
@@ -1761,7 +1753,7 @@ from
jtt.js,'strict $[*]' as p
columns (
n for ordinality,
- a int path 'lax $.a' default -1 on empty,
+ a int path 'lax $.a' error on empty default -1 on error,
nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
nested path 'strict $.c[*]' as pc columns ( c int path '$' )
)
@@ -1790,7 +1782,7 @@ from
jtt.js,'strict $[*]' as p
columns (
n for ordinality,
- a int path 'lax $.a' default -1 on empty,
+ a int path 'lax $.a' error on empty default -1 on error,
nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
nested path 'strict $.c[*]' as pc columns ( c int path '$' )
)
@@ -1820,7 +1812,7 @@ from
jtt.js,'strict $[*]' as p
columns (
n for ordinality,
- a int path 'lax $.a' default -1 on empty,
+ a int path 'lax $.a' error on empty default -1 on error,
nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
nested path 'strict $.c[*]' as pc columns ( c int path '$' )
)
@@ -1850,7 +1842,7 @@ from
jtt.js,'strict $[*]' as p
columns (
n for ordinality,
- a int path 'lax $.a' default -1 on empty,
+ a int path 'lax $.a' error on empty default -1 on error,
nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
nested path 'strict $.c[*]' as pc columns ( c int path '$' )
)
@@ -1880,7 +1872,7 @@ from
jtt.js,'strict $[*]' as p
columns (
n for ordinality,
- a int path 'lax $.a' default -1 on empty,
+ a int path 'lax $.a' error on empty default -1 on error,
nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
nested path 'strict $.c[*]' as pc columns ( c int path '$' )
)
@@ -1909,7 +1901,7 @@ from
jtt.js,'strict $[*]' as p
columns (
n for ordinality,
- a int path 'lax $.a' default -1 on empty,
+ a int path 'lax $.a' error on empty default -1 on error,
nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
nested path 'strict $.c[*]' as pc columns ( c int path '$' )
)
@@ -1938,7 +1930,7 @@ from
jtt.js,'strict $[*]' as p
columns (
n for ordinality,
- a int path 'lax $.a' default -1 on empty,
+ a int path 'lax $.a' error on empty default -1 on error,
nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
nested path 'strict $.c[*]' as pc columns ( c int path '$' )
)
@@ -1966,7 +1958,7 @@ from
jtt.js,'strict $[*]' as p
columns (
n for ordinality,
- a int path 'lax $.a' default -1 on empty,
+ a int path 'lax $.a' error on empty default -1 on error,
nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
nested path 'strict $.c[*]' as pc columns ( c int path '$' )
)
@@ -1994,7 +1986,7 @@ from
jtt.js,'strict $[*]' as p
columns (
n for ordinality,
- a int path 'lax $.a' default -1 on empty,
+ a int path 'lax $.a' error on empty default -1 on error,
nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
nested path 'strict $.c[*]' as pc columns ( c int path '$' )
)
@@ -2025,7 +2017,7 @@ from
jtt.js,'strict $[*]' as p
columns (
n for ordinality,
- a int path 'lax $.a' default -1 on empty,
+ a int path 'lax $.a' error on empty default -1 on error,
nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
nested path 'strict $.c[*]' as pc columns ( c int path '$' )
)
@@ -2195,7 +2187,7 @@ SELECT JSON_VALUE(jsonb '{"a": 123}', '$' || '.' || 'a');
123
(1 row)
-SELECT JSON_VALUE(jsonb '{"a": 123}', '$' || '.' || 'b' DEFAULT 'foo' ON EMPTY);
+SELECT JSON_VALUE(jsonb '{"a": 123}', '$' || '.' || 'b' ERROR ON EMPTY DEFAULT 'foo' ON ERROR);
json_value
------------
foo
diff --git a/src/test/regress/sql/jsonb_sqljson.sql b/src/test/regress/sql/jsonb_sqljson.sql
index 43a194ad09f..76d4c9cc14e 100644
--- a/src/test/regress/sql/jsonb_sqljson.sql
+++ b/src/test/regress/sql/jsonb_sqljson.sql
@@ -148,7 +148,7 @@ SELECT
jsonb '{"a": 1, "b": 2}',
'$.* ? (@ > $x)' PASSING x AS x
RETURNING int
- DEFAULT -1 ON EMPTY
+ NULL ON EMPTY
DEFAULT -2 ON ERROR
) y
FROM
@@ -237,7 +237,7 @@ SELECT JSON_QUERY(jsonb '[]', '$[*]' EMPTY ON EMPTY);
SELECT JSON_QUERY(jsonb '[]', '$[*]' EMPTY ARRAY ON EMPTY);
SELECT JSON_QUERY(jsonb '[]', '$[*]' EMPTY OBJECT ON EMPTY);
SELECT JSON_QUERY(jsonb '[]', '$[*]' ERROR ON EMPTY);
-SELECT JSON_QUERY(jsonb '[]', '$[*]' DEFAULT '"empty"' ON EMPTY);
+SELECT JSON_QUERY(jsonb '[]', '$[*]' ERROR ON EMPTY DEFAULT '"empty"' ON ERROR);
SELECT JSON_QUERY(jsonb '[]', '$[*]' ERROR ON EMPTY NULL ON ERROR);
SELECT JSON_QUERY(jsonb '[]', '$[*]' ERROR ON EMPTY EMPTY ARRAY ON ERROR);
@@ -309,7 +309,7 @@ CREATE TABLE test_jsonb_constraints (
CONSTRAINT test_jsonb_constraint2
CHECK (JSON_EXISTS(js::jsonb, '$.a' PASSING i + 5 AS int, i::text AS txt, array[1,2,3] as arr))
CONSTRAINT test_jsonb_constraint3
- CHECK (JSON_VALUE(js::jsonb, '$.a' RETURNING int DEFAULT ('12' || i)::int ON EMPTY ERROR ON ERROR) > i)
+ CHECK (JSON_VALUE(js::jsonb, '$.a' RETURNING int NULL ON EMPTY ERROR ON ERROR) > i)
CONSTRAINT test_jsonb_constraint4
CHECK (JSON_QUERY(js::jsonb, '$.a' WITH CONDITIONAL WRAPPER EMPTY OBJECT ON ERROR) < jsonb '[10]')
CONSTRAINT test_jsonb_constraint5
@@ -515,9 +515,9 @@ SELECT * FROM JSON_TABLE(jsonb '1', '$' COLUMNS (a int PATH '$.a' ERROR ON EMPTY
SELECT * FROM JSON_TABLE(jsonb '1', '$' COLUMNS (a int PATH 'strict $.a' ERROR ON EMPTY) ERROR ON ERROR) jt;
SELECT * FROM JSON_TABLE(jsonb '1', '$' COLUMNS (a int PATH 'lax $.a' ERROR ON EMPTY) ERROR ON ERROR) jt;
-SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a int PATH '$' DEFAULT 1 ON EMPTY DEFAULT 2 ON ERROR)) jt;
-SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a int PATH 'strict $.a' DEFAULT 1 ON EMPTY DEFAULT 2 ON ERROR)) jt;
-SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a int PATH 'lax $.a' DEFAULT 1 ON EMPTY DEFAULT 2 ON ERROR)) jt;
+SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a int PATH '$' NULL ON EMPTY DEFAULT 2 ON ERROR)) jt;
+SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a int PATH 'strict $.a' NULL ON EMPTY DEFAULT 2 ON ERROR)) jt;
+SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a int PATH 'lax $.a' NULL ON EMPTY DEFAULT 2 ON ERROR)) jt;
-- JSON_TABLE: EXISTS PATH types
SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a int4 EXISTS PATH '$.a'));
@@ -776,7 +776,7 @@ from
jtt.js,'strict $[*]' as p
columns (
n for ordinality,
- a int path 'lax $.a' default -1 on empty,
+ a int path 'lax $.a' error on empty default -1 on error,
nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
nested path 'strict $.c[*]' as pc columns ( c int path '$' )
)
@@ -791,7 +791,7 @@ from
jtt.js,'strict $[*]' as p
columns (
n for ordinality,
- a int path 'lax $.a' default -1 on empty,
+ a int path 'lax $.a' error on empty default -1 on error,
nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
nested path 'strict $.c[*]' as pc columns ( c int path '$' )
)
@@ -807,7 +807,7 @@ from
jtt.js,'strict $[*]' as p
columns (
n for ordinality,
- a int path 'lax $.a' default -1 on empty,
+ a int path 'lax $.a' error on empty default -1 on error,
nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
nested path 'strict $.c[*]' as pc columns ( c int path '$' )
)
@@ -823,7 +823,7 @@ from
jtt.js,'strict $[*]' as p
columns (
n for ordinality,
- a int path 'lax $.a' default -1 on empty,
+ a int path 'lax $.a' error on empty default -1 on error,
nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
nested path 'strict $.c[*]' as pc columns ( c int path '$' )
)
@@ -839,7 +839,7 @@ from
jtt.js,'strict $[*]' as p
columns (
n for ordinality,
- a int path 'lax $.a' default -1 on empty,
+ a int path 'lax $.a' error on empty default -1 on error,
nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
nested path 'strict $.c[*]' as pc columns ( c int path '$' )
)
@@ -855,7 +855,7 @@ from
jtt.js,'strict $[*]' as p
columns (
n for ordinality,
- a int path 'lax $.a' default -1 on empty,
+ a int path 'lax $.a' error on empty default -1 on error,
nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
nested path 'strict $.c[*]' as pc columns ( c int path '$' )
)
@@ -871,7 +871,7 @@ from
jtt.js,'strict $[*]' as p
columns (
n for ordinality,
- a int path 'lax $.a' default -1 on empty,
+ a int path 'lax $.a' error on empty default -1 on error,
nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
nested path 'strict $.c[*]' as pc columns ( c int path '$' )
)
@@ -887,7 +887,7 @@ from
jtt.js,'strict $[*]' as p
columns (
n for ordinality,
- a int path 'lax $.a' default -1 on empty,
+ a int path 'lax $.a' error on empty default -1 on error,
nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
nested path 'strict $.c[*]' as pc columns ( c int path '$' )
)
@@ -903,7 +903,7 @@ from
jtt.js,'strict $[*]' as p
columns (
n for ordinality,
- a int path 'lax $.a' default -1 on empty,
+ a int path 'lax $.a' error on empty default -1 on error,
nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
nested path 'strict $.c[*]' as pc columns ( c int path '$' )
)
@@ -919,7 +919,7 @@ from
jtt.js,'strict $[*]' as p
columns (
n for ordinality,
- a int path 'lax $.a' default -1 on empty,
+ a int path 'lax $.a' error on empty default -1 on error,
nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
nested path 'strict $.c[*]' as pc columns ( c int path '$' )
)
@@ -987,7 +987,7 @@ FROM JSON_TABLE(
-- Extension: non-constant JSON path
SELECT JSON_EXISTS(jsonb '{"a": 123}', '$' || '.' || 'a');
SELECT JSON_VALUE(jsonb '{"a": 123}', '$' || '.' || 'a');
-SELECT JSON_VALUE(jsonb '{"a": 123}', '$' || '.' || 'b' DEFAULT 'foo' ON EMPTY);
+SELECT JSON_VALUE(jsonb '{"a": 123}', '$' || '.' || 'b' ERROR ON EMPTY DEFAULT 'foo' ON ERROR);
SELECT JSON_QUERY(jsonb '{"a": 123}', '$' || '.' || 'a');
SELECT JSON_QUERY(jsonb '{"a": 123}', '$' || '.' || 'a' WITH WRAPPER);
-- Should fail (invalid path)
--
2.17.1
v10-0010-Remove-support-of-DEFAULT-ON-ERROR-in-SQL-JSON-f.patchtext/x-patch; charset=UTF-8; name=v10-0010-Remove-support-of-DEFAULT-ON-ERROR-in-SQL-JSON-f.patchDownload
From f2e7583b51bb529d09bb4e80bd993d1991470f37 Mon Sep 17 00:00:00 2001
From: Nikita Glukhov <n.gluhov@postgrespro.ru>
Date: Tue, 30 Aug 2022 22:32:06 +0300
Subject: [PATCH v10 10/10] Remove support of DEFAULT ON ERROR in SQL/JSON
functions
---
doc/src/sgml/func.sgml | 9 +-
src/backend/executor/execExpr.c | 15 -
src/backend/executor/execExprInterp.c | 31 +-
src/backend/jit/llvm/llvmjit_expr.c | 24 +-
src/backend/nodes/makefuncs.c | 15 -
src/backend/nodes/nodeFuncs.c | 27 --
src/backend/parser/gram.y | 49 +--
src/backend/parser/parse_expr.c | 85 +---
src/backend/parser/parse_jsontable.c | 11 +-
src/backend/utils/adt/ruleutils.c | 23 +-
src/backend/utils/misc/queryjumble.c | 9 +-
src/include/executor/execExpr.h | 4 +-
src/include/nodes/makefuncs.h | 1 -
src/include/nodes/parsenodes.h | 10 +-
src/include/nodes/primnodes.h | 21 +-
src/test/regress/expected/jsonb_sqljson.out | 432 +++++++++-----------
src/test/regress/sql/jsonb_sqljson.sql | 24 +-
src/tools/pgindent/typedefs.list | 1 -
18 files changed, 285 insertions(+), 506 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 8e03a1831ab..a374b6b011c 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -17977,7 +17977,7 @@ FROM
<optional> <literal>PASSING</literal> { <parameter>value</parameter> <literal>AS</literal> <parameter>varname</parameter> } <optional>, ...</optional></optional>
<optional> <literal>RETURNING</literal> <parameter>data_type</parameter> </optional>
<optional> { <literal>ERROR</literal> | <literal>NULL</literal> } <literal>ON EMPTY</literal> </optional>
- <optional> { <literal>ERROR</literal> | <literal>NULL</literal> | <literal>DEFAULT</literal> <parameter>expression</parameter> } <literal>ON ERROR</literal> </optional>)
+ <optional> { <literal>ERROR</literal> | <literal>NULL</literal> } <literal>ON ERROR</literal> </optional>)
</para>
<para>
Returns the result of applying the
@@ -18004,10 +18004,7 @@ FROM
<literal>json_value(jsonb '"03:04 2015-02-01"', '$.datetime("HH24:MI YYYY-MM-DD")' RETURNING date)</literal>
<returnvalue>2015-02-01</returnvalue>
</para>
- <para>
- <literal>json_value(jsonb '[1,2]', 'strict $[*]' DEFAULT 9 ON ERROR)</literal>
- <returnvalue>9</returnvalue>
- </para></entry>
+ </entry>
</row>
<row>
<entry role="func_table_entry"><para role="func_signature">
@@ -18018,7 +18015,7 @@ FROM
<optional> { <literal>WITHOUT</literal> | <literal>WITH</literal> { <literal>CONDITIONAL</literal> | <optional><literal>UNCONDITIONAL</literal></optional> } } <optional> <literal>ARRAY</literal> </optional> <literal>WRAPPER</literal> </optional>
<optional> { <literal>KEEP</literal> | <literal>OMIT</literal> } <literal>QUOTES</literal> <optional> <literal>ON SCALAR STRING</literal> </optional> </optional>
<optional> { <literal>ERROR</literal> | <literal>NULL</literal> | <literal>EMPTY</literal> { <optional> <literal>ARRAY</literal> </optional> | <literal>OBJECT</literal> } } <literal>ON EMPTY</literal> </optional>
- <optional> { <literal>ERROR</literal> | <literal>NULL</literal> | <literal>EMPTY</literal> { <optional> <literal>ARRAY</literal> </optional> | <literal>OBJECT</literal> } | <literal>DEFAULT</literal> <parameter>expression</parameter> } <literal>ON ERROR</literal> </optional>)
+ <optional> { <literal>ERROR</literal> | <literal>NULL</literal> | <literal>EMPTY</literal> { <optional> <literal>ARRAY</literal> </optional> | <literal>OBJECT</literal> } } <literal>ON ERROR</literal> </optional>)
</para>
<para>
Returns the result of applying the
diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
index 5ea7e6b421a..91efa259657 100644
--- a/src/backend/executor/execExpr.c
+++ b/src/backend/executor/execExpr.c
@@ -4164,9 +4164,7 @@ ExecInitJsonExpr(JsonExpr *jexpr, ExprState *state, ExprEvalStep *scratch,
ListCell *argexprlc;
ListCell *argnamelc;
int skip_step_off;
- int execpath_step_off;
int done_step_off;
- int default_on_error_step_off = -1;
/* JSON_TABLE preudo-function returns context item as a result */
if (jexpr->op == JSON_TABLE_OP)
@@ -4230,25 +4228,12 @@ ExecInitJsonExpr(JsonExpr *jexpr, ExprState *state, ExprEvalStep *scratch,
/* 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);
- if (jexpr->on_error->default_expr)
- {
- default_on_error_step_off = state->steps_len;
- ExecInitExprRec((Expr *) jexpr->on_error->default_expr, state, resv, resnull);
- }
-
/* Adjust jump locations */
done_step_off = state->steps_len;
/* EEOP_JSONEXPR_SKIP */
step = &state->steps[skip_step_off];
step->d.jsonexpr_skip.jump_done = done_step_off;
-
- /* EEOP_JSONEXPR_PATH */
- step = &state->steps[execpath_step_off];
- step->d.jsonexpr.jump_done = done_step_off;
- step->d.jsonexpr.jump_default_on_error =
- default_on_error_step_off > 0 ? default_on_error_step_off : done_step_off;
}
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index 35f20d88232..a27887b3cb6 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -1855,10 +1855,8 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
EEO_CASE(EEOP_JSONEXPR_PATH)
{
/* too complex for an inline implementation */
- if (ExecEvalJsonExpr(state, op))
- EEO_JUMP(op->d.jsonexpr.jump_done);
- else
- EEO_JUMP(op->d.jsonexpr.jump_default_on_error);
+ ExecEvalJsonExpr(state, op);
+ EEO_NEXT();
}
EEO_CASE(EEOP_LAST)
@@ -5234,12 +5232,12 @@ ExecJsonQueryCoercion(JsonExpr *jexpr, Oid typid, int32 typmod,
* type.
*/
static Datum
-ExecEvalJsonBehavior(JsonBehavior *behavior, Oid ret_typid,
+ExecEvalJsonBehavior(JsonBehaviorType behavior, Oid ret_typid,
int32 ret_typmod, bool *is_null)
{
*is_null = false;
- switch (behavior->btype)
+ switch (behavior)
{
case JSON_BEHAVIOR_EMPTY_ARRAY:
if (ret_typid == JSONBOID)
@@ -5270,7 +5268,7 @@ ExecEvalJsonBehavior(JsonBehavior *behavior, Oid ret_typid,
{
Datum res;
bool ok PG_USED_FOR_ASSERTS_ONLY =
- ExecJsonCoerceBool(behavior->btype == JSON_BEHAVIOR_TRUE,
+ ExecJsonCoerceBool(behavior == JSON_BEHAVIOR_TRUE,
ret_typid, ret_typmod, &res);
Assert(ok); /* returning type must be checked in parser */
@@ -5284,12 +5282,13 @@ ExecEvalJsonBehavior(JsonBehavior *behavior, Oid ret_typid,
*is_null = true;
return (Datum) 0;
- case JSON_BEHAVIOR_DEFAULT:
+ case JSON_BEHAVIOR_UNSPEC:
+ case JSON_BEHAVIOR_ERROR:
Assert(0);
return (Datum) 0; /* must be handled by caller */
default:
- elog(ERROR, "unrecognized SQL/JSON behavior %d", behavior->btype);
+ elog(ERROR, "unrecognized SQL/JSON behavior %d", behavior);
return (Datum) 0;
}
}
@@ -5402,7 +5401,7 @@ ExecEvalJsonExprSkip(ExprState *state, ExprEvalStep *op)
* true - Ok, jump to the end of JsonExpr
* false - error occured, need to execute DEFAULT ON ERROR expression
*/
-bool
+void
ExecEvalJsonExpr(ExprState *state, ExprEvalStep *op)
{
JsonExprState *jsestate = op->d.jsonexpr.jsestate;
@@ -5412,7 +5411,7 @@ ExecEvalJsonExpr(ExprState *state, ExprEvalStep *op)
JsonPath *path;
bool empty = false;
bool error = false;
- bool throw_errors = jexpr->on_error->btype == JSON_BEHAVIOR_ERROR;
+ bool throw_errors = jexpr->on_error == JSON_BEHAVIOR_ERROR;
item = jsestate->formatted_expr.value;
path = DatumGetJsonPathP(jsestate->pathspec.value);
@@ -5423,9 +5422,9 @@ ExecEvalJsonExpr(ExprState *state, ExprEvalStep *op)
if (empty && !error)
{
- Assert(jexpr->on_empty); /* it is not JSON_EXISTS */
+ Assert(jexpr->on_empty != JSON_BEHAVIOR_UNSPEC); /* it is not JSON_EXISTS */
- if (jexpr->on_empty->btype == JSON_BEHAVIOR_ERROR)
+ if (jexpr->on_empty == JSON_BEHAVIOR_ERROR)
{
if (throw_errors)
ereport(ERROR,
@@ -5436,7 +5435,7 @@ ExecEvalJsonExpr(ExprState *state, ExprEvalStep *op)
}
else
{
- Assert(jexpr->on_empty->btype != JSON_BEHAVIOR_DEFAULT); /* not supported */
+ Assert(jexpr->on_empty != JSON_BEHAVIOR_DEFAULT); /* not supported */
/* Execute ON EMPTY behavior */
res = ExecEvalJsonBehavior(jexpr->on_empty,
@@ -5450,9 +5449,6 @@ ExecEvalJsonExpr(ExprState *state, ExprEvalStep *op)
{
Assert(!throw_errors);
- if (jexpr->on_error->btype == JSON_BEHAVIOR_DEFAULT)
- return false; /* jump to DEFAULT ON ERROR expression */
-
/* Execute ON ERROR behavior */
res = ExecEvalJsonBehavior(jexpr->on_error,
jexpr->returning->typid,
@@ -5461,5 +5457,4 @@ ExecEvalJsonExpr(ExprState *state, ExprEvalStep *op)
}
*op->resvalue = res;
- return true; /* jump to the end of expression */
}
diff --git a/src/backend/jit/llvm/llvmjit_expr.c b/src/backend/jit/llvm/llvmjit_expr.c
index 69eb86aaf0a..d517ed480f5 100644
--- a/src/backend/jit/llvm/llvmjit_expr.c
+++ b/src/backend/jit/llvm/llvmjit_expr.c
@@ -2439,27 +2439,9 @@ llvm_compile_expr(ExprState *state)
}
case EEOP_JSONEXPR_PATH:
- {
- LLVMValueRef v_ret;
-
- v_ret = build_EvalXFunc(b, mod, "ExecEvalJsonExpr",
- v_state, op);
- v_ret = LLVMBuildZExt(b, v_ret, TypeStorageBool, "");
-
- if (op->d.jsonexpr.jump_default_on_error != op->d.jsonexpr.jump_done)
- LLVMBuildCondBr(b,
- LLVMBuildICmp(b,
- LLVMIntEQ,
- v_ret,
- l_sbool_const(0),
- ""),
- opblocks[op->d.jsonexpr.jump_default_on_error],
- opblocks[op->d.jsonexpr.jump_done]);
- else
- LLVMBuildBr(b, opblocks[op->d.jsonexpr.jump_done]);
-
- break;
- }
+ build_EvalXFunc(b, mod, "ExecEvalJsonExpr", v_state, op);
+ LLVMBuildBr(b, opblocks[opno + 1]);
+ break;
case EEOP_LAST:
Assert(false);
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index 28288dcfc10..f05e7f79627 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -852,21 +852,6 @@ makeJsonValueExpr(Expr *expr, JsonFormat *format)
return jve;
}
-/*
- * makeJsonBehavior -
- * creates a JsonBehavior node
- */
-JsonBehavior *
-makeJsonBehavior(JsonBehaviorType type, Node *default_expr)
-{
- JsonBehavior *behavior = makeNode(JsonBehavior);
-
- behavior->btype = type;
- behavior->default_expr = default_expr;
-
- return behavior;
-}
-
/*
* makeJsonTableJoinedPlan -
* creates a joined JsonTablePlan node
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index e0240beeeab..01f02eb78b1 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -2501,11 +2501,6 @@ expression_tree_walker(Node *node,
if (walker(jexpr->passing_values, context))
return true;
/* we assume walker doesn't care about passing_names */
- if (jexpr->on_empty &&
- walker(jexpr->on_empty->default_expr, context))
- return true;
- if (walker(jexpr->on_error->default_expr, context))
- return true;
}
break;
default:
@@ -3537,11 +3532,6 @@ expression_tree_mutator(Node *node,
MUTATE(newnode->formatted_expr, jexpr->formatted_expr, Node *);
MUTATE(newnode->passing_values, jexpr->passing_values, List *);
/* assume mutator does not care about passing_names */
- if (newnode->on_empty)
- MUTATE(newnode->on_empty->default_expr,
- jexpr->on_empty->default_expr, Node *);
- MUTATE(newnode->on_error->default_expr,
- jexpr->on_error->default_expr, Node *);
return (Node *) newnode;
}
break;
@@ -4438,15 +4428,6 @@ raw_expression_tree_walker(Node *node,
return true;
}
break;
- case T_JsonBehavior:
- {
- JsonBehavior *jb = (JsonBehavior *) node;
-
- if (jb->btype == JSON_BEHAVIOR_DEFAULT &&
- walker(jb->default_expr, context))
- return true;
- }
- break;
case T_JsonFuncExpr:
{
JsonFuncExpr *jfe = (JsonFuncExpr *) node;
@@ -4455,10 +4436,6 @@ raw_expression_tree_walker(Node *node,
return true;
if (jfe->output && walker(jfe->output, context))
return true;
- if (walker(jfe->on_empty, context))
- return true;
- if (walker(jfe->on_error, context))
- return true;
}
break;
case T_JsonTable:
@@ -4477,10 +4454,6 @@ raw_expression_tree_walker(Node *node,
if (walker(jtc->typeName, context))
return true;
- if (walker(jtc->on_empty, context))
- return true;
- if (walker(jtc->on_error, context))
- return true;
if (jtc->coltype == JTC_NESTED && walker(jtc->columns, context))
return true;
}
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index f9037761f96..3ab323bd791 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -281,11 +281,11 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
MergeWhenClause *mergewhen;
struct KeyActions *keyactions;
struct KeyAction *keyaction;
- JsonBehavior *jsbehavior;
+ JsonBehaviorType jsbehavior;
struct
{
- JsonBehavior *on_empty;
- JsonBehavior *on_error;
+ JsonBehaviorType on_empty;
+ JsonBehaviorType on_error;
} on_behavior;
JsonQuotes js_quotes;
}
@@ -730,7 +730,6 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
json_behavior_empty
json_behavior_empty_array
json_behavior_empty_object
- json_behavior_default
json_value_behavior
json_query_behavior
json_exists_error_behavior
@@ -16532,59 +16531,54 @@ json_encoding:
;
json_behavior_error:
- ERROR_P { $$ = makeJsonBehavior(JSON_BEHAVIOR_ERROR, NULL); }
+ ERROR_P { $$ = JSON_BEHAVIOR_ERROR; }
;
json_behavior_null:
- NULL_P { $$ = makeJsonBehavior(JSON_BEHAVIOR_NULL, NULL); }
+ NULL_P { $$ = JSON_BEHAVIOR_NULL; }
;
json_behavior_true:
- TRUE_P { $$ = makeJsonBehavior(JSON_BEHAVIOR_TRUE, NULL); }
+ TRUE_P { $$ = JSON_BEHAVIOR_TRUE; }
;
json_behavior_false:
- FALSE_P { $$ = makeJsonBehavior(JSON_BEHAVIOR_FALSE, NULL); }
+ FALSE_P { $$ = JSON_BEHAVIOR_FALSE; }
;
json_behavior_unknown:
- UNKNOWN { $$ = makeJsonBehavior(JSON_BEHAVIOR_UNKNOWN, NULL); }
+ UNKNOWN { $$ = JSON_BEHAVIOR_UNKNOWN; }
;
json_behavior_empty:
- EMPTY_P { $$ = makeJsonBehavior(JSON_BEHAVIOR_EMPTY_OBJECT, NULL); }
+ EMPTY_P { $$ = JSON_BEHAVIOR_EMPTY_OBJECT; }
;
json_behavior_empty_array:
- EMPTY_P ARRAY { $$ = makeJsonBehavior(JSON_BEHAVIOR_EMPTY_ARRAY, NULL); }
+ EMPTY_P ARRAY { $$ = JSON_BEHAVIOR_EMPTY_ARRAY; }
/* non-standard, for Oracle compatibility only */
- | EMPTY_P { $$ = makeJsonBehavior(JSON_BEHAVIOR_EMPTY_ARRAY, NULL); }
+ | EMPTY_P { $$ = JSON_BEHAVIOR_EMPTY_ARRAY; }
;
json_behavior_empty_object:
- EMPTY_P OBJECT_P { $$ = makeJsonBehavior(JSON_BEHAVIOR_EMPTY_OBJECT, NULL); }
- ;
-
-json_behavior_default:
- DEFAULT a_expr { $$ = makeJsonBehavior(JSON_BEHAVIOR_DEFAULT, $2); }
+ EMPTY_P OBJECT_P { $$ = JSON_BEHAVIOR_EMPTY_OBJECT; }
;
json_value_behavior:
json_behavior_null
| json_behavior_error
- | json_behavior_default
;
json_value_on_behavior_clause_opt:
json_value_behavior ON EMPTY_P
- { $$.on_empty = $1; $$.on_error = NULL; }
+ { $$.on_empty = $1; $$.on_error = JSON_BEHAVIOR_UNSPEC; }
| json_value_behavior ON EMPTY_P json_value_behavior ON ERROR_P
{ $$.on_empty = $1; $$.on_error = $4; }
| json_value_behavior ON ERROR_P
- { $$.on_empty = NULL; $$.on_error = $1; }
+ { $$.on_empty = JSON_BEHAVIOR_UNSPEC; $$.on_error = $1; }
| /* EMPTY */
- { $$.on_empty = NULL; $$.on_error = NULL; }
+ { $$.on_empty = JSON_BEHAVIOR_UNSPEC; $$.on_error = JSON_BEHAVIOR_UNSPEC; }
;
json_query_expr:
@@ -16656,18 +16650,17 @@ json_query_behavior:
| json_behavior_null
| json_behavior_empty_array
| json_behavior_empty_object
- | json_behavior_default
;
json_query_on_behavior_clause_opt:
json_query_behavior ON EMPTY_P
- { $$.on_empty = $1; $$.on_error = NULL; }
+ { $$.on_empty = $1; $$.on_error = JSON_BEHAVIOR_UNSPEC; }
| json_query_behavior ON EMPTY_P json_query_behavior ON ERROR_P
{ $$.on_empty = $1; $$.on_error = $4; }
| json_query_behavior ON ERROR_P
- { $$.on_empty = NULL; $$.on_error = $1; }
+ { $$.on_empty = JSON_BEHAVIOR_UNSPEC; $$.on_error = $1; }
| /* EMPTY */
- { $$.on_empty = NULL; $$.on_error = NULL; }
+ { $$.on_empty = JSON_BEHAVIOR_UNSPEC; $$.on_error = JSON_BEHAVIOR_UNSPEC; }
;
json_table:
@@ -16757,7 +16750,7 @@ json_table_exists_column_definition:
n->wrapper = JSW_NONE;
n->omit_quotes = false;
n->pathspec = $4;
- n->on_empty = NULL;
+ n->on_empty = JSON_BEHAVIOR_UNSPEC;
n->on_error = $5;
n->location = @1;
$$ = (Node *) n;
@@ -16771,7 +16764,7 @@ json_table_error_behavior:
json_table_error_clause_opt:
json_table_error_behavior ON ERROR_P { $$ = $1; }
- | /* EMPTY */ { $$ = NULL; }
+ | /* EMPTY */ { $$ = JSON_BEHAVIOR_UNSPEC; }
;
json_table_column_path_specification_clause_opt:
@@ -16977,7 +16970,7 @@ json_exists_predicate:
json_exists_error_clause_opt:
json_exists_error_behavior ON ERROR_P { $$ = $1; }
- | /* EMPTY */ { $$ = NULL; }
+ | /* EMPTY */ { $$ = JSON_BEHAVIOR_UNSPEC; }
;
json_exists_error_behavior:
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index f96334ce088..d1cedf222d7 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -4067,20 +4067,14 @@ transformJsonPassingArgs(ParseState *pstate, JsonFormatType format, List *args,
/*
* Transform a JSON BEHAVIOR clause.
*/
-static JsonBehavior *
-transformJsonBehavior(ParseState *pstate, JsonBehavior *behavior,
+static JsonBehaviorType
+transformJsonBehavior(JsonBehaviorType behavior,
JsonBehaviorType default_behavior)
{
- JsonBehaviorType behavior_type = default_behavior;
- Node *default_expr = NULL;
+ if (behavior == JSON_BEHAVIOR_UNSPEC)
+ return default_behavior;
- if (behavior)
- {
- behavior_type = behavior->btype;
- if (behavior_type == JSON_BEHAVIOR_DEFAULT)
- default_expr = transformExprRecurse(pstate, behavior->default_expr);
- }
- return makeJsonBehavior(behavior_type, default_expr);
+ return behavior;
}
/*
@@ -4132,17 +4126,17 @@ transformJsonExprCommon(ParseState *pstate, JsonFuncExpr *func)
&jsexpr->passing_values, &jsexpr->passing_names);
if (func->op != JSON_EXISTS_OP && func->op != JSON_TABLE_OP)
- jsexpr->on_empty = transformJsonBehavior(pstate, func->on_empty,
+ jsexpr->on_empty = transformJsonBehavior(func->on_empty,
JSON_BEHAVIOR_NULL);
if (func->op == JSON_EXISTS_OP)
- jsexpr->on_error = transformJsonBehavior(pstate, func->on_error,
+ jsexpr->on_error = transformJsonBehavior(func->on_error,
JSON_BEHAVIOR_FALSE);
else if (func->op == JSON_TABLE_OP)
- jsexpr->on_error = transformJsonBehavior(pstate, func->on_error,
+ jsexpr->on_error = transformJsonBehavior(func->on_error,
JSON_BEHAVIOR_EMPTY);
else
- jsexpr->on_error = transformJsonBehavior(pstate, func->on_error,
+ jsexpr->on_error = transformJsonBehavior(func->on_error,
JSON_BEHAVIOR_NULL);
return jsexpr;
@@ -4256,51 +4250,6 @@ transformJsonFuncExprOutput(ParseState *pstate, JsonFuncExpr *func,
jsexpr->returning);
}
-/*
- * Coerce an expression in JSON DEFAULT behavior to the target output type.
- */
-static Node *
-coerceDefaultJsonExpr(ParseState *pstate, JsonExpr *jsexpr,
- bool is_on_empty, Node *defexpr)
-{
- int location;
- Oid exprtype;
-
- if (!defexpr)
- return NULL;
-
- exprtype = exprType(defexpr);
- location = exprLocation(defexpr);
-
- if (location < 0)
- location = jsexpr->location;
-
- if (is_on_empty)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("DEFAULT ON EMPTY expressions are not supported"),
- parser_errposition(pstate, location)));
-
- defexpr = coerce_to_target_type(pstate,
- defexpr,
- exprtype,
- jsexpr->returning->typid,
- jsexpr->returning->typmod,
- COERCION_EXPLICIT,
- COERCE_IMPLICIT_CAST,
- location);
-
- if (!defexpr)
- ereport(ERROR,
- (errcode(ERRCODE_CANNOT_COERCE),
- errmsg("cannot cast DEFAULT expression type %s to %s",
- format_type_be(exprtype),
- format_type_be(jsexpr->returning->typid)),
- parser_errposition(pstate, location)));
-
- return defexpr;
-}
-
/*
* Transform JSON_VALUE, JSON_QUERY, JSON_EXISTS functions into a JsonExpr node.
*/
@@ -4321,14 +4270,6 @@ transformJsonFuncExpr(ParseState *pstate, JsonFuncExpr *func)
jsexpr->returning->format->format_type = JS_FORMAT_DEFAULT;
jsexpr->returning->format->encoding = JS_ENC_DEFAULT;
- jsexpr->on_empty->default_expr =
- coerceDefaultJsonExpr(pstate, jsexpr, true,
- jsexpr->on_empty->default_expr);
-
- jsexpr->on_error->default_expr =
- coerceDefaultJsonExpr(pstate, jsexpr, false,
- jsexpr->on_error->default_expr);
-
break;
case JSON_QUERY_OP:
@@ -4336,14 +4277,6 @@ transformJsonFuncExpr(ParseState *pstate, JsonFuncExpr *func)
transformJsonFuncExprOutput(pstate, func, jsexpr);
- jsexpr->on_empty->default_expr =
- coerceDefaultJsonExpr(pstate, jsexpr, true,
- jsexpr->on_empty->default_expr);
-
- jsexpr->on_error->default_expr =
- coerceDefaultJsonExpr(pstate, jsexpr, false,
- jsexpr->on_error->default_expr);
-
jsexpr->wrapper = func->wrapper;
jsexpr->omit_quotes = func->omit_quotes;
diff --git a/src/backend/parser/parse_jsontable.c b/src/backend/parser/parse_jsontable.c
index bc3272017ef..db1cb99ed40 100644
--- a/src/backend/parser/parse_jsontable.c
+++ b/src/backend/parser/parse_jsontable.c
@@ -83,8 +83,8 @@ transformJsonTableColumn(JsonTableColumn *jtc, Node *contextItemExpr,
jfexpr->output = output;
jfexpr->on_empty = jtc->on_empty;
jfexpr->on_error = jtc->on_error;
- if (!jfexpr->on_error && errorOnError)
- jfexpr->on_error = makeJsonBehavior(JSON_BEHAVIOR_ERROR, NULL);
+ if (jfexpr->on_error == JSON_BEHAVIOR_UNSPEC && errorOnError)
+ jfexpr->on_error = JSON_BEHAVIOR_ERROR;
jfexpr->omit_quotes = jtc->omit_quotes;
jfexpr->wrapper = jtc->wrapper;
jfexpr->location = jtc->location;
@@ -423,8 +423,7 @@ appendJsonTableColumns(JsonTableContext *cxt, List *columns)
ParseState *pstate = cxt->pstate;
JsonTable *jt = cxt->table;
TableFunc *tf = cxt->tablefunc;
- bool errorOnError = jt->on_error &&
- jt->on_error->btype == JSON_BEHAVIOR_ERROR;
+ bool errorOnError = jt->on_error == JSON_BEHAVIOR_ERROR;
foreach(col, columns)
{
@@ -542,9 +541,7 @@ makeParentJsonTableNode(JsonTableContext *cxt, char *pathSpec, List *columns)
/* save end of column range */
node->colMax = list_length(cxt->tablefunc->colvalexprs) - 1;
- node->errorOnError =
- cxt->table->on_error &&
- cxt->table->on_error->btype == JSON_BEHAVIOR_ERROR;
+ node->errorOnError = cxt->table->on_error == JSON_BEHAVIOR_ERROR;
return node;
}
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 8964f73b929..23bbb09c712 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -8544,7 +8544,7 @@ get_json_returning(JsonReturning *returning, StringInfo buf,
}
static void
-get_json_behavior(JsonBehavior *behavior, deparse_context *context,
+get_json_behavior(JsonBehaviorType behavior, deparse_context *context,
const char *on)
{
/*
@@ -8553,6 +8553,7 @@ get_json_behavior(JsonBehavior *behavior, deparse_context *context,
*/
const char *behavior_names[] =
{
+ " UNSPECIFIED",
" NULL",
" ERROR",
" EMPTY",
@@ -8560,17 +8561,13 @@ get_json_behavior(JsonBehavior *behavior, deparse_context *context,
" FALSE",
" UNKNOWN",
" EMPTY ARRAY",
- " EMPTY OBJECT",
- " DEFAULT "
+ " EMPTY OBJECT"
};
- if ((int) behavior->btype < 0 || behavior->btype >= lengthof(behavior_names))
- elog(ERROR, "invalid json behavior type: %d", behavior->btype);
+ if ((int) behavior < 1 || behavior >= lengthof(behavior_names))
+ elog(ERROR, "invalid json behavior type: %d", behavior);
- appendStringInfoString(context->buf, behavior_names[behavior->btype]);
-
- if (behavior->btype == JSON_BEHAVIOR_DEFAULT)
- get_rule_expr(behavior->default_expr, context, false);
+ appendStringInfoString(context->buf, behavior_names[behavior]);
appendStringInfo(context->buf, " ON %s", on);
}
@@ -8597,10 +8594,10 @@ get_json_expr_options(JsonExpr *jsexpr, deparse_context *context,
}
if (jsexpr->op != JSON_EXISTS_OP &&
- jsexpr->on_empty->btype != default_behavior)
+ jsexpr->on_empty != default_behavior)
get_json_behavior(jsexpr->on_empty, context, "EMPTY");
- if (jsexpr->on_error->btype != default_behavior)
+ if (jsexpr->on_error != default_behavior)
get_json_behavior(jsexpr->on_error, context, "ERROR");
}
@@ -11364,7 +11361,7 @@ get_json_table_columns(TableFunc *tf, JsonTableParent *node,
default_behavior = JSON_BEHAVIOR_NULL;
}
- if (jexpr->on_error->btype == JSON_BEHAVIOR_ERROR)
+ if (jexpr->on_error == JSON_BEHAVIOR_ERROR)
default_behavior = JSON_BEHAVIOR_ERROR;
appendStringInfoString(buf, " PATH ");
@@ -11447,7 +11444,7 @@ get_json_table(TableFunc *tf, deparse_context *context, bool showimplicit)
appendContextKeyword(context, "PLAN ", 0, 0, 0);
get_json_table_plan(tf, (Node *) root, context, true);
- if (jexpr->on_error->btype != JSON_BEHAVIOR_EMPTY)
+ if (jexpr->on_error != JSON_BEHAVIOR_EMPTY)
get_json_behavior(jexpr->on_error, context, "ERROR");
if (PRETTY_INDENT(context))
diff --git a/src/backend/utils/misc/queryjumble.c b/src/backend/utils/misc/queryjumble.c
index eeaa0b31fe2..aab74345a80 100644
--- a/src/backend/utils/misc/queryjumble.c
+++ b/src/backend/utils/misc/queryjumble.c
@@ -798,13 +798,8 @@ JumbleExpr(JumbleState *jstate, Node *node)
APP_JUMB_STRING(lfirst_node(String, temp)->sval);
}
JumbleExpr(jstate, (Node *) jexpr->passing_values);
- if (jexpr->on_empty)
- {
- APP_JUMB(jexpr->on_empty->btype);
- JumbleExpr(jstate, jexpr->on_empty->default_expr);
- }
- APP_JUMB(jexpr->on_error->btype);
- JumbleExpr(jstate, jexpr->on_error->default_expr);
+ APP_JUMB(jexpr->on_empty);
+ APP_JUMB(jexpr->on_error);
}
break;
case T_List:
diff --git a/src/include/executor/execExpr.h b/src/include/executor/execExpr.h
index d67419c66a9..4d2159ba7ef 100644
--- a/src/include/executor/execExpr.h
+++ b/src/include/executor/execExpr.h
@@ -697,8 +697,6 @@ typedef struct ExprEvalStep
struct
{
struct JsonExprState *jsestate;
- int jump_done;
- int jump_default_on_error; /* ON ERROR DEFAULT expression */
} jsonexpr;
/* for EEOP_JSONEXPR_SKIP */
@@ -838,7 +836,7 @@ extern void ExecEvalSysVar(ExprState *state, ExprEvalStep *op,
ExprContext *econtext, TupleTableSlot *slot);
extern void ExecEvalJsonConstructor(ExprState *state, ExprEvalStep *op,
ExprContext *econtext);
-extern bool ExecEvalJsonExpr(ExprState *state, ExprEvalStep *op);
+extern void ExecEvalJsonExpr(ExprState *state, ExprEvalStep *op);
extern bool ExecEvalJsonExprSkip(ExprState *state, ExprEvalStep *op);
extern void ExecAggInitGroup(AggState *aggstate, AggStatePerTrans pertrans, AggStatePerGroup pergroup,
diff --git a/src/include/nodes/makefuncs.h b/src/include/nodes/makefuncs.h
index 06e6369026a..303b66e0a27 100644
--- a/src/include/nodes/makefuncs.h
+++ b/src/include/nodes/makefuncs.h
@@ -109,7 +109,6 @@ extern VacuumRelation *makeVacuumRelation(RangeVar *relation, Oid oid, List *va_
extern JsonFormat *makeJsonFormat(JsonFormatType type, JsonEncoding encoding,
int location);
extern JsonValueExpr *makeJsonValueExpr(Expr *expr, JsonFormat *format);
-extern JsonBehavior *makeJsonBehavior(JsonBehaviorType type, Node *expr);
extern Node *makeJsonTableJoinedPlan(JsonTablePlanJoinType type,
Node *plan1, Node *plan2, int location);
extern Node *makeJsonKeyValue(Node *key, Node *value);
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index b3760318562..3a663cde89c 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1678,8 +1678,8 @@ typedef struct JsonFuncExpr
JsonExprOp op; /* expression type */
JsonCommon *common; /* common syntax */
JsonOutput *output; /* output clause, if specified */
- JsonBehavior *on_empty; /* ON EMPTY behavior, if specified */
- JsonBehavior *on_error; /* ON ERROR behavior, if specified */
+ JsonBehaviorType on_empty; /* ON EMPTY behavior, if specified */
+ JsonBehaviorType on_error; /* ON ERROR behavior, if specified */
JsonWrapper wrapper; /* array wrapper behavior (JSON_QUERY only) */
bool omit_quotes; /* omit or keep quotes? (JSON_QUERY only) */
int location; /* token location, or -1 if unknown */
@@ -1701,8 +1701,8 @@ typedef struct JsonTableColumn
JsonWrapper wrapper; /* WRAPPER behavior for formatted columns */
bool omit_quotes; /* omit or keep quotes on scalar strings? */
List *columns; /* nested columns */
- JsonBehavior *on_empty; /* ON EMPTY behavior */
- JsonBehavior *on_error; /* ON ERROR behavior */
+ JsonBehaviorType on_empty; /* ON EMPTY behavior */
+ JsonBehaviorType on_error; /* ON ERROR behavior */
int location; /* token location, or -1 if unknown */
} JsonTableColumn;
@@ -1756,7 +1756,7 @@ typedef struct JsonTable
JsonCommon *common; /* common JSON path syntax fields */
List *columns; /* list of JsonTableColumn */
JsonTablePlan *plan; /* join plan, if specified */
- JsonBehavior *on_error; /* ON ERROR behavior, if specified */
+ JsonBehaviorType on_error; /* ON ERROR behavior, if specified */
Alias *alias; /* table alias in FROM clause */
bool lateral; /* does it have LATERAL prefix? */
int location; /* token location, or -1 if unknown */
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 25a0fb8a445..e00c403fde7 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -1428,15 +1428,15 @@ typedef enum JsonFormatType
*/
typedef enum JsonBehaviorType
{
- JSON_BEHAVIOR_NULL = 0,
+ JSON_BEHAVIOR_UNSPEC = 0,
+ JSON_BEHAVIOR_NULL,
JSON_BEHAVIOR_ERROR,
JSON_BEHAVIOR_EMPTY,
JSON_BEHAVIOR_TRUE,
JSON_BEHAVIOR_FALSE,
JSON_BEHAVIOR_UNKNOWN,
JSON_BEHAVIOR_EMPTY_ARRAY,
- JSON_BEHAVIOR_EMPTY_OBJECT,
- JSON_BEHAVIOR_DEFAULT
+ JSON_BEHAVIOR_EMPTY_OBJECT
} JsonBehaviorType;
/*
@@ -1540,17 +1540,6 @@ typedef struct JsonIsPredicate
int location; /* token location, or -1 if unknown */
} JsonIsPredicate;
-/*
- * JsonBehavior -
- * representation of JSON ON ... BEHAVIOR clause
- */
-typedef struct JsonBehavior
-{
- NodeTag type;
- JsonBehaviorType btype; /* behavior type */
- Node *default_expr; /* default expression, if any */
-} JsonBehavior;
-
/*
* JsonExpr -
* transformed representation of JSON_VALUE(), JSON_QUERY(), JSON_EXISTS()
@@ -1565,8 +1554,8 @@ typedef struct JsonExpr
List *passing_names; /* PASSING argument names */
List *passing_values; /* PASSING argument values */
JsonReturning *returning; /* RETURNING clause type/format info */
- JsonBehavior *on_empty; /* ON EMPTY behavior */
- JsonBehavior *on_error; /* ON ERROR behavior */
+ JsonBehaviorType on_empty; /* ON EMPTY behavior */
+ JsonBehaviorType on_error; /* ON ERROR behavior */
JsonWrapper wrapper; /* WRAPPER for JSON_QUERY */
Oid collation; /* Collation of result, or InvalidOid if none */
bool omit_quotes; /* KEEP/OMIT QUOTES for JSON_QUERY */
diff --git a/src/test/regress/expected/jsonb_sqljson.out b/src/test/regress/expected/jsonb_sqljson.out
index fcd921130f5..5610a94f3dd 100644
--- a/src/test/regress/expected/jsonb_sqljson.out
+++ b/src/test/regress/expected/jsonb_sqljson.out
@@ -334,11 +334,9 @@ SELECT JSON_VALUE(jsonb '"aaa"', '$' RETURNING int);
SELECT JSON_VALUE(jsonb '"aaa"', '$' RETURNING int ERROR ON ERROR);
ERROR: invalid input syntax for type integer: "aaa"
SELECT JSON_VALUE(jsonb '"aaa"', '$' RETURNING int DEFAULT 111 ON ERROR);
- json_value
-------------
- 111
-(1 row)
-
+ERROR: syntax error at or near "DEFAULT"
+LINE 1: ...ELECT JSON_VALUE(jsonb '"aaa"', '$' RETURNING int DEFAULT 11...
+ ^
SELECT JSON_VALUE(jsonb '"123"', '$' RETURNING int) + 234;
?column?
----------
@@ -376,11 +374,9 @@ SELECT JSON_VALUE(jsonb '"-inf"', '$' RETURNING float4);
(1 row)
SELECT JSON_VALUE(jsonb '"err"', '$' RETURNING float4 DEFAULT -1 ON ERROR);
- json_value
-------------
- -1
-(1 row)
-
+ERROR: syntax error at or near "DEFAULT"
+LINE 1: ...CT JSON_VALUE(jsonb '"err"', '$' RETURNING float4 DEFAULT -1...
+ ^
SELECT JSON_VALUE(jsonb '"err"', '$' RETURNING float4 ERROR ON ERROR);
ERROR: invalid input syntax for type real: "err"
SELECT JSON_VALUE(jsonb '1.23', '$' RETURNING float8);
@@ -408,11 +404,9 @@ SELECT JSON_VALUE(jsonb '"-inf"', '$' RETURNING float8);
(1 row)
SELECT JSON_VALUE(jsonb '"err"', '$' RETURNING float8 DEFAULT -1 ON ERROR);
- json_value
-------------
- -1
-(1 row)
-
+ERROR: syntax error at or near "DEFAULT"
+LINE 1: ...CT JSON_VALUE(jsonb '"err"', '$' RETURNING float8 DEFAULT -1...
+ ^
SELECT JSON_VALUE(jsonb '"err"', '$' RETURNING float8 ERROR ON ERROR);
ERROR: invalid input syntax for type double precision: "err"
SELECT JSON_VALUE(jsonb '1.23', '$' RETURNING numeric);
@@ -440,11 +434,9 @@ SELECT JSON_VALUE(jsonb '"-inf"', '$' RETURNING numeric);
(1 row)
SELECT JSON_VALUE(jsonb '"err"', '$' RETURNING numeric DEFAULT -1 ON ERROR);
- json_value
-------------
- -1
-(1 row)
-
+ERROR: syntax error at or near "DEFAULT"
+LINE 1: ...T JSON_VALUE(jsonb '"err"', '$' RETURNING numeric DEFAULT -1...
+ ^
SELECT JSON_VALUE(jsonb '"err"', '$' RETURNING numeric ERROR ON ERROR);
ERROR: invalid input syntax for type numeric: "err"
-- Test for domain types
@@ -470,9 +462,9 @@ ERROR: returning type sqljsonb_int_not_null is not supported in JSON_VALUE()
LINE 1: SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_int_no...
^
SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_int_not_null DEFAULT NULL ON ERROR);
-ERROR: returning type sqljsonb_int_not_null is not supported in JSON_VALUE()
-LINE 1: SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_int_no...
- ^
+ERROR: syntax error at or near "DEFAULT"
+LINE 1: ...(jsonb '1', '$.a' RETURNING sqljsonb_int_not_null DEFAULT NU...
+ ^
-- Test returning of non-scalar items
SELECT JSON_VALUE(jsonb '[]', '$');
json_value
@@ -499,11 +491,9 @@ SELECT JSON_VALUE(jsonb '1', '$.a');
SELECT JSON_VALUE(jsonb '1', 'strict $.a' ERROR ON ERROR);
ERROR: jsonpath member accessor can only be applied to an object
SELECT JSON_VALUE(jsonb '1', 'strict $.a' DEFAULT 'error' ON ERROR);
- json_value
-------------
- error
-(1 row)
-
+ERROR: syntax error at or near "DEFAULT"
+LINE 1: SELECT JSON_VALUE(jsonb '1', 'strict $.a' DEFAULT 'error' ON...
+ ^
SELECT JSON_VALUE(jsonb '1', 'lax $.a' ERROR ON ERROR);
json_value
------------
@@ -513,61 +503,45 @@ SELECT JSON_VALUE(jsonb '1', 'lax $.a' ERROR ON ERROR);
SELECT JSON_VALUE(jsonb '1', 'lax $.a' ERROR ON EMPTY ERROR ON ERROR);
ERROR: no SQL/JSON item
SELECT JSON_VALUE(jsonb '1', 'strict $.a' DEFAULT 2 ON ERROR);
- json_value
-------------
- 2
-(1 row)
-
+ERROR: syntax error at or near "DEFAULT"
+LINE 1: SELECT JSON_VALUE(jsonb '1', 'strict $.a' DEFAULT 2 ON ERROR...
+ ^
SELECT JSON_VALUE(jsonb '1', 'lax $.a' DEFAULT 2 ON ERROR);
- json_value
-------------
-
-(1 row)
-
+ERROR: syntax error at or near "DEFAULT"
+LINE 1: SELECT JSON_VALUE(jsonb '1', 'lax $.a' DEFAULT 2 ON ERROR);
+ ^
SELECT JSON_VALUE(jsonb '1', 'lax $.a' DEFAULT '2' ON ERROR);
- json_value
-------------
-
-(1 row)
-
+ERROR: syntax error at or near "DEFAULT"
+LINE 1: SELECT JSON_VALUE(jsonb '1', 'lax $.a' DEFAULT '2' ON ERROR)...
+ ^
SELECT JSON_VALUE(jsonb '1', 'lax $.a' NULL ON EMPTY DEFAULT '2' ON ERROR);
- json_value
-------------
-
-(1 row)
-
+ERROR: syntax error at or near "DEFAULT"
+LINE 1: ...ECT JSON_VALUE(jsonb '1', 'lax $.a' NULL ON EMPTY DEFAULT '2...
+ ^
SELECT JSON_VALUE(jsonb '1', 'lax $.a' DEFAULT '2' ON EMPTY DEFAULT '3' ON ERROR);
-ERROR: DEFAULT ON EMPTY expressions are not supported
+ERROR: syntax error at or near "DEFAULT"
LINE 1: SELECT JSON_VALUE(jsonb '1', 'lax $.a' DEFAULT '2' ON EMPTY ...
- ^
+ ^
SELECT JSON_VALUE(jsonb '1', 'lax $.a' ERROR ON EMPTY DEFAULT '3' ON ERROR);
- json_value
-------------
- 3
-(1 row)
-
+ERROR: syntax error at or near "DEFAULT"
+LINE 1: ...CT JSON_VALUE(jsonb '1', 'lax $.a' ERROR ON EMPTY DEFAULT '3...
+ ^
SELECT JSON_VALUE(jsonb '[1,2]', '$[*]' ERROR ON ERROR);
ERROR: JSON path expression in JSON_VALUE should return singleton scalar item
SELECT JSON_VALUE(jsonb '[1,2]', '$[*]' DEFAULT '0' ON ERROR);
- json_value
-------------
- 0
-(1 row)
-
+ERROR: syntax error at or near "DEFAULT"
+LINE 1: SELECT JSON_VALUE(jsonb '[1,2]', '$[*]' DEFAULT '0' ON ERROR...
+ ^
SELECT JSON_VALUE(jsonb '[" "]', '$[*]' RETURNING int ERROR ON ERROR);
ERROR: invalid input syntax for type integer: " "
SELECT JSON_VALUE(jsonb '[" "]', '$[*]' RETURNING int DEFAULT 2 + 3 ON ERROR);
- json_value
-------------
- 5
-(1 row)
-
+ERROR: syntax error at or near "DEFAULT"
+LINE 1: ...CT JSON_VALUE(jsonb '[" "]', '$[*]' RETURNING int DEFAULT 2 ...
+ ^
SELECT JSON_VALUE(jsonb '["1"]', '$[*]' RETURNING int DEFAULT 2 + 3 ON ERROR);
- json_value
-------------
- 1
-(1 row)
-
+ERROR: syntax error at or near "DEFAULT"
+LINE 1: ...CT JSON_VALUE(jsonb '["1"]', '$[*]' RETURNING int DEFAULT 2 ...
+ ^
SELECT
x,
JSON_VALUE(
@@ -575,15 +549,15 @@ SELECT
'$.* ? (@ > $x)' PASSING x AS x
RETURNING int
NULL ON EMPTY
- DEFAULT -2 ON ERROR
+ NULL ON ERROR
) y
FROM
generate_series(0, 2) x;
- x | y
----+----
- 0 | -2
- 1 | 2
- 2 |
+ x | y
+---+---
+ 0 |
+ 1 | 2
+ 2 |
(3 rows)
SELECT JSON_VALUE(jsonb 'null', '$a' PASSING point ' (1, 2 )' AS a);
@@ -632,25 +606,25 @@ SELECT JSON_VALUE('1'::jsonb, '$.x' RETURNING int
DEFAULT 1 / x ON EMPTY
DEFAULT 2 ON ERROR)
FROM (VALUES (1::int), (0)) x(x);
-ERROR: DEFAULT ON EMPTY expressions are not supported
+ERROR: syntax error at or near "DEFAULT"
LINE 2: DEFAULT 1 / x ON EMPTY
- ^
+ ^
SELECT JSON_VALUE('1'::jsonb, '$.x' RETURNING int DEFAULT -1 ON EMPTY);
-ERROR: DEFAULT ON EMPTY expressions are not supported
-LINE 1: ...SON_VALUE('1'::jsonb, '$.x' RETURNING int DEFAULT -1 ON EMPT...
- ^
+ERROR: syntax error at or near "DEFAULT"
+LINE 1: SELECT JSON_VALUE('1'::jsonb, '$.x' RETURNING int DEFAULT -1...
+ ^
SELECT JSON_VALUE('1'::jsonb, '$.x' RETURNING int DEFAULT '-1' ON EMPTY);
-ERROR: DEFAULT ON EMPTY expressions are not supported
-LINE 1: ...SON_VALUE('1'::jsonb, '$.x' RETURNING int DEFAULT '-1' ON EM...
- ^
+ERROR: syntax error at or near "DEFAULT"
+LINE 1: SELECT JSON_VALUE('1'::jsonb, '$.x' RETURNING int DEFAULT '-...
+ ^
SELECT JSON_VALUE('1'::jsonb, '$.x' RETURNING int DEFAULT 'err' ON EMPTY);
-ERROR: DEFAULT ON EMPTY expressions are not supported
-LINE 1: ...SON_VALUE('1'::jsonb, '$.x' RETURNING int DEFAULT 'err' ON E...
- ^
+ERROR: syntax error at or near "DEFAULT"
+LINE 1: SELECT JSON_VALUE('1'::jsonb, '$.x' RETURNING int DEFAULT 'e...
+ ^
SELECT JSON_VALUE('1'::jsonb, '$.x' RETURNING int DEFAULT -1::float ON EMPTY);
-ERROR: DEFAULT ON EMPTY expressions are not supported
-LINE 1: ...SON_VALUE('1'::jsonb, '$.x' RETURNING int DEFAULT -1::float ...
- ^
+ERROR: syntax error at or near "DEFAULT"
+LINE 1: SELECT JSON_VALUE('1'::jsonb, '$.x' RETURNING int DEFAULT -1...
+ ^
-- JSON_QUERY
SELECT
JSON_QUERY(js, '$'),
@@ -820,11 +794,9 @@ SELECT JSON_QUERY(jsonb '[]', '$[*]' ERROR ON EMPTY);
(1 row)
SELECT JSON_QUERY(jsonb '[]', '$[*]' ERROR ON EMPTY DEFAULT '"empty"' ON ERROR);
- json_query
-------------
- "empty"
-(1 row)
-
+ERROR: syntax error at or near "DEFAULT"
+LINE 1: ...LECT JSON_QUERY(jsonb '[]', '$[*]' ERROR ON EMPTY DEFAULT '"...
+ ^
SELECT JSON_QUERY(jsonb '[]', '$[*]' ERROR ON EMPTY NULL ON ERROR);
json_query
------------
@@ -855,11 +827,9 @@ SELECT JSON_QUERY(jsonb '[1,2]', '$[*]' ERROR ON ERROR);
ERROR: JSON path expression in JSON_QUERY should return singleton item without wrapper
HINT: Use WITH WRAPPER clause to wrap SQL/JSON item sequence into array.
SELECT JSON_QUERY(jsonb '[1,2]', '$[*]' DEFAULT '"empty"' ON ERROR);
- json_query
-------------
- "empty"
-(1 row)
-
+ERROR: syntax error at or near "DEFAULT"
+LINE 1: SELECT JSON_QUERY(jsonb '[1,2]', '$[*]' DEFAULT '"empty"' ON...
+ ^
SELECT JSON_QUERY(jsonb '[1,2]', '$' RETURNING json);
json_query
------------
@@ -1417,23 +1387,17 @@ ERROR: jsonpath member accessor can only be applied to an object
SELECT * FROM JSON_TABLE(jsonb '1', '$' COLUMNS (a int PATH 'lax $.a' ERROR ON EMPTY) ERROR ON ERROR) jt;
ERROR: no SQL/JSON item
SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a int PATH '$' NULL ON EMPTY DEFAULT 2 ON ERROR)) jt;
- a
----
- 2
-(1 row)
-
+ERROR: syntax error at or near "DEFAULT"
+LINE 1: ..."a"', '$' COLUMNS (a int PATH '$' NULL ON EMPTY DEFAULT 2 ...
+ ^
SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a int PATH 'strict $.a' NULL ON EMPTY DEFAULT 2 ON ERROR)) jt;
- a
----
- 2
-(1 row)
-
+ERROR: syntax error at or near "DEFAULT"
+LINE 1: ...$' COLUMNS (a int PATH 'strict $.a' NULL ON EMPTY DEFAULT 2 ...
+ ^
SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a int PATH 'lax $.a' NULL ON EMPTY DEFAULT 2 ON ERROR)) jt;
- a
----
-
-(1 row)
-
+ERROR: syntax error at or near "DEFAULT"
+LINE 1: ..., '$' COLUMNS (a int PATH 'lax $.a' NULL ON EMPTY DEFAULT 2 ...
+ ^
-- JSON_TABLE: EXISTS PATH types
SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a int4 EXISTS PATH '$.a'));
a
@@ -1753,24 +1717,24 @@ from
jtt.js,'strict $[*]' as p
columns (
n for ordinality,
- a int path 'lax $.a' error on empty default -1 on error,
+ a int path 'lax $.a' error on empty null on error,
nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
nested path 'strict $.c[*]' as pc columns ( c int path '$' )
)
) jt;
- n | a | b | c
----+----+---+----
- 1 | 1 | |
- 2 | 2 | 1 |
- 2 | 2 | 2 |
- 2 | 2 | 3 |
- 2 | 2 | | 10
- 2 | 2 | |
- 2 | 2 | | 20
- 3 | 3 | 1 |
- 3 | 3 | 2 |
- 4 | -1 | 1 |
- 4 | -1 | 2 |
+ n | a | b | c
+---+---+---+----
+ 1 | 1 | |
+ 2 | 2 | 1 |
+ 2 | 2 | 2 |
+ 2 | 2 | 3 |
+ 2 | 2 | | 10
+ 2 | 2 | |
+ 2 | 2 | | 20
+ 3 | 3 | 1 |
+ 3 | 3 | 2 |
+ 4 | | 1 |
+ 4 | | 2 |
(11 rows)
-- default plan (outer, union)
@@ -1782,25 +1746,25 @@ from
jtt.js,'strict $[*]' as p
columns (
n for ordinality,
- a int path 'lax $.a' error on empty default -1 on error,
+ a int path 'lax $.a' error on empty null on error,
nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
nested path 'strict $.c[*]' as pc columns ( c int path '$' )
)
plan default (outer, union)
) jt;
- n | a | b | c
----+----+---+----
- 1 | 1 | |
- 2 | 2 | 1 |
- 2 | 2 | 2 |
- 2 | 2 | 3 |
- 2 | 2 | | 10
- 2 | 2 | |
- 2 | 2 | | 20
- 3 | 3 | 1 |
- 3 | 3 | 2 |
- 4 | -1 | 1 |
- 4 | -1 | 2 |
+ n | a | b | c
+---+---+---+----
+ 1 | 1 | |
+ 2 | 2 | 1 |
+ 2 | 2 | 2 |
+ 2 | 2 | 3 |
+ 2 | 2 | | 10
+ 2 | 2 | |
+ 2 | 2 | | 20
+ 3 | 3 | 1 |
+ 3 | 3 | 2 |
+ 4 | | 1 |
+ 4 | | 2 |
(11 rows)
-- specific plan (p outer (pb union pc))
@@ -1812,25 +1776,25 @@ from
jtt.js,'strict $[*]' as p
columns (
n for ordinality,
- a int path 'lax $.a' error on empty default -1 on error,
+ a int path 'lax $.a' error on empty null on error,
nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
nested path 'strict $.c[*]' as pc columns ( c int path '$' )
)
plan (p outer (pb union pc))
) jt;
- n | a | b | c
----+----+---+----
- 1 | 1 | |
- 2 | 2 | 1 |
- 2 | 2 | 2 |
- 2 | 2 | 3 |
- 2 | 2 | | 10
- 2 | 2 | |
- 2 | 2 | | 20
- 3 | 3 | 1 |
- 3 | 3 | 2 |
- 4 | -1 | 1 |
- 4 | -1 | 2 |
+ n | a | b | c
+---+---+---+----
+ 1 | 1 | |
+ 2 | 2 | 1 |
+ 2 | 2 | 2 |
+ 2 | 2 | 3 |
+ 2 | 2 | | 10
+ 2 | 2 | |
+ 2 | 2 | | 20
+ 3 | 3 | 1 |
+ 3 | 3 | 2 |
+ 4 | | 1 |
+ 4 | | 2 |
(11 rows)
-- specific plan (p outer (pc union pb))
@@ -1842,25 +1806,25 @@ from
jtt.js,'strict $[*]' as p
columns (
n for ordinality,
- a int path 'lax $.a' error on empty default -1 on error,
+ a int path 'lax $.a' error on empty null on error,
nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
nested path 'strict $.c[*]' as pc columns ( c int path '$' )
)
plan (p outer (pc union pb))
) jt;
- n | a | c | b
----+----+----+---
- 1 | 1 | |
- 2 | 2 | 10 |
- 2 | 2 | |
- 2 | 2 | 20 |
- 2 | 2 | | 1
- 2 | 2 | | 2
- 2 | 2 | | 3
- 3 | 3 | | 1
- 3 | 3 | | 2
- 4 | -1 | | 1
- 4 | -1 | | 2
+ n | a | c | b
+---+---+----+---
+ 1 | 1 | |
+ 2 | 2 | 10 |
+ 2 | 2 | |
+ 2 | 2 | 20 |
+ 2 | 2 | | 1
+ 2 | 2 | | 2
+ 2 | 2 | | 3
+ 3 | 3 | | 1
+ 3 | 3 | | 2
+ 4 | | | 1
+ 4 | | | 2
(11 rows)
-- default plan (inner, union)
@@ -1872,24 +1836,24 @@ from
jtt.js,'strict $[*]' as p
columns (
n for ordinality,
- a int path 'lax $.a' error on empty default -1 on error,
+ a int path 'lax $.a' error on empty null on error,
nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
nested path 'strict $.c[*]' as pc columns ( c int path '$' )
)
plan default (inner)
) jt;
- n | a | b | c
----+----+---+----
- 2 | 2 | 1 |
- 2 | 2 | 2 |
- 2 | 2 | 3 |
- 2 | 2 | | 10
- 2 | 2 | |
- 2 | 2 | | 20
- 3 | 3 | 1 |
- 3 | 3 | 2 |
- 4 | -1 | 1 |
- 4 | -1 | 2 |
+ n | a | b | c
+---+---+---+----
+ 2 | 2 | 1 |
+ 2 | 2 | 2 |
+ 2 | 2 | 3 |
+ 2 | 2 | | 10
+ 2 | 2 | |
+ 2 | 2 | | 20
+ 3 | 3 | 1 |
+ 3 | 3 | 2 |
+ 4 | | 1 |
+ 4 | | 2 |
(10 rows)
-- specific plan (p inner (pb union pc))
@@ -1901,24 +1865,24 @@ from
jtt.js,'strict $[*]' as p
columns (
n for ordinality,
- a int path 'lax $.a' error on empty default -1 on error,
+ a int path 'lax $.a' error on empty null on error,
nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
nested path 'strict $.c[*]' as pc columns ( c int path '$' )
)
plan (p inner (pb union pc))
) jt;
- n | a | b | c
----+----+---+----
- 2 | 2 | 1 |
- 2 | 2 | 2 |
- 2 | 2 | 3 |
- 2 | 2 | | 10
- 2 | 2 | |
- 2 | 2 | | 20
- 3 | 3 | 1 |
- 3 | 3 | 2 |
- 4 | -1 | 1 |
- 4 | -1 | 2 |
+ n | a | b | c
+---+---+---+----
+ 2 | 2 | 1 |
+ 2 | 2 | 2 |
+ 2 | 2 | 3 |
+ 2 | 2 | | 10
+ 2 | 2 | |
+ 2 | 2 | | 20
+ 3 | 3 | 1 |
+ 3 | 3 | 2 |
+ 4 | | 1 |
+ 4 | | 2 |
(10 rows)
-- default plan (inner, cross)
@@ -1930,7 +1894,7 @@ from
jtt.js,'strict $[*]' as p
columns (
n for ordinality,
- a int path 'lax $.a' error on empty default -1 on error,
+ a int path 'lax $.a' error on empty null on error,
nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
nested path 'strict $.c[*]' as pc columns ( c int path '$' )
)
@@ -1958,7 +1922,7 @@ from
jtt.js,'strict $[*]' as p
columns (
n for ordinality,
- a int path 'lax $.a' error on empty default -1 on error,
+ a int path 'lax $.a' error on empty null on error,
nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
nested path 'strict $.c[*]' as pc columns ( c int path '$' )
)
@@ -1986,26 +1950,26 @@ from
jtt.js,'strict $[*]' as p
columns (
n for ordinality,
- a int path 'lax $.a' error on empty default -1 on error,
+ a int path 'lax $.a' error on empty null on error,
nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
nested path 'strict $.c[*]' as pc columns ( c int path '$' )
)
plan default (outer, cross)
) jt;
- n | a | b | c
----+----+---+----
- 1 | 1 | |
- 2 | 2 | 1 | 10
- 2 | 2 | 1 |
- 2 | 2 | 1 | 20
- 2 | 2 | 2 | 10
- 2 | 2 | 2 |
- 2 | 2 | 2 | 20
- 2 | 2 | 3 | 10
- 2 | 2 | 3 |
- 2 | 2 | 3 | 20
- 3 | 3 | |
- 4 | -1 | |
+ n | a | b | c
+---+---+---+----
+ 1 | 1 | |
+ 2 | 2 | 1 | 10
+ 2 | 2 | 1 |
+ 2 | 2 | 1 | 20
+ 2 | 2 | 2 | 10
+ 2 | 2 | 2 |
+ 2 | 2 | 2 | 20
+ 2 | 2 | 3 | 10
+ 2 | 2 | 3 |
+ 2 | 2 | 3 | 20
+ 3 | 3 | |
+ 4 | | |
(12 rows)
-- specific plan (p outer (pb cross pc))
@@ -2017,26 +1981,26 @@ from
jtt.js,'strict $[*]' as p
columns (
n for ordinality,
- a int path 'lax $.a' error on empty default -1 on error,
+ a int path 'lax $.a' error on empty null on error,
nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
nested path 'strict $.c[*]' as pc columns ( c int path '$' )
)
plan (p outer (pb cross pc))
) jt;
- n | a | b | c
----+----+---+----
- 1 | 1 | |
- 2 | 2 | 1 | 10
- 2 | 2 | 1 |
- 2 | 2 | 1 | 20
- 2 | 2 | 2 | 10
- 2 | 2 | 2 |
- 2 | 2 | 2 | 20
- 2 | 2 | 3 | 10
- 2 | 2 | 3 |
- 2 | 2 | 3 | 20
- 3 | 3 | |
- 4 | -1 | |
+ n | a | b | c
+---+---+---+----
+ 1 | 1 | |
+ 2 | 2 | 1 | 10
+ 2 | 2 | 1 |
+ 2 | 2 | 1 | 20
+ 2 | 2 | 2 | 10
+ 2 | 2 | 2 |
+ 2 | 2 | 2 | 20
+ 2 | 2 | 3 | 10
+ 2 | 2 | 3 |
+ 2 | 2 | 3 | 20
+ 3 | 3 | |
+ 4 | | |
(12 rows)
select
@@ -2051,7 +2015,7 @@ from
'$[*]' as p
columns (
n for ordinality,
- a int path 'lax $.a' default -1 on error,
+ a int path 'lax $.a' null on error,
nested path 'strict $.b[*]' as pb columns (
b text format json path '$',
nested path 'strict $[*]' as pb1 columns (
@@ -2188,11 +2152,9 @@ SELECT JSON_VALUE(jsonb '{"a": 123}', '$' || '.' || 'a');
(1 row)
SELECT JSON_VALUE(jsonb '{"a": 123}', '$' || '.' || 'b' ERROR ON EMPTY DEFAULT 'foo' ON ERROR);
- json_value
-------------
- foo
-(1 row)
-
+ERROR: syntax error at or near "DEFAULT"
+LINE 1: ...nb '{"a": 123}', '$' || '.' || 'b' ERROR ON EMPTY DEFAULT 'f...
+ ^
SELECT JSON_QUERY(jsonb '{"a": 123}', '$' || '.' || 'a');
json_query
------------
diff --git a/src/test/regress/sql/jsonb_sqljson.sql b/src/test/regress/sql/jsonb_sqljson.sql
index 76d4c9cc14e..6442fa0bc64 100644
--- a/src/test/regress/sql/jsonb_sqljson.sql
+++ b/src/test/regress/sql/jsonb_sqljson.sql
@@ -149,7 +149,7 @@ SELECT
'$.* ? (@ > $x)' PASSING x AS x
RETURNING int
NULL ON EMPTY
- DEFAULT -2 ON ERROR
+ NULL ON ERROR
) y
FROM
generate_series(0, 2) x;
@@ -776,7 +776,7 @@ from
jtt.js,'strict $[*]' as p
columns (
n for ordinality,
- a int path 'lax $.a' error on empty default -1 on error,
+ a int path 'lax $.a' error on empty null on error,
nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
nested path 'strict $.c[*]' as pc columns ( c int path '$' )
)
@@ -791,7 +791,7 @@ from
jtt.js,'strict $[*]' as p
columns (
n for ordinality,
- a int path 'lax $.a' error on empty default -1 on error,
+ a int path 'lax $.a' error on empty null on error,
nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
nested path 'strict $.c[*]' as pc columns ( c int path '$' )
)
@@ -807,7 +807,7 @@ from
jtt.js,'strict $[*]' as p
columns (
n for ordinality,
- a int path 'lax $.a' error on empty default -1 on error,
+ a int path 'lax $.a' error on empty null on error,
nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
nested path 'strict $.c[*]' as pc columns ( c int path '$' )
)
@@ -823,7 +823,7 @@ from
jtt.js,'strict $[*]' as p
columns (
n for ordinality,
- a int path 'lax $.a' error on empty default -1 on error,
+ a int path 'lax $.a' error on empty null on error,
nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
nested path 'strict $.c[*]' as pc columns ( c int path '$' )
)
@@ -839,7 +839,7 @@ from
jtt.js,'strict $[*]' as p
columns (
n for ordinality,
- a int path 'lax $.a' error on empty default -1 on error,
+ a int path 'lax $.a' error on empty null on error,
nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
nested path 'strict $.c[*]' as pc columns ( c int path '$' )
)
@@ -855,7 +855,7 @@ from
jtt.js,'strict $[*]' as p
columns (
n for ordinality,
- a int path 'lax $.a' error on empty default -1 on error,
+ a int path 'lax $.a' error on empty null on error,
nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
nested path 'strict $.c[*]' as pc columns ( c int path '$' )
)
@@ -871,7 +871,7 @@ from
jtt.js,'strict $[*]' as p
columns (
n for ordinality,
- a int path 'lax $.a' error on empty default -1 on error,
+ a int path 'lax $.a' error on empty null on error,
nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
nested path 'strict $.c[*]' as pc columns ( c int path '$' )
)
@@ -887,7 +887,7 @@ from
jtt.js,'strict $[*]' as p
columns (
n for ordinality,
- a int path 'lax $.a' error on empty default -1 on error,
+ a int path 'lax $.a' error on empty null on error,
nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
nested path 'strict $.c[*]' as pc columns ( c int path '$' )
)
@@ -903,7 +903,7 @@ from
jtt.js,'strict $[*]' as p
columns (
n for ordinality,
- a int path 'lax $.a' error on empty default -1 on error,
+ a int path 'lax $.a' error on empty null on error,
nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
nested path 'strict $.c[*]' as pc columns ( c int path '$' )
)
@@ -919,7 +919,7 @@ from
jtt.js,'strict $[*]' as p
columns (
n for ordinality,
- a int path 'lax $.a' error on empty default -1 on error,
+ a int path 'lax $.a' error on empty null on error,
nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
nested path 'strict $.c[*]' as pc columns ( c int path '$' )
)
@@ -939,7 +939,7 @@ from
'$[*]' as p
columns (
n for ordinality,
- a int path 'lax $.a' default -1 on error,
+ a int path 'lax $.a' null on error,
nested path 'strict $.b[*]' as pb columns (
b text format json path '$',
nested path 'strict $[*]' as pb1 columns (
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 8a0585994ee..21e7fd48131 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1232,7 +1232,6 @@ JsonArrayAgg
JsonArrayConstructor
JsonArrayQueryConstructor
JsonBaseObjectInfo
-JsonBehavior
JsonBehaviorType
JsonCommon
JsonConstructorExpr
--
2.17.1
v10-0001-Add-safe-input-function-for-bool.patchtext/x-patch; charset=UTF-8; name=v10-0001-Add-safe-input-function-for-bool.patchDownload
From 36b4160825f5686829f7c6a642b47b449ee5a17d Mon Sep 17 00:00:00 2001
From: Nikita Glukhov <n.gluhov@postgrespro.ru>
Date: Mon, 29 Aug 2022 23:53:06 +0300
Subject: [PATCH v10 01/10] Add safe input function for bool
---
src/backend/utils/adt/bool.c | 39 +++++++++++++++++++++++++-----------
src/include/utils/builtins.h | 1 +
2 files changed, 28 insertions(+), 12 deletions(-)
diff --git a/src/backend/utils/adt/bool.c b/src/backend/utils/adt/bool.c
index cd7335287f9..a60044066c3 100644
--- a/src/backend/utils/adt/bool.c
+++ b/src/backend/utils/adt/bool.c
@@ -126,12 +126,10 @@ parse_bool_with_len(const char *value, size_t len, bool *result)
*
* In the switch statement, check the most-used possibilities first.
*/
-Datum
-boolin(PG_FUNCTION_ARGS)
+static inline bool
+boolin_internal(const char *in_str, size_t len, bool *error)
{
- const char *in_str = PG_GETARG_CSTRING(0);
const char *str;
- size_t len;
bool result;
/*
@@ -139,22 +137,39 @@ boolin(PG_FUNCTION_ARGS)
*/
str = in_str;
while (isspace((unsigned char) *str))
+ {
str++;
+ len--;
+ }
- len = strlen(str);
while (len > 0 && isspace((unsigned char) str[len - 1]))
len--;
if (parse_bool_with_len(str, len, &result))
- PG_RETURN_BOOL(result);
+ return result;
+
+ if (!error)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+ errmsg("invalid input syntax for type %s: \"%s\"",
+ "boolean", in_str)));
+
+ *error = true;
+ return false;
+}
+
+bool
+boolin_opt_error(const char *in_str, size_t len, bool *error)
+{
+ return boolin_internal(in_str, len, error);
+}
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
- errmsg("invalid input syntax for type %s: \"%s\"",
- "boolean", in_str)));
+Datum
+boolin(PG_FUNCTION_ARGS)
+{
+ char *str = PG_GETARG_CSTRING(0);
- /* not reached */
- PG_RETURN_BOOL(false);
+ PG_RETURN_BOOL(boolin_internal(str, strlen(str), NULL));
}
/*
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index 221c3e6c3de..2c03ce4fe12 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -24,6 +24,7 @@
/* bool.c */
extern bool parse_bool(const char *value, bool *result);
extern bool parse_bool_with_len(const char *value, size_t len, bool *result);
+extern bool boolin_opt_error(const char *value, size_t len, bool *error);
/* domains.c */
extern void domain_check(Datum value, bool isnull, Oid domainType,
--
2.17.1
v10-0002-Add-safe-input-function-for-int8.patchtext/x-patch; charset=UTF-8; name=v10-0002-Add-safe-input-function-for-int8.patchDownload
From 2bfad0c91cf0f30905867861ee7238f2d05361b1 Mon Sep 17 00:00:00 2001
From: Nikita Glukhov <n.gluhov@postgrespro.ru>
Date: Tue, 30 Aug 2022 00:13:54 +0300
Subject: [PATCH v10 02/10] Add safe input function for int8
---
src/backend/utils/adt/numutils.c | 28 ++++++++++++++++++++++++++--
src/include/utils/builtins.h | 1 +
2 files changed, 27 insertions(+), 2 deletions(-)
diff --git a/src/backend/utils/adt/numutils.c b/src/backend/utils/adt/numutils.c
index cc3f95d3990..1ad44e9bf31 100644
--- a/src/backend/utils/adt/numutils.c
+++ b/src/backend/utils/adt/numutils.c
@@ -247,8 +247,8 @@ invalid_syntax:
* representation of the most negative number, which can't be represented as a
* positive number.
*/
-int64
-pg_strtoint64(const char *s)
+static inline int64
+pg_strtoint64_internal(const char *s, bool *error)
{
const char *ptr = s;
int64 tmp = 0;
@@ -307,12 +307,24 @@ pg_strtoint64(const char *s)
return tmp;
out_of_range:
+ if (error)
+ {
+ *error = true;
+ return 0;
+ }
+
ereport(ERROR,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("value \"%s\" is out of range for type %s",
s, "bigint")));
invalid_syntax:
+ if (error)
+ {
+ *error = true;
+ return 0;
+ }
+
ereport(ERROR,
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
errmsg("invalid input syntax for type %s: \"%s\"",
@@ -321,6 +333,18 @@ invalid_syntax:
return 0; /* keep compiler quiet */
}
+int64
+pg_strtoint64_opt_error(const char *s, bool *error)
+{
+ return pg_strtoint64_internal(s, error);
+}
+
+int64
+pg_strtoint64(const char *s)
+{
+ return pg_strtoint64_internal(s, NULL);
+}
+
/*
* pg_itoa: converts a signed 16-bit integer to its string representation
* and returns strlen(a).
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index 2c03ce4fe12..5e1aa37c1f5 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -47,6 +47,7 @@ extern int namestrcmp(Name name, const char *str);
extern int16 pg_strtoint16(const char *s);
extern int32 pg_strtoint32(const char *s);
extern int64 pg_strtoint64(const char *s);
+extern int64 pg_strtoint64_opt_error(const char *s, bool *error);
extern int pg_itoa(int16 i, char *a);
extern int pg_ultoa_n(uint32 l, char *a);
extern int pg_ulltoa_n(uint64 l, char *a);
--
2.17.1
v10-0003-Add-safe-input-function-for-float4.patchtext/x-patch; charset=UTF-8; name=v10-0003-Add-safe-input-function-for-float4.patchDownload
From 500a8818282eff79054c90282e6ee3a41c00506d Mon Sep 17 00:00:00 2001
From: Nikita Glukhov <n.gluhov@postgrespro.ru>
Date: Tue, 30 Aug 2022 00:13:36 +0300
Subject: [PATCH v10 03/10] Add safe input function for float4
---
src/backend/utils/adt/float.c | 76 +++++++++++++++++++++--------------
src/include/utils/float.h | 1 +
2 files changed, 46 insertions(+), 31 deletions(-)
diff --git a/src/backend/utils/adt/float.c b/src/backend/utils/adt/float.c
index fc8f39a7a98..ba220152ec4 100644
--- a/src/backend/utils/adt/float.c
+++ b/src/backend/utils/adt/float.c
@@ -73,6 +73,16 @@ static double sind_q1(double x);
static double cosd_q1(double x);
static void init_degree_constants(void);
+/* Convenience macro: set *have_error flag (if provided) or throw error */
+#define RETURN_ERROR(throw_error, have_error) \
+do { \
+ if (have_error) { \
+ *have_error = true; \
+ return 0.0; \
+ } else { \
+ throw_error; \
+ } \
+} while (0)
/*
* We use these out-of-line ereport() calls to report float overflow,
@@ -159,10 +169,9 @@ is_infinite(double val)
* result of 0xAE43FEp-107.
*
*/
-Datum
-float4in(PG_FUNCTION_ARGS)
+static float
+float4in_internal(char *num, bool *have_error)
{
- char *num = PG_GETARG_CSTRING(0);
char *orig_num;
float val;
char *endptr;
@@ -183,10 +192,11 @@ float4in(PG_FUNCTION_ARGS)
* strtod() on different platforms.
*/
if (*num == '\0')
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
- errmsg("invalid input syntax for type %s: \"%s\"",
- "real", orig_num)));
+ RETURN_ERROR(ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+ errmsg("invalid input syntax for type %s: \"%s\"",
+ "real", orig_num))),
+ have_error);
errno = 0;
val = strtof(num, &endptr);
@@ -257,16 +267,18 @@ float4in(PG_FUNCTION_ARGS)
(val >= HUGE_VALF || val <= -HUGE_VALF)
#endif
)
- ereport(ERROR,
- (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
- errmsg("\"%s\" is out of range for type real",
- orig_num)));
+ RETURN_ERROR(ereport(ERROR,
+ (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+ errmsg("\"%s\" is out of range for type real",
+ orig_num))),
+ have_error);
}
else
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
- errmsg("invalid input syntax for type %s: \"%s\"",
- "real", orig_num)));
+ RETURN_ERROR(ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+ errmsg("invalid input syntax for type %s: \"%s\"",
+ "real", orig_num))),
+ have_error);
}
/* skip trailing whitespace */
@@ -275,12 +287,25 @@ float4in(PG_FUNCTION_ARGS)
/* if there is any junk left at the end of the string, bail out */
if (*endptr != '\0')
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
- errmsg("invalid input syntax for type %s: \"%s\"",
- "real", orig_num)));
+ RETURN_ERROR(ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+ errmsg("invalid input syntax for type %s: \"%s\"",
+ "real", orig_num))),
+ have_error);
- PG_RETURN_FLOAT4(val);
+ return val;
+}
+
+Datum
+float4in(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_FLOAT4(float4in_internal(PG_GETARG_CSTRING(0), NULL));
+}
+
+float
+float4in_opt_error(char *num, bool *have_error)
+{
+ return float4in_internal(num, have_error);
}
/*
@@ -340,17 +365,6 @@ float8in(PG_FUNCTION_ARGS)
PG_RETURN_FLOAT8(float8in_internal(num, NULL, "double precision", num));
}
-/* Convenience macro: set *have_error flag (if provided) or throw error */
-#define RETURN_ERROR(throw_error, have_error) \
-do { \
- if (have_error) { \
- *have_error = true; \
- return 0.0; \
- } else { \
- throw_error; \
- } \
-} while (0)
-
/*
* float8in_internal_opt_error - guts of float8in()
*
diff --git a/src/include/utils/float.h b/src/include/utils/float.h
index 4bf0e3ac07a..0ad4d9f3973 100644
--- a/src/include/utils/float.h
+++ b/src/include/utils/float.h
@@ -46,6 +46,7 @@ extern float8 float8in_internal(char *num, char **endptr_p,
extern float8 float8in_internal_opt_error(char *num, char **endptr_p,
const char *type_name, const char *orig_string,
bool *have_error);
+extern float float4in_opt_error(char *num, bool *have_error);
extern char *float8out_internal(float8 num);
extern int float4_cmp_internal(float4 a, float4 b);
extern int float8_cmp_internal(float8 a, float8 b);
--
2.17.1
v10-0004-Add-safe-input-and-type-conversion-functions-for.patchtext/x-patch; charset=UTF-8; name=v10-0004-Add-safe-input-and-type-conversion-functions-for.patchDownload
From 672b51742e2fab41ed7ccbd58e1d0276f2b6ed42 Mon Sep 17 00:00:00 2001
From: Nikita Glukhov <n.gluhov@postgrespro.ru>
Date: Mon, 29 Aug 2022 23:55:52 +0300
Subject: [PATCH v10 04/10] Add safe input and type conversion functions for
numeric
---
src/backend/utils/adt/numeric.c | 258 ++++++++++++++++++++++----------
src/include/utils/numeric.h | 6 +-
2 files changed, 187 insertions(+), 77 deletions(-)
diff --git a/src/backend/utils/adt/numeric.c b/src/backend/utils/adt/numeric.c
index 920a63b0081..f580036c773 100644
--- a/src/backend/utils/adt/numeric.c
+++ b/src/backend/utils/adt/numeric.c
@@ -498,7 +498,7 @@ static void free_var(NumericVar *var);
static void zero_var(NumericVar *var);
static const char *set_var_from_str(const char *str, const char *cp,
- NumericVar *dest);
+ NumericVar *dest, bool *have_error);
static void set_var_from_num(Numeric value, NumericVar *dest);
static void init_var_from_num(Numeric num, NumericVar *dest);
static void set_var_from_var(const NumericVar *value, NumericVar *dest);
@@ -512,8 +512,8 @@ static Numeric duplicate_numeric(Numeric num);
static Numeric make_result(const NumericVar *var);
static Numeric make_result_opt_error(const NumericVar *var, bool *error);
-static void apply_typmod(NumericVar *var, int32 typmod);
-static void apply_typmod_special(Numeric num, int32 typmod);
+static bool apply_typmod(NumericVar *var, int32 typmod, bool *error);
+static void apply_typmod_special(Numeric num, int32 typmod, bool *error);
static bool numericvar_to_int32(const NumericVar *var, int32 *result);
static bool numericvar_to_int64(const NumericVar *var, int64 *result);
@@ -607,21 +607,25 @@ static void accum_sum_combine(NumericSumAccum *accum, NumericSumAccum *accum2);
* ----------------------------------------------------------------------
*/
+/* Convenience macro: set *have_error flag (if provided) or throw error */
+#define RETURN_ERROR(throw_error, have_error) \
+do { \
+ if (have_error) { \
+ *(have_error) = true; \
+ return NULL; \
+ } else { \
+ throw_error; \
+ } \
+} while (0)
/*
* numeric_in() -
*
* Input function for numeric data type
*/
-Datum
-numeric_in(PG_FUNCTION_ARGS)
+static inline Numeric
+numeric_in_internal(char *str, int32 typmod, bool *have_error)
{
- char *str = PG_GETARG_CSTRING(0);
-
-#ifdef NOT_USED
- Oid typelem = PG_GETARG_OID(1);
-#endif
- int32 typmod = PG_GETARG_INT32(2);
Numeric res;
const char *cp;
@@ -682,7 +686,10 @@ numeric_in(PG_FUNCTION_ARGS)
init_var(&value);
- cp = set_var_from_str(str, cp, &value);
+ cp = set_var_from_str(str, cp, &value, have_error);
+
+ if (!cp)
+ return NULL; /* error */
/*
* We duplicate a few lines of code here because we would like to
@@ -693,38 +700,65 @@ numeric_in(PG_FUNCTION_ARGS)
while (*cp)
{
if (!isspace((unsigned char) *cp))
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
- errmsg("invalid input syntax for type %s: \"%s\"",
- "numeric", str)));
+ RETURN_ERROR(ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+ errmsg("invalid input syntax for type %s: \"%s\"",
+ "numeric", str))),
+ have_error);
cp++;
}
- apply_typmod(&value, typmod);
+ if (!apply_typmod(&value, typmod, have_error))
+ return NULL; /* error */
- res = make_result(&value);
+ res = make_result_opt_error(&value, have_error);
free_var(&value);
- PG_RETURN_NUMERIC(res);
+ return res;
}
/* Should be nothing left but spaces */
while (*cp)
{
if (!isspace((unsigned char) *cp))
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
- errmsg("invalid input syntax for type %s: \"%s\"",
- "numeric", str)));
+ RETURN_ERROR(ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+ errmsg("invalid input syntax for type %s: \"%s\"",
+ "numeric", str))),
+ have_error);
+
cp++;
}
/* As above, throw any typmod error after finishing syntax check */
- apply_typmod_special(res, typmod);
+ apply_typmod_special(res, typmod, have_error);
- PG_RETURN_NUMERIC(res);
+ return res;
}
+/*
+ * numeric_in() -
+ *
+ * Input function for numeric data type
+ */
+Datum
+numeric_in(PG_FUNCTION_ARGS)
+{
+ char *str = PG_GETARG_CSTRING(0);
+
+#ifdef NOT_USED
+ Oid typelem = PG_GETARG_OID(1);
+#endif
+ int32 typmod = PG_GETARG_INT32(2);
+
+ PG_RETURN_NUMERIC(numeric_in_internal(str, typmod, NULL));
+}
+
+Numeric
+numeric_in_opt_error(char *str, int32 typmod, bool *have_error)
+{
+ return numeric_in_internal(str, typmod, have_error);
+}
/*
* numeric_out() -
@@ -1058,7 +1092,7 @@ numeric_recv(PG_FUNCTION_ARGS)
{
trunc_var(&value, value.dscale);
- apply_typmod(&value, typmod);
+ apply_typmod(&value, typmod, NULL);
res = make_result(&value);
}
@@ -1067,7 +1101,7 @@ numeric_recv(PG_FUNCTION_ARGS)
/* apply_typmod_special wants us to make the Numeric first */
res = make_result(&value);
- apply_typmod_special(res, typmod);
+ apply_typmod_special(res, typmod, NULL);
}
free_var(&value);
@@ -1180,7 +1214,7 @@ numeric (PG_FUNCTION_ARGS)
*/
if (NUMERIC_IS_SPECIAL(num))
{
- apply_typmod_special(num, typmod);
+ apply_typmod_special(num, typmod, NULL);
PG_RETURN_NUMERIC(duplicate_numeric(num));
}
@@ -1231,7 +1265,7 @@ numeric (PG_FUNCTION_ARGS)
init_var(&var);
set_var_from_num(num, &var);
- apply_typmod(&var, typmod);
+ apply_typmod(&var, typmod, NULL);
new = make_result(&var);
free_var(&var);
@@ -4219,8 +4253,8 @@ int4_numeric(PG_FUNCTION_ARGS)
PG_RETURN_NUMERIC(int64_to_numeric(val));
}
-int32
-numeric_int4_opt_error(Numeric num, bool *have_error)
+static inline int32
+numeric_int4_internal(Numeric num, bool *have_error)
{
NumericVar x;
int32 result;
@@ -4274,7 +4308,13 @@ numeric_int4(PG_FUNCTION_ARGS)
{
Numeric num = PG_GETARG_NUMERIC(0);
- PG_RETURN_INT32(numeric_int4_opt_error(num, NULL));
+ PG_RETURN_INT32(numeric_int4_internal(num, NULL));
+}
+
+int32
+numeric_int4_opt_error(Numeric num, bool *have_error)
+{
+ return numeric_int4_internal(num, have_error);
}
/*
@@ -4308,15 +4348,20 @@ int8_numeric(PG_FUNCTION_ARGS)
}
-Datum
-numeric_int8(PG_FUNCTION_ARGS)
+static inline int64
+numeric_int8_internal(Numeric num, bool *have_error)
{
- Numeric num = PG_GETARG_NUMERIC(0);
NumericVar x;
int64 result;
if (NUMERIC_IS_SPECIAL(num))
{
+ if (have_error)
+ {
+ *have_error = true;
+ return 0;
+ }
+
if (NUMERIC_IS_NAN(num))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
@@ -4331,13 +4376,32 @@ numeric_int8(PG_FUNCTION_ARGS)
init_var_from_num(num, &x);
if (!numericvar_to_int64(&x, &result))
+ {
+ if (have_error)
+ {
+ *have_error = true;
+ return 0;
+ }
+
ereport(ERROR,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("bigint out of range")));
+ }
- PG_RETURN_INT64(result);
+ return result;
+}
+
+Datum
+numeric_int8(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_INT64(numeric_int8_internal(PG_GETARG_NUMERIC(0), NULL));
}
+int64
+numeric_int8_opt_error(Numeric num, bool *have_error)
+{
+ return numeric_int8_internal(num, have_error);
+}
Datum
int2_numeric(PG_FUNCTION_ARGS)
@@ -4347,17 +4411,20 @@ int2_numeric(PG_FUNCTION_ARGS)
PG_RETURN_NUMERIC(int64_to_numeric(val));
}
-
-Datum
-numeric_int2(PG_FUNCTION_ARGS)
+static inline int16
+numeric_int2_internal(Numeric num, bool *have_error)
{
- Numeric num = PG_GETARG_NUMERIC(0);
NumericVar x;
int64 val;
- int16 result;
if (NUMERIC_IS_SPECIAL(num))
{
+ if (have_error)
+ {
+ *have_error = true;
+ return 0;
+ }
+
if (NUMERIC_IS_NAN(num))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
@@ -4372,21 +4439,46 @@ numeric_int2(PG_FUNCTION_ARGS)
init_var_from_num(num, &x);
if (!numericvar_to_int64(&x, &val))
+ {
+ if (have_error)
+ {
+ *have_error = true;
+ return 0;
+ }
+
ereport(ERROR,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("smallint out of range")));
+ }
if (unlikely(val < PG_INT16_MIN) || unlikely(val > PG_INT16_MAX))
+ {
+ if (have_error)
+ {
+ *have_error = true;
+ return 0;
+ }
+
ereport(ERROR,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("smallint out of range")));
+ }
/* Down-convert to int2 */
- result = (int16) val;
+ return (int16) val;
+}
- PG_RETURN_INT16(result);
+Datum
+numeric_int2(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_INT16(numeric_int2_internal(PG_GETARG_NUMERIC(0), NULL));
}
+int16
+numeric_int2_opt_error(Numeric num, bool *have_error)
+{
+ return numeric_int2_internal(num, have_error);
+}
Datum
float8_numeric(PG_FUNCTION_ARGS)
@@ -4412,7 +4504,7 @@ float8_numeric(PG_FUNCTION_ARGS)
init_var(&result);
/* Assume we need not worry about leading/trailing spaces */
- (void) set_var_from_str(buf, buf, &result);
+ (void) set_var_from_str(buf, buf, &result, NULL);
res = make_result(&result);
@@ -4505,7 +4597,7 @@ float4_numeric(PG_FUNCTION_ARGS)
init_var(&result);
/* Assume we need not worry about leading/trailing spaces */
- (void) set_var_from_str(buf, buf, &result);
+ (void) set_var_from_str(buf, buf, &result, NULL);
res = make_result(&result);
@@ -6811,7 +6903,8 @@ zero_var(NumericVar *var)
* reports. (Typically cp would be the same except advanced over spaces.)
*/
static const char *
-set_var_from_str(const char *str, const char *cp, NumericVar *dest)
+set_var_from_str(const char *str, const char *cp, NumericVar *dest,
+ bool *have_error)
{
bool have_dp = false;
int i;
@@ -6849,10 +6942,11 @@ set_var_from_str(const char *str, const char *cp, NumericVar *dest)
}
if (!isdigit((unsigned char) *cp))
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
- errmsg("invalid input syntax for type %s: \"%s\"",
- "numeric", str)));
+ RETURN_ERROR(ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+ errmsg("invalid input syntax for type %s: \"%s\"",
+ "numeric", str))),
+ have_error);
decdigits = (unsigned char *) palloc(strlen(cp) + DEC_DIGITS * 2);
@@ -6873,10 +6967,11 @@ set_var_from_str(const char *str, const char *cp, NumericVar *dest)
else if (*cp == '.')
{
if (have_dp)
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
- errmsg("invalid input syntax for type %s: \"%s\"",
- "numeric", str)));
+ RETURN_ERROR(ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+ errmsg("invalid input syntax for type %s: \"%s\"",
+ "numeric", str))),
+ have_error);
have_dp = true;
cp++;
}
@@ -6897,10 +6992,11 @@ set_var_from_str(const char *str, const char *cp, NumericVar *dest)
cp++;
exponent = strtol(cp, &endptr, 10);
if (endptr == cp)
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
- errmsg("invalid input syntax for type %s: \"%s\"",
- "numeric", str)));
+ RETURN_ERROR(ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+ errmsg("invalid input syntax for type %s: \"%s\"",
+ "numeric", str))),
+ have_error);
cp = endptr;
/*
@@ -6912,9 +7008,10 @@ set_var_from_str(const char *str, const char *cp, NumericVar *dest)
* for consistency use the same ereport errcode/text as make_result().
*/
if (exponent >= INT_MAX / 2 || exponent <= -(INT_MAX / 2))
- ereport(ERROR,
- (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
- errmsg("value overflows numeric format")));
+ RETURN_ERROR(ereport(ERROR,
+ (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+ errmsg("value overflows numeric format"))),
+ have_error);
dweight += (int) exponent;
dscale -= (int) exponent;
if (dscale < 0)
@@ -7420,17 +7517,10 @@ make_result_opt_error(const NumericVar *var, bool *have_error)
if (NUMERIC_WEIGHT(result) != weight ||
NUMERIC_DSCALE(result) != var->dscale)
{
- if (have_error)
- {
- *have_error = true;
- return NULL;
- }
- else
- {
- ereport(ERROR,
- (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
- errmsg("value overflows numeric format")));
- }
+ RETURN_ERROR(ereport(ERROR,
+ (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+ errmsg("value overflows numeric format"))),
+ have_error);
}
dump_numeric("make_result()", result);
@@ -7456,8 +7546,8 @@ make_result(const NumericVar *var)
* Do bounds checking and rounding according to the specified typmod.
* Note that this is only applied to normal finite values.
*/
-static void
-apply_typmod(NumericVar *var, int32 typmod)
+static bool
+apply_typmod(NumericVar *var, int32 typmod, bool *have_error)
{
int precision;
int scale;
@@ -7467,7 +7557,7 @@ apply_typmod(NumericVar *var, int32 typmod)
/* Do nothing if we have an invalid typmod */
if (!is_valid_numeric_typmod(typmod))
- return;
+ return true;
precision = numeric_typmod_precision(typmod);
scale = numeric_typmod_scale(typmod);
@@ -7514,6 +7604,13 @@ apply_typmod(NumericVar *var, int32 typmod)
#error unsupported NBASE
#endif
if (ddigits > maxdigits)
+ {
+ if (have_error)
+ {
+ *have_error = true;
+ return false;
+ }
+
ereport(ERROR,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("numeric field overflow"),
@@ -7523,11 +7620,14 @@ apply_typmod(NumericVar *var, int32 typmod)
maxdigits ? "10^" : "",
maxdigits ? maxdigits : 1
)));
+ }
break;
}
ddigits -= DEC_DIGITS;
}
}
+
+ return true;
}
/*
@@ -7537,7 +7637,7 @@ apply_typmod(NumericVar *var, int32 typmod)
* For convenience of most callers, the value is presented in packed form.
*/
static void
-apply_typmod_special(Numeric num, int32 typmod)
+apply_typmod_special(Numeric num, int32 typmod, bool *have_error)
{
int precision;
int scale;
@@ -7560,6 +7660,12 @@ apply_typmod_special(Numeric num, int32 typmod)
precision = numeric_typmod_precision(typmod);
scale = numeric_typmod_scale(typmod);
+ if (have_error)
+ {
+ *have_error = true;
+ return;
+ }
+
ereport(ERROR,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("numeric field overflow"),
diff --git a/src/include/utils/numeric.h b/src/include/utils/numeric.h
index 3caa74dfe7a..f32807cfdaa 100644
--- a/src/include/utils/numeric.h
+++ b/src/include/utils/numeric.h
@@ -85,6 +85,10 @@ extern Numeric numeric_div_opt_error(Numeric num1, Numeric num2,
bool *have_error);
extern Numeric numeric_mod_opt_error(Numeric num1, Numeric num2,
bool *have_error);
-extern int32 numeric_int4_opt_error(Numeric num, bool *error);
+extern int16 numeric_int2_opt_error(Numeric num, bool *have_error);
+extern int32 numeric_int4_opt_error(Numeric num, bool *have_error);
+extern int64 numeric_int8_opt_error(Numeric num, bool *have_error);
+extern Numeric numeric_in_opt_error(char *str, int32 typmod,
+ bool *have_error);
#endif /* _PG_NUMERIC_H_ */
--
2.17.1
v10-0005-Add-safe-input-and-type-conversion-functions-for.patchtext/x-patch; charset=UTF-8; name=v10-0005-Add-safe-input-and-type-conversion-functions-for.patchDownload
From 65a8de6fd2d77ff4dd594c0e39a4ac59de39c637 Mon Sep 17 00:00:00 2001
From: Nikita Glukhov <n.gluhov@postgrespro.ru>
Date: Mon, 29 Aug 2022 23:54:45 +0300
Subject: [PATCH v10 05/10] Add safe input and type conversion functions for
datetime types
---
contrib/adminpack/adminpack.c | 2 +-
src/backend/utils/adt/date.c | 325 +++++++++++++++++++++++++-----
src/backend/utils/adt/datetime.c | 16 +-
src/backend/utils/adt/timestamp.c | 138 ++++++++++---
src/backend/utils/misc/guc.c | 2 +-
src/include/utils/date.h | 13 ++
src/include/utils/datetime.h | 6 +-
src/include/utils/timestamp.h | 6 +
8 files changed, 423 insertions(+), 85 deletions(-)
diff --git a/contrib/adminpack/adminpack.c b/contrib/adminpack/adminpack.c
index 03addf1dc5f..4bf795d42a4 100644
--- a/contrib/adminpack/adminpack.c
+++ b/contrib/adminpack/adminpack.c
@@ -571,7 +571,7 @@ pg_logdir_ls_internal(FunctionCallInfo fcinfo)
if (ParseDateTime(timestampbuf, lowstr, MAXDATELEN, field, ftype, MAXDATEFIELDS, &nf))
continue;
- if (DecodeDateTime(field, ftype, nf, &dtype, &date, &fsec, &tz))
+ if (DecodeDateTime(field, ftype, nf, &dtype, &date, &fsec, &tz, true))
continue;
/* Seems the timestamp is OK; prepare and return tuple */
diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index 081dfa2450f..a564e134eae 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -108,10 +108,9 @@ anytime_typmodout(bool istz, int32 typmod)
/* date_in()
* Given date text string, convert to internal date format.
*/
-Datum
-date_in(PG_FUNCTION_ARGS)
+static inline DateADT
+date_in_internal(char *str, bool *error)
{
- char *str = PG_GETARG_CSTRING(0);
DateADT date;
fsec_t fsec;
struct pg_tm tt,
@@ -127,9 +126,18 @@ date_in(PG_FUNCTION_ARGS)
dterr = ParseDateTime(str, workbuf, sizeof(workbuf),
field, ftype, MAXDATEFIELDS, &nf);
if (dterr == 0)
- dterr = DecodeDateTime(field, ftype, nf, &dtype, tm, &fsec, &tzp);
+ dterr = DecodeDateTime(field, ftype, nf, &dtype, tm, &fsec, &tzp,
+ !error);
if (dterr != 0)
+ {
+ if (error)
+ {
+ *error = true;
+ return 0;
+ }
+
DateTimeParseError(dterr, str, "date");
+ }
switch (dtype)
{
@@ -149,25 +157,62 @@ date_in(PG_FUNCTION_ARGS)
PG_RETURN_DATEADT(date);
default:
+ if (*error)
+ {
+ *error = true;
+ return 0;
+ }
+
DateTimeParseError(DTERR_BAD_FORMAT, str, "date");
break;
}
/* Prevent overflow in Julian-day routines */
if (!IS_VALID_JULIAN(tm->tm_year, tm->tm_mon, tm->tm_mday))
+ {
+ if (error)
+ {
+ *error = true;
+ return 0;
+ }
+
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("date out of range: \"%s\"", str)));
+ }
date = date2j(tm->tm_year, tm->tm_mon, tm->tm_mday) - POSTGRES_EPOCH_JDATE;
/* Now check for just-out-of-range dates */
if (!IS_VALID_DATE(date))
+ {
+ if (error)
+ {
+ *error = true;
+ return 0;
+ }
+
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("date out of range: \"%s\"", str)));
+ }
- PG_RETURN_DATEADT(date);
+ return date;
+}
+
+/* date_in()
+ * Given date text string, convert to internal date format.
+ */
+Datum
+date_in(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_DATEADT(date_in_internal(PG_GETARG_CSTRING(0), NULL));
+}
+
+DateADT
+date_in_opt_error(char *str, bool *error)
+{
+ return date_in_internal(str, error);
}
/* date_out()
@@ -1286,10 +1331,9 @@ date_timestamp(PG_FUNCTION_ARGS)
/* timestamp_date()
* Convert timestamp to date data type.
*/
-Datum
-timestamp_date(PG_FUNCTION_ARGS)
+static inline DateADT
+timestamp_date_internal(Timestamp timestamp, bool *error)
{
- Timestamp timestamp = PG_GETARG_TIMESTAMP(0);
DateADT result;
struct pg_tm tt,
*tm = &tt;
@@ -1302,16 +1346,38 @@ timestamp_date(PG_FUNCTION_ARGS)
else
{
if (timestamp2tm(timestamp, NULL, tm, &fsec, NULL, NULL) != 0)
+ {
+ if (error)
+ {
+ *error = true;
+ return 0;
+ }
+
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("timestamp out of range")));
+ }
result = date2j(tm->tm_year, tm->tm_mon, tm->tm_mday) - POSTGRES_EPOCH_JDATE;
}
- PG_RETURN_DATEADT(result);
+ return result;
}
+/* timestamp_date()
+ * Convert timestamp to date data type.
+ */
+Datum
+timestamp_date(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_DATEADT(timestamp_date_internal(PG_GETARG_TIMESTAMP(0), NULL));
+}
+
+DateADT
+timestamp_date_opt_error(Timestamp timestamp, bool *error)
+{
+ return timestamp_date_internal(timestamp, error);
+}
/* date_timestamptz()
* Convert date to timestamp with time zone data type.
@@ -1327,14 +1393,9 @@ date_timestamptz(PG_FUNCTION_ARGS)
PG_RETURN_TIMESTAMP(result);
}
-
-/* timestamptz_date()
- * Convert timestamp with time zone to date data type.
- */
-Datum
-timestamptz_date(PG_FUNCTION_ARGS)
+static inline DateADT
+timestamptz_date_internal(TimestampTz timestamp, bool *error)
{
- TimestampTz timestamp = PG_GETARG_TIMESTAMP(0);
DateADT result;
struct pg_tm tt,
*tm = &tt;
@@ -1348,30 +1409,46 @@ timestamptz_date(PG_FUNCTION_ARGS)
else
{
if (timestamp2tm(timestamp, &tz, tm, &fsec, NULL, NULL) != 0)
+ {
+ if (error)
+ {
+ *error = true;
+ return 0;
+ }
+
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("timestamp out of range")));
+ }
result = date2j(tm->tm_year, tm->tm_mon, tm->tm_mday) - POSTGRES_EPOCH_JDATE;
}
- PG_RETURN_DATEADT(result);
+ return result;
}
+/* timestamptz_date()
+ * Convert timestamp with time zone to date data type.
+ */
+Datum
+timestamptz_date(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_DATEADT(timestamptz_date_internal(PG_GETARG_TIMESTAMP(0), NULL));
+}
+
+DateADT
+timestamptz_date_opt_error(TimestampTz timestamp, bool *error)
+{
+ return timestamptz_date_internal(timestamp, error);
+}
/*****************************************************************************
* Time ADT
*****************************************************************************/
-Datum
-time_in(PG_FUNCTION_ARGS)
+static inline TimeADT
+time_in_internal(char *str, int32 typmod, bool *error)
{
- char *str = PG_GETARG_CSTRING(0);
-
-#ifdef NOT_USED
- Oid typelem = PG_GETARG_OID(1);
-#endif
- int32 typmod = PG_GETARG_INT32(2);
TimeADT result;
fsec_t fsec;
struct pg_tm tt,
@@ -1387,14 +1464,41 @@ time_in(PG_FUNCTION_ARGS)
dterr = ParseDateTime(str, workbuf, sizeof(workbuf),
field, ftype, MAXDATEFIELDS, &nf);
if (dterr == 0)
- dterr = DecodeTimeOnly(field, ftype, nf, &dtype, tm, &fsec, &tz);
+ dterr = DecodeTimeOnly(field, ftype, nf, &dtype, tm, &fsec, &tz,
+ !error);
if (dterr != 0)
+ {
+ if (error)
+ {
+ *error = true;
+ return 0;
+ }
+
DateTimeParseError(dterr, str, "time");
+ }
tm2time(tm, fsec, &result);
AdjustTimeForTypmod(&result, typmod);
- PG_RETURN_TIMEADT(result);
+ return result;
+}
+
+Datum
+time_in(PG_FUNCTION_ARGS)
+{
+ char *str = PG_GETARG_CSTRING(0);
+#ifdef NOT_USED
+ Oid typelem = PG_GETARG_OID(1);
+#endif
+ int32 typmod = PG_GETARG_INT32(2);
+
+ PG_RETURN_TIMEADT(time_in_internal(str, typmod, NULL));
+}
+
+TimeADT
+time_in_opt_error(char *str, int32 typmod, bool *error)
+{
+ return time_in_internal(str, typmod, error);
}
/* tm2time()
@@ -1889,22 +1993,32 @@ overlaps_time(PG_FUNCTION_ARGS)
/* timestamp_time()
* Convert timestamp to time data type.
*/
-Datum
-timestamp_time(PG_FUNCTION_ARGS)
+static inline TimeADT
+timestamp_time_internal(Timestamp timestamp, bool *isnull, bool *error)
{
- Timestamp timestamp = PG_GETARG_TIMESTAMP(0);
TimeADT result;
struct pg_tm tt,
*tm = &tt;
fsec_t fsec;
if (TIMESTAMP_NOT_FINITE(timestamp))
- PG_RETURN_NULL();
+ {
+ *isnull = true;
+ return 0;
+ }
if (timestamp2tm(timestamp, NULL, tm, &fsec, NULL, NULL) != 0)
+ {
+ if (error)
+ {
+ *error = *isnull = true;
+ return (Datum) 0;
+ }
+
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("timestamp out of range")));
+ }
/*
* Could also do this with time = (timestamp / USECS_PER_DAY *
@@ -1912,17 +2026,39 @@ timestamp_time(PG_FUNCTION_ARGS)
*/
result = ((((tm->tm_hour * MINS_PER_HOUR + tm->tm_min) * SECS_PER_MINUTE) + tm->tm_sec) *
USECS_PER_SEC) + fsec;
+ *isnull = false;
- PG_RETURN_TIMEADT(result);
+ return result;
}
-/* timestamptz_time()
- * Convert timestamptz to time data type.
+/* timestamp_time()
+ * Convert timestamp to time data type.
*/
Datum
-timestamptz_time(PG_FUNCTION_ARGS)
+timestamp_time(PG_FUNCTION_ARGS)
+{
+ bool isnull;
+ TimeADT time =
+ timestamp_time_internal(PG_GETARG_TIMESTAMP(0), &isnull, NULL);
+
+ if (isnull)
+ PG_RETURN_NULL();
+ else
+ PG_RETURN_TIMEADT(time);
+}
+
+TimeADT
+timestamp_time_opt_error(Timestamp timestamp, bool *isnull, bool *error)
+{
+ return timestamp_time_internal(timestamp, isnull, error);
+}
+
+/* timestamptz_time_opt_error()
+ * Convert timestamptz to time data type.
+ */
+static inline TimeADT
+timestamptz_time_internal(TimestampTz timestamp, bool *isnull, bool *error)
{
- TimestampTz timestamp = PG_GETARG_TIMESTAMP(0);
TimeADT result;
struct pg_tm tt,
*tm = &tt;
@@ -1930,12 +2066,23 @@ timestamptz_time(PG_FUNCTION_ARGS)
fsec_t fsec;
if (TIMESTAMP_NOT_FINITE(timestamp))
- PG_RETURN_NULL();
+ {
+ *isnull = true;
+ return 0;
+ }
if (timestamp2tm(timestamp, &tz, tm, &fsec, NULL, NULL) != 0)
+ {
+ if (error)
+ {
+ *error = *isnull = true;
+ return 0;
+ }
+
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("timestamp out of range")));
+ }
/*
* Could also do this with time = (timestamp / USECS_PER_DAY *
@@ -1943,8 +2090,31 @@ timestamptz_time(PG_FUNCTION_ARGS)
*/
result = ((((tm->tm_hour * MINS_PER_HOUR + tm->tm_min) * SECS_PER_MINUTE) + tm->tm_sec) *
USECS_PER_SEC) + fsec;
+ *isnull = false;
- PG_RETURN_TIMEADT(result);
+ return result;
+}
+
+/* timestamptz_time()
+ * Convert timestamptz to time data type.
+ */
+Datum
+timestamptz_time(PG_FUNCTION_ARGS)
+{
+ bool isnull;
+ TimeADT result =
+ timestamptz_time_internal(PG_GETARG_TIMESTAMP(0), &isnull, NULL);
+
+ if (isnull)
+ PG_RETURN_NULL();
+ else
+ PG_RETURN_TIMEADT(result);
+}
+
+TimeADT
+timestamptz_time_opt_error(TimestampTz timestamp, bool *isnull, bool *error)
+{
+ return timestamptz_time_internal(timestamp, isnull, error);
}
/* datetime_timestamp()
@@ -2249,15 +2419,9 @@ tm2timetz(struct pg_tm *tm, fsec_t fsec, int tz, TimeTzADT *result)
return 0;
}
-Datum
-timetz_in(PG_FUNCTION_ARGS)
+static inline TimeTzADT *
+timetz_in_internal(char *str, int32 typmod, bool *error)
{
- char *str = PG_GETARG_CSTRING(0);
-
-#ifdef NOT_USED
- Oid typelem = PG_GETARG_OID(1);
-#endif
- int32 typmod = PG_GETARG_INT32(2);
TimeTzADT *result;
fsec_t fsec;
struct pg_tm tt,
@@ -2273,15 +2437,43 @@ timetz_in(PG_FUNCTION_ARGS)
dterr = ParseDateTime(str, workbuf, sizeof(workbuf),
field, ftype, MAXDATEFIELDS, &nf);
if (dterr == 0)
- dterr = DecodeTimeOnly(field, ftype, nf, &dtype, tm, &fsec, &tz);
+ dterr = DecodeTimeOnly(field, ftype, nf, &dtype, tm, &fsec, &tz,
+ !error);
if (dterr != 0)
+ {
+ if (error)
+ {
+ *error = true;
+ return NULL;
+ }
+
DateTimeParseError(dterr, str, "time with time zone");
+ }
result = (TimeTzADT *) palloc(sizeof(TimeTzADT));
tm2timetz(tm, fsec, tz, result);
AdjustTimeForTypmod(&(result->time), typmod);
- PG_RETURN_TIMETZADT_P(result);
+ return result;
+}
+
+Datum
+timetz_in(PG_FUNCTION_ARGS)
+{
+ char *str = PG_GETARG_CSTRING(0);
+
+#ifdef NOT_USED
+ Oid typelem = PG_GETARG_OID(1);
+#endif
+ int32 typmod = PG_GETARG_INT32(2);
+
+ PG_RETURN_TIMETZADT_P(timetz_in_internal(str, typmod, NULL));
+}
+
+TimeTzADT *
+timetz_in_opt_error(char *str, int32 typmod, bool *error)
+{
+ return timetz_in_internal(str, typmod, error);
}
Datum
@@ -2812,10 +3004,9 @@ time_timetz(PG_FUNCTION_ARGS)
/* timestamptz_timetz()
* Convert timestamp to timetz data type.
*/
-Datum
-timestamptz_timetz(PG_FUNCTION_ARGS)
+static inline TimeTzADT *
+timestamptz_timetz_internal(TimestampTz timestamp, bool *error)
{
- TimestampTz timestamp = PG_GETARG_TIMESTAMP(0);
TimeTzADT *result;
struct pg_tm tt,
*tm = &tt;
@@ -2823,20 +3014,48 @@ timestamptz_timetz(PG_FUNCTION_ARGS)
fsec_t fsec;
if (TIMESTAMP_NOT_FINITE(timestamp))
- PG_RETURN_NULL();
+ return NULL;
if (timestamp2tm(timestamp, &tz, tm, &fsec, NULL, NULL) != 0)
+ {
+ if (error)
+ {
+ *error = true;
+ return NULL;
+ }
+
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("timestamp out of range")));
+ }
result = (TimeTzADT *) palloc(sizeof(TimeTzADT));
tm2timetz(tm, fsec, tz, result);
- PG_RETURN_TIMETZADT_P(result);
+ return result;
}
+/* timestamptz_timetz()
+ * Convert timestamp to timetz data type.
+ */
+Datum
+timestamptz_timetz(PG_FUNCTION_ARGS)
+{
+ TimeTzADT *result =
+ timestamptz_timetz_internal(PG_GETARG_TIMESTAMP(0), NULL);
+
+ if (result)
+ PG_RETURN_TIMETZADT_P(result);
+ else
+ PG_RETURN_NULL();
+}
+
+TimeTzADT *
+timestamptz_timetz_opt_error(TimestampTz timestamp, bool *error)
+{
+ return timestamptz_timetz_internal(timestamp, error);
+}
/* datetimetz_timestamptz()
* Convert date and timetz to timestamp with time zone data type.
diff --git a/src/backend/utils/adt/datetime.c b/src/backend/utils/adt/datetime.c
index 43fff50d490..3275e5fd948 100644
--- a/src/backend/utils/adt/datetime.c
+++ b/src/backend/utils/adt/datetime.c
@@ -971,7 +971,8 @@ ParseDateTime(const char *timestr, char *workbuf, size_t buflen,
*/
int
DecodeDateTime(char **field, int *ftype, int nf,
- int *dtype, struct pg_tm *tm, fsec_t *fsec, int *tzp)
+ int *dtype, struct pg_tm *tm, fsec_t *fsec, int *tzp,
+ bool throw_errors)
{
int fmask = 0,
tmask,
@@ -1114,8 +1115,13 @@ DecodeDateTime(char **field, int *ftype, int nf,
/*
* We should return an error code instead of
* ereport'ing directly, but then there is no way
- * to report the bad time zone name.
+ * to report the bad time zone name. But
+ * throwing errors is disallowed, simply
+ * return error code.
*/
+ if (!throw_errors)
+ return DTERR_BAD_FORMAT;
+
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("time zone \"%s\" not recognized",
@@ -1921,7 +1927,8 @@ DetermineTimeZoneAbbrevOffsetInternal(pg_time_t t, const char *abbr, pg_tz *tzp,
*/
int
DecodeTimeOnly(char **field, int *ftype, int nf,
- int *dtype, struct pg_tm *tm, fsec_t *fsec, int *tzp)
+ int *dtype, struct pg_tm *tm, fsec_t *fsec, int *tzp,
+ bool throw_errors)
{
int fmask = 0,
tmask,
@@ -2022,6 +2029,9 @@ DecodeTimeOnly(char **field, int *ftype, int nf,
* ereport'ing directly, but then there is no way
* to report the bad time zone name.
*/
+ if (!throw_errors)
+ return DTERR_BAD_FORMAT;
+
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("time zone \"%s\" not recognized",
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 49cdb290ac2..55e5486f481 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -139,18 +139,12 @@ anytimestamp_typmodout(bool istz, int32 typmod)
* USER I/O ROUTINES *
*****************************************************************************/
-/* timestamp_in()
- * Convert a string to internal form.
+/* timestamp_in_internal()
+ * Convert a string to internal form, returning error instead of throwing if needed.
*/
-Datum
-timestamp_in(PG_FUNCTION_ARGS)
+static inline Timestamp
+timestamp_in_internal(char *str, int32 typmod, bool *error)
{
- char *str = PG_GETARG_CSTRING(0);
-
-#ifdef NOT_USED
- Oid typelem = PG_GETARG_OID(1);
-#endif
- int32 typmod = PG_GETARG_INT32(2);
Timestamp result;
fsec_t fsec;
struct pg_tm tt,
@@ -166,17 +160,34 @@ timestamp_in(PG_FUNCTION_ARGS)
dterr = ParseDateTime(str, workbuf, sizeof(workbuf),
field, ftype, MAXDATEFIELDS, &nf);
if (dterr == 0)
- dterr = DecodeDateTime(field, ftype, nf, &dtype, tm, &fsec, &tz);
+ dterr = DecodeDateTime(field, ftype, nf, &dtype, tm, &fsec, &tz,
+ !error);
if (dterr != 0)
+ {
+ if (error)
+ {
+ *error = true;
+ return 0;
+ }
+
DateTimeParseError(dterr, str, "timestamp");
+ }
switch (dtype)
{
case DTK_DATE:
if (tm2timestamp(tm, fsec, NULL, &result) != 0)
+ {
+ if (error)
+ {
+ *error = true;
+ return 0;
+ }
+
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("timestamp out of range: \"%s\"", str)));
+ }
break;
case DTK_EPOCH:
@@ -197,11 +208,33 @@ timestamp_in(PG_FUNCTION_ARGS)
TIMESTAMP_NOEND(result);
}
- AdjustTimestampForTypmod(&result, typmod);
+ AdjustTimestampForTypmodError(&result, typmod, error);
PG_RETURN_TIMESTAMP(result);
}
+/* timestamp_in()
+ * Convert a string to internal form.
+ */
+Datum
+timestamp_in(PG_FUNCTION_ARGS)
+{
+ char *str = PG_GETARG_CSTRING(0);
+
+#ifdef NOT_USED
+ Oid typelem = PG_GETARG_OID(1);
+#endif
+ int32 typmod = PG_GETARG_INT32(2);
+
+ PG_RETURN_TIMESTAMP(timestamp_in_internal(str, typmod, NULL));
+}
+
+Timestamp
+timestamp_in_opt_error(char *str, int32 typmod, bool *error)
+{
+ return timestamp_in_internal(str, typmod, error);
+}
+
/* timestamp_out()
* Convert a timestamp to external form.
*/
@@ -400,15 +433,9 @@ AdjustTimestampForTypmod(Timestamp *time, int32 typmod)
/* timestamptz_in()
* Convert a string to internal form.
*/
-Datum
-timestamptz_in(PG_FUNCTION_ARGS)
+static inline TimestampTz
+timestamptz_in_internal(char *str, int32 typmod, bool *error)
{
- char *str = PG_GETARG_CSTRING(0);
-
-#ifdef NOT_USED
- Oid typelem = PG_GETARG_OID(1);
-#endif
- int32 typmod = PG_GETARG_INT32(2);
TimestampTz result;
fsec_t fsec;
struct pg_tm tt,
@@ -424,17 +451,34 @@ timestamptz_in(PG_FUNCTION_ARGS)
dterr = ParseDateTime(str, workbuf, sizeof(workbuf),
field, ftype, MAXDATEFIELDS, &nf);
if (dterr == 0)
- dterr = DecodeDateTime(field, ftype, nf, &dtype, tm, &fsec, &tz);
+ dterr = DecodeDateTime(field, ftype, nf, &dtype, tm, &fsec, &tz,
+ !error);
if (dterr != 0)
+ {
+ if (error)
+ {
+ *error = true;
+ return 0;
+ }
+
DateTimeParseError(dterr, str, "timestamp with time zone");
+ }
switch (dtype)
{
case DTK_DATE:
if (tm2timestamp(tm, fsec, &tz, &result) != 0)
+ {
+ if (error)
+ {
+ *error = true;
+ return 0;
+ }
+
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("timestamp out of range: \"%s\"", str)));
+ }
break;
case DTK_EPOCH:
@@ -455,9 +499,30 @@ timestamptz_in(PG_FUNCTION_ARGS)
TIMESTAMP_NOEND(result);
}
- AdjustTimestampForTypmod(&result, typmod);
+ AdjustTimestampForTypmodError(&result, typmod, error);
- PG_RETURN_TIMESTAMPTZ(result);
+ return result;
+}
+
+/* timestamptz_in()
+ * Convert a string to internal form.
+ */
+Datum
+timestamptz_in(PG_FUNCTION_ARGS)
+{
+ char *str = PG_GETARG_CSTRING(0);
+#ifdef NOT_USED
+ Oid typelem = PG_GETARG_OID(1);
+#endif
+ int32 typmod = PG_GETARG_INT32(2);
+
+ PG_RETURN_TIMESTAMPTZ(timestamptz_in_internal(str, typmod, NULL));
+}
+
+TimestampTz
+timestamptz_in_opt_error(char *str, int32 typmod, bool *error)
+{
+ return timestamptz_in_internal(str, typmod, error);
}
/*
@@ -5614,8 +5679,8 @@ timestamptz_timestamp(PG_FUNCTION_ARGS)
PG_RETURN_TIMESTAMP(timestamptz2timestamp(timestamp));
}
-static Timestamp
-timestamptz2timestamp(TimestampTz timestamp)
+Timestamp
+timestamptz2timestamp_opt_error(TimestampTz timestamp, bool *error)
{
Timestamp result;
struct pg_tm tt,
@@ -5628,17 +5693,40 @@ timestamptz2timestamp(TimestampTz timestamp)
else
{
if (timestamp2tm(timestamp, &tz, tm, &fsec, NULL, NULL) != 0)
+ {
+ if (error)
+ {
+ *error = true;
+ return 0;
+ }
+
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("timestamp out of range")));
+ }
+
if (tm2timestamp(tm, fsec, NULL, &result) != 0)
+ {
+ if (error)
+ {
+ *error = true;
+ return 0;
+ }
+
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("timestamp out of range")));
+ }
}
return result;
}
+static Timestamp
+timestamptz2timestamp(TimestampTz timestamp)
+{
+ return timestamptz2timestamp_opt_error(timestamp, NULL);
+}
+
/* timestamptz_zone()
* Evaluate timestamp with time zone type at the specified time zone.
* Returns a timestamp without time zone.
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 9fbbfb1be54..7877bce6043 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -12866,7 +12866,7 @@ check_recovery_target_time(char **newval, void **extra, GucSource source)
dterr = ParseDateTime(str, workbuf, sizeof(workbuf),
field, ftype, MAXDATEFIELDS, &nf);
if (dterr == 0)
- dterr = DecodeDateTime(field, ftype, nf, &dtype, tm, &fsec, &tz);
+ dterr = DecodeDateTime(field, ftype, nf, &dtype, tm, &fsec, &tz, true);
if (dterr != 0)
return false;
if (dtype != DTK_DATE)
diff --git a/src/include/utils/date.h b/src/include/utils/date.h
index 91ae24254df..f29168a025b 100644
--- a/src/include/utils/date.h
+++ b/src/include/utils/date.h
@@ -87,4 +87,17 @@ extern bool time_overflows(int hour, int min, int sec, fsec_t fsec);
extern bool float_time_overflows(int hour, int min, double sec);
extern void AdjustTimeForTypmod(TimeADT *time, int32 typmod);
+extern DateADT timestamp_date_opt_error(Timestamp timestamp, bool *error);
+extern TimeADT timestamp_time_opt_error(Timestamp timestamp,
+ bool *isnull, bool *error);
+
+extern DateADT timestamptz_date_opt_error(Timestamp timestamp, bool *error);
+extern TimeADT timestamptz_time_opt_error(TimestampTz timestamp,
+ bool *isnull, bool *error);
+extern TimeTzADT *timestamptz_timetz_opt_error(TimestampTz timestamp,
+ bool *error);
+extern DateADT date_in_opt_error(char *str, bool *error);
+extern TimeADT time_in_opt_error(char *str, int32 typmod, bool *error);
+extern TimeTzADT *timetz_in_opt_error(char *str, int32 typmod, bool *error);
+
#endif /* DATE_H */
diff --git a/src/include/utils/datetime.h b/src/include/utils/datetime.h
index 4527e825177..68241d7c4cc 100644
--- a/src/include/utils/datetime.h
+++ b/src/include/utils/datetime.h
@@ -295,11 +295,13 @@ extern int ParseDateTime(const char *timestr, char *workbuf, size_t buflen,
int maxfields, int *numfields);
extern int DecodeDateTime(char **field, int *ftype,
int nf, int *dtype,
- struct pg_tm *tm, fsec_t *fsec, int *tzp);
+ struct pg_tm *tm, fsec_t *fsec, int *tzp,
+ bool throw_errors);
extern int DecodeTimezone(char *str, int *tzp);
extern int DecodeTimeOnly(char **field, int *ftype,
int nf, int *dtype,
- struct pg_tm *tm, fsec_t *fsec, int *tzp);
+ struct pg_tm *tm, fsec_t *fsec, int *tzp,
+ bool throw_errors);
extern int DecodeInterval(char **field, int *ftype, int nf, int range,
int *dtype, struct pg_itm_in *itm_in);
extern int DecodeISO8601Interval(char *str,
diff --git a/src/include/utils/timestamp.h b/src/include/utils/timestamp.h
index edf3a973186..2685d14342d 100644
--- a/src/include/utils/timestamp.h
+++ b/src/include/utils/timestamp.h
@@ -102,8 +102,14 @@ extern int timestamp_cmp_internal(Timestamp dt1, Timestamp dt2);
extern TimestampTz timestamp2timestamptz_opt_overflow(Timestamp timestamp,
int *overflow);
+extern Timestamp timestamptz2timestamp_opt_error(TimestampTz timestamp,
+ bool *error);
extern int32 timestamp_cmp_timestamptz_internal(Timestamp timestampVal,
TimestampTz dt2);
+extern Timestamp timestamp_in_opt_error(char *str, int32 typmod,
+ bool *error);
+extern TimestampTz timestamptz_in_opt_error(char *str, int32 typmod,
+ bool *error);
extern int isoweek2j(int year, int week);
extern void isoweek2date(int woy, int *year, int *mon, int *mday);
--
2.17.1
On Wed, Aug 31, 2022 at 6:25 AM Nikita Glukhov <n.gluhov@postgrespro.ru> wrote:
On 30.08.2022 11:09, Amit Langote wrote:
First of all, regarding 0009, my understanding was that we should
disallow DEFAULT expression ON ERROR too for now, so something like
the following does not occur:SELECT JSON_VALUE(jsonb '"err"', '$' RETURNING numeric DEFAULT ('{"'
|| -1+a || '"}')::text ON ERROR) from foo;
ERROR: invalid input syntax for type numeric: "{"0"}"Personally, I don't like complete removal of DEFAULT behaviors, but
I've done it in patch #10 (JsonBehavior node removed, grammar fixed).
To clarify, I had meant to ask if the standard specifies how to handle
the errors of evaluating the DEFAULT ON ERROR expressions themselves?
My understanding is that the sub-transaction that is being removed
would have caught and suppressed the above error too, so along with
removing the sub-transactions, we should also remove anything that
might cause such errors.
On 30.08.2022 13:29, Amit Langote wrote:
On Tue, Aug 30, 2022 at 6:19 PM Alvaro Herrera <alvherre@alvh.no-ip.org> wrote:
On 2022-Aug-30, Amit Langote wrote:Patches 0001-0006:
Yeah, these add the overhead of an extra function call (typin() ->
typin_opt_error()) in possibly very common paths. Other than
refactoring *all* places that call typin() to use the new API, the
only other option seems to be to leave the typin() functions alone and
duplicate their code in typin_opt_error() versions for all the types
that this patch cares about. Though maybe, that's not necessarily a
better compromise than accepting the extra function call overhead.I think another possibility is to create a static inline function in the
corresponding .c module (say boolin_impl() in bool.c), which is called
by both the opt_error variant as well as the regular one. This would
avoid the duplicate code as well as the added function-call overhead.+1
I always thought about such internal inline functions, I 've added them in v10.
Thanks.
In 0003:
-Datum
-float4in(PG_FUNCTION_ARGS)
+static float
+float4in_internal(char *num, bool *have_error)
Looks like you forgot the inline marker?
In 0006:
-static inline Datum jsonb_from_cstring(char *json, int len, bool unique_keys);
...
+extern Datum jsonb_from_cstring(char *json, int len, bool unique_keys,
+ bool *error);
Did you intentionally remove the inline marker from
jsonb_from_cstring() as opposed to the other cases?
Patch 0007:
+ + /* Override default coercion in OMIT QUOTES case */ + if (ExecJsonQueryNeedsIOCoercion(jexpr, res, *resnull)) + { + char *str = JsonbUnquote(DatumGetJsonbP(res)); ... + else if (ret_typid == VARCHAROID || ret_typid == BPCHAROID || + ret_typid == BYTEAOID) + { + Jsonb *jb = DatumGetJsonbP(res); + char *str = JsonbToCString(NULL, &jb->root, VARSIZE(jb)); + + return ExecJsonStringCoercion(str, strlen(str), ret_typid, ret_typmod); + }I think it might be better to create ExecJsonQueryCoercion() similar
to ExecJsonValueCoercion() and put the above block in that function
rather than inlining it in ExecEvalJsonExprInternal().Extracted ExecJsonQueryCoercion().
Thanks.
+/* Coerce JSONB datum to the output typid(typmod) */
static Datum
+ExecJsonQueryCoercion(JsonExpr *jexpr, Oid typid, int32 typmod,
+ Datum jb, bool *error)
Might make sense to expand to comment to mention JSON_QUERY, say as:
/* Coerce JSONB datum returned by JSON_QUERY() to the output typid(typmod) */
+/* Coerce SQL/JSON item to the output typid */
+static Datum
+ExecJsonValueCoercion(JsonbValue *item, Oid typid, int32 typmod,
+ bool *isnull, bool *error)
While at it, also update the comment of ExecJsonValueCoercion() as:
/* Coerce SQL/JSON item returned by JSON_VALUE() to the output typid */
+ /* + * XXX coercion to text is done using output functions, and they + * are mutable for non-time[tz] types due to using of DateStyle. + * We can pass USE_ISO_DATES, which is used inside jsonpath, to + * make these coercions and JSON_VALUE(RETURNING text) immutable. + * + * XXX Also timestamp[tz] output functions can throw "out of range" + * error, but this error seem to be not possible. + */Are we planning to fix these before committing?
I don't know, but the first issue is critical for building functional indexes
on JSON_VALUE().
Ok.
- coercion = &coercions->composite; - res = JsonbPGetDatum(JsonbValueToJsonb(item)); + Assert(0); /* non-scalars must be rejected by JsonPathValue() */I didn't notice any changes to JsonPathValue(). Is the new comment
referring to an existing behavior of JsonPathValue() or something that
must be done by the patch?JsonPathValue() has a check for non-scalars items, this is simply a new comment.
Ok.
@@ -411,6 +411,26 @@ contain_mutable_functions_walker(Node *node, void *context) { JsonExpr *jexpr = castNode(JsonExpr, node); Const *cnst; + bool returns_datetime; + + /* + * Input fuctions for datetime types are stable. They can be + * called in JSON_VALUE(), when the resulting SQL/JSON is a + * string. + */ ...Sorry if you've mentioned it before, but are these hunks changing
contain_mutable_functions_walker() fixing a bug? That is, did the
original SQL/JSON patch miss doing this?In the original patch there were checks for mutability of expressions contained
in JsonCoercion nodes. After their removal, we need to use hardcoded checks.
Ah, okay, makes sense. Though I do wonder why list the individual
type OIDs here rather than checking the mutability markings on their
input/output functions? For example, we could do what the following
blob in check_funcs_in_node() that is called by
contain_mutable_functions_walker() does:
case T_CoerceViaIO:
{
CoerceViaIO *expr = (CoerceViaIO *) node;
Oid iofunc;
Oid typioparam;
bool typisvarlena;
/* check the result type's input function */
getTypeInputInfo(expr->resulttype,
&iofunc, &typioparam);
if (checker(iofunc, context))
return true;
/* check the input type's output function */
getTypeOutputInfo(exprType((Node *) expr->arg),
&iofunc, &typisvarlena);
if (checker(iofunc, context))
return true;
}
I guess that's what would get used when the JsonCoercion nodes were present.
On 0010:
@@ -5402,7 +5401,7 @@ ExecEvalJsonExprSkip(ExprState *state, ExprEvalStep *op)
* true - Ok, jump to the end of JsonExpr
* false - error occured, need to execute DEFAULT ON ERROR expression
*/
-bool
+void
Looks like you forgot to update the comment.
SELECT JSON_VALUE(jsonb '"aaa"', '$' RETURNING int DEFAULT 111 ON ERROR);
- json_value
-------------
- 111
-(1 row)
-
+ERROR: syntax error at or near "DEFAULT"
+LINE 1: ...ELECT JSON_VALUE(jsonb '"aaa"', '$' RETURNING int DEFAULT 11...
Is it intentional that you left many instances of the regression test
output changes like the above?
Finally, I get this warning:
execExprInterp.c: In function ‘ExecJsonCoerceCStringToText’:
execExprInterp.c:4765:3: warning: missing braces around initializer
[-Wmissing-braces]
NameData encoding = {0};
^
execExprInterp.c:4765:3: warning: (near initialization for
‘encoding.data’) [-Wmissing-braces]
--
Thanks, Amit Langote
EDB: http://www.enterprisedb.com
On Wed, Aug 31, 2022 at 3:51 PM Amit Langote <amitlangote09@gmail.com> wrote:
On Wed, Aug 31, 2022 at 6:25 AM Nikita Glukhov <n.gluhov@postgrespro.ru> wrote:
v10 patches
Finally, I get this warning:
execExprInterp.c: In function ‘ExecJsonCoerceCStringToText’:
execExprInterp.c:4765:3: warning: missing braces around initializer
[-Wmissing-braces]
NameData encoding = {0};
^
execExprInterp.c:4765:3: warning: (near initialization for
‘encoding.data’) [-Wmissing-braces]
Given the time constraints on making a decision on this, I'd like to
also mention that other than the things mentioned in my last email,
which don't sound like a big deal for Nikita to take care of, I don't
have any further comments on the patches.
--
Thanks, Amit Langote
EDB: http://www.enterprisedb.com
On Wed, Aug 31, 2022 at 3:51 PM Amit Langote <amitlangote09@gmail.com> wrote:
SELECT JSON_VALUE(jsonb '"aaa"', '$' RETURNING int DEFAULT 111 ON ERROR); - json_value ------------- - 111 -(1 row) - +ERROR: syntax error at or near "DEFAULT" +LINE 1: ...ELECT JSON_VALUE(jsonb '"aaa"', '$' RETURNING int DEFAULT 11...Is it intentional that you left many instances of the regression test
output changes like the above?
Actually, thinking more about this, I am wondering if we should not
remove the DEFAULT expression productions in gram.y. Maybe we can
keep the syntax and give an unsupported error during parse-analysis,
like the last version of the patch did for DEFAULT ON EMPTY. Which
also means to also leave JsonBehavior alone but with default_expr
always NULL for now.
--
Thanks, Amit Langote
EDB: http://www.enterprisedb.com
On 2022-08-31 We 07:01, Amit Langote wrote:
On Wed, Aug 31, 2022 at 3:51 PM Amit Langote <amitlangote09@gmail.com> wrote:
SELECT JSON_VALUE(jsonb '"aaa"', '$' RETURNING int DEFAULT 111 ON ERROR); - json_value ------------- - 111 -(1 row) - +ERROR: syntax error at or near "DEFAULT" +LINE 1: ...ELECT JSON_VALUE(jsonb '"aaa"', '$' RETURNING int DEFAULT 11...Is it intentional that you left many instances of the regression test
output changes like the above?Actually, thinking more about this, I am wondering if we should not
remove the DEFAULT expression productions in gram.y. Maybe we can
keep the syntax and give an unsupported error during parse-analysis,
like the last version of the patch did for DEFAULT ON EMPTY. Which
also means to also leave JsonBehavior alone but with default_expr
always NULL for now.
Producing an error in the parse analysis phase seems best to me.
cheers
andrew
--
Andrew Dunstan
EDB: https://www.enterprisedb.com
On 8/31/22 8:38 AM, Andrew Dunstan wrote:
On 2022-08-31 We 07:01, Amit Langote wrote:
On Wed, Aug 31, 2022 at 3:51 PM Amit Langote <amitlangote09@gmail.com> wrote:
SELECT JSON_VALUE(jsonb '"aaa"', '$' RETURNING int DEFAULT 111 ON ERROR); - json_value ------------- - 111 -(1 row) - +ERROR: syntax error at or near "DEFAULT" +LINE 1: ...ELECT JSON_VALUE(jsonb '"aaa"', '$' RETURNING int DEFAULT 11...Is it intentional that you left many instances of the regression test
output changes like the above?Actually, thinking more about this, I am wondering if we should not
remove the DEFAULT expression productions in gram.y. Maybe we can
keep the syntax and give an unsupported error during parse-analysis,
like the last version of the patch did for DEFAULT ON EMPTY. Which
also means to also leave JsonBehavior alone but with default_expr
always NULL for now.Producing an error in the parse analysis phase seems best to me.
Andres, Robert, Tom: With this recent work, have any of your opinions
changed on including SQL/JSON in v15?
Thanks,
Jonathan
Hi,
On 2022-08-31 10:20:24 -0400, Jonathan S. Katz wrote:
Andres, Robert, Tom: With this recent work, have any of your opinions
changed on including SQL/JSON in v15?
I don't really know what to do here. It feels blatantly obvious that this code
isn't even remotely close to being releasable. I'm worried about the impact of
the big revert at this stage of the release cycle, and that's not getting
better by delaying further. And I'm getting weary of being asked to make the
obvious call that the authors of this feature as well as the RMT should have
made a while ago.
From my POV the only real discussion is whether we'd want to revert this in 15
and HEAD or just 15. There's imo a decent point to be made to just revert in
15 and aggressively press forward with the changes posted in this thread.
Greetings,
Andres Freund
On 2022-08-30 Tu 17:25, Nikita Glukhov wrote:
Patches 0001-0006:
Yeah, these add the overhead of an extra function call (typin() ->
typin_opt_error()) in possibly very common paths. Other than
refactoring *all* places that call typin() to use the new API, the
only other option seems to be to leave the typin() functions alone and
duplicate their code in typin_opt_error() versions for all the types
that this patch cares about. Though maybe, that's not necessarily a
better compromise than accepting the extra function call overhead.I think another possibility is to create a static inline function in the
corresponding .c module (say boolin_impl() in bool.c), which is called
by both the opt_error variant as well as the regular one. This would
avoid the duplicate code as well as the added function-call overhead.+1
I always thought about such internal inline functions, I 've added them in v10.
A couple of questions about these:
1. Patch 5 changes the API of DecodeDateTime() and DecodeTimeOnly() by
adding an extra parameter bool *error. Would it be better to provide
_opt_error flavors of these?
2. Patch 6 changes jsonb_from_cstring so that it's no longer static
inline. Shouldn't we have a static inline function that can be called
from inside jsonb.c and is called by the extern function?
changing both of these things would be quite trivial and should not hold
anything up.
cheers
andrew
--
Andrew Dunstan
EDB: https://www.enterprisedb.com
Andres Freund <andres@anarazel.de> writes:
On 2022-08-31 10:20:24 -0400, Jonathan S. Katz wrote:
Andres, Robert, Tom: With this recent work, have any of your opinions
changed on including SQL/JSON in v15?
I don't really know what to do here. It feels blatantly obvious that this code
isn't even remotely close to being releasable. I'm worried about the impact of
the big revert at this stage of the release cycle, and that's not getting
better by delaying further. And I'm getting weary of being asked to make the
obvious call that the authors of this feature as well as the RMT should have
made a while ago.
I have to agree. There is a large amount of code at stake here.
We're being asked to review a bunch of hastily-produced patches
to that code on an even more hasty schedule (and personally
I have other things I need to do today...) I think the odds
of a favorable end result are small.
From my POV the only real discussion is whether we'd want to revert this in 15
and HEAD or just 15. There's imo a decent point to be made to just revert in
15 and aggressively press forward with the changes posted in this thread.
I'm not for that. Code that we don't think is ready to ship
has no business being in the common tree, nor does it make
review any easier to be looking at one bulky set of
already-committed patches and another bulky set of deltas.
I'm okay with making an exception for the include/nodes/ and
backend/nodes/ files in HEAD, since the recent changes in that
area mean it'd be a lot of error-prone work to produce a reverting
patch there. We can leave those in as dead code temporarily, I think.
regards, tom lane
On Wed, Aug 31, 2022 at 10:20 AM Jonathan S. Katz <jkatz@postgresql.org> wrote:
Andres, Robert, Tom: With this recent work, have any of your opinions
changed on including SQL/JSON in v15?
No. Nothing's been committed, and there's no time to review anything
in detail, and there was never going to be. Nikita said he was ready
to start hacking in mid-August. That's good of him, but feature freeze
was in April. We don't start hacking on a feature 4 months after the
freeze. I'm unwilling to drop everything I'm working on to review
patches that were written in a last minute rush. Even if these patches
were more important to me than my own work, which they are not, I
couldn't possibly do a good job reviewing complex patches on top of
other complex patches in an area that I haven't studied in years. And
if I could do a good job, no doubt I'd find a bunch of problems -
whether they would be large or small, I don't know - and then that
would lead to more changes even closer to the intended release date.
I just don't understand what the RMT thinks it is doing here. When a
concern is raised about whether a feature is anywhere close to being
in a releasable state in August, "hack on it some more and then see
where we're at" seems like an obviously impractical way forward. It
seemed clear to me from the moment that Andres raised his concerns
that the only two viable strategies were (1) revert the feature and be
sad or (2) decide to ship it anyway and hope that Andres is incorrect
in thinking that it will become an embarrassment to the project. The
RMT has chosen neither of these, and in fact, really seems to want
someone else to make the decision. But that's not how it works. The
RMT concept was invented precisely to solve problems like this one,
where the patch authors don't really want to revert it but other
people think it's pretty busted. If such problems were best addressed
by waiting for a long time to see whether anything changes, we
wouldn't need an RMT. That's exactly how we used to handle these kinds
of problems, and it sucked.
--
Robert Haas
EDB: http://www.enterprisedb.com
Hi,
On 2022-08-31 12:26:29 -0400, Robert Haas wrote:
On Wed, Aug 31, 2022 at 10:20 AM Jonathan S. Katz <jkatz@postgresql.org> wrote:
Andres, Robert, Tom: With this recent work, have any of your opinions
changed on including SQL/JSON in v15?No. Nothing's been committed, and there's no time to review anything
in detail, and there was never going to be. Nikita said he was ready
to start hacking in mid-August. That's good of him, but feature freeze
was in April.
As additional context: I had started raising those concerns mid June.
Greetings,
Andres Freund
On 8/31/22 12:26 PM, Robert Haas wrote:
On Wed, Aug 31, 2022 at 10:20 AM Jonathan S. Katz <jkatz@postgresql.org> wrote:
Andres, Robert, Tom: With this recent work, have any of your opinions
changed on including SQL/JSON in v15?No. Nothing's been committed, and there's no time to review anything
in detail, and there was never going to be.
OK. Based on this feedback, the RMT is going to request that this is
reverted.
With RMT hat on -- Andrew can you please revert the patchset?
Nikita said he was ready
to start hacking in mid-August. That's good of him, but feature freeze
was in April. We don't start hacking on a feature 4 months after the
freeze. I'm unwilling to drop everything I'm working on to review
patches that were written in a last minute rush. Even if these patches
were more important to me than my own work, which they are not, I
couldn't possibly do a good job reviewing complex patches on top of
other complex patches in an area that I haven't studied in years. And
if I could do a good job, no doubt I'd find a bunch of problems -
whether they would be large or small, I don't know - and then that
would lead to more changes even closer to the intended release date.I just don't understand what the RMT thinks it is doing here. When a
concern is raised about whether a feature is anywhere close to being
in a releasable state in August, "hack on it some more and then see
where we're at" seems like an obviously impractical way forward. It
seemed clear to me from the moment that Andres raised his concerns
that the only two viable strategies were (1) revert the feature and be
sad or (2) decide to ship it anyway and hope that Andres is incorrect
in thinking that it will become an embarrassment to the project. The
RMT has chosen neither of these, and in fact, really seems to want
someone else to make the decision. But that's not how it works. The
RMT concept was invented precisely to solve problems like this one,
where the patch authors don't really want to revert it but other
people think it's pretty busted. If such problems were best addressed
by waiting for a long time to see whether anything changes, we
wouldn't need an RMT. That's exactly how we used to handle these kinds
of problems, and it sucked.
This is fair feedback. However, there are a few things to consider here:
1. When Andres raised his initial concerns, the RMT did recommend to
revert but did not force it. Part of the RMT charter is to try to get
consensus before doing so and after we've exhausted the community
process. As we moved closer, the patch others proposed some suggestions
which other folks were amenable to trying.
Unfortunately, time has run out. However,
2. One of the other main goals of the RMT is to ensure the release ships
"on time" which we define to be late Q3/early Q4. We factored that into
the decision making process around this. We are still on time for the
release.
I take responsibility for the decision making. I would be open to
discuss this further around what worked / what didn't with the RMT and
where we can improve in the future.
Thanks,
Jonathan
I wrote:
Andres Freund <andres@anarazel.de> writes:
From my POV the only real discussion is whether we'd want to revert this in 15
and HEAD or just 15. There's imo a decent point to be made to just revert in
15 and aggressively press forward with the changes posted in this thread.
I'm not for that. Code that we don't think is ready to ship
has no business being in the common tree, nor does it make
review any easier to be looking at one bulky set of
already-committed patches and another bulky set of deltas.
To enlarge on that a bit: it seems to me that the really fundamental
issue here is how to catch datatype-specific input and conversion
errors without using subtransactions, because those are too expensive
and can mask errors we'd rather not be masking, such as OOM. (Andres
had some additional, more localized concerns, but I think this is the
one with big-picture implications.)
The currently proposed patchset hacks up a relatively small number
of core datatypes to be able to do that. But it's just a hack
and there's no prospect of extension types being able to join
in the fun. I think where we need to start, for v16, is making
an API design that will let any datatype have this functionality.
(I don't say that we'd convert every datatype to do so right away;
in the long run we should, but I'm content to start with just the
same core types touched here.) Beside the JSON stuff, there is
another even more pressing application for such behavior, namely
the often-requested COPY functionality to be able to shunt bad data
off somewhere without losing the entire transfer. In the COPY case
I think we'd want to be able to capture the error message that
would have been issued, which means the current patches are not
at all appropriate as a basis for that API design: they're just
returning a bool without any details.
So that's why I'm in favor of reverting and starting over.
There are probably big chunks of what's been done that can be
re-used, but it all needs to be re-examined with this sort of
design in mind.
As a really quick sketch of what such an API might look like:
we could invent a new node type, say IOCallContext, which is
intended to be passed as FunctionCallInfo.context to type
input functions and perhaps type conversion functions.
Call sites wishing to have no-thrown-error functionality would
initialize one of these to show "no error" and then pass it
to the data type's usual input function. Old-style input
functions would ignore this and just throw errors as usual;
sorry, you don't get the no-error functionality you wanted.
But I/O functions that had been updated would know to store the
report of a relevant error into that node and then return NULL.
(Although I think there may be assumptions somewhere that
I/O functions don't return NULL, so maybe "just return any
dummy value" is a better idea? Although likely it wouldn't
be hard to remove such assumptions from callers using this
functionality.) The caller would detect the presence of an error
by examining the node contents and then do whatever it needs to do.
regards, tom lane
On Wed, Aug 31, 2022 at 1:06 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:
The currently proposed patchset hacks up a relatively small number
of core datatypes to be able to do that. But it's just a hack
and there's no prospect of extension types being able to join
in the fun. I think where we need to start, for v16, is making
an API design that will let any datatype have this functionality.
This would be really nice to have.
(I don't say that we'd convert every datatype to do so right away;
in the long run we should, but I'm content to start with just the
same core types touched here.)
I would be in favor of making more of an effort than just a few token
data types. The initial patch could just touch a few, but once the
infrastructure is in place we should really make a sweep through the
tree and tidy up.
Beside the JSON stuff, there is
another even more pressing application for such behavior, namely
the often-requested COPY functionality to be able to shunt bad data
off somewhere without losing the entire transfer. In the COPY case
I think we'd want to be able to capture the error message that
would have been issued, which means the current patches are not
at all appropriate as a basis for that API design: they're just
returning a bool without any details.
Fully agreed.
--
Robert Haas
EDB: http://www.enterprisedb.com
Robert Haas <robertmhaas@gmail.com> writes:
On Wed, Aug 31, 2022 at 1:06 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:
(I don't say that we'd convert every datatype to do so right away;
in the long run we should, but I'm content to start with just the
same core types touched here.)
I would be in favor of making more of an effort than just a few token
data types. The initial patch could just touch a few, but once the
infrastructure is in place we should really make a sweep through the
tree and tidy up.
Sure, but my point is that we can do that in a time-extended fashion
rather than having a flag day where everything must be updated.
The initial patch just needs to update a few types as proof of concept.
regards, tom lane
On 2022-08-31 We 12:48, Jonathan S. Katz wrote:
With RMT hat on -- Andrew can you please revert the patchset?
:-(
Yes, I'll do it, starting with the v15 branch. Might take a day or so.
cheers (kinda)
andrew
--
Andrew Dunstan
EDB: https://www.enterprisedb.com
On Wed, Aug 31, 2022 at 12:26:29PM -0400, Robert Haas wrote:
someone else to make the decision. But that's not how it works. The
RMT concept was invented precisely to solve problems like this one,
where the patch authors don't really want to revert it but other
people think it's pretty busted. If such problems were best addressed
by waiting for a long time to see whether anything changes, we
wouldn't need an RMT. That's exactly how we used to handle these kinds
of problems, and it sucked.
I saw the RMT/Jonathan stated August 28 as the cut-off date for a
decision, which was later changed to September 1:
The RMT is still inclined to revert, but will give folks until Sep 1 0:00
AoE[1] to reach consensus on if SQL/JSON can be included in v15. This matches
up to Andrew's availability timeline for a revert, and gives enough time to
get through the buildfarm prior to the Beta 4 release[2].
I guess you are saying that setting a cut-off was a bad idea, or that
the cut-off was too close to the final release date. For me, I think
there were three questions:
1. Were subtransactions acceptable, consensus no
2. Could trapping errors work for PG 15, consensus no
3. Could the feature be trimmed back for PG 15 to avoid these, consensus ?
I don't think our community works well when there are three issues in
play at once.
--
Bruce Momjian <bruce@momjian.us> https://momjian.us
EDB https://enterprisedb.com
Indecision is a decision. Inaction is an action. Mark Batterson
On Wed, Aug 31, 2022 at 12:04:44PM -0400, Tom Lane wrote:
Andres Freund <andres@anarazel.de> writes:
From my POV the only real discussion is whether we'd want to revert this in 15
and HEAD or just 15. There's imo a decent point to be made to just revert in
15 and aggressively press forward with the changes posted in this thread.I'm not for that. Code that we don't think is ready to ship
has no business being in the common tree, nor does it make
review any easier to be looking at one bulky set of
already-committed patches and another bulky set of deltas.
Agreed on removing from PG 15 and master --- it would be confusing to
have lots of incomplete code in master that is not in PG 15.
I'm okay with making an exception for the include/nodes/ and
backend/nodes/ files in HEAD, since the recent changes in that
area mean it'd be a lot of error-prone work to produce a reverting
patch there. We can leave those in as dead code temporarily, I think.
I don't have an opinion on this.
--
Bruce Momjian <bruce@momjian.us> https://momjian.us
EDB https://enterprisedb.com
Indecision is a decision. Inaction is an action. Mark Batterson
Bruce Momjian <bruce@momjian.us> writes:
I guess you are saying that setting a cut-off was a bad idea, or that
the cut-off was too close to the final release date. For me, I think
there were three questions:
1. Were subtransactions acceptable, consensus no
2. Could trapping errors work for PG 15, consensus no
3. Could the feature be trimmed back for PG 15 to avoid these, consensus ?
We could probably have accomplished #3 if there was more time,
but we're out of time. (I'm not entirely convinced that spending
effort towards #3 was productive anyway, given that we're now thinking
about a much differently-scoped patch with API changes.)
I don't think our community works well when there are three issues in
play at once.
To the extent that there was a management failure here, it was that
we didn't press for a resolution sooner. Given the scale of the
concerns raised in June, I kind of agree with Andres' opinion that
fixing them post-freeze was doomed to failure. It was definitely
doomed once we reached August with no real work done towards it.
regards, tom lane
On 2022-08-31 We 14:45, Tom Lane wrote:
To the extent that there was a management failure here, it was that
we didn't press for a resolution sooner. Given the scale of the
concerns raised in June, I kind of agree with Andres' opinion that
fixing them post-freeze was doomed to failure. It was definitely
doomed once we reached August with no real work done towards it.
I'm not going to comment publicly in general about this, you might
imagine what my reaction is. The decision is the RMT's to make and I
have no quarrel with that.
But I do want it understood that there was work being done right from
the time in June when Andres' complaints were published. These were
difficult issues, and we didn't let the grass grow looking for a fix. I
concede that might not have been visible until later.
cheers
andrew
--
Andrew Dunstan
EDB: https://www.enterprisedb.com
On 8/31/22 3:08 PM, Andrew Dunstan wrote:
On 2022-08-31 We 14:45, Tom Lane wrote:
To the extent that there was a management failure here, it was that
we didn't press for a resolution sooner. Given the scale of the
concerns raised in June, I kind of agree with Andres' opinion that
fixing them post-freeze was doomed to failure. It was definitely
doomed once we reached August with no real work done towards it.I'm not going to comment publicly in general about this, you might
imagine what my reaction is. The decision is the RMT's to make and I
have no quarrel with that.But I do want it understood that there was work being done right from
the time in June when Andres' complaints were published. These were
difficult issues, and we didn't let the grass grow looking for a fix. I
concede that might not have been visible until later.
June was a bit of a rough month too -- we had the issues that spawned
the out-of-cycle release at the top of the month, which started almost
right after Beta 1, and then almost immediately into Beta 2 after 14.4.
I know that consumed a lot of my cycles. At that point in time for the
v15 release process I was primarily focused on monitoring open items at
that point, so I missed the June comments.
Jonathan
On 31.08.2022 20:14, Tom Lane wrote:
Robert Haas<robertmhaas@gmail.com> writes:
On Wed, Aug 31, 2022 at 1:06 PM Tom Lane<tgl@sss.pgh.pa.us> wrote:
The currently proposed patchset hacks up a relatively small number
of core datatypes to be able to do that. But it's just a hack
and there's no prospect of extension types being able to join
in the fun. I think where we need to start, for v16, is making
an API design that will let any datatype have this functionality.(I don't say that we'd convert every datatype to do so right away;
in the long run we should, but I'm content to start with just the
same core types touched here.) Beside the JSON stuff, there is
another even more pressing application for such behavior, namely
the often-requested COPY functionality to be able to shunt bad data
off somewhere without losing the entire transfer. In the COPY case
I think we'd want to be able to capture the error message that
would have been issued, which means the current patches are not
at all appropriate as a basis for that API design: they're just
returning a bool without any details.I would be in favor of making more of an effort than just a few token
data types. The initial patch could just touch a few, but once the
infrastructure is in place we should really make a sweep through the
tree and tidy up.Sure, but my point is that we can do that in a time-extended fashion
rather than having a flag day where everything must be updated.
The initial patch just needs to update a few types as proof of concept.
And here is a quick POC patch with an example for COPY and float4:
=# CREATE TABLE test (i int, f float4);
CREATE TABLE
=# COPY test (f) FROM stdin WITH (null_on_error (f));
1
err
2
\.
COPY 3
=# SELECT f FROM test;
f
---
1
2
(3 rows)
=# COPY test (i) FROM stdin WITH (null_on_error (i));
ERROR: input function for datatype "integer" does not support error handling
PG_RETURN_ERROR() is a reincarnation of ereport_safe() macro for returning
ErrorData, which was present in older versions (~v18) of SQL/JSON patches.
Later it was replaced with `bool *have_error` and less magical
`if (have_error) ... else ereport(...)`.
Obviously, this needs a separate thread.
--
Nikita Glukhov
Postgres Professional:http://www.postgrespro.com
The Russian Postgres Company
Attachments:
0001-Introduce-PG_RETURN_ERROR-and-FuncCallError-node.patchtext/x-patch; charset=UTF-8; name=0001-Introduce-PG_RETURN_ERROR-and-FuncCallError-node.patchDownload
From 537e65e1ebcccdf4a760d3aeeeea743c88611e7c Mon Sep 17 00:00:00 2001
From: Nikita Glukhov <n.gluhov@postgrespro.ru>
Date: Wed, 31 Aug 2022 22:24:33 +0300
Subject: [PATCH 1/5] Introduce PG_RETURN_ERROR and FuncCallError node
---
src/include/nodes/execnodes.h | 7 +++++++
src/include/utils/elog.h | 25 +++++++++++++++++++++++++
2 files changed, 32 insertions(+)
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 01b1727fc09..b401d354656 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -2729,4 +2729,11 @@ typedef struct LimitState
TupleTableSlot *last_slot; /* slot for evaluation of ties */
} LimitState;
+typedef struct FuncCallError
+{
+ NodeTag type;
+ ErrorData *error; /* place where function should put
+ * error data instead of throwing it */
+} FuncCallError;
+
#endif /* EXECNODES_H */
diff --git a/src/include/utils/elog.h b/src/include/utils/elog.h
index 56398176901..5fd3deed61f 100644
--- a/src/include/utils/elog.h
+++ b/src/include/utils/elog.h
@@ -159,6 +159,31 @@
#define ereport(elevel, ...) \
ereport_domain(elevel, TEXTDOMAIN, __VA_ARGS__)
+/*
+ * PG_RETURN_ERROR() -- special macro for copying error info into the
+ * FuncCallError node, if it was as FunctionCallInfo.context,
+ * instead of throwing it with ordinary ereport().
+ *
+ * This is intended for handling of errors of categories like
+ * ERRCODE_DATA_EXCEPTION without PG_TRY/PG_CATCH and substransactions,
+ * but not for errors like ERRCODE_OUT_OF_MEMORY.
+ */
+#define PG_RETURN_ERROR(...) \
+ do { \
+ if (fcinfo->context && IsA(fcinfo->context, FuncCallError)) { \
+ pg_prevent_errno_in_scope(); \
+ \
+ errstart(ERROR, TEXTDOMAIN); \
+ (__VA_ARGS__); \
+ castNode(FuncCallError, fcinfo->context)->error = CopyErrorData(); \
+ FlushErrorState(); \
+ \
+ PG_RETURN_NULL(); \
+ } else { \
+ ereport(ERROR, (__VA_ARGS__)); \
+ } \
+ } while (0)
+
#define TEXTDOMAIN NULL
extern bool message_level_is_interesting(int elevel);
--
2.25.1
0002-Introduce-pg_proc.proissafe-and-func_is_safe.patchtext/x-patch; charset=UTF-8; name=0002-Introduce-pg_proc.proissafe-and-func_is_safe.patchDownload
From 7831b2c571784a614dd4ee4b48730187b0c0a8d1 Mon Sep 17 00:00:00 2001
From: Nikita Glukhov <n.gluhov@postgrespro.ru>
Date: Wed, 31 Aug 2022 23:02:06 +0300
Subject: [PATCH 2/5] Introduce pg_proc.proissafe and func_is_safe()
---
src/backend/utils/cache/lsyscache.c | 19 +++++++++++++++++++
src/include/catalog/pg_proc.h | 3 +++
src/include/utils/lsyscache.h | 1 +
3 files changed, 23 insertions(+)
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index a16a63f4957..ff3005077b2 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -1830,6 +1830,25 @@ get_func_leakproof(Oid funcid)
return result;
}
+/*
+ * func_is_safe
+ * Given procedure id, return the function's proissafe flag.
+ */
+bool
+func_is_safe(Oid funcid)
+{
+ HeapTuple tp;
+ bool result;
+
+ tp = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcid));
+ if (!HeapTupleIsValid(tp))
+ elog(ERROR, "cache lookup failed for function %u", funcid);
+
+ result = ((Form_pg_proc) GETSTRUCT(tp))->proissafe;
+ ReleaseSysCache(tp);
+ return result;
+}
+
/*
* get_func_support
*
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index 76310d4cc9a..2ae8942b014 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -70,6 +70,9 @@ CATALOG(pg_proc,1255,ProcedureRelationId) BKI_BOOTSTRAP BKI_ROWTYPE_OID(81,Proce
/* returns a set? */
bool proretset BKI_DEFAULT(f);
+ /* can return error info through FuncCallError instead of throwing it? */
+ bool proissafe BKI_DEFAULT(f);
+
/* see PROVOLATILE_ categories below */
char provolatile BKI_DEFAULT(i);
diff --git a/src/include/utils/lsyscache.h b/src/include/utils/lsyscache.h
index 50f02883052..c66365fdf2c 100644
--- a/src/include/utils/lsyscache.h
+++ b/src/include/utils/lsyscache.h
@@ -130,6 +130,7 @@ extern char func_volatile(Oid funcid);
extern char func_parallel(Oid funcid);
extern char get_func_prokind(Oid funcid);
extern bool get_func_leakproof(Oid funcid);
+extern bool func_is_safe(Oid funcid);
extern RegProcedure get_func_support(Oid funcid);
extern Oid get_relname_relid(const char *relname, Oid relnamespace);
extern char *get_rel_name(Oid relid);
--
2.25.1
0003-Use-PG_RETURN_ERROR-in-float4in.patchtext/x-patch; charset=UTF-8; name=0003-Use-PG_RETURN_ERROR-in-float4in.patchDownload
From 4eba0bd771b85abef94125f374261d7734178657 Mon Sep 17 00:00:00 2001
From: Nikita Glukhov <n.gluhov@postgrespro.ru>
Date: Wed, 31 Aug 2022 22:28:09 +0300
Subject: [PATCH 3/5] Use PG_RETURN_ERROR in float4in()
---
src/backend/utils/adt/float.c | 9 +++++----
src/include/catalog/pg_proc.dat | 2 +-
2 files changed, 6 insertions(+), 5 deletions(-)
diff --git a/src/backend/utils/adt/float.c b/src/backend/utils/adt/float.c
index fc8f39a7a98..e3bde72b328 100644
--- a/src/backend/utils/adt/float.c
+++ b/src/backend/utils/adt/float.c
@@ -25,6 +25,7 @@
#include "common/shortest_dec.h"
#include "libpq/pqformat.h"
#include "miscadmin.h"
+#include "nodes/execnodes.h"
#include "utils/array.h"
#include "utils/float.h"
#include "utils/fmgrprotos.h"
@@ -183,7 +184,7 @@ float4in(PG_FUNCTION_ARGS)
* strtod() on different platforms.
*/
if (*num == '\0')
- ereport(ERROR,
+ PG_RETURN_ERROR(
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
errmsg("invalid input syntax for type %s: \"%s\"",
"real", orig_num)));
@@ -257,13 +258,13 @@ float4in(PG_FUNCTION_ARGS)
(val >= HUGE_VALF || val <= -HUGE_VALF)
#endif
)
- ereport(ERROR,
+ PG_RETURN_ERROR(
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("\"%s\" is out of range for type real",
orig_num)));
}
else
- ereport(ERROR,
+ PG_RETURN_ERROR(
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
errmsg("invalid input syntax for type %s: \"%s\"",
"real", orig_num)));
@@ -275,7 +276,7 @@ float4in(PG_FUNCTION_ARGS)
/* if there is any junk left at the end of the string, bail out */
if (*endptr != '\0')
- ereport(ERROR,
+ PG_RETURN_ERROR(
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
errmsg("invalid input syntax for type %s: \"%s\"",
"real", orig_num)));
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index be47583122b..b25bfa55ab3 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -585,7 +585,7 @@
{ oid => '200', descr => 'I/O',
proname => 'float4in', prorettype => 'float4', proargtypes => 'cstring',
- prosrc => 'float4in' },
+ proissafe => 't', prosrc => 'float4in' },
{ oid => '201', descr => 'I/O',
proname => 'float4out', prorettype => 'cstring', proargtypes => 'float4',
prosrc => 'float4out' },
--
2.25.1
0004-Introduce-InputFunctionCallOptError.patchtext/x-patch; charset=UTF-8; name=0004-Introduce-InputFunctionCallOptError.patchDownload
From b8001f469e83df048e972dcd17120929996ba5fa Mon Sep 17 00:00:00 2001
From: Nikita Glukhov <n.gluhov@postgrespro.ru>
Date: Wed, 31 Aug 2022 22:28:44 +0300
Subject: [PATCH 4/5] Introduce InputFunctionCallOptError()
---
src/backend/utils/fmgr/fmgr.c | 38 +++++++++++++++++++++++++++++++++--
src/include/fmgr.h | 3 +++
2 files changed, 39 insertions(+), 2 deletions(-)
diff --git a/src/backend/utils/fmgr/fmgr.c b/src/backend/utils/fmgr/fmgr.c
index a9dd068095b..fbea0df61df 100644
--- a/src/backend/utils/fmgr/fmgr.c
+++ b/src/backend/utils/fmgr/fmgr.c
@@ -1511,11 +1511,14 @@ OidFunctionCall9Coll(Oid functionId, Oid collation, Datum arg1, Datum arg2,
* function anyway if it's not strict. So this is almost but not quite
* the same as FunctionCall3.
*/
-Datum
-InputFunctionCall(FmgrInfo *flinfo, char *str, Oid typioparam, int32 typmod)
+static inline Datum
+InputFunctionCallInternal(FmgrInfo *flinfo, char *str,
+ Oid typioparam, int32 typmod,
+ ErrorData **edata)
{
LOCAL_FCINFO(fcinfo, 3);
Datum result;
+ FuncCallError *fcerror;
if (str == NULL && flinfo->fn_strict)
return (Datum) 0; /* just return null result */
@@ -1529,8 +1532,22 @@ InputFunctionCall(FmgrInfo *flinfo, char *str, Oid typioparam, int32 typmod)
fcinfo->args[2].value = Int32GetDatum(typmod);
fcinfo->args[2].isnull = false;
+ if (edata)
+ {
+ fcerror = makeNode(FuncCallError);
+ fcinfo->context = (Node *) fcerror;
+ }
+
result = FunctionCallInvoke(fcinfo);
+ if (edata)
+ {
+ *edata = fcerror->error;
+ pfree(fcerror);
+ if (*edata)
+ return (Datum) 0;
+ }
+
/* Should get null result if and only if str is NULL */
if (str == NULL)
{
@@ -1548,6 +1565,23 @@ InputFunctionCall(FmgrInfo *flinfo, char *str, Oid typioparam, int32 typmod)
return result;
}
+Datum
+InputFunctionCall(FmgrInfo *flinfo, char *str, Oid typioparam, int32 typmod)
+{
+ return InputFunctionCallInternal(flinfo, str, typioparam, typmod, NULL);
+}
+
+Datum
+InputFunctionCallOptError(FmgrInfo *flinfo, char *str, Oid typioparam,
+ int32 typmod, ErrorData **edata)
+{
+ if (edata)
+ return InputFunctionCallInternal(flinfo, str, typioparam, typmod, edata);
+ else
+ return InputFunctionCall(flinfo, str, typioparam, typmod);
+}
+
+
/*
* Call a previously-looked-up datatype output function.
*
diff --git a/src/include/fmgr.h b/src/include/fmgr.h
index 380a82b9de3..8f18f2408e1 100644
--- a/src/include/fmgr.h
+++ b/src/include/fmgr.h
@@ -711,6 +711,9 @@ extern Datum OidReceiveFunctionCall(Oid functionId, fmStringInfo buf,
extern bytea *SendFunctionCall(FmgrInfo *flinfo, Datum val);
extern bytea *OidSendFunctionCall(Oid functionId, Datum val);
+extern Datum InputFunctionCallOptError(FmgrInfo *flinfo, char *str,
+ Oid typioparam, int32 typmod,
+ ErrorData **edata);
/*
* Routines in fmgr.c
--
2.25.1
0005-Introduce-NULL_ON_ERROR-option-for-COPY-FROM.patchtext/x-patch; charset=UTF-8; name=0005-Introduce-NULL_ON_ERROR-option-for-COPY-FROM.patchDownload
From 1fc1b4231b079078c303be75864c4ef71d0742a1 Mon Sep 17 00:00:00 2001
From: Nikita Glukhov <n.gluhov@postgrespro.ru>
Date: Wed, 31 Aug 2022 22:29:47 +0300
Subject: [PATCH 5/5] Introduce NULL_ON_ERROR option for COPY FROM
---
src/backend/commands/copy.c | 20 +++++++++++++++++
src/backend/commands/copyfrom.c | 33 ++++++++++++++++++++++++++++
src/backend/commands/copyfromparse.c | 21 +++++++++++++-----
src/include/commands/copy.h | 2 ++
src/test/regress/expected/copy.out | 15 +++++++++++++
src/test/regress/sql/copy.sql | 13 +++++++++++
6 files changed, 99 insertions(+), 5 deletions(-)
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 49924e476af..ce7e875f655 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -551,6 +551,19 @@ ProcessCopyOptions(ParseState *pstate,
defel->defname),
parser_errposition(pstate, defel->location)));
}
+ else if (strcmp(defel->defname, "null_on_error") == 0)
+ {
+ if (opts_out->null_on_error)
+ errorConflictingDefElem(defel, pstate);
+ if (defel->arg && IsA(defel->arg, List))
+ opts_out->null_on_error = castNode(List, defel->arg);
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("argument to option \"%s\" must be a list of column names",
+ defel->defname),
+ parser_errposition(pstate, defel->location)));
+ }
else
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
@@ -701,6 +714,13 @@ ProcessCopyOptions(ParseState *pstate,
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("CSV quote character must not appear in the NULL specification")));
+
+ /* Check null_on_error */
+ if (opts_out->null_on_error != NIL && !is_from)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("COPY null on error only available using COPY FROM")));
+
}
/*
diff --git a/src/backend/commands/copyfrom.c b/src/backend/commands/copyfrom.c
index e8bb168aea8..99b39d8d7c2 100644
--- a/src/backend/commands/copyfrom.c
+++ b/src/backend/commands/copyfrom.c
@@ -47,6 +47,7 @@
#include "rewrite/rewriteHandler.h"
#include "storage/fd.h"
#include "tcop/tcopprot.h"
+#include "utils/builtins.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
#include "utils/portal.h"
@@ -1316,6 +1317,29 @@ BeginCopyFrom(ParseState *pstate,
}
}
+ /* Convert NULL_ON_ERROR name list to per-column flags, check validity */
+ cstate->opts.null_on_error_flags = (bool *) palloc0(num_phys_attrs * sizeof(bool));
+ if (cstate->opts.null_on_error)
+ {
+ List *attnums;
+ ListCell *cur;
+
+ attnums = CopyGetAttnums(tupDesc, cstate->rel, cstate->opts.null_on_error);
+
+ foreach(cur, attnums)
+ {
+ int attnum = lfirst_int(cur);
+ Form_pg_attribute attr = TupleDescAttr(tupDesc, attnum - 1);
+
+ if (!list_member_int(cstate->attnumlist, attnum))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
+ errmsg("NULL_ON_ERROR column \"%s\" not referenced by COPY",
+ NameStr(attr->attname))));
+ cstate->opts.null_on_error_flags[attnum - 1] = true;
+ }
+ }
+
/* Use client encoding when ENCODING option is not specified. */
if (cstate->opts.file_encoding < 0)
cstate->file_encoding = pg_get_client_encoding();
@@ -1417,6 +1441,15 @@ BeginCopyFrom(ParseState *pstate,
&in_func_oid, &typioparams[attnum - 1]);
fmgr_info(in_func_oid, &in_functions[attnum - 1]);
+ /* check whether input function supports returning errors */
+ if (cstate->opts.null_on_error_flags[attnum - 1] &&
+ !func_is_safe(in_func_oid))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("input function for datatype \"%s\" does not support error handling",
+ format_type_be(att->atttypid))));
+
+
/* Get default info if needed */
if (!list_member_int(cstate->attnumlist, attnum) && !att->attgenerated)
{
diff --git a/src/backend/commands/copyfromparse.c b/src/backend/commands/copyfromparse.c
index 097414ef12d..04a36de444c 100644
--- a/src/backend/commands/copyfromparse.c
+++ b/src/backend/commands/copyfromparse.c
@@ -897,6 +897,8 @@ NextCopyFrom(CopyFromState cstate, ExprContext *econtext,
int attnum = lfirst_int(cur);
int m = attnum - 1;
Form_pg_attribute att = TupleDescAttr(tupDesc, m);
+ ErrorData *edata = NULL;
+ ErrorData **p_edata;
if (fieldno >= fldct)
ereport(ERROR,
@@ -938,11 +940,20 @@ NextCopyFrom(CopyFromState cstate, ExprContext *econtext,
cstate->cur_attname = NameStr(att->attname);
cstate->cur_attval = string;
- values[m] = InputFunctionCall(&in_functions[m],
- string,
- typioparams[m],
- att->atttypmod);
- if (string != NULL)
+
+ p_edata = cstate->opts.null_on_error_flags[m] ? &edata : NULL;
+
+ values[m] = InputFunctionCallOptError(&in_functions[m],
+ string,
+ typioparams[m],
+ att->atttypmod,
+ p_edata);
+ if (edata)
+ {
+ FreeErrorData(edata);
+ nulls[m] = true;
+ }
+ else if (string != NULL)
nulls[m] = false;
cstate->cur_attname = NULL;
cstate->cur_attval = NULL;
diff --git a/src/include/commands/copy.h b/src/include/commands/copy.h
index cb0096aeb67..359fb4cc9e0 100644
--- a/src/include/commands/copy.h
+++ b/src/include/commands/copy.h
@@ -59,6 +59,8 @@ typedef struct CopyFormatOptions
bool *force_null_flags; /* per-column CSV FN flags */
bool convert_selectively; /* do selective binary conversion? */
List *convert_select; /* list of column names (can be NIL) */
+ List *null_on_error; /* list of column names */
+ bool *null_on_error_flags; /* per-column NOE flags */
} CopyFormatOptions;
/* These are private in commands/copy[from|to].c */
diff --git a/src/test/regress/expected/copy.out b/src/test/regress/expected/copy.out
index 3fad1c52d1f..82839d2f67a 100644
--- a/src/test/regress/expected/copy.out
+++ b/src/test/regress/expected/copy.out
@@ -240,3 +240,18 @@ SELECT * FROM header_copytest ORDER BY a;
(5 rows)
drop table header_copytest;
+create table null_on_error_copytest(i int, f float4);
+copy null_on_error_copytest from stdin with (null_on_error (i, f));
+ERROR: input function for datatype "integer" does not support error handling
+copy null_on_error_copytest from stdin with (null_on_error (f));
+copy null_on_error_copytest from stdin with (null_on_error (f));
+ERROR: invalid input syntax for type integer: "err"
+CONTEXT: COPY null_on_error_copytest, line 2, column i: "err"
+select * from null_on_error_copytest;
+ i | f
+---+-----
+ 1 | 2.3
+ 2 |
+(2 rows)
+
+drop table null_on_error_copytest;
diff --git a/src/test/regress/sql/copy.sql b/src/test/regress/sql/copy.sql
index 285022e07c6..914e29d5ba1 100644
--- a/src/test/regress/sql/copy.sql
+++ b/src/test/regress/sql/copy.sql
@@ -268,3 +268,16 @@ a c b
SELECT * FROM header_copytest ORDER BY a;
drop table header_copytest;
+
+create table null_on_error_copytest(i int, f float4);
+copy null_on_error_copytest from stdin with (null_on_error (i, f));
+copy null_on_error_copytest from stdin with (null_on_error (f));
+1 2.3
+2 err
+\.
+copy null_on_error_copytest from stdin with (null_on_error (f));
+1 2.3
+err 4.5
+\.
+select * from null_on_error_copytest;
+drop table null_on_error_copytest;
--
2.25.1
On 31.08.2022 23:39, Nikita Glukhov wrote:
And here is a quick POC patch with an example for COPY and float4
I decided to go further and use new API in SQL/JSON functions
(even if it does not make real sense now).
I have added function for checking expressions trees, special
executor steps for handling errors in FuncExpr, CoerceViaIO,
CoerceToDomain which are passed through ExprState.edata.
Of course, there is still a lot of work:
1. JIT for new expression steps
2. Removal of subsidary ExprStates (needs another solution for
ErrorData passing)
3. Checking of domain constraint expressions
4. Error handling in coercion to bytea
5. Error handling in json_populate_type()
6. Error handling in jsonb::type casts
7. ...
Also I have added lazy creation of JSON_VALUE coercions, which was
not present in previous patches. It really greatly speeds up JIT
and reduces memory consumption. But it requires using of subsidary
ExprStates.
jsonb_sqljson test now fails because of points 4, 5, 6.
--
Nikita Glukhov
Postgres Professional:http://www.postgrespro.com
The Russian Postgres Company
Attachments:
On 2022-08-31 We 14:22, Andrew Dunstan wrote:
On 2022-08-31 We 12:48, Jonathan S. Katz wrote:
With RMT hat on -- Andrew can you please revert the patchset?
:-(
Yes, I'll do it, starting with the v15 branch. Might take a day or so.
done
cheers
andrew
--
Andrew Dunstan
EDB: https://www.enterprisedb.com
On 9/1/22 5:13 PM, Andrew Dunstan wrote:
On 2022-08-31 We 14:22, Andrew Dunstan wrote:
On 2022-08-31 We 12:48, Jonathan S. Katz wrote:
With RMT hat on -- Andrew can you please revert the patchset?
:-(
Yes, I'll do it, starting with the v15 branch. Might take a day or so.
done
Thank you Andrew.
Jonathan
On Wed, Aug 31, 2022 at 03:51:18PM +0900, Amit Langote wrote:
Finally, I get this warning:
execExprInterp.c: In function ‘ExecJsonCoerceCStringToText’:
execExprInterp.c:4765:3: warning: missing braces around initializer
[-Wmissing-braces]
NameData encoding = {0};
^
execExprInterp.c:4765:3: warning: (near initialization for
‘encoding.data’) [-Wmissing-braces]
With what compiler ?
This has came up before:
20211202033145.GK17618@telsasoft.com
20220716115932.GV18011@telsasoft.com
On Fri, Sep 2, 2022 at 8:56 PM Justin Pryzby <pryzby@telsasoft.com> wrote:
On Wed, Aug 31, 2022 at 03:51:18PM +0900, Amit Langote wrote:
Finally, I get this warning:
execExprInterp.c: In function ‘ExecJsonCoerceCStringToText’:
execExprInterp.c:4765:3: warning: missing braces around initializer
[-Wmissing-braces]
NameData encoding = {0};
^
execExprInterp.c:4765:3: warning: (near initialization for
‘encoding.data’) [-Wmissing-braces]With what compiler ?
This has came up before:
20211202033145.GK17618@telsasoft.com
20220716115932.GV18011@telsasoft.com
Didn't realize it when I was reviewing the patch but somehow my build
script had started using gcc (GCC) 4.8.5 20150623 (Red Hat 4.8.5-44),
which I know is old.
- Amit
On 2022-09-01 Th 09:54, Nikita Glukhov wrote:
On 31.08.2022 23:39, Nikita Glukhov wrote:
And here is a quick POC patch with an example for COPY and float4
I decided to go further and use new API in SQL/JSON functions
(even if it does not make real sense now).I have added function for checking expressions trees, special
executor steps for handling errors in FuncExpr, CoerceViaIO,
CoerceToDomain which are passed through ExprState.edata.Of course, there is still a lot of work:
1. JIT for new expression steps
2. Removal of subsidary ExprStates (needs another solution for
ErrorData passing)
3. Checking of domain constraint expressions
4. Error handling in coercion to bytea
5. Error handling in json_populate_type()
6. Error handling in jsonb::type casts
7. ...Also I have added lazy creation of JSON_VALUE coercions, which was
not present in previous patches. It really greatly speeds up JIT
and reduces memory consumption. But it requires using of subsidary
ExprStates.jsonb_sqljson test now fails because of points 4, 5, 6.
It looks like this needs to be rebased anyway.
I suggest just submitting the Input function stuff on its own, I think
that means not patches 3,4,15 at this stage. Maybe we would also need a
small test module to call the functions, or at least some of them.
The earlier we can get this in the earlier SQL/JSON patches based on it
can be considered.
A few comments:
. proissafe isn't really a very informative name. Safe for what? maybe
proerrorsafe or something would be better?
. I don't think we need the if test or else clause here:
+ if (edata)
+ return InputFunctionCallInternal(flinfo, str, typioparam,
typmod, edata);
+ else
+ return InputFunctionCall(flinfo, str, typioparam, typmod);
. I think we should probably cover float8 as well as float4, and there
might be some other odd gaps.
As mentioned previously, this should really go in a new thread, so
please don't reply to this but start a completely new thread.
cheers
andrew
--
Andrew Dunstan
EDB: https://www.enterprisedb.com
Andrew Dunstan <andrew@dunslane.net> writes:
I suggest just submitting the Input function stuff on its own, I think
that means not patches 3,4,15 at this stage. Maybe we would also need a
small test module to call the functions, or at least some of them.
The earlier we can get this in the earlier SQL/JSON patches based on it
can be considered.
+1
. proissafe isn't really a very informative name. Safe for what? maybe
proerrorsafe or something would be better?
I strongly recommend against having a new pg_proc column at all.
I doubt that you really need it, and having one will create
enormous mechanical burdens to making the conversion. (For example,
needing a catversion bump every time we convert one more function,
or an extension version bump to convert extensions.)
regards, tom lane