Centralize json and jsonb handling of datetime types
authorAndrew Dunstan <[email protected]>
Wed, 17 Jan 2018 00:07:13 +0000 (19:07 -0500)
committerAndrew Dunstan <[email protected]>
Wed, 17 Jan 2018 00:07:13 +0000 (19:07 -0500)
The creates a single function JsonEncodeDateTime which will format these
data types in an efficient and consistent manner. This will be all the
more important when we come to jsonpath so we don't have to implement yet
more code doing the same thing in two more places.

This also extends the code to handle time and timetz types which were
not previously handled specially. This requires exposing the time2tm and
timetz2tm functions.

Patch from Nikita Glukhov

src/backend/utils/adt/date.c
src/backend/utils/adt/json.c
src/backend/utils/adt/jsonb.c
src/include/utils/date.h
src/include/utils/jsonapi.h

index 95a999857c7cf17cb4d2843427a0a21d512a253d..747ef497897dc6f3c4da89a1066044b3334940c6 100644 (file)
@@ -41,8 +41,6 @@
 #endif
 
 
-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);
 static int     tm2timetz(struct pg_tm *tm, fsec_t fsec, int tz, TimeTzADT *result);
 static void AdjustTimeForTypmod(TimeADT *time, int32 typmod);
@@ -1249,7 +1247,7 @@ tm2time(struct pg_tm *tm, fsec_t fsec, TimeADT *result)
  * If out of this range, leave as UTC (in practice that could only happen
  * if pg_time_t is just 32 bits) - thomas 97/05/27
  */
