Render infinite date/timestamps as 'infinity' for json/jsonb
authorAndrew Dunstan <andrew@dunslane.net>
Thu, 26 Feb 2015 17:25:21 +0000 (12:25 -0500)
committerAndrew Dunstan <andrew@dunslane.net>
Thu, 26 Feb 2015 17:25:21 +0000 (12:25 -0500)
Commit ab14a73a6c raised an error in these cases and later the
behaviour was copied to jsonb. This is what the XML code, which we
then adopted, does, as the XSD types don't accept infinite values.
However, json dates and timestamps are just strings as far as json is
concerned, so there is no reason not to render these values as
'infinity'.

The json portion of this is backpatched to 9.4 where the behaviour was
introduced. The jsonb portion only affects the development branch.

Per gripe on pgsql-general.

src/backend/utils/adt/json.c
src/backend/utils/adt/jsonb.c
src/test/regress/expected/json.out
src/test/regress/expected/json_1.out
src/test/regress/expected/jsonb.out
src/test/regress/expected/jsonb_1.out
src/test/regress/sql/json.sql
src/test/regress/sql/jsonb.sql

index 951b6554007b2272d917e1e431c084130a7ed6d7..d0d7206ae9307aaaa7bb6a474fbc5b22ca14c871 100644 (file)
@@ -32,6 +32,9 @@
 #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
