Commit 6dda292d authored by Alexander Korotkov's avatar Alexander Korotkov

Allow datetime values in JsonbValue

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
parent 5bc45062
...@@ -1506,7 +1506,7 @@ datum_to_json(Datum val, bool is_null, StringInfo result, ...@@ -1506,7 +1506,7 @@ datum_to_json(Datum val, bool is_null, StringInfo result,
{ {
char buf[MAXDATELEN + 1]; char buf[MAXDATELEN + 1];
JsonEncodeDateTime(buf, val, DATEOID); JsonEncodeDateTime(buf, val, DATEOID, NULL);
appendStringInfo(result, "\"%s\"", buf); appendStringInfo(result, "\"%s\"", buf);
} }
break; break;
...@@ -1514,7 +1514,7 @@ datum_to_json(Datum val, bool is_null, StringInfo result, ...@@ -1514,7 +1514,7 @@ datum_to_json(Datum val, bool is_null, StringInfo result,
{ {
char buf[MAXDATELEN + 1]; char buf[MAXDATELEN + 1];
JsonEncodeDateTime(buf, val, TIMESTAMPOID); JsonEncodeDateTime(buf, val, TIMESTAMPOID, NULL);
appendStringInfo(result, "\"%s\"", buf); appendStringInfo(result, "\"%s\"", buf);
} }
break; break;
...@@ -1522,7 +1522,7 @@ datum_to_json(Datum val, bool is_null, StringInfo result, ...@@ -1522,7 +1522,7 @@ datum_to_json(Datum val, bool is_null, StringInfo result,
{ {
char buf[MAXDATELEN + 1]; char buf[MAXDATELEN + 1];
JsonEncodeDateTime(buf, val, TIMESTAMPTZOID); JsonEncodeDateTime(buf, val, TIMESTAMPTZOID, NULL);
appendStringInfo(result, "\"%s\"", buf); appendStringInfo(result, "\"%s\"", buf);
} }
break; break;
...@@ -1550,10 +1550,11 @@ datum_to_json(Datum val, bool is_null, StringInfo result, ...@@ -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 * 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 * char *
JsonEncodeDateTime(char *buf, Datum value, Oid typid) JsonEncodeDateTime(char *buf, Datum value, Oid typid, const int *tzp)
{ {
if (!buf) if (!buf)
buf = palloc(MAXDATELEN + 1); buf = palloc(MAXDATELEN + 1);
...@@ -1630,11 +1631,30 @@ JsonEncodeDateTime(char *buf, Datum value, Oid typid) ...@@ -1630,11 +1631,30 @@ JsonEncodeDateTime(char *buf, Datum value, Oid typid)
const char *tzn = NULL; const char *tzn = NULL;
timestamp = DatumGetTimestampTz(value); 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 */ /* Same as timestamptz_out(), but forcing DateStyle */
if (TIMESTAMP_NOT_FINITE(timestamp)) if (TIMESTAMP_NOT_FINITE(timestamp))
EncodeSpecialTimestamp(timestamp, buf); 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); EncodeDateTime(&tm, fsec, true, tz, tzn, USE_XSD_DATES, buf);
}
else else
ereport(ERROR, ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
......
...@@ -206,6 +206,24 @@ JsonbTypeName(JsonbValue *jbv) ...@@ -206,6 +206,24 @@ JsonbTypeName(JsonbValue *jbv)
return "boolean"; return "boolean";
case jbvNull: case jbvNull:
return "null"; 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: default:
elog(ERROR, "unrecognized jsonb value type: %d", jbv->type); elog(ERROR, "unrecognized jsonb value type: %d", jbv->type);
return "unknown"; return "unknown";
...@@ -805,17 +823,20 @@ datum_to_jsonb(Datum val, bool is_null, JsonbInState *result, ...@@ -805,17 +823,20 @@ datum_to_jsonb(Datum val, bool is_null, JsonbInState *result,
break; break;
case JSONBTYPE_DATE: case JSONBTYPE_DATE:
jb.type = jbvString; 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); jb.val.string.len = strlen(jb.val.string.val);
break; break;
case JSONBTYPE_TIMESTAMP: case JSONBTYPE_TIMESTAMP:
jb.type = jbvString; 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); jb.val.string.len = strlen(jb.val.string.val);
break; break;
case JSONBTYPE_TIMESTAMPTZ: case JSONBTYPE_TIMESTAMPTZ:
jb.type = jbvString; 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); jb.val.string.len = strlen(jb.val.string.val);
break; break;
case JSONBTYPE_JSONCAST: case JSONBTYPE_JSONCAST:
......
...@@ -14,9 +14,12 @@ ...@@ -14,9 +14,12 @@
#include "postgres.h" #include "postgres.h"
#include "catalog/pg_collation.h" #include "catalog/pg_collation.h"
#include "catalog/pg_type.h"
#include "miscadmin.h" #include "miscadmin.h"
#include "utils/builtins.h" #include "utils/builtins.h"
#include "utils/datetime.h"
#include "utils/hashutils.h" #include "utils/hashutils.h"
#include "utils/jsonapi.h"
#include "utils/jsonb.h" #include "utils/jsonb.h"
#include "utils/memutils.h" #include "utils/memutils.h"
#include "utils/varlena.h" #include "utils/varlena.h"
...@@ -244,6 +247,8 @@ compareJsonbContainers(JsonbContainer *a, JsonbContainer *b) ...@@ -244,6 +247,8 @@ compareJsonbContainers(JsonbContainer *a, JsonbContainer *b)
break; break;
case jbvBinary: case jbvBinary:
elog(ERROR, "unexpected jbvBinary value"); elog(ERROR, "unexpected jbvBinary value");
case jbvDatetime:
elog(ERROR, "unexpected jbvDatetime value");
} }
} }
else else
...@@ -1786,6 +1791,22 @@ convertJsonbScalar(StringInfo buffer, JEntry *jentry, JsonbValue *scalarVal) ...@@ -1786,6 +1791,22 @@ convertJsonbScalar(StringInfo buffer, JEntry *jentry, JsonbValue *scalarVal)
JENTRY_ISBOOL_TRUE : JENTRY_ISBOOL_FALSE; JENTRY_ISBOOL_TRUE : JENTRY_ISBOOL_FALSE;
break; 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: default:
elog(ERROR, "invalid jsonb scalar type"); elog(ERROR, "invalid jsonb scalar type");
} }
......
...@@ -161,6 +161,7 @@ extern Jsonb *transform_jsonb_string_values(Jsonb *jsonb, void *action_state, ...@@ -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, extern text *transform_json_string_values(text *json, void *action_state,
JsonTransformStringValuesAction transform_action); 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 */ #endif /* JSONAPI_H */
...@@ -241,7 +241,15 @@ enum jbvType ...@@ -241,7 +241,15 @@ enum jbvType
jbvArray = 0x10, jbvArray = 0x10,
jbvObject, jbvObject,
/* Binary (i.e. struct Jsonb) jbvArray/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 ...@@ -282,11 +290,21 @@ struct JsonbValue
int len; int len;
JsonbContainer *data; JsonbContainer *data;
} binary; /* Array or object, in on-disk format */ } 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; } val;
}; };
#define IsAJsonbScalar(jsonbval) ((jsonbval)->type >= jbvNull && \ #define IsAJsonbScalar(jsonbval) (((jsonbval)->type >= jbvNull && \
(jsonbval)->type <= jbvBool) (jsonbval)->type <= jbvBool) || \
(jsonbval)->type == jbvDatetime)
/* /*
* Key/value pair within an Object. * Key/value pair within an Object.
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment