pg.ddx.io pgsql-hackers@postgresql.org mailing list archive
help / color / mirror / Atom feed From: Nikita Glukhov <n.gluhov@postgrespro.ru>
To: Amit Langote <amitlangote09@gmail.com>
To: Alvaro Herrera <alvherre@alvh.no-ip.org>
Cc: Andrew Dunstan <andrew@dunslane.net>
Cc: Jonathan S. Katz <jkatz@postgresql.org>
Cc: Tom Lane <tgl@sss.pgh.pa.us>
Cc: Robert Haas <robertmhaas@gmail.com>
Cc: Andres Freund <andres@anarazel.de>
Cc: PostgreSQL Hackers <pgsql-hackers@lists.postgresql.org>
Cc: Michael Paquier <michael@paquier.xyz>
Cc: John Naylor <john.naylor@enterprisedb.com>
Subject: Re: SQL/JSON features for v15
Date: Wed, 31 Aug 2022 00:25:01 +0300
Message-ID: <f54ebd2b-0e67-d1c6-4ff7-5d732492d1a0@postgrespro.ru> (raw )
In-Reply-To: <CA+HiwqFvP23i3kTHt6TW4FqO6he1YSJXeyGOdFV_YbP=xeS99A@mail.gmail.com >
References: <CA+HiwqH8gTp2uNN3QtgeOSgHhZUZz1=0U+JObJJUmEoch_269Q@mail.gmail.com >
<20220830092008.6dbublx6la226zis@alvherre.pgsql >
<CA+HiwqFvP23i3kTHt6TW4FqO6he1YSJXeyGOdFV_YbP=xeS99A@mail.gmail.com >
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-0001-Add-safe-input-function-for-bool.patch (text/x-patch, 2.4K) download
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.patch (text/x-patch, 2.3K) download
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.patch (text/x-patch, 4.4K) download
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.patch (text/x-patch, 14.8K) download
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.patch (text/x-patch, 23.9K) download
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
v10-0006-Add-safe-input-function-for-jsonb.patch (text/x-patch, 3.1K) download
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.patch (text/x-patch, 107.0K) download
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.patch (text/x-patch, 22.4K) download
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.patch (text/x-patch, 29.3K) download
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.patch (text/x-patch, 56.0K) download
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
view thread (95+ messages) latest in thread
Message-ID: <f54ebd2b-0e67-d1c6-4ff7-5d732492d1a0@postgrespro.ru>
Permalink: ../f54ebd2b-0e67-d1c6-4ff7-5d732492d1a0@postgrespro.ru/
Also on: postgresql.org/message-id/f54ebd2b-0e67-d1c6-4ff7-5d732492d1a0@postgrespro.ru
copy link · copy postgr.es · copy git am
reply Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Reply to all the recipients using the --to and --cc options:
reply via email
To: pgsql-hackers@postgresql.org
Cc: n.gluhov@postgrespro.ru, amitlangote09@gmail.com, alvherre@alvh.no-ip.org, andrew@dunslane.net, jkatz@postgresql.org, tgl@sss.pgh.pa.us, robertmhaas@gmail.com, andres@anarazel.de, pgsql-hackers@lists.postgresql.org, michael@paquier.xyz, john.naylor@enterprisedb.com
Subject: Re: SQL/JSON features for v15
In-Reply-To: <f54ebd2b-0e67-d1c6-4ff7-5d732492d1a0@postgrespro.ru>
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
This inbox is served by DDX for PostgreSQL; see mirroring instructions
for how to clone and mirror all data and code used for this inbox