]> granicus.if.org Git - postgresql/commitdiff
Allow datetime values in JsonbValue
authorAlexander Korotkov <akorotkov@postgresql.org>
Wed, 25 Sep 2019 18:53:41 +0000 (21:53 +0300)
committerAlexander Korotkov <akorotkov@postgresql.org>
Wed, 25 Sep 2019 19:51:51 +0000 (22:51 +0300)
SQL/JSON standard allows manipulation with datetime values.  So, it appears to
be convinient to allow datetime values to be represented in JsonbValue struct.
These datetime values are allowed for temporary representation only.  During
serialization datetime values are converted into strings.

SQL/JSON requires writing timestamps with timezone in the same timezone offset
as they were parsed.  This is why we allow storage of timezone offset in
JsonbValue struct.  For the same reason timezone offset argument is added to
JsonEncodeDateTime() function.

Extracted from original patch by Nikita Glukhov, Teodor Sigaev, Oleg Bartunov.
Revised by me.  Comments were adjusted by Liudmila Mantrova.

Discussion: https://postgr.es/m/fcc6fc6a-b497-f39a-923d-aa34d0c588e8%402ndQuadrant.com
Discussion: https://postgr.es/m/CAPpHfdsZgYEra_PeCLGNoXOWYx6iU-S3wF8aX0ObQUcZU%2B4XTw%40mail.gmail.com
Author: Nikita Glukhov, Teodor Sigaev, Oleg Bartunov, Alexander Korotkov, Liudmila Mantrova
Reviewed-by: Anastasia Lubennikova, Peter Eisentraut
src/backend/utils/adt/json.c
src/backend/utils/adt/jsonb.c
src/backend/utils/adt/jsonb_util.c
src/include/utils/jsonapi.h
src/include/utils/jsonb.h