@@ -1436,20 +1439,18 @@ datum_to_json(Datum val, bool is_null, StringInfo result,
 
                date = DatumGetDateADT(val);
 
-               /* XSD doesn't support infinite values */
                if (DATE_NOT_FINITE(date))
-                   ereport(ERROR,
-                           (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
-                            errmsg("date out of range"),
-                            errdetail("JSON does not support infinite date values.")));
+               {
+                   /* we have to format infinity ourselves */
+                   appendStringInfoString(result,DT_INFINITY);
+               }
                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:
@@ -1461,20 +1462,20 @@ datum_to_json(Datum val, bool is_null, StringInfo result,
 
                timestamp = DatumGetTimestamp(val);
 
-               /* XSD doesn't support infinite values */
                if (TIMESTAMP_NOT_FINITE(timestamp))
-                   ereport(ERROR,
-                           (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
-                            errmsg("timestamp out of range"),
-                            errdetail("JSON does not support infinite timestamp values.")));
+               {
+                   /* we have to format infinity ourselves */
+                   appendStringInfoString(result,DT_INFINITY);
+               }
                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:
@@ -1488,20 +1489,20 @@ datum_to_json(Datum val, bool is_null, StringInfo result,
 
                timestamp = DatumGetTimestamp(val);
 
-               /* XSD doesn't support infinite values */
                if (TIMESTAMP_NOT_FINITE(timestamp))
-                   ereport(ERROR,
-                           (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
-                            errmsg("timestamp out of range"),
-                            errdetail("JSON does not support infinite timestamp values.")));
+               {
+                   /* we have to format infinity ourselves */
+                   appendStringInfoString(result,DT_INFINITY);
+               }
                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:
index 644ea6d9414758d7fce4b83038b7081d9c188f19..aac97565f959867181ac0445417b251f67342e7b 100644 (file)
 #include "utils/syscache.h"
 #include "utils/typcache.h"
 
+/*
+ * String to output for infinite dates and timestamps.
+ * Note the we don't use embedded quotes, unlike for json, because
+ * we store jsonb strings dequoted.
+ */
+
+#define DT_INFINITY "infinity"
+
 typedef struct JsonbInState
 {
    JsonbParseState *parseState;
@@ -714,23 +722,21 @@ datum_to_jsonb(Datum val, bool is_null, JsonbInState *result,
                char        buf[MAXDATELEN + 1];
 
                date = DatumGetDateADT(val);
+               jb.type = jbvString;
 
-               /* XSD doesn't support infinite values */
                if (DATE_NOT_FINITE(date))
-                   ereport(ERROR,
-                           (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
-                            errmsg("date out of range"),
-                            errdetail("JSON does not support infinite date values.")));
+               {
+                   jb.val.string.len = strlen(DT_INFINITY);
+                   jb.val.string.val = pstrdup(DT_INFINITY);
+               }
                else
                {
                    j2date(date + POSTGRES_EPOCH_JDATE,
                           &(tm.tm_year), &(tm.tm_mon), &(tm.tm_mday));
                    EncodeDateOnly(&tm, USE_XSD_DATES, buf);
+                   jb.val.string.len = strlen(buf);
+                   jb.val.string.val = pstrdup(buf);
                }
-
-               jb.type = jbvString;
-               jb.val.string.len = strlen(buf);
-               jb.val.string.val = pstrdup(buf);
            }
            break;
            case JSONBTYPE_TIMESTAMP:
@@ -741,23 +747,24 @@ datum_to_jsonb(Datum val, bool is_null, JsonbInState *result,
                    char        buf[MAXDATELEN + 1];
 
                    timestamp = DatumGetTimestamp(val);
+                   jb.type = jbvString;
 
-                   /* XSD doesn't support infinite values */
                    if (TIMESTAMP_NOT_FINITE(timestamp))
-                       ereport(ERROR,
-                               (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
-                                errmsg("timestamp out of range"),
-                                errdetail("JSON does not support infinite timestamp values.")));
+                   {
+                       jb.val.string.len = strlen(DT_INFINITY);
+                       jb.val.string.val = pstrdup(DT_INFINITY);
+                   }
                    else if (timestamp2tm(timestamp, NULL, &tm, &fsec, NULL, NULL) == 0)
+                   {
+
                        EncodeDateTime(&tm, fsec, false, 0, NULL, USE_XSD_DATES, buf);
+                       jb.val.string.len = strlen(buf);
+                       jb.val.string.val = pstrdup(buf);
+                   }
                    else
                        ereport(ERROR,
                                (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
                                 errmsg("timestamp out of range")));
-
-                   jb.type = jbvString;
-                   jb.val.string.len = strlen(buf);
-                   jb.val.string.val = pstrdup(buf);
                }
                break;
            case JSONBTYPE_TIMESTAMPTZ:
@@ -770,23 +777,23 @@ datum_to_jsonb(Datum val, bool is_null, JsonbInState *result,
                    char        buf[MAXDATELEN + 1];
 
                    timestamp = DatumGetTimestamp(val);
+                   jb.type = jbvString;
 
-                   /* XSD doesn't support infinite values */
                    if (TIMESTAMP_NOT_FINITE(timestamp))
-                       ereport(ERROR,
-                               (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
-                                errmsg("timestamp out of range"),
-                                errdetail("JSON does not support infinite timestamp values.")));
+                   {
+                       jb.val.string.len = strlen(DT_INFINITY);
+                       jb.val.string.val = pstrdup(DT_INFINITY);
+                   }
                    else if (timestamp2tm(timestamp, &tz, &tm, &fsec, &tzn, NULL) == 0)
+                   {
                        EncodeDateTime(&tm, fsec, true, tz, tzn, USE_XSD_DATES, buf);
+                       jb.val.string.len = strlen(buf);
+                       jb.val.string.val = pstrdup(buf);
+                   }
                    else
                        ereport(ERROR,
                                (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
                                 errmsg("timestamp out of range")));
-
-                   jb.type = jbvString;
-                   jb.val.string.len = strlen(buf);
-                   jb.val.string.val = pstrdup(buf);
                }
                break;
            case JSONBTYPE_JSONCAST:
index 16704363dc62b9ccfedab1b124f574821f5c936b..3942c3bee91065d323a6b684a6ee8fdbb2e47638 100644 (file)
@@ -426,6 +426,30 @@ select to_json(timestamptz '2014-05-28 12:22:35.614298-04');
 (1 row)
 
 COMMIT;
+select to_json(date '2014-05-28');
+   to_json    
+--------------
+ "2014-05-28"
+(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(timestamptz 'Infinity');
+  to_json   
+------------
+ "infinity"
+(1 row)
+
 --json_agg
 SELECT json_agg(q)
   FROM ( SELECT $$a$$ || x AS b, y AS c,
index 807814641dd897f6e47c3b5eb8141c21f3269714..38f15262883b057719544b9bcd52ebd567a6ced2 100644 (file)
@@ -426,6 +426,30 @@ select to_json(timestamptz '2014-05-28 12:22:35.614298-04');
 (1 row)
 
 COMMIT;
+select to_json(date '2014-05-28');
+   to_json    
+--------------
+ "2014-05-28"
+(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(timestamptz 'Infinity');
+  to_json   
+------------
+ "infinity"
+(1 row)
+
 --json_agg
 SELECT json_agg(q)
   FROM ( SELECT $$a$$ || x AS b, y AS c,
index 6c6ed950f0830c8323d48134618e57ed0c0fc9de..0d558901e9d84302077c8ce1856d0549b6ea7dc1 100644 (file)
@@ -330,6 +330,30 @@ select to_jsonb(timestamptz '2014-05-28 12:22:35.614298-04');
 (1 row)
 
 COMMIT;
+select to_jsonb(date '2014-05-28');
+   to_jsonb   
+--------------
+ "2014-05-28"
+(1 row)
+
+select to_jsonb(date 'Infinity');
+  to_jsonb  
+------------
+ "infinity"
+(1 row)
+
+select to_jsonb(timestamp 'Infinity');
+  to_jsonb  
+------------
+ "infinity"
+(1 row)
+
+select to_jsonb(timestamptz 'Infinity');
+  to_jsonb  
+------------
+ "infinity"
+(1 row)
+
 --jsonb_agg
 CREATE TEMP TABLE rows AS
 SELECT x, 'txt' || x as y
index f30148d51c1bdc232266ca5a6998237b67f39de0..694b6ea5f5caf8169d5fbaa5854a3e0c4101c912 100644 (file)
@@ -330,6 +330,30 @@ select to_jsonb(timestamptz '2014-05-28 12:22:35.614298-04');
 (1 row)
 
 COMMIT;
+select to_jsonb(date '2014-05-28');
+   to_jsonb   
+--------------
+ "2014-05-28"
+(1 row)
+
+select to_jsonb(date 'Infinity');
+  to_jsonb  
+------------
+ "infinity"
+(1 row)
+
+select to_jsonb(timestamp 'Infinity');
+  to_jsonb  
+------------
+ "infinity"
+(1 row)
+
+select to_jsonb(timestamptz 'Infinity');
+  to_jsonb  
+------------
+ "infinity"
+(1 row)
+
 --jsonb_agg
 CREATE TEMP TABLE rows AS
 SELECT x, 'txt' || x as y
index 53a37a88439171127c220470f5319dcdc172239d..53832a01fa18f99db163c96333891f4d91b0a4b9 100644 (file)
@@ -111,6 +111,12 @@ SET LOCAL TIME ZONE -8;
 select to_json(timestamptz '2014-05-28 12:22:35.614298-04');
 COMMIT;
 
+select to_json(date '2014-05-28');
+
+select to_json(date 'Infinity');
+select to_json(timestamp 'Infinity');
+select to_json(timestamptz 'Infinity');
+
 --json_agg
 
 SELECT json_agg(q)
index 53cc2393c626c01d8303a3b9bb63293a1b50593b..676e1a7d4c93767602b96bdfc6a39a13f020cc45 100644 (file)
@@ -74,6 +74,12 @@ SET LOCAL TIME ZONE -8;
 select to_jsonb(timestamptz '2014-05-28 12:22:35.614298-04');
 COMMIT;
 
+select to_jsonb(date '2014-05-28');
+
+select to_jsonb(date 'Infinity');
+select to_jsonb(timestamp 'Infinity');
+select to_jsonb(timestamptz 'Infinity');
+
 --jsonb_agg
 
 CREATE TEMP TABLE rows AS