* json.c
* JSON data type support.
*
- * Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1996-2019, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
#include "access/transam.h"
#include "catalog/pg_type.h"
#include "executor/spi.h"
+#include "funcapi.h"
#include "lib/stringinfo.h"
#include "libpq/pqformat.h"
#include "mb/pg_wchar.h"
static inline void json_lex(JsonLexContext *lex);
static inline void json_lex_string(JsonLexContext *lex);
static inline void json_lex_number(JsonLexContext *lex, char *s,
- bool *num_err, int *total_len);
+ bool *num_err, int *total_len);
static inline void parse_scalar(JsonLexContext *lex, JsonSemAction *sem);
static void parse_object_field(JsonLexContext *lex, JsonSemAction *sem);
static void parse_object(JsonLexContext *lex, JsonSemAction *sem);
static void parse_array_element(JsonLexContext *lex, JsonSemAction *sem);
static void parse_array(JsonLexContext *lex, JsonSemAction *sem);
-static void report_parse_error(JsonParseContext ctx, JsonLexContext *lex);
-static void report_invalid_token(JsonLexContext *lex);
+static void report_parse_error(JsonParseContext ctx, JsonLexContext *lex) pg_attribute_noreturn();
+static void report_invalid_token(JsonLexContext *lex) pg_attribute_noreturn();
static int report_json_context(JsonLexContext *lex);
static char *extract_mb_char(char *s);
static void composite_to_json(Datum composite, StringInfo result,
- bool use_line_feeds);
+ bool use_line_feeds);
static void array_dim_to_json(StringInfo result, int dim, int ndims, int *dims,
- Datum *vals, bool *nulls, int *valcount,
- JsonTypeCategory tcategory, Oid outfuncoid,
- bool use_line_feeds);
+ Datum *vals, bool *nulls, int *valcount,
+ JsonTypeCategory tcategory, Oid outfuncoid,
+ bool use_line_feeds);
static void array_to_json_internal(Datum array, StringInfo result,
- bool use_line_feeds);
+ bool use_line_feeds);
static void json_categorize_type(Oid typoid,
- JsonTypeCategory *tcategory,
- Oid *outfuncoid);
+ JsonTypeCategory *tcategory,
+ Oid *outfuncoid);
static void datum_to_json(Datum val, bool is_null, StringInfo result,
- JsonTypeCategory tcategory, Oid outfuncoid,
- bool key_scalar);
+ JsonTypeCategory tcategory, Oid outfuncoid,
+ bool key_scalar);
static void add_json(Datum val, bool is_null, StringInfo result,
- Oid val_type, bool key_scalar);
+ Oid val_type, bool key_scalar);
static text *catenate_stringinfo_string(StringInfo buffer, const char *addon);
/* the null action object used for pure validation */
*/
if (*str == '-')
{
- dummy_lex.input = (char *) str + 1;
+ dummy_lex.input = unconstify(char *, str) +1;
dummy_lex.input_length = len - 1;
}
else
{
- dummy_lex.input = (char *) str;
+ dummy_lex.input = unconstify(char *, str);
dummy_lex.input_length = len;
}
pfree(outputstr);
break;
case JSONTYPE_DATE:
+ {
+ char buf[MAXDATELEN + 1];
+
+ JsonEncodeDateTime(buf, val, DATEOID, NULL);
+ appendStringInfo(result, "\"%s\"", buf);
+ }
+ break;
+ case JSONTYPE_TIMESTAMP:
+ {
+ char buf[MAXDATELEN + 1];
+
+ JsonEncodeDateTime(buf, val, TIMESTAMPOID, NULL);
+ appendStringInfo(result, "\"%s\"", buf);
+ }
+ break;
+ case JSONTYPE_TIMESTAMPTZ:
+ {
+ char buf[MAXDATELEN + 1];
+
+ JsonEncodeDateTime(buf, val, TIMESTAMPTZOID, NULL);
+ 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'. Optional 'tzp' determines time-zone
+ * offset (in seconds) in which we want to show timestamptz.
+ */
+char *
+JsonEncodeDateTime(char *buf, Datum value, Oid typid, const int *tzp)
+{
+ 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);
&(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);
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);
+
+ /*
+ * 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),
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;
}
/*
char *attname;
JsonTypeCategory tcategory;
Oid outfuncoid;
+ Form_pg_attribute att = TupleDescAttr(tupdesc, i);
- if (tupdesc->attrs[i]->attisdropped)
+ if (att->attisdropped)
continue;
if (needsep)
appendStringInfoString(result, sep);
needsep = true;
- attname = NameStr(tupdesc->attrs[i]->attname);
+ attname = NameStr(att->attname);
escape_json(result, attname);
appendStringInfoChar(result, ':');
outfuncoid = InvalidOid;
}
else
- json_categorize_type(tupdesc->attrs[i]->atttypid,
- &tcategory, &outfuncoid);
+ json_categorize_type(att->atttypid, &tcategory, &outfuncoid);
datum_to_json(val, isnull, result, tcategory, outfuncoid, false);
}
/*
* SQL function array_to_json(row)
*/
-extern Datum
+Datum
array_to_json(PG_FUNCTION_ARGS)
{
Datum array = PG_GETARG_DATUM(0);
/*
* SQL function array_to_json(row, prettybool)
*/
-extern Datum
+Datum
array_to_json_pretty(PG_FUNCTION_ARGS)
{
Datum array = PG_GETARG_DATUM(0);
/*
* SQL function row_to_json(row)
*/
-extern Datum
+Datum
row_to_json(PG_FUNCTION_ARGS)
{
Datum array = PG_GETARG_DATUM(0);
/*
* SQL function row_to_json(row, prettybool)
*/
-extern Datum
+Datum
row_to_json_pretty(PG_FUNCTION_ARGS)
{
Datum array = PG_GETARG_DATUM(0);
state->val_output_func, false);
/*
- * The transition type for array_agg() is declared to be "internal", which
+ * The transition type for json_agg() is declared to be "internal", which
* is a pass-by-value type the same size as a pointer. So we can safely
* pass the JsonAggState pointer through nodeAgg.c's machinations.
*/
{
int nargs = PG_NARGS();
int i;
- Datum arg;
const char *sep = "";
StringInfo result;
- Oid val_type;
+ Datum *args;
+ bool *nulls;
+ Oid *types;
+
+ /* fetch argument values to build the object */
+ nargs = extract_variadic_args(fcinfo, 0, false, &args, &types, &nulls);
+
+ if (nargs < 0)
+ PG_RETURN_NULL();
if (nargs % 2 != 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("argument list must have even number of elements"),
- errhint("The arguments of json_build_object() must consist of alternating keys and values.")));
+ /* translator: %s is a SQL function name */
+ errhint("The arguments of %s must consist of alternating keys and values.",
+ "json_build_object()")));
result = makeStringInfo();
for (i = 0; i < nargs; i += 2)
{
- /*
- * Note: since json_build_object() is declared as taking type "any",
- * the parser will not do any type conversion on unknown-type literals
- * (that is, undecorated strings or NULLs). Such values will arrive
- * here as type UNKNOWN, which fortunately does not matter to us,
- * since unknownout() works fine.
- */
appendStringInfoString(result, sep);
sep = ", ";
/* process key */
- val_type = get_fn_expr_argtype(fcinfo->flinfo, i);
-
- if (val_type == InvalidOid)
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("could not determine data type for argument %d",
- i + 1)));
-
- if (PG_ARGISNULL(i))
+ if (nulls[i])
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("argument %d cannot be null", i + 1),
errhint("Object keys should be text.")));
- arg = PG_GETARG_DATUM(i);
-
- add_json(arg, false, result, val_type, true);
+ add_json(args[i], false, result, types[i], true);
appendStringInfoString(result, " : ");
/* process value */
- val_type = get_fn_expr_argtype(fcinfo->flinfo, i + 1);
-
- if (val_type == InvalidOid)
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("could not determine data type for argument %d",
- i + 2)));
-
- if (PG_ARGISNULL(i + 1))
- arg = (Datum) 0;
- else
- arg = PG_GETARG_DATUM(i + 1);
-
- add_json(arg, PG_ARGISNULL(i + 1), result, val_type, false);
+ add_json(args[i + 1], nulls[i + 1], result, types[i + 1], false);
}
appendStringInfoChar(result, '}');
Datum
json_build_array(PG_FUNCTION_ARGS)
{
- int nargs = PG_NARGS();
+ int nargs;
int i;
- Datum arg;
const char *sep = "";
StringInfo result;
- Oid val_type;
+ Datum *args;
+ bool *nulls;
+ Oid *types;
+
+ /* fetch argument values to build the array */
+ nargs = extract_variadic_args(fcinfo, 0, false, &args, &types, &nulls);
+
+ if (nargs < 0)
+ PG_RETURN_NULL();
result = makeStringInfo();
for (i = 0; i < nargs; i++)
{
- /*
- * Note: since json_build_array() is declared as taking type "any",
- * the parser will not do any type conversion on unknown-type literals
- * (that is, undecorated strings or NULLs). Such values will arrive
- * here as type UNKNOWN, which fortunately does not matter to us,
- * since unknownout() works fine.
- */
appendStringInfoString(result, sep);
sep = ", ";
-
- val_type = get_fn_expr_argtype(fcinfo->flinfo, i);
-
- if (val_type == InvalidOid)
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("could not determine data type for argument %d",
- i + 1)));
-
- if (PG_ARGISNULL(i))
- arg = (Datum) 0;
- else
- arg = PG_GETARG_DATUM(i);
-
- add_json(arg, PG_ARGISNULL(i), result, val_type, false);
+ add_json(args[i], nulls[i], result, types[i], false);
}
appendStringInfoChar(result, ']');