index 26d293709aa374056aad9b58bc73b276839c3e38..d4ba3bd87db344305738250db64733f13593ce22 100644 (file)
@@ -1506,7 +1506,7 @@ datum_to_json(Datum val, bool is_null, StringInfo result,
                        {
                                char            buf[MAXDATELEN + 1];
 
-                               JsonEncodeDateTime(buf, val, DATEOID);
+                               JsonEncodeDateTime(buf, val, DATEOID, NULL);
                                appendStringInfo(result, "\"%s\"", buf);
                        }
                        break;
@@ -1514,7 +1514,7 @@ datum_to_json(Datum val, bool is_null, StringInfo result,
                        {
                                char            buf[MAXDATELEN + 1];
 
-                               JsonEncodeDateTime(buf, val, TIMESTAMPOID);
+                               JsonEncodeDateTime(buf, val, TIMESTAMPOID, NULL);
                                appendStringInfo(result, "\"%s\"", buf);
                        }
                        break;
@@ -1522,7 +1522,7 @@ datum_to_json(Datum val, bool is_null, StringInfo result,
                        {
                                char            buf[MAXDATELEN + 1];
 
-                               JsonEncodeDateTime(buf, val, TIMESTAMPTZOID);
+                               JsonEncodeDateTime(buf, val, TIMESTAMPTZOID, NULL);
                                appendStringInfo(result, "\"%s\"", buf);
                        }
                        break;
@@ -1550,10 +1550,11 @@ datum_to_json(Datum val, bool is_null, StringInfo result,
 
 /*
  * Encode 'value' of datetime type 'typid' into JSON string in ISO format using
- * optionally preallocated buffer 'buf'.
+ * optionally preallocated buffer 'buf'.  Optional 'tzp' determines time-zone
+ * offset (in seconds) in which we want to show timestamptz.
  */
 char *
-JsonEncodeDateTime(char *buf, Datum value, Oid typid)
+JsonEncodeDateTime(char *buf, Datum value, Oid typid, const int *tzp)
 {
        if (!buf)
                buf = palloc(MAXDATELEN + 1);
@@ -1630,11 +1631,30 @@ JsonEncodeDateTime(char *buf, Datum value, Oid typid)
                                const char *tzn = NULL;
 
                                timestamp = DatumGetTimestampTz(value);
+
+                               /*
+                                * If a time zone is specified, we apply the time-zone shift,
+                                * convert timestamptz to pg_tm as if it were without a time
+                                * zone, and then use the specified time zone for converting
+                                * the timestamp into a string.
+                                */
+                               if (tzp)
+                               {
+                                       tz = *tzp;
+                                       timestamp -= (TimestampTz) tz * USECS_PER_SEC;
+                               }
+
                                /* 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)
+                               else if (timestamp2tm(timestamp, tzp ? NULL : &tz, &tm, &fsec,
+                                                                         tzp ? NULL : &tzn, NULL) == 0)
+                               {
+                                       if (tzp)
+                                               tm.tm_isdst = 1;        /* set time-zone presence flag */
+
                                        EncodeDateTime(&tm, fsec, true, tz, tzn, USE_XSD_DATES, buf);
+                               }
                                else
                                        ereport(ERROR,
                                                        (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
index 69f41ab4556ed4b323120068e56241b52248040b..74b4bbe44c616a95ce9849e1d7b9f75bc4749c81 100644 (file)
@@ -206,6 +206,24 @@ JsonbTypeName(JsonbValue *jbv)
                        return "boolean";
                case jbvNull:
                        return "null";
+               case jbvDatetime:
+                       switch (jbv->val.datetime.typid)
+                       {
+                               case DATEOID:
+                                       return "date";
+                               case TIMEOID:
+                                       return "time without time zone";
+                               case TIMETZOID:
+                                       return "time with time zone";
+                               case TIMESTAMPOID:
+                                       return "timestamp without time zone";
+                               case TIMESTAMPTZOID:
+                                       return "timestamp with time zone";
+                               default:
+                                       elog(ERROR, "unrecognized jsonb value datetime type: %d",
+                                                jbv->val.datetime.typid);
+                       }
+                       return "unknown";
                default:
                        elog(ERROR, "unrecognized jsonb value type: %d", jbv->type);
                        return "unknown";
@@ -805,17 +823,20 @@ datum_to_jsonb(Datum val, bool is_null, JsonbInState *result,
                                break;
                        case JSONBTYPE_DATE:
                                jb.type = jbvString;
-                               jb.val.string.val = JsonEncodeDateTime(NULL, val, DATEOID);
+                               jb.val.string.val = JsonEncodeDateTime(NULL, val,
+                                                                                                          DATEOID, NULL);
                                jb.val.string.len = strlen(jb.val.string.val);
                                break;
                        case JSONBTYPE_TIMESTAMP:
                                jb.type = jbvString;
-                               jb.val.string.val = JsonEncodeDateTime(NULL, val, TIMESTAMPOID);
+                               jb.val.string.val = JsonEncodeDateTime(NULL, val,
+                                                                                                          TIMESTAMPOID, NULL);
                                jb.val.string.len = strlen(jb.val.string.val);
                                break;
                        case JSONBTYPE_TIMESTAMPTZ:
                                jb.type = jbvString;
-                               jb.val.string.val = JsonEncodeDateTime(NULL, val, TIMESTAMPTZOID);
+                               jb.val.string.val = JsonEncodeDateTime(NULL, val,
+                                                                                                          TIMESTAMPTZOID, NULL);
                                jb.val.string.len = strlen(jb.val.string.val);
                                break;
                        case JSONBTYPE_JSONCAST:
index 7e0d9de7f0c6f531f38053c445d78aaaa308b82a..f7f79eb965a241c352673686c4c4a2efc367228d 100644 (file)
 #include "postgres.h"
 
 #include "catalog/pg_collation.h"
+#include "catalog/pg_type.h"
 #include "miscadmin.h"
 #include "utils/builtins.h"
+#include "utils/datetime.h"
 #include "utils/hashutils.h"
+#include "utils/jsonapi.h"
 #include "utils/jsonb.h"
 #include "utils/memutils.h"
 #include "utils/varlena.h"
@@ -244,6 +247,8 @@ compareJsonbContainers(JsonbContainer *a, JsonbContainer *b)
                                                break;
                                        case jbvBinary:
                                                elog(ERROR, "unexpected jbvBinary value");
+                                       case jbvDatetime:
+                                               elog(ERROR, "unexpected jbvDatetime value");
                                }
                        }
                        else
@@ -1786,6 +1791,22 @@ convertJsonbScalar(StringInfo buffer, JEntry *jentry, JsonbValue *scalarVal)
                                JENTRY_ISBOOL_TRUE : JENTRY_ISBOOL_FALSE;
                        break;
 
+               case jbvDatetime:
+                       {
+                               char            buf[MAXDATELEN + 1];
+                               size_t          len;
+
+                               JsonEncodeDateTime(buf,
+                                                                  scalarVal->val.datetime.value,
+                                                                  scalarVal->val.datetime.typid,
+                                                                  &scalarVal->val.datetime.tz);
+                               len = strlen(buf);
+                               appendToBuffer(buffer, buf, len);
+
+                               *jentry = len;
+                       }
+                       break;
+
                default:
                        elog(ERROR, "invalid jsonb scalar type");
        }
index 1c56acca55a72e9dd7b7bee6dfaf469b1ff3ed44..e1dab24d5dd91319f381e351777ce46d29371bc7 100644 (file)
@@ -161,6 +161,7 @@ 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);
+extern char *JsonEncodeDateTime(char *buf, Datum value, Oid typid,
+                                                               const int *tzp);
 
 #endif                                                 /* JSONAPI_H */
index 35766e106a8a5794474578f78563a9224b9ac36e..5e8179665e2a2f61727de2e0cb8f7b2742d96294 100644 (file)
@@ -241,7 +241,15 @@ enum jbvType
        jbvArray = 0x10,
        jbvObject,
        /* Binary (i.e. struct Jsonb) jbvArray/jbvObject */
-       jbvBinary
+       jbvBinary,
+
+       /*
+        * Virtual types.
+        *
+        * These types are used only for in-memory JSON processing and serialized
+        * into JSON strings when outputted to json/jsonb.
+        */
+       jbvDatetime = 0x20,
 };
 
 /*
@@ -282,11 +290,21 @@ struct JsonbValue
                        int                     len;
                        JsonbContainer *data;
                }                       binary;         /* Array or object, in on-disk format */
+
+               struct
+               {
+                       Datum           value;
+                       Oid                     typid;
+                       int32           typmod;
+                       int                     tz;             /* Numeric time zone, in seconds, for
+                                                                * TimestampTz data type */
+               }                       datetime;
        }                       val;
 };
 
-#define IsAJsonbScalar(jsonbval)       ((jsonbval)->type >= jbvNull && \
-                                                                        (jsonbval)->type <= jbvBool)
+#define IsAJsonbScalar(jsonbval)       (((jsonbval)->type >= jbvNull && \
+                                                                         (jsonbval)->type <= jbvBool) || \
+                                                                         (jsonbval)->type == jbvDatetime)
 
 /*
  * Key/value pair within an Object.