-static int
+int
 time2tm(TimeADT time, struct pg_tm *tm, fsec_t *fsec)
 {
        tm->tm_hour = time / USECS_PER_HOUR;
@@ -2073,7 +2071,7 @@ timetztypmodout(PG_FUNCTION_ARGS)
 /* timetz2tm()
  * Convert TIME WITH TIME ZONE data type to POSIX time structure.
  */
-static int
+int
 timetz2tm(TimeTzADT *time, struct pg_tm *tm, fsec_t *fsec, int *tzp)
 {
        TimeOffset      trem = time->time;
index 151345ab2ffea14917ef3c7e6750afbac64262d6..97a5b85516fa8d4be0e4a351263f52d3538ef76c 100644 (file)
@@ -1503,12 +1503,70 @@ datum_to_json(Datum val, bool is_null, StringInfo result,
                        pfree(outputstr);
                        break;
                case JSONTYPE_DATE:
+                       {
+                               char            buf[MAXDATELEN + 1];
+
+                               JsonEncodeDateTime(buf, val, DATEOID);
+                               appendStringInfo(result, "\"%s\"", buf);
+                       }
+                       break;
+               case JSONTYPE_TIMESTAMP:
+                       {
+                               char            buf[MAXDATELEN + 1];
+
+                               JsonEncodeDateTime(buf, val, TIMESTAMPOID);
+                               appendStringInfo(result, "\"%s\"", buf);
+                       }
+                       break;
+               case JSONTYPE_TIMESTAMPTZ:
+                       {
+                               char            buf[MAXDATELEN + 1];
+
+                               JsonEncodeDateTime(buf, val, TIMESTAMPTZOID);
+                               appendStringInfo(result, "\"%s\"", buf);
+                       }
+                       break;
+               case JSONTYPE_JSON:
+                       /* JSON and JSONB output will already be escaped */
+                       outputstr = OidOutputFunctionCall(outfuncoid, val);
+                       appendStringInfoString(result, outputstr);
+                       pfree(outputstr);
+                       break;
+               case JSONTYPE_CAST:
+                       /* outfuncoid refers to a cast function, not an output function */
+                       jsontext = DatumGetTextPP(OidFunctionCall1(outfuncoid, val));
+                       outputstr = text_to_cstring(jsontext);
+                       appendStringInfoString(result, outputstr);
+                       pfree(outputstr);
+                       pfree(jsontext);
+                       break;
+               default:
+                       outputstr = OidOutputFunctionCall(outfuncoid, val);
+                       escape_json(result, outputstr);
+                       pfree(outputstr);
+                       break;
+       }
+}
+
+/*
+ * Encode 'value' of datetime type 'typid' into JSON string in ISO format using
+ * optionally preallocated buffer 'buf'.
+ */
+char *
+JsonEncodeDateTime(char *buf, Datum value, Oid typid)
+{
+       if (!buf)
+               buf = palloc(MAXDATELEN + 1);
+
+       switch (typid)
+       {
+               case DATEOID:
                        {
                                DateADT         date;
                                struct pg_tm tm;
-                               char            buf[MAXDATELEN + 1];
 
-                               date = DatumGetDateADT(val);
+                               date = DatumGetDateADT(value);
+
                                /* Same as date_out(), but forcing DateStyle */
                                if (DATE_NOT_FINITE(date))
                                        EncodeSpecialDate(date, buf);
@@ -1518,17 +1576,40 @@ datum_to_json(Datum val, bool is_null, StringInfo result,
                                                   &(tm.tm_year), &(tm.tm_mon), &(tm.tm_mday));
                                        EncodeDateOnly(&tm, USE_XSD_DATES, buf);
                                }
-                               appendStringInfo(result, "\"%s\"", buf);
                        }
                        break;
-               case JSONTYPE_TIMESTAMP:
+               case TIMEOID:
+                       {
+                               TimeADT         time = DatumGetTimeADT(value);
+                               struct pg_tm tt,
+                                                  *tm = &tt;
+                               fsec_t          fsec;
+
+                               /* Same as time_out(), but forcing DateStyle */
+                               time2tm(time, tm, &fsec);
+                               EncodeTimeOnly(tm, fsec, false, 0, USE_XSD_DATES, buf);
+                       }
+                       break;
+               case TIMETZOID:
+                       {
+                               TimeTzADT  *time = DatumGetTimeTzADTP(value);
+                               struct pg_tm tt,
+                                                  *tm = &tt;
+                               fsec_t          fsec;
+                               int                     tz;
+
+                               /* Same as timetz_out(), but forcing DateStyle */
+                               timetz2tm(time, tm, &fsec, &tz);
+                               EncodeTimeOnly(tm, fsec, true, tz, USE_XSD_DATES, buf);
+                       }
+                       break;
+               case TIMESTAMPOID:
                        {
                                Timestamp       timestamp;
                                struct pg_tm tm;
                                fsec_t          fsec;
-                               char            buf[MAXDATELEN + 1];
 
-                               timestamp = DatumGetTimestamp(val);
+                               timestamp = DatumGetTimestamp(value);
                                /* Same as timestamp_out(), but forcing DateStyle */
                                if (TIMESTAMP_NOT_FINITE(timestamp))
                                        EncodeSpecialTimestamp(timestamp, buf);
@@ -1538,19 +1619,17 @@ datum_to_json(Datum val, bool is_null, StringInfo result,
                                        ereport(ERROR,
                                                        (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
                                                         errmsg("timestamp out of range")));
-                               appendStringInfo(result, "\"%s\"", buf);
                        }
                        break;
-               case JSONTYPE_TIMESTAMPTZ:
+               case TIMESTAMPTZOID:
                        {
                                TimestampTz timestamp;
                                struct pg_tm tm;
                                int                     tz;
                                fsec_t          fsec;
                                const char *tzn = NULL;
-                               char            buf[MAXDATELEN + 1];
 
-                               timestamp = DatumGetTimestampTz(val);
+                               timestamp = DatumGetTimestampTz(value);
                                /* Same as timestamptz_out(), but forcing DateStyle */
                                if (TIMESTAMP_NOT_FINITE(timestamp))
                                        EncodeSpecialTimestamp(timestamp, buf);
@@ -1560,29 +1639,14 @@ datum_to_json(Datum val, bool is_null, StringInfo result,
                                        ereport(ERROR,
                                                        (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
                                                         errmsg("timestamp out of range")));
-                               appendStringInfo(result, "\"%s\"", buf);
                        }
                        break;
-               case JSONTYPE_JSON:
-                       /* JSON and JSONB output will already be escaped */
-                       outputstr = OidOutputFunctionCall(outfuncoid, val);
-                       appendStringInfoString(result, outputstr);
-                       pfree(outputstr);
-                       break;
-               case JSONTYPE_CAST:
-                       /* outfuncoid refers to a cast function, not an output function */
-                       jsontext = DatumGetTextPP(OidFunctionCall1(outfuncoid, val));
-                       outputstr = text_to_cstring(jsontext);
-                       appendStringInfoString(result, outputstr);
-                       pfree(outputstr);
-                       pfree(jsontext);
-                       break;
                default:
-                       outputstr = OidOutputFunctionCall(outfuncoid, val);
-                       escape_json(result, outputstr);
-                       pfree(outputstr);
-                       break;
+                       elog(ERROR, "unknown jsonb value datetime type oid %d", typid);
+                       return NULL;
        }
+
+       return buf;
 }
 
 /*
index 014e7aa6e3863fe01ca666b346a000037db30a15..0f701801641aa8bc696f8917f8491be303faab75 100644 (file)
@@ -786,71 +786,19 @@ datum_to_jsonb(Datum val, bool is_null, JsonbInState *result,
                                }
                                break;
                        case JSONBTYPE_DATE:
-                               {
-                                       DateADT         date;
-                                       struct pg_tm tm;
-                                       char            buf[MAXDATELEN + 1];
-
-                                       date = DatumGetDateADT(val);
-                                       /* Same as date_out(), but forcing DateStyle */
-                                       if (DATE_NOT_FINITE(date))
-                                               EncodeSpecialDate(date, buf);
-                                       else
-                                       {
-                                               j2date(date + POSTGRES_EPOCH_JDATE,
-                                                          &(tm.tm_year), &(tm.tm_mon), &(tm.tm_mday));
-                                               EncodeDateOnly(&tm, USE_XSD_DATES, buf);
-                                       }
-                                       jb.type = jbvString;
-                                       jb.val.string.len = strlen(buf);
-                                       jb.val.string.val = pstrdup(buf);
-                               }
+                               jb.type = jbvString;
+                               jb.val.string.val = JsonEncodeDateTime(NULL, val, DATEOID);
+                               jb.val.string.len = strlen(jb.val.string.val);
                                break;
                        case JSONBTYPE_TIMESTAMP:
-                               {
-                                       Timestamp       timestamp;
-                                       struct pg_tm tm;
-                                       fsec_t          fsec;
-                                       char            buf[MAXDATELEN + 1];
-
-                                       timestamp = DatumGetTimestamp(val);
-                                       /* Same as timestamp_out(), but forcing DateStyle */
-                                       if (TIMESTAMP_NOT_FINITE(timestamp))
-                                               EncodeSpecialTimestamp(timestamp, buf);
-                                       else if (timestamp2tm(timestamp, NULL, &tm, &fsec, NULL, NULL) == 0)
-                                               EncodeDateTime(&tm, fsec, false, 0, NULL, USE_XSD_DATES, 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);
-                               }
+                               jb.type = jbvString;
+                               jb.val.string.val = JsonEncodeDateTime(NULL, val, TIMESTAMPOID);
+                               jb.val.string.len = strlen(jb.val.string.val);
                                break;
                        case JSONBTYPE_TIMESTAMPTZ:
-                               {
-                                       TimestampTz timestamp;
-                                       struct pg_tm tm;
-                                       int                     tz;
-                                       fsec_t          fsec;
-                                       const char *tzn = NULL;
-                                       char            buf[MAXDATELEN + 1];
-
-                                       timestamp = DatumGetTimestampTz(val);
-                                       /* Same as timestamptz_out(), but forcing DateStyle */
-                                       if (TIMESTAMP_NOT_FINITE(timestamp))
-                                               EncodeSpecialTimestamp(timestamp, buf);
-                                       else if (timestamp2tm(timestamp, &tz, &tm, &fsec, &tzn, NULL) == 0)
-                                               EncodeDateTime(&tm, fsec, true, tz, tzn, USE_XSD_DATES, 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);
-                               }
+                               jb.type = jbvString;
+                               jb.val.string.val = JsonEncodeDateTime(NULL, val, TIMESTAMPTZOID);
+                               jb.val.string.len = strlen(jb.val.string.val);
                                break;
                        case JSONBTYPE_JSONCAST:
                        case JSONBTYPE_JSON:
index 274959231b481251f484375b4f18d476d2c280bd..e17cd49602e40304c239b28718341573a0138956 100644 (file)
@@ -17,7 +17,7 @@
 #include <math.h>
 
 #include "fmgr.h"
-
+#include "datatype/timestamp.h"
 
 typedef int32 DateADT;
 
@@ -73,5 +73,7 @@ extern void EncodeSpecialDate(DateADT dt, char *str);
 extern DateADT GetSQLCurrentDate(void);
 extern TimeTzADT *GetSQLCurrentTime(int32 typmod);
 extern TimeADT GetSQLLocalTime(int32 typmod);
+extern int     time2tm(TimeADT time, struct pg_tm *tm, fsec_t *fsec);
+extern int     timetz2tm(TimeTzADT *time, struct pg_tm *tm, fsec_t *fsec, int *tzp);
 
 #endif                                                 /* DATE_H */
index d6baea5368cbe0d19efc5f8c98893f6daaa5b263..e39572e00f2e36addc6181032debb44f9836754f 100644 (file)
@@ -147,4 +147,6 @@ extern Jsonb *transform_jsonb_string_values(Jsonb *jsonb, void *action_state,
 extern text *transform_json_string_values(text *json, void *action_state,
                                                         JsonTransformStringValuesAction transform_action);
 
+extern char *JsonEncodeDateTime(char *buf, Datum value, Oid typid);
+
 #endif                                                 /* JSONAPI_H */