On Sun, Apr 3, 2022 at 12:44 PM Joseph Koshakow <kosh...@gmail.com> wrote: > > On Sun, Apr 3, 2022 at 12:30 PM Tom Lane <t...@sss.pgh.pa.us> wrote: > > > > Joseph Koshakow <kosh...@gmail.com> writes: > > > So I think we need to check that endptr has moved both after > > > the call to strtoi64() and strtod(). > > > > I'm not sure we need to do that explicitly, given that there's > > a check later as to whether endptr is pointing at \0; that will > > fail if endptr wasn't advanced. > > > > The fix I was loosely envisioning was to check for cp[1] == '\0' > > and not bother calling strtod() in that case. > > Ah, ok I see what you mean. I agree an approach like that should > work, but I don't actually think cp is null terminated in this case. The > entire Interval is passed to DecodeISO8601Interval() as one big > string, so the specific number we're parsing may be somewhere > in the middle. > > If we just do the opposite and check isdigit(cp[1]) and only call > strtod() in that case I think it should work. > > - Joe Koshakow
How does this patch look? I don't really have any way to test it on AIX. - Joe Koshakow
From 46b1ce5a78e21b65536c62ca6270c26c992a1ef7 Mon Sep 17 00:00:00 2001 From: Joseph Koshakow <kosh...@gmail.com> Date: Sun, 3 Apr 2022 12:58:36 -0400 Subject: [PATCH] Fix parsing trailing decimal point in ISO8601 --- src/backend/utils/adt/datetime.c | 9 +++-- src/test/regress/expected/interval.out | 49 ++++++++++++++++++++++++-- src/test/regress/sql/interval.sql | 11 +++++- 3 files changed, 63 insertions(+), 6 deletions(-) diff --git a/src/backend/utils/adt/datetime.c b/src/backend/utils/adt/datetime.c index 462f2ed7a8..178313e0d1 100644 --- a/src/backend/utils/adt/datetime.c +++ b/src/backend/utils/adt/datetime.c @@ -3676,8 +3676,13 @@ ParseISO8601Number(char *str, char **endptr, int64 *ipart, double *fpart) /* Parse fractional part if there is any */ if (**endptr == '.') - *fpart = strtod(*endptr, endptr) * sign; - + { + /* A decimal point with no trailing numbers should be parsed as 0 */ + if (isdigit((unsigned char) *(*endptr + 1))) + *fpart = strtod(*endptr, endptr) * sign; + else + (*endptr)++; + } /* did we not see anything that looks like a number? */ if (*endptr == str || errno != 0) return DTERR_BAD_FORMAT; diff --git a/src/test/regress/expected/interval.out b/src/test/regress/expected/interval.out index 86c8d4bc99..ed051a55c4 100644 --- a/src/test/regress/expected/interval.out +++ b/src/test/regress/expected/interval.out @@ -1464,9 +1464,9 @@ select interval 'PT2562047788.1:00:54.775807'; ERROR: interval field value out of range: "PT2562047788.1:00:54.775807" LINE 1: select interval 'PT2562047788.1:00:54.775807'; ^ -select interval 'PT2562047788:01.:54.775807'; -ERROR: interval field value out of range: "PT2562047788:01.:54.775807" -LINE 1: select interval 'PT2562047788:01.:54.775807'; +select interval 'PT2562047788:01:54.775807'; +ERROR: interval field value out of range: "PT2562047788:01:54.775807" +LINE 1: select interval 'PT2562047788:01:54.775807'; ^ -- overflowing with fractional fields - SQL standard format select interval '0.1 2562047788:0:54.775807'; @@ -1539,6 +1539,49 @@ select interval '-2147483648 months -2147483648 days -9223372036854775808 us'; @ 178956970 years 8 mons 2147483648 days 2562047788 hours 54.775808 secs ago (1 row) +-- check that ISO8601 format accepts trailing '.' +select interval 'P1.Y2M3DT4H5M6S'; + interval +---------------------------------------------- + @ 1 year 2 mons 3 days 4 hours 5 mins 6 secs +(1 row) + +select interval 'P1Y2.M3DT4H5M6S'; + interval +---------------------------------------------- + @ 1 year 2 mons 3 days 4 hours 5 mins 6 secs +(1 row) + +select interval 'P1Y2M3.DT4H5M6S'; + interval +---------------------------------------------- + @ 1 year 2 mons 3 days 4 hours 5 mins 6 secs +(1 row) + +select interval 'P1Y2M3DT4.H5M6S'; + interval +---------------------------------------------- + @ 1 year 2 mons 3 days 4 hours 5 mins 6 secs +(1 row) + +select interval 'P1Y2M3DT4H5.M6S'; + interval +---------------------------------------------- + @ 1 year 2 mons 3 days 4 hours 5 mins 6 secs +(1 row) + +select interval 'P1Y2M3DT4H5M6.S'; + interval +---------------------------------------------- + @ 1 year 2 mons 3 days 4 hours 5 mins 6 secs +(1 row) + +select interval 'P1.Y2.M3.DT4.H5.M6.S'; + interval +---------------------------------------------- + @ 1 year 2 mons 3 days 4 hours 5 mins 6 secs +(1 row) + -- check that '30 days' equals '1 month' according to the hash function select '30 days'::interval = '1 month'::interval as t; t diff --git a/src/test/regress/sql/interval.sql b/src/test/regress/sql/interval.sql index f05055e03a..62d97f232c 100644 --- a/src/test/regress/sql/interval.sql +++ b/src/test/regress/sql/interval.sql @@ -474,7 +474,7 @@ select interval 'P00-0.1-2147483647'; select interval 'P00-0.01-00T2562047788:00:54.775807'; select interval 'P00-00-0.1T2562047788:00:54.775807'; select interval 'PT2562047788.1:00:54.775807'; -select interval 'PT2562047788:01.:54.775807'; +select interval 'PT2562047788:01:54.775807'; -- overflowing with fractional fields - SQL standard format select interval '0.1 2562047788:0:54.775807'; @@ -502,6 +502,15 @@ select interval '-2147483648 months -2147483648 days -9223372036854775808 us'; SET IntervalStyle to postgres_verbose; select interval '-2147483648 months -2147483648 days -9223372036854775808 us'; +-- check that ISO8601 format accepts trailing '.' +select interval 'P1.Y2M3DT4H5M6S'; +select interval 'P1Y2.M3DT4H5M6S'; +select interval 'P1Y2M3.DT4H5M6S'; +select interval 'P1Y2M3DT4.H5M6S'; +select interval 'P1Y2M3DT4H5.M6S'; +select interval 'P1Y2M3DT4H5M6.S'; +select interval 'P1.Y2.M3.DT4.H5.M6.S'; + -- check that '30 days' equals '1 month' according to the hash function select '30 days'::interval = '1 month'::interval as t; select interval_hash('30 days'::interval) = interval_hash('1 month'::interval) as t; -- 2.25.1