From cb599b9ddfccd15e77f4c167c4e5bdf1ddc3af38 Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Thu, 12 Sep 2024 14:30:29 -0400 Subject: [PATCH] Make jsonpath .string() be immutable for datetimes. Discussion of commit ed055d249 revealed that we don't actually want jsonpath's .string() method to depend on DateStyle, nor TimeZone either, because the non-"_tz" jsonpath functions are supposed to be immutable. Potentially we could allow a TimeZone dependency in the "_tz" variants, but it seems better to just uniformly define this method as returning the same string that jsonb text output would do. That's easier to implement too, saving a couple dozen lines. Patch by me, per complaint from Peter Eisentraut. Back-patch to v17 where this feature came in (in 66ea94e8e). Also back-patch ed055d249 to provide test cases. Discussion: https://postgr.es/m/5e8879d0-a3c8-4be2-950f-d83aa2af953a@eisentraut.org --- doc/src/sgml/func.sgml | 7 +- src/backend/utils/adt/jsonpath_exec.c | 34 ++------ src/test/regress/expected/jsonb_jsonpath.out | 83 +++++++------------- src/test/regress/sql/jsonb_jsonpath.sql | 12 ++- 4 files changed, 44 insertions(+), 92 deletions(-) diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml index e468ee62ff..d36e4979b6 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -18017,16 +18017,15 @@ ERROR: jsonpath member accessor can only be applied to an object String value converted from a JSON boolean, number, string, or - datetime (the output format for datetimes is determined by - the parameter) + datetime jsonb_path_query_array('[1.23, "xyz", false]', '$[*].string()') ["1.23", "xyz", "false"] - jsonb_path_query('"2023-08-15"', '$.datetime().string()') - "2023-08-15" + jsonb_path_query('"2023-08-15 12:34:56"', '$.timestamp().string()') + "2023-08-15T12:34:56" diff --git a/src/backend/utils/adt/jsonpath_exec.c b/src/backend/utils/adt/jsonpath_exec.c index e569c7efb8..1184cba983 100644 --- a/src/backend/utils/adt/jsonpath_exec.c +++ b/src/backend/utils/adt/jsonpath_exec.c @@ -72,6 +72,7 @@ #include "utils/datetime.h" #include "utils/float.h" #include "utils/formatting.h" +#include "utils/json.h" #include "utils/jsonpath.h" #include "utils/lsyscache.h" #include "utils/memutils.h" @@ -1629,32 +1630,13 @@ executeItemOptUnwrapTarget(JsonPathExecContext *cxt, JsonPathItem *jsp, break; case jbvDatetime: { - switch (jb->val.datetime.typid) - { - case DATEOID: - tmp = DatumGetCString(DirectFunctionCall1(date_out, - jb->val.datetime.value)); - break; - case TIMEOID: - tmp = DatumGetCString(DirectFunctionCall1(time_out, - jb->val.datetime.value)); - break; - case TIMETZOID: - tmp = DatumGetCString(DirectFunctionCall1(timetz_out, - jb->val.datetime.value)); - break; - case TIMESTAMPOID: - tmp = DatumGetCString(DirectFunctionCall1(timestamp_out, - jb->val.datetime.value)); - break; - case TIMESTAMPTZOID: - tmp = DatumGetCString(DirectFunctionCall1(timestamptz_out, - jb->val.datetime.value)); - break; - default: - elog(ERROR, "unrecognized SQL/JSON datetime type oid: %u", - jb->val.datetime.typid); - } + char buf[MAXDATELEN + 1]; + + JsonEncodeDateTime(buf, + jb->val.datetime.value, + jb->val.datetime.typid, + &jb->val.datetime.tz); + tmp = pstrdup(buf); } break; case jbvNull: diff --git a/src/test/regress/expected/jsonb_jsonpath.out b/src/test/regress/expected/jsonb_jsonpath.out index 70eeb655a2..acdf7e436f 100644 --- a/src/test/regress/expected/jsonb_jsonpath.out +++ b/src/test/regress/expected/jsonb_jsonpath.out @@ -2652,73 +2652,30 @@ select jsonb_path_query('"2023-08-15 12:34:56 +5:30"', '$.timestamp().string()') ERROR: cannot convert value from timestamptz to timestamp without time zone usage HINT: Use *_tz() function for time zone support. select jsonb_path_query_tz('"2023-08-15 12:34:56 +5:30"', '$.timestamp().string()'); -- should work - jsonb_path_query_tz ----------------------------- - "Tue Aug 15 00:04:56 2023" + jsonb_path_query_tz +----------------------- + "2023-08-15T00:04:56" (1 row) select jsonb_path_query('"2023-08-15 12:34:56"', '$.timestamp_tz().string()'); ERROR: cannot convert value from timestamp to timestamptz without time zone usage HINT: Use *_tz() function for time zone support. select jsonb_path_query_tz('"2023-08-15 12:34:56"', '$.timestamp_tz().string()'); -- should work - jsonb_path_query_tz --------------------------------- - "Tue Aug 15 12:34:56 2023 PDT" + jsonb_path_query_tz +----------------------------- + "2023-08-15T12:34:56-07:00" (1 row) select jsonb_path_query('"2023-08-15 12:34:56 +5:30"', '$.timestamp_tz().string()'); - jsonb_path_query --------------------------------- - "Tue Aug 15 00:04:56 2023 PDT" -(1 row) - -select jsonb_path_query('"2023-08-15 12:34:56"', '$.timestamp().string()'); - jsonb_path_query ----------------------------- - "Tue Aug 15 12:34:56 2023" -(1 row) - -select jsonb_path_query('"12:34:56 +5:30"', '$.time_tz().string()'); - jsonb_path_query ------------------- - "12:34:56+05:30" -(1 row) - -select jsonb_path_query_tz('"12:34:56"', '$.time_tz().string()'); - jsonb_path_query_tz ---------------------- - "12:34:56-07" -(1 row) - -select jsonb_path_query('"12:34:56"', '$.time().string()'); - jsonb_path_query ------------------- - "12:34:56" -(1 row) - -select jsonb_path_query('"2023-08-15"', '$.date().string()'); - jsonb_path_query ------------------- - "08-15-2023" -(1 row) - -set datestyle = 'ISO'; -select jsonb_path_query_tz('"2023-08-15 12:34:56"', '$.timestamp_tz().string()'); - jsonb_path_query_tz --------------------------- - "2023-08-15 12:34:56-07" -(1 row) - -select jsonb_path_query('"2023-08-15 12:34:56 +5:30"', '$.timestamp_tz().string()'); - jsonb_path_query --------------------------- - "2023-08-15 00:04:56-07" + jsonb_path_query +----------------------------- + "2023-08-15T12:34:56+05:30" (1 row) select jsonb_path_query('"2023-08-15 12:34:56"', '$.timestamp().string()'); jsonb_path_query ----------------------- - "2023-08-15 12:34:56" + "2023-08-15T12:34:56" (1 row) select jsonb_path_query('"12:34:56 +5:30"', '$.time_tz().string()'); @@ -2730,7 +2687,7 @@ select jsonb_path_query('"12:34:56 +5:30"', '$.time_tz().string()'); select jsonb_path_query_tz('"12:34:56"', '$.time_tz().string()'); jsonb_path_query_tz --------------------- - "12:34:56-07" + "12:34:56-07:00" (1 row) select jsonb_path_query('"12:34:56"', '$.time().string()'); @@ -2745,7 +2702,23 @@ select jsonb_path_query('"2023-08-15"', '$.date().string()'); "2023-08-15" (1 row) -reset datestyle; +-- .string() does not react to timezone or datestyle +begin; +set local timezone = 'UTC'; +set local datestyle = 'German'; +select jsonb_path_query('"2023-08-15 12:34:56 +5:30"', '$.timestamp_tz().string()'); + jsonb_path_query +----------------------------- + "2023-08-15T12:34:56+05:30" +(1 row) + +select jsonb_path_query('"2023-08-15 12:34:56"', '$.timestamp().string()'); + jsonb_path_query +----------------------- + "2023-08-15T12:34:56" +(1 row) + +rollback; -- Test .time() select jsonb_path_query('null', '$.time()'); ERROR: jsonpath item method .time() can only be applied to a string diff --git a/src/test/regress/sql/jsonb_jsonpath.sql b/src/test/regress/sql/jsonb_jsonpath.sql index 4d57e13eda..da3f7969ca 100644 --- a/src/test/regress/sql/jsonb_jsonpath.sql +++ b/src/test/regress/sql/jsonb_jsonpath.sql @@ -611,15 +611,13 @@ select jsonb_path_query_tz('"12:34:56"', '$.time_tz().string()'); select jsonb_path_query('"12:34:56"', '$.time().string()'); select jsonb_path_query('"2023-08-15"', '$.date().string()'); -set datestyle = 'ISO'; -select jsonb_path_query_tz('"2023-08-15 12:34:56"', '$.timestamp_tz().string()'); +-- .string() does not react to timezone or datestyle +begin; +set local timezone = 'UTC'; +set local datestyle = 'German'; select jsonb_path_query('"2023-08-15 12:34:56 +5:30"', '$.timestamp_tz().string()'); select jsonb_path_query('"2023-08-15 12:34:56"', '$.timestamp().string()'); -select jsonb_path_query('"12:34:56 +5:30"', '$.time_tz().string()'); -select jsonb_path_query_tz('"12:34:56"', '$.time_tz().string()'); -select jsonb_path_query('"12:34:56"', '$.time().string()'); -select jsonb_path_query('"2023-08-15"', '$.date().string()'); -reset datestyle; +rollback; -- Test .time() select jsonb_path_query('null', '$.time()');