From 4f33572ee68b515dc2750e265fc0d0312c0d5d3d Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Tue, 20 Oct 2015 11:06:24 -0700 Subject: [PATCH] Fix incorrect translation of minus-infinity datetimes for json/jsonb. Commit bda76c1c8cfb1d11751ba6be88f0242850481733 caused both plus and minus infinity to be rendered as "infinity", which is not only wrong but inconsistent with the pre-9.4 behavior of to_json(). Fix that by duplicating the coding in date_out/timestamp_out/timestamptz_out more closely. Per bug #13687 from Stepan Perlov. Back-patch to 9.4, like the previous commit. In passing, also re-pgindent json.c, since it had gotten a bit messed up by recent patches (and I was already annoyed by indentation-related problems in back-patching this fix ...) --- src/backend/utils/adt/date.c | 3 +-- src/backend/utils/adt/json.c | 36 +++++++++--------------------- src/backend/utils/adt/timestamp.c | 3 +-- src/include/utils/date.h | 1 + src/include/utils/datetime.h | 1 + src/test/regress/expected/json.out | 18 +++++++++++++++ src/test/regress/sql/json.sql | 3 +++ 7 files changed, 35 insertions(+), 30 deletions(-) diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c index bb23b12c171..02f0afba4c1 100644 --- a/src/backend/utils/adt/date.c +++ b/src/backend/utils/adt/date.c @@ -40,7 +40,6 @@ #endif -static void EncodeSpecialDate(DateADT dt, char *str); static int time2tm(TimeADT time, struct pg_tm * tm, fsec_t *fsec); static int timetz2tm(TimeTzADT *time, struct pg_tm * tm, fsec_t *fsec, int *tzp); static int tm2time(struct pg_tm * tm, fsec_t fsec, TimeADT *result); @@ -273,7 +272,7 @@ make_date(PG_FUNCTION_ARGS) /* * Convert reserved date values to string. */ -static void +void EncodeSpecialDate(DateADT dt, char *str) { if (DATE_IS_NOBEGIN(dt)) diff --git a/src/backend/utils/adt/json.c b/src/backend/utils/adt/json.c index 679315b6587..c3df54c018c 100644 --- a/src/backend/utils/adt/json.c +++ b/src/backend/utils/adt/json.c @@ -33,9 +33,6 @@ #include "utils/typcache.h" #include "utils/syscache.h" -/* String to output for infinite dates and timestamps */ -#define DT_INFINITY "\"infinity\"" - /* * The context of the parser is maintained by the recursive descent * mechanism, but is passed explicitly to the error reporting routine @@ -1435,19 +1432,16 @@ datum_to_json(Datum val, bool is_null, StringInfo result, char buf[MAXDATELEN + 1]; date = DatumGetDateADT(val); - + /* Same as date_out(), but forcing DateStyle */ if (DATE_NOT_FINITE(date)) - { - /* we have to format infinity ourselves */ - appendStringInfoString(result,DT_INFINITY); - } + EncodeSpecialDate(date, buf); else { j2date(date + POSTGRES_EPOCH_JDATE, &(tm.tm_year), &(tm.tm_mon), &(tm.tm_mday)); EncodeDateOnly(&tm, USE_XSD_DATES, buf); - appendStringInfo(result, "\"%s\"", buf); } + appendStringInfo(result, "\"%s\"", buf); } break; case JSONTYPE_TIMESTAMP: @@ -1458,21 +1452,16 @@ datum_to_json(Datum val, bool is_null, StringInfo result, char buf[MAXDATELEN + 1]; timestamp = DatumGetTimestamp(val); - + /* Same as timestamp_out(), but forcing DateStyle */ if (TIMESTAMP_NOT_FINITE(timestamp)) - { - /* we have to format infinity ourselves */ - appendStringInfoString(result,DT_INFINITY); - } + EncodeSpecialTimestamp(timestamp, buf); else if (timestamp2tm(timestamp, NULL, &tm, &fsec, NULL, NULL) == 0) - { EncodeDateTime(&tm, fsec, false, 0, NULL, USE_XSD_DATES, buf); - appendStringInfo(result, "\"%s\"", buf); - } else ereport(ERROR, (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), errmsg("timestamp out of range"))); + appendStringInfo(result, "\"%s\"", buf); } break; case JSONTYPE_TIMESTAMPTZ: @@ -1484,22 +1473,17 @@ datum_to_json(Datum val, bool is_null, StringInfo result, const char *tzn = NULL; char buf[MAXDATELEN + 1]; - timestamp = DatumGetTimestamp(val); - + timestamp = DatumGetTimestampTz(val); + /* Same as timestamptz_out(), but forcing DateStyle */ if (TIMESTAMP_NOT_FINITE(timestamp)) - { - /* we have to format infinity ourselves */ - appendStringInfoString(result,DT_INFINITY); - } + EncodeSpecialTimestamp(timestamp, buf); else if (timestamp2tm(timestamp, &tz, &tm, &fsec, &tzn, NULL) == 0) - { EncodeDateTime(&tm, fsec, true, tz, tzn, USE_XSD_DATES, buf); - appendStringInfo(result, "\"%s\"", buf); - } else ereport(ERROR, (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), errmsg("timestamp out of range"))); + appendStringInfo(result, "\"%s\"", buf); } break; case JSONTYPE_JSON: diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c index 59108273cde..37c99559b8b 100644 --- a/src/backend/utils/adt/timestamp.c +++ b/src/backend/utils/adt/timestamp.c @@ -75,7 +75,6 @@ typedef struct static TimeOffset time2t(const int hour, const int min, const int sec, const fsec_t fsec); -static void EncodeSpecialTimestamp(Timestamp dt, char *str); static Timestamp dt2local(Timestamp dt, int timezone); static void AdjustTimestampForTypmod(Timestamp *time, int32 typmod); static void AdjustIntervalForTypmod(Interval *interval, int32 typmod); @@ -1507,7 +1506,7 @@ make_interval(PG_FUNCTION_ARGS) /* EncodeSpecialTimestamp() * Convert reserved timestamp data type to string. */ -static void +void EncodeSpecialTimestamp(Timestamp dt, char *str) { if (TIMESTAMP_IS_NOBEGIN(dt)) diff --git a/src/include/utils/date.h b/src/include/utils/date.h index 622aa194901..03513e07d7d 100644 --- a/src/include/utils/date.h +++ b/src/include/utils/date.h @@ -92,6 +92,7 @@ typedef struct /* date.c */ extern double date2timestamp_no_overflow(DateADT dateVal); +extern void EncodeSpecialDate(DateADT dt, char *str); extern Datum date_in(PG_FUNCTION_ARGS); extern Datum date_out(PG_FUNCTION_ARGS); diff --git a/src/include/utils/datetime.h b/src/include/utils/datetime.h index 9b53ee38ccf..a091f6fa04b 100644 --- a/src/include/utils/datetime.h +++ b/src/include/utils/datetime.h @@ -319,6 +319,7 @@ extern void EncodeDateOnly(struct pg_tm * tm, int style, char *str); extern void EncodeTimeOnly(struct pg_tm * tm, fsec_t fsec, bool print_tz, int tz, int style, char *str); extern void EncodeDateTime(struct pg_tm * tm, fsec_t fsec, bool print_tz, int tz, const char *tzn, int style, char *str); extern void EncodeInterval(struct pg_tm * tm, fsec_t fsec, int style, char *str); +extern void EncodeSpecialTimestamp(Timestamp dt, char *str); extern int ValidateDate(int fmask, bool isjulian, bool is2digits, bool bc, struct pg_tm * tm); diff --git a/src/test/regress/expected/json.out b/src/test/regress/expected/json.out index 7aa114ba1f0..65c43c9b65c 100644 --- a/src/test/regress/expected/json.out +++ b/src/test/regress/expected/json.out @@ -418,18 +418,36 @@ select to_json(date 'Infinity'); "infinity" (1 row) +select to_json(date '-Infinity'); + to_json +------------- + "-infinity" +(1 row) + select to_json(timestamp 'Infinity'); to_json ------------ "infinity" (1 row) +select to_json(timestamp '-Infinity'); + to_json +------------- + "-infinity" +(1 row) + select to_json(timestamptz 'Infinity'); to_json ------------ "infinity" (1 row) +select to_json(timestamptz '-Infinity'); + to_json +------------- + "-infinity" +(1 row) + --json_agg SELECT json_agg(q) FROM ( SELECT $$a$$ || x AS b, y AS c, diff --git a/src/test/regress/sql/json.sql b/src/test/regress/sql/json.sql index 26da2b37401..bf540c06a5f 100644 --- a/src/test/regress/sql/json.sql +++ b/src/test/regress/sql/json.sql @@ -116,8 +116,11 @@ COMMIT; select to_json(date '2014-05-28'); select to_json(date 'Infinity'); +select to_json(date '-Infinity'); select to_json(timestamp 'Infinity'); +select to_json(timestamp '-Infinity'); select to_json(timestamptz 'Infinity'); +select to_json(timestamptz '-Infinity'); --json_agg