JSON_PARSE_END /* saw the end of a document, expect nothing */
} JsonParseContext;
+typedef enum /* type categories for datum_to_json */
+{
+ JSONTYPE_NULL, /* null, so we didn't bother to identify */
+ JSONTYPE_BOOL, /* boolean (built-in types only) */
+ JSONTYPE_NUMERIC, /* numeric (ditto) */
+ JSONTYPE_JSON, /* JSON itself (and JSONB) */
+ JSONTYPE_ARRAY, /* array */
+ JSONTYPE_COMPOSITE, /* composite */
+ JSONTYPE_CAST, /* something with an explicit cast to JSON */
+ JSONTYPE_OTHER /* all else */
+} JsonTypeCategory;
+
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);
bool use_line_feeds);
static void array_dim_to_json(StringInfo result, int dim, int ndims, int *dims,
Datum *vals, bool *nulls, int *valcount,
- TYPCATEGORY tcategory, Oid typoutputfunc,
+ JsonTypeCategory tcategory, Oid outfuncoid,
bool use_line_feeds);
static void array_to_json_internal(Datum array, StringInfo result,
bool use_line_feeds);
+static void json_categorize_type(Oid typoid,
+ JsonTypeCategory *tcategory,
+ Oid *outfuncoid);
static void datum_to_json(Datum val, bool is_null, StringInfo result,
- TYPCATEGORY tcategory, Oid typoutputfunc, 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);
report_parse_error(ctx, lex);;
}
-/*
- * All the defined type categories are upper case , so use lower case here
- * so we avoid any possible clash.
- */
-/* fake type category for JSON so we can distinguish it in datum_to_json */
-#define TYPCATEGORY_JSON 'j'
-/* fake category for types that have a cast to json */
-#define TYPCATEGORY_JSON_CAST 'c'
/* chars to consider as part of an alphanumeric token */
#define JSON_ALPHANUMERIC_CHAR(c) \
(((c) >= 'a' && (c) <= 'z') || \
}
/*
- * Turn a scalar Datum into JSON, appending the string to "result".
+ * Determine how we want to print values of a given type in datum_to_json.
*
- * Hand off a non-scalar datum to composite_to_json or array_to_json_internal
- * as appropriate.
+ * Given the datatype OID, return its JsonTypeCategory, as well as the type's
+ * output function OID. If the returned category is JSONTYPE_CAST, we
+ * return the OID of the type->JSON cast function instead.
+ */
+static void
+json_categorize_type(Oid typoid,
+ JsonTypeCategory *tcategory,
+ Oid *outfuncoid)
+{
+ bool typisvarlena;
+
+ /* Look through any domain */
+ typoid = getBaseType(typoid);
+
+ /* We'll usually need to return the type output function */
+ getTypeOutputInfo(typoid, outfuncoid, &typisvarlena);
+
+ /* Check for known types */
+ switch (typoid)
+ {
+ case BOOLOID:
+ *tcategory = JSONTYPE_BOOL;
+ break;
+
+ case INT2OID:
+ case INT4OID:
+ case INT8OID:
+ case FLOAT4OID:
+ case FLOAT8OID:
+ case NUMERICOID:
+ *tcategory = JSONTYPE_NUMERIC;
+ break;
+
+ case JSONOID:
+ case JSONBOID:
+ *tcategory = JSONTYPE_JSON;
+ break;
+
+ default:
+ /* Check for arrays and composites */
+ if (OidIsValid(get_element_type(typoid)))
+ *tcategory = JSONTYPE_ARRAY;
+ else if (type_is_rowtype(typoid))
+ *tcategory = JSONTYPE_COMPOSITE;
+ else
+ {
+ /* It's probably the general case ... */
+ *tcategory = JSONTYPE_OTHER;
+ /* but let's look for a cast to json, if it's not built-in */
+ if (typoid >= FirstNormalObjectId)
+ {
+ HeapTuple tuple;
+
+ tuple = SearchSysCache2(CASTSOURCETARGET,
+ ObjectIdGetDatum(typoid),
+ ObjectIdGetDatum(JSONOID));
+ if (HeapTupleIsValid(tuple))
+ {
+ Form_pg_cast castForm = (Form_pg_cast) GETSTRUCT(tuple);
+
+ if (castForm->castmethod == COERCION_METHOD_FUNCTION)
+ {
+ *tcategory = JSONTYPE_CAST;
+ *outfuncoid = castForm->castfunc;
+ }
+
+ ReleaseSysCache(tuple);
+ }
+ }
+ }
+ break;
+ }
+}
+
+/*
+ * Turn a Datum into JSON text, appending the string to "result".
+ *
+ * tcategory and outfuncoid are from a previous call to json_categorize_type,
+ * except that if is_null is true then they can be invalid.
+ *
+ * If key_scalar is true, the value is being printed as a key, so insist
+ * it's of an acceptable type, and force it to be quoted.
*/
static void
datum_to_json(Datum val, bool is_null, StringInfo result,
- TYPCATEGORY tcategory, Oid typoutputfunc, bool key_scalar)
+ JsonTypeCategory tcategory, Oid outfuncoid,
+ bool key_scalar)
{
char *outputstr;
text *jsontext;
return;
}
+ if (key_scalar &&
+ (tcategory == JSONTYPE_ARRAY ||
+ tcategory == JSONTYPE_COMPOSITE ||
+ tcategory == JSONTYPE_JSON ||
+ tcategory == JSONTYPE_CAST))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("key value must be scalar, not array, composite or json")));
+
switch (tcategory)
{
- case TYPCATEGORY_ARRAY:
+ case JSONTYPE_ARRAY:
array_to_json_internal(val, result, false);
break;
- case TYPCATEGORY_COMPOSITE:
+ case JSONTYPE_COMPOSITE:
composite_to_json(val, result, false);
break;
- case TYPCATEGORY_BOOLEAN:
- if (!key_scalar)
- appendStringInfoString(result, DatumGetBool(val) ? "true" : "false");
+ case JSONTYPE_BOOL:
+ outputstr = DatumGetBool(val) ? "true" : "false";
+ if (key_scalar)
+ escape_json(result, outputstr);
else
- escape_json(result, DatumGetBool(val) ? "true" : "false");
+ appendStringInfoString(result, outputstr);
break;
- case TYPCATEGORY_NUMERIC:
- outputstr = OidOutputFunctionCall(typoutputfunc, val);
+ case JSONTYPE_NUMERIC:
+ outputstr = OidOutputFunctionCall(outfuncoid, val);
if (key_scalar)
{
/* always quote keys */
}
pfree(outputstr);
break;
- case TYPCATEGORY_JSON:
- /* JSON and JSONB will already be escaped */
- outputstr = OidOutputFunctionCall(typoutputfunc, val);
+ case JSONTYPE_JSON:
+ /* JSON and JSONB output will already be escaped */
+ outputstr = OidOutputFunctionCall(outfuncoid, val);
appendStringInfoString(result, outputstr);
pfree(outputstr);
break;
- case TYPCATEGORY_JSON_CAST:
- jsontext = DatumGetTextP(OidFunctionCall1(typoutputfunc, val));
+ case JSONTYPE_CAST:
+ /* outfuncoid refers to a cast function, not an output function */
+ jsontext = DatumGetTextP(OidFunctionCall1(outfuncoid, val));
outputstr = text_to_cstring(jsontext);
appendStringInfoString(result, outputstr);
pfree(outputstr);
pfree(jsontext);
break;
default:
- outputstr = OidOutputFunctionCall(typoutputfunc, val);
+ outputstr = OidOutputFunctionCall(outfuncoid, val);
if (key_scalar && *outputstr == '\0')
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
*/
static void
array_dim_to_json(StringInfo result, int dim, int ndims, int *dims, Datum *vals,
- bool *nulls, int *valcount, TYPCATEGORY tcategory,
- Oid typoutputfunc, bool use_line_feeds)
+ bool *nulls, int *valcount, JsonTypeCategory tcategory,
+ Oid outfuncoid, bool use_line_feeds)
{
int i;
const char *sep;
if (dim + 1 == ndims)
{
datum_to_json(vals[*valcount], nulls[*valcount], result, tcategory,
- typoutputfunc, false);
+ outfuncoid, false);
(*valcount)++;
}
else
* we'll say no.
*/
array_dim_to_json(result, dim + 1, ndims, dims, vals, nulls,
- valcount, tcategory, typoutputfunc, false);
+ valcount, tcategory, outfuncoid, false);
}
}
bool *nulls;
int16 typlen;
bool typbyval;
- char typalign,
- typdelim;
- Oid typioparam;
- Oid typoutputfunc;
- TYPCATEGORY tcategory;
- Oid castfunc = InvalidOid;
+ char typalign;
+ JsonTypeCategory tcategory;
+ Oid outfuncoid;
ndim = ARR_NDIM(v);
dim = ARR_DIMS(v);
return;
}
- get_type_io_data(element_type, IOFunc_output,
- &typlen, &typbyval, &typalign,
- &typdelim, &typioparam, &typoutputfunc);
-
- if (element_type > FirstNormalObjectId)
- {
- HeapTuple tuple;
- Form_pg_cast castForm;
-
- tuple = SearchSysCache2(CASTSOURCETARGET,
- ObjectIdGetDatum(element_type),
- ObjectIdGetDatum(JSONOID));
- if (HeapTupleIsValid(tuple))
- {
- castForm = (Form_pg_cast) GETSTRUCT(tuple);
-
- if (castForm->castmethod == COERCION_METHOD_FUNCTION)
- castfunc = typoutputfunc = castForm->castfunc;
+ get_typlenbyvalalign(element_type,
+ &typlen, &typbyval, &typalign);
- ReleaseSysCache(tuple);
- }
- }
+ json_categorize_type(element_type,
+ &tcategory, &outfuncoid);
deconstruct_array(v, element_type, typlen, typbyval,
typalign, &elements, &nulls,
&nitems);
- if (castfunc != InvalidOid)
- tcategory = TYPCATEGORY_JSON_CAST;
- else if (element_type == RECORDOID)
- tcategory = TYPCATEGORY_COMPOSITE;
- else if (element_type == JSONOID || element_type == JSONBOID)
- tcategory = TYPCATEGORY_JSON;
- else
- tcategory = TypeCategory(element_type);
-
array_dim_to_json(result, 0, ndim, dim, elements, nulls, &count, tcategory,
- typoutputfunc, use_line_feeds);
+ outfuncoid, use_line_feeds);
pfree(elements);
pfree(nulls);
Datum val;
bool isnull;
char *attname;
- TYPCATEGORY tcategory;
- Oid typoutput;
- bool typisvarlena;
- Oid castfunc = InvalidOid;
+ JsonTypeCategory tcategory;
+ Oid outfuncoid;
if (tupdesc->attrs[i]->attisdropped)
continue;
val = heap_getattr(tuple, i + 1, tupdesc, &isnull);
- getTypeOutputInfo(tupdesc->attrs[i]->atttypid,
- &typoutput, &typisvarlena);
-
- if (tupdesc->attrs[i]->atttypid > FirstNormalObjectId)
+ if (isnull)
{
- HeapTuple cast_tuple;
- Form_pg_cast castForm;
-
- cast_tuple = SearchSysCache2(CASTSOURCETARGET,
- ObjectIdGetDatum(tupdesc->attrs[i]->atttypid),
- ObjectIdGetDatum(JSONOID));
- if (HeapTupleIsValid(cast_tuple))
- {
- castForm = (Form_pg_cast) GETSTRUCT(cast_tuple);
-
- if (castForm->castmethod == COERCION_METHOD_FUNCTION)
- castfunc = typoutput = castForm->castfunc;
-
- ReleaseSysCache(cast_tuple);
- }
+ tcategory = JSONTYPE_NULL;
+ outfuncoid = InvalidOid;
}
-
- if (castfunc != InvalidOid)
- tcategory = TYPCATEGORY_JSON_CAST;
- else if (tupdesc->attrs[i]->atttypid == RECORDARRAYOID)
- tcategory = TYPCATEGORY_ARRAY;
- else if (tupdesc->attrs[i]->atttypid == RECORDOID)
- tcategory = TYPCATEGORY_COMPOSITE;
- else if (tupdesc->attrs[i]->atttypid == JSONOID ||
- tupdesc->attrs[i]->atttypid == JSONBOID)
- tcategory = TYPCATEGORY_JSON;
else
- tcategory = TypeCategory(tupdesc->attrs[i]->atttypid);
+ json_categorize_type(tupdesc->attrs[i]->atttypid,
+ &tcategory, &outfuncoid);
- datum_to_json(val, isnull, result, tcategory, typoutput, false);
+ datum_to_json(val, isnull, result, tcategory, outfuncoid, false);
}
appendStringInfoChar(result, '}');
}
/*
- * append Json for orig_val to result. If it's a field key, make sure it's
- * of an acceptable type and is quoted.
+ * Append JSON text for "val" to "result".
+ *
+ * This is just a thin wrapper around datum_to_json. If the same type will be
+ * printed many times, avoid using this; better to do the json_categorize_type
+ * lookups only once.
*/
static void
-add_json(Datum val, bool is_null, StringInfo result, Oid val_type, bool key_scalar)
+add_json(Datum val, bool is_null, StringInfo result,
+ Oid val_type, bool key_scalar)
{
- TYPCATEGORY tcategory;
- Oid typoutput;
- bool typisvarlena;
- Oid castfunc = InvalidOid;
+ JsonTypeCategory tcategory;
+ Oid outfuncoid;
if (val_type == InvalidOid)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("could not determine input data type")));
-
- getTypeOutputInfo(val_type, &typoutput, &typisvarlena);
-
- if (val_type > FirstNormalObjectId)
+ if (is_null)
{
- HeapTuple tuple;
- Form_pg_cast castForm;
-
- tuple = SearchSysCache2(CASTSOURCETARGET,
- ObjectIdGetDatum(val_type),
- ObjectIdGetDatum(JSONOID));
- if (HeapTupleIsValid(tuple))
- {
- castForm = (Form_pg_cast) GETSTRUCT(tuple);
-
- if (castForm->castmethod == COERCION_METHOD_FUNCTION)
- castfunc = typoutput = castForm->castfunc;
-
- ReleaseSysCache(tuple);
- }
+ tcategory = JSONTYPE_NULL;
+ outfuncoid = InvalidOid;
}
-
- if (castfunc != InvalidOid)
- tcategory = TYPCATEGORY_JSON_CAST;
- else if (val_type == RECORDARRAYOID)
- tcategory = TYPCATEGORY_ARRAY;
- else if (val_type == RECORDOID)
- tcategory = TYPCATEGORY_COMPOSITE;
- else if (val_type == JSONOID || val_type == JSONBOID)
- tcategory = TYPCATEGORY_JSON;
else
- tcategory = TypeCategory(val_type);
+ json_categorize_type(val_type,
+ &tcategory, &outfuncoid);
- if (key_scalar &&
- (tcategory == TYPCATEGORY_ARRAY ||
- tcategory == TYPCATEGORY_COMPOSITE ||
- tcategory == TYPCATEGORY_JSON ||
- tcategory == TYPCATEGORY_JSON_CAST))
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("key value must be scalar, not array, composite or json")));
-
- datum_to_json(val, is_null, result, tcategory, typoutput, key_scalar);
+ datum_to_json(val, is_null, result, tcategory, outfuncoid, key_scalar);
}
/*
Datum val = PG_GETARG_DATUM(0);
Oid val_type = get_fn_expr_argtype(fcinfo->flinfo, 0);
StringInfo result;
- TYPCATEGORY tcategory;
- Oid typoutput;
- bool typisvarlena;
- Oid castfunc = InvalidOid;
+ JsonTypeCategory tcategory;
+ Oid outfuncoid;
if (val_type == InvalidOid)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("could not determine input data type")));
- result = makeStringInfo();
+ json_categorize_type(val_type,
+ &tcategory, &outfuncoid);
- getTypeOutputInfo(val_type, &typoutput, &typisvarlena);
-
- if (val_type > FirstNormalObjectId)
- {
- HeapTuple tuple;
- Form_pg_cast castForm;
-
- tuple = SearchSysCache2(CASTSOURCETARGET,
- ObjectIdGetDatum(val_type),
- ObjectIdGetDatum(JSONOID));
- if (HeapTupleIsValid(tuple))
- {
- castForm = (Form_pg_cast) GETSTRUCT(tuple);
-
- if (castForm->castmethod == COERCION_METHOD_FUNCTION)
- castfunc = typoutput = castForm->castfunc;
-
- ReleaseSysCache(tuple);
- }
- }
-
- if (castfunc != InvalidOid)
- tcategory = TYPCATEGORY_JSON_CAST;
- else if (val_type == RECORDARRAYOID)
- tcategory = TYPCATEGORY_ARRAY;
- else if (val_type == RECORDOID)
- tcategory = TYPCATEGORY_COMPOSITE;
- else if (val_type == JSONOID || val_type == JSONBOID)
- tcategory = TYPCATEGORY_JSON;
- else
- tcategory = TypeCategory(val_type);
+ result = makeStringInfo();
- datum_to_json(val, false, result, tcategory, typoutput, false);
+ datum_to_json(val, false, result, tcategory, outfuncoid, false);
PG_RETURN_TEXT_P(cstring_to_text_with_len(result->data, result->len));
}
oldcontext;
StringInfo state;
Datum val;
- TYPCATEGORY tcategory;
- Oid typoutput;
- bool typisvarlena;
- Oid castfunc = InvalidOid;
+ JsonTypeCategory tcategory;
+ Oid outfuncoid;
if (val_type == InvalidOid)
ereport(ERROR,
{
/*
* Make this StringInfo in a context where it will persist for the
- * duration off the aggregate call. It's only needed for this initial
- * piece, as the StringInfo routines make sure they use the right
- * context to enlarge the object if necessary.
+ * duration of the aggregate call. MemoryContextSwitchTo is only
+ * needed the first time, as the StringInfo routines make sure they
+ * use the right context to enlarge the object if necessary.
*/
oldcontext = MemoryContextSwitchTo(aggcontext);
state = makeStringInfo();
/* fast path for NULLs */
if (PG_ARGISNULL(1))
{
- val = (Datum) 0;
- datum_to_json(val, true, state, 0, InvalidOid, false);
+ datum_to_json((Datum) 0, true, state, JSONTYPE_NULL, InvalidOid, false);
PG_RETURN_POINTER(state);
}
val = PG_GETARG_DATUM(1);
- getTypeOutputInfo(val_type, &typoutput, &typisvarlena);
-
- if (val_type > FirstNormalObjectId)
- {
- HeapTuple tuple;
- Form_pg_cast castForm;
-
- tuple = SearchSysCache2(CASTSOURCETARGET,
- ObjectIdGetDatum(val_type),
- ObjectIdGetDatum(JSONOID));
- if (HeapTupleIsValid(tuple))
- {
- castForm = (Form_pg_cast) GETSTRUCT(tuple);
-
- if (castForm->castmethod == COERCION_METHOD_FUNCTION)
- castfunc = typoutput = castForm->castfunc;
-
- ReleaseSysCache(tuple);
- }
- }
-
- if (castfunc != InvalidOid)
- tcategory = TYPCATEGORY_JSON_CAST;
- else if (val_type == RECORDARRAYOID)
- tcategory = TYPCATEGORY_ARRAY;
- else if (val_type == RECORDOID)
- tcategory = TYPCATEGORY_COMPOSITE;
- else if (val_type == JSONOID || val_type == JSONBOID)
- tcategory = TYPCATEGORY_JSON;
- else
- tcategory = TypeCategory(val_type);
+ /* XXX we do this every time?? */
+ json_categorize_type(val_type,
+ &tcategory, &outfuncoid);
+ /* add some whitespace if structured type and not first item */
if (!PG_ARGISNULL(0) &&
- (tcategory == TYPCATEGORY_ARRAY || tcategory == TYPCATEGORY_COMPOSITE))
+ (tcategory == JSONTYPE_ARRAY || tcategory == JSONTYPE_COMPOSITE))
{
appendStringInfoString(state, "\n ");
}
- datum_to_json(val, false, state, tcategory, typoutput, false);
+ datum_to_json(val, false, state, tcategory, outfuncoid, false);
/*
* The transition type for array_agg() is declared to be "internal", which