forgotten attachment

Regards

Pavel

2015-07-10 14:34 GMT+02:00 Pavel Stehule <pavel.steh...@gmail.com>:

> Hi
>
> I am sending review of this patch:
>
> 1. I reread a previous discussion and almost all are for this patch (me
> too)
>
> 2. I have to fix a typo in hstore_io.c function (update attached), other
> (patching, regress tests) without problems
>
> My objections:
>
> 1. comments - missing comment for some basic API, basic fields like
> "key_scalar" and similar
> 2. why you did indirect call via JsonOutContext?
>
> What is benefit
>
> dst.value(&dst, (Datum) 0, JSONTYPE_NULL, InvalidOid, InvalidOid, false);
>
> instead
>
> json_out_value(&dst, ....)
>
> ? Is it necessary?
>
> 3. if it should be used everywhere, then in EXPLAIN statement too.
>
> Regards
>
> Pavel
>
>
> 2015-07-10 6:31 GMT+02:00 Pavel Stehule <pavel.steh...@gmail.com>:
>
>>
>>
>> 2015-07-03 12:27 GMT+02:00 Heikki Linnakangas <hlinn...@iki.fi>:
>>
>>> On 05/27/2015 09:51 PM, Andrew Dunstan wrote:
>>>
>>>>
>>>> On 05/27/2015 02:37 PM, Robert Haas wrote:
>>>>
>>>>> On Tue, May 26, 2015 at 2:50 AM, Shulgin, Oleksandr
>>>>> <oleksandr.shul...@zalando.de> wrote:
>>>>>
>>>>>> Is it reasonable to add this patch to CommitFest now?
>>>>>>
>>>>> It's always reasonable to add a patch to the CommitFest if you would
>>>>> like for it to be reviewed and avoid having it get forgotten about.
>>>>> There seems to be some disagreement about whether we want this, but
>>>>> don't let that stop you from adding it to the next CommitFest.
>>>>>
>>>>
>>>> I'm not dead set against it either. When I have time I will take a
>>>> closer look.
>>>>
>>>
>>> Andrew, will you have the time to review this? Please add yourself as
>>> reviewer in the commitfest app if you do.
>>>
>>> My 2 cents is that I agree with your initial reaction: This is a lot of
>>> infrastructure and generalizing things, for little benefit. Let's change
>>> the current code where we generate JSON to be consistent with whitespace,
>>> and call it a day.
>>>
>>
>> I am  thinking so it is not bad idea. This code can enforce uniform
>> format, and it can check if produced value is correct. It can be used in
>> our code, it can be used by extension's developers.
>>
>> This patch is not small, but really new lines are not too much.
>>
>> I'll do review today.
>>
>> Regards
>>
>> Pavel
>>
>>
>>
>>
>>> - Heikki
>>>
>>>
>>> --
>>> Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
>>> To make changes to your subscription:
>>> http://www.postgresql.org/mailpref/pgsql-hackers
>>>
>>
>>
>
diff --git a/contrib/hstore/hstore_io.c b/contrib/hstore/hstore_io.c
new file mode 100644
index 7d89867..0ca223f
*** a/contrib/hstore/hstore_io.c
--- b/contrib/hstore/hstore_io.c
*************** hstore_to_json_loose(PG_FUNCTION_ARGS)
*** 1241,1286 ****
  	int			count = HS_COUNT(in);
  	char	   *base = STRPTR(in);
  	HEntry	   *entries = ARRPTR(in);
! 	StringInfoData tmp,
! 				dst;
  
  	if (count == 0)
  		PG_RETURN_TEXT_P(cstring_to_text_with_len("{}", 2));
  
  	initStringInfo(&tmp);
! 	initStringInfo(&dst);
! 
! 	appendStringInfoChar(&dst, '{');
  
  	for (i = 0; i < count; i++)
  	{
  		resetStringInfo(&tmp);
  		appendBinaryStringInfo(&tmp, HS_KEY(entries, base, i), HS_KEYLEN(entries, i));
! 		escape_json(&dst, tmp.data);
! 		appendStringInfoString(&dst, ": ");
  		if (HS_VALISNULL(entries, i))
! 			appendStringInfoString(&dst, "null");
  		/* guess that values of 't' or 'f' are booleans */
  		else if (HS_VALLEN(entries, i) == 1 && *(HS_VAL(entries, base, i)) == 't')
! 			appendStringInfoString(&dst, "true");
  		else if (HS_VALLEN(entries, i) == 1 && *(HS_VAL(entries, base, i)) == 'f')
! 			appendStringInfoString(&dst, "false");
  		else
  		{
  			resetStringInfo(&tmp);
  			appendBinaryStringInfo(&tmp, HS_VAL(entries, base, i), HS_VALLEN(entries, i));
  			if (IsValidJsonNumber(tmp.data, tmp.len))
! 				appendBinaryStringInfo(&dst, tmp.data, tmp.len);
  			else
! 				escape_json(&dst, tmp.data);
  		}
- 
- 		if (i + 1 != count)
- 			appendStringInfoString(&dst, ", ");
  	}
- 	appendStringInfoChar(&dst, '}');
  
! 	PG_RETURN_TEXT_P(cstring_to_text(dst.data));
  }
  
  PG_FUNCTION_INFO_V1(hstore_to_json);
--- 1241,1289 ----
  	int			count = HS_COUNT(in);
  	char	   *base = STRPTR(in);
  	HEntry	   *entries = ARRPTR(in);
! 	StringInfoData	tmp;
! 	JsonOutContext	dst;
  
  	if (count == 0)
  		PG_RETURN_TEXT_P(cstring_to_text_with_len("{}", 2));
  
  	initStringInfo(&tmp);
! 	json_out_init_context(&dst, JSON_OUT_USE_SPACES);
! 	dst.object_start(&dst);
  
  	for (i = 0; i < count; i++)
  	{
  		resetStringInfo(&tmp);
  		appendBinaryStringInfo(&tmp, HS_KEY(entries, base, i), HS_KEYLEN(entries, i));
! 		json_out_cstring(&dst, tmp.data, true);
! 
  		if (HS_VALISNULL(entries, i))
! 			dst.value(&dst, (Datum) 0, JSONTYPE_NULL, InvalidOid, InvalidOid, false);
! 
  		/* guess that values of 't' or 'f' are booleans */
  		else if (HS_VALLEN(entries, i) == 1 && *(HS_VAL(entries, base, i)) == 't')
! 			dst.value(&dst, BoolGetDatum(true), JSONTYPE_BOOL,
! 					  InvalidOid, InvalidOid, false);
! 
  		else if (HS_VALLEN(entries, i) == 1 && *(HS_VAL(entries, base, i)) == 'f')
! 			dst.value(&dst, BoolGetDatum(false), JSONTYPE_BOOL,
! 					  InvalidOid, InvalidOid, false);
  		else
  		{
  			resetStringInfo(&tmp);
  			appendBinaryStringInfo(&tmp, HS_VAL(entries, base, i), HS_VALLEN(entries, i));
+ 
+ 			/* this is a bit of a hack, but strictly it is not incorrect */
  			if (IsValidJsonNumber(tmp.data, tmp.len))
! 				dst.value(&dst, CStringGetDatum(tmp.data), JSONTYPE_JSON,
! 						  CSTRINGOID, 2293 /* cstring_out */, false);
  			else
! 				json_out_cstring(&dst, tmp.data, false);
  		}
  	}
  
! 	dst.object_end(&dst);
! 	PG_RETURN_TEXT_P(cstring_to_text_with_len(dst.result.data, dst.result.len));
  }
  
  PG_FUNCTION_INFO_V1(hstore_to_json);
*************** hstore_to_json(PG_FUNCTION_ARGS)
*** 1292,1329 ****
  	int			count = HS_COUNT(in);
  	char	   *base = STRPTR(in);
  	HEntry	   *entries = ARRPTR(in);
! 	StringInfoData tmp,
! 				dst;
  
  	if (count == 0)
  		PG_RETURN_TEXT_P(cstring_to_text_with_len("{}", 2));
  
  	initStringInfo(&tmp);
! 	initStringInfo(&dst);
! 
! 	appendStringInfoChar(&dst, '{');
  
  	for (i = 0; i < count; i++)
  	{
  		resetStringInfo(&tmp);
  		appendBinaryStringInfo(&tmp, HS_KEY(entries, base, i), HS_KEYLEN(entries, i));
! 		escape_json(&dst, tmp.data);
! 		appendStringInfoString(&dst, ": ");
  		if (HS_VALISNULL(entries, i))
! 			appendStringInfoString(&dst, "null");
  		else
  		{
  			resetStringInfo(&tmp);
  			appendBinaryStringInfo(&tmp, HS_VAL(entries, base, i), HS_VALLEN(entries, i));
! 			escape_json(&dst, tmp.data);
  		}
- 
- 		if (i + 1 != count)
- 			appendStringInfoString(&dst, ", ");
  	}
- 	appendStringInfoChar(&dst, '}');
  
! 	PG_RETURN_TEXT_P(cstring_to_text(dst.data));
  }
  
  PG_FUNCTION_INFO_V1(hstore_to_jsonb);
--- 1295,1328 ----
  	int			count = HS_COUNT(in);
  	char	   *base = STRPTR(in);
  	HEntry	   *entries = ARRPTR(in);
! 	StringInfoData	tmp;
! 	JsonOutContext	dst;
  
  	if (count == 0)
  		PG_RETURN_TEXT_P(cstring_to_text_with_len("{}", 2));
  
  	initStringInfo(&tmp);
! 	json_out_init_context(&dst, JSON_OUT_USE_SPACES);
! 	dst.object_start(&dst);
  
  	for (i = 0; i < count; i++)
  	{
  		resetStringInfo(&tmp);
  		appendBinaryStringInfo(&tmp, HS_KEY(entries, base, i), HS_KEYLEN(entries, i));
! 		json_out_cstring(&dst, tmp.data, true);
! 
  		if (HS_VALISNULL(entries, i))
! 			dst.value(&dst, (Datum) 0, JSONTYPE_NULL, InvalidOid, InvalidOid, false);
  		else
  		{
  			resetStringInfo(&tmp);
  			appendBinaryStringInfo(&tmp, HS_VAL(entries, base, i), HS_VALLEN(entries, i));
! 			json_out_cstring(&dst, tmp.data, false);
  		}
  	}
  
! 	dst.object_end(&dst);
! 	PG_RETURN_TEXT_P(cstring_to_text_with_len(dst.result.data, dst.result.len));
  }
  
  PG_FUNCTION_INFO_V1(hstore_to_jsonb);
diff --git a/src/backend/utils/adt/json.c b/src/backend/utils/adt/json.c
new file mode 100644
index 26d3843..1ed9deb
*** a/src/backend/utils/adt/json.c
--- b/src/backend/utils/adt/json.c
*************** typedef enum					/* contexts of JSON par
*** 53,73 ****
  	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_DATE,				/* we use special formatting for datetimes */
- 	JSONTYPE_TIMESTAMP,
- 	JSONTYPE_TIMESTAMPTZ,
- 	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);
--- 53,58 ----
*************** static void report_parse_error(JsonParse
*** 80,102 ****
  static void report_invalid_token(JsonLexContext *lex);
  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);
! 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);
! 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,
! 			  JsonTypeCategory tcategory, Oid outfuncoid,
! 			  bool key_scalar);
! static void add_json(Datum val, bool is_null, StringInfo result,
  		 Oid val_type, bool key_scalar);
- static text *catenate_stringinfo_string(StringInfo buffer, const char *addon);
  
  /* the null action object used for pure validation */
  static JsonSemAction nullSemAction =
--- 65,80 ----
  static void report_invalid_token(JsonLexContext *lex);
  static int	report_json_context(JsonLexContext *lex);
  static char *extract_mb_char(char *s);
! 
! static void array_dim_to_json(JsonOutContext *out, int dim, int ndims, int *dims,
  				  Datum *vals, bool *nulls, int *valcount,
! 				  JsonTypeCategory tcategory, Oid elemtypoid, Oid outfuncoid);
! static void array_to_json_internal(Datum array, JsonOutContext *out);
  static void json_categorize_type(Oid typoid,
  					 JsonTypeCategory *tcategory,
  					 Oid *outfuncoid);
! static void add_json(Datum val, bool is_null, JsonOutContext *out,
  		 Oid val_type, bool key_scalar);
  
  /* the null action object used for pure validation */
  static JsonSemAction nullSemAction =
*************** json_categorize_type(Oid typoid,
*** 1377,1399 ****
   * 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,
! 			  JsonTypeCategory tcategory, Oid outfuncoid,
  			  bool key_scalar)
  {
- 	char	   *outputstr;
- 	text	   *jsontext;
- 
  	/* callers are expected to ensure that null keys are not passed in */
  	Assert(!(key_scalar && is_null));
  
- 	if (is_null)
- 	{
- 		appendStringInfoString(result, "null");
- 		return;
- 	}
- 
  	if (key_scalar &&
  		(tcategory == JSONTYPE_ARRAY ||
  		 tcategory == JSONTYPE_COMPOSITE ||
--- 1355,1368 ----
   * 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.
   */
! void
! datum_to_json(Datum val, bool is_null, JsonOutContext *out,
! 			  JsonTypeCategory tcategory, Oid typoid, Oid outfuncoid,
  			  bool key_scalar)
  {
  	/* callers are expected to ensure that null keys are not passed in */
  	Assert(!(key_scalar && is_null));
  
  	if (key_scalar &&
  		(tcategory == JSONTYPE_ARRAY ||
  		 tcategory == JSONTYPE_COMPOSITE ||
*************** datum_to_json(Datum val, bool is_null, S
*** 1406,1415 ****
  	switch (tcategory)
  	{
  		case JSONTYPE_ARRAY:
! 			array_to_json_internal(val, result, false);
  			break;
  		case JSONTYPE_COMPOSITE:
! 			composite_to_json(val, result, false);
  			break;
  		case JSONTYPE_BOOL:
  			outputstr = DatumGetBool(val) ? "true" : "false";
--- 1375,1500 ----
  	switch (tcategory)
  	{
  		case JSONTYPE_ARRAY:
! 			array_to_json_internal(val, out);
  			break;
  		case JSONTYPE_COMPOSITE:
! 			composite_to_json(val, out);
! 			break;
! 		default:
! 			out->value(out, val, tcategory, typoid, outfuncoid, key_scalar);
! 			break;
! 	}
! }
! 
! void
! json_out_init_context(JsonOutContext *out, int flags)
! {
! 	out->object_start = json_out_object_start;
! 	out->object_end = json_out_object_end;
! 	out->array_start = json_out_array_start;
! 	out->array_end = json_out_array_end;
! 	out->before_value = json_out_before_value;
! 	out->value = json_out_value;
! 	out->after_value = json_out_after_value;
! 
! 	initStringInfo(&out->result);
! 	out->flags = flags;
! 	out->need_comma = false;
! 	out->depth = 0;
! 
! 	out->agg_tcategory = JSONTYPE_OTHER;
! 	out->agg_outfuncoid = InvalidOid;
! }
! 
! void
! json_out_before_value(JsonOutContext *out)
! {
! 	if (out->need_comma)
! 	{
! 		out->need_comma = false;
! 		appendStringInfoChar(&out->result, ',');
! 
! 		/* don't get into all the prettiness deep in the object structure */
! 		if (out->depth == 1)
! 		{
! 			if ((out->flags & JSON_OUT_USE_SPACES) != 0)
! 				appendStringInfoChar(&out->result, ' ');
! 
! 			if ((out->flags & JSON_OUT_USE_LINE_FEEDS) != 0)
! 				appendStringInfoString(&out->result, "\n ");
! 		}
! 	}
! }
! 
! void
! json_out_after_value(JsonOutContext *out, bool key_scalar)
! {
! 	if (key_scalar)
! 	{
! 		appendStringInfoChar(&out->result, ':');
! 
! 		if (out->depth == 1 && (out->flags & JSON_OUT_USE_SPACES) != 0)
! 			appendStringInfoChar(&out->result, ' ');
! 
! 		out->need_comma = false;
! 	}
! 	else
! 		out->need_comma = true;
! }
! 
! void
! json_out_object_start(JsonOutContext *out)
! {
! 	out->before_value(out);
! 
! 	appendStringInfoChar(&out->result, '{');
! 	out->depth++;
! }
! 
! void
! json_out_object_end(JsonOutContext *out)
! {
! 	appendStringInfoChar(&out->result, '}');
! 
! 	out->after_value(out, false);
! 	out->depth--;
! }
! 
! void
! json_out_array_start(JsonOutContext *out)
! {
! 	out->before_value(out);
! 
! 	appendStringInfoChar(&out->result, '[');
! 	out->depth++;
! }
! 
! void
! json_out_array_end(JsonOutContext *out)
! {
! 	appendStringInfoChar(&out->result, ']');
! 
! 	out->after_value(out, false);
! 	out->depth--;
! }
! 
! void
! json_out_value(JsonOutContext *out, Datum val, JsonTypeCategory tcategory,
! 			Oid typoid, Oid outfuncoid, bool key_scalar)
! {
! 	char	   *outputstr;
! 	text	   *jsontext;
! 	StringInfo		result = &out->result;
! 
! 	Assert(!(tcategory == JSONTYPE_ARRAY || tcategory == JSONTYPE_COMPOSITE));
! 
! 	/* check if there was an element before this one and add a separator */
! 	out->before_value(out);
! 
! 	switch (tcategory)
! 	{
! 		case JSONTYPE_NULL:
! 			appendStringInfoString(result, "null");
  			break;
  		case JSONTYPE_BOOL:
  			outputstr = DatumGetBool(val) ? "true" : "false";
*************** datum_to_json(Datum val, bool is_null, S
*** 1520,1530 ****
  			pfree(jsontext);
  			break;
  		default:
! 			outputstr = OidOutputFunctionCall(outfuncoid, val);
! 			escape_json(result, outputstr);
! 			pfree(outputstr);
  			break;
  	}
  }
  
  /*
--- 1605,1630 ----
  			pfree(jsontext);
  			break;
  		default:
! 			if (typoid == CSTRINGOID)
! 				escape_json(result, DatumGetCString(val));
! 			else
! 			{
! 				outputstr = OidOutputFunctionCall(outfuncoid, val);
! 				escape_json(result, outputstr);
! 				pfree(outputstr);
! 			}
  			break;
  	}
+ 
+ 	/* output key-value separator if needed and set need_comma accordingly */
+ 	out->after_value(out, key_scalar);
+ }
+ 
+ void
+ json_out_cstring(JsonOutContext *out, const char *str, bool key_scalar)
+ {
+ 	out->value(out, CStringGetDatum(str), JSONTYPE_OTHER,
+ 			   CSTRINGOID, 2293 /* cstring_out */, key_scalar);
  }
  
  /*
*************** datum_to_json(Datum val, bool is_null, S
*** 1533,1581 ****
   * ourselves recursively to process the next dimension.
   */
  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)
  {
  	int			i;
- 	const char *sep;
  
  	Assert(dim < ndims);
  
! 	sep = use_line_feeds ? ",\n " : ",";
! 
! 	appendStringInfoChar(result, '[');
  
  	for (i = 1; i <= dims[dim]; i++)
  	{
- 		if (i > 1)
- 			appendStringInfoString(result, sep);
- 
  		if (dim + 1 == ndims)
  		{
! 			datum_to_json(vals[*valcount], nulls[*valcount], result, tcategory,
! 						  outfuncoid, false);
  			(*valcount)++;
  		}
  		else
! 		{
! 			/*
! 			 * Do we want line feeds on inner dimensions of arrays? For now
! 			 * we'll say no.
! 			 */
! 			array_dim_to_json(result, dim + 1, ndims, dims, vals, nulls,
! 							  valcount, tcategory, outfuncoid, false);
! 		}
  	}
  
! 	appendStringInfoChar(result, ']');
  }
  
  /*
   * Turn an array into JSON.
   */
  static void
! array_to_json_internal(Datum array, StringInfo result, bool use_line_feeds)
  {
  	ArrayType  *v = DatumGetArrayTypeP(array);
  	Oid			element_type = ARR_ELEMTYPE(v);
--- 1633,1669 ----
   * ourselves recursively to process the next dimension.
   */
  static void
! array_dim_to_json(JsonOutContext *out, int dim, int ndims, int *dims, Datum *vals,
  				  bool *nulls, int *valcount, JsonTypeCategory tcategory,
! 				  Oid elemtypoid, Oid outfuncoid)
  {
  	int			i;
  
  	Assert(dim < ndims);
  
! 	out->array_start(out);
  
  	for (i = 1; i <= dims[dim]; i++)
  	{
  		if (dim + 1 == ndims)
  		{
! 			datum_to_json(vals[*valcount], nulls[*valcount], out, tcategory,
! 						  elemtypoid, outfuncoid, false);
  			(*valcount)++;
  		}
  		else
! 			array_dim_to_json(out, dim + 1, ndims, dims, vals, nulls,
! 							  valcount, tcategory, elemtypoid, outfuncoid);
  	}
  
! 	out->array_end(out);
  }
  
  /*
   * Turn an array into JSON.
   */
  static void
! array_to_json_internal(Datum array, JsonOutContext *out)
  {
  	ArrayType  *v = DatumGetArrayTypeP(array);
  	Oid			element_type = ARR_ELEMTYPE(v);
*************** array_to_json_internal(Datum array, Stri
*** 1597,1603 ****
  
  	if (nitems <= 0)
  	{
! 		appendStringInfoString(result, "[]");
  		return;
  	}
  
--- 1685,1692 ----
  
  	if (nitems <= 0)
  	{
! 		out->array_start(out);
! 		out->array_end(out);
  		return;
  	}
  
*************** array_to_json_internal(Datum array, Stri
*** 1611,1618 ****
  					  typalign, &elements, &nulls,
  					  &nitems);
  
! 	array_dim_to_json(result, 0, ndim, dim, elements, nulls, &count, tcategory,
! 					  outfuncoid, use_line_feeds);
  
  	pfree(elements);
  	pfree(nulls);
--- 1700,1707 ----
  					  typalign, &elements, &nulls,
  					  &nitems);
  
! 	array_dim_to_json(out, 0, ndim, dim, elements, nulls, &count, tcategory,
! 					  element_type, outfuncoid);
  
  	pfree(elements);
  	pfree(nulls);
*************** array_to_json_internal(Datum array, Stri
*** 1621,1628 ****
  /*
   * Turn a composite / record into JSON.
   */
! static void
! composite_to_json(Datum composite, StringInfo result, bool use_line_feeds)
  {
  	HeapTupleHeader td;
  	Oid			tupType;
--- 1710,1717 ----
  /*
   * Turn a composite / record into JSON.
   */
! void
! composite_to_json(Datum composite, JsonOutContext *out)
  {
  	HeapTupleHeader td;
  	Oid			tupType;
*************** composite_to_json(Datum composite, Strin
*** 1631,1640 ****
  	HeapTupleData tmptup,
  			   *tuple;
  	int			i;
- 	bool		needsep = false;
- 	const char *sep;
- 
- 	sep = use_line_feeds ? ",\n " : ",";
  
  	td = DatumGetHeapTupleHeader(composite);
  
--- 1720,1725 ----
*************** composite_to_json(Datum composite, Strin
*** 1648,1654 ****
  	tmptup.t_data = td;
  	tuple = &tmptup;
  
! 	appendStringInfoChar(result, '{');
  
  	for (i = 0; i < tupdesc->natts; i++)
  	{
--- 1733,1739 ----
  	tmptup.t_data = td;
  	tuple = &tmptup;
  
! 	out->object_start(out);
  
  	for (i = 0; i < tupdesc->natts; i++)
  	{
*************** composite_to_json(Datum composite, Strin
*** 1661,1676 ****
  		if (tupdesc->attrs[i]->attisdropped)
  			continue;
  
- 		if (needsep)
- 			appendStringInfoString(result, sep);
- 		needsep = true;
- 
  		attname = NameStr(tupdesc->attrs[i]->attname);
! 		escape_json(result, attname);
! 		appendStringInfoChar(result, ':');
  
  		val = heap_getattr(tuple, i + 1, tupdesc, &isnull);
- 
  		if (isnull)
  		{
  			tcategory = JSONTYPE_NULL;
--- 1746,1755 ----
  		if (tupdesc->attrs[i]->attisdropped)
  			continue;
  
  		attname = NameStr(tupdesc->attrs[i]->attname);
! 		json_out_cstring(out, attname, true);
  
  		val = heap_getattr(tuple, i + 1, tupdesc, &isnull);
  		if (isnull)
  		{
  			tcategory = JSONTYPE_NULL;
*************** composite_to_json(Datum composite, Strin
*** 1680,1689 ****
  			json_categorize_type(tupdesc->attrs[i]->atttypid,
  								 &tcategory, &outfuncoid);
  
! 		datum_to_json(val, isnull, result, tcategory, outfuncoid, false);
  	}
  
! 	appendStringInfoChar(result, '}');
  	ReleaseTupleDesc(tupdesc);
  }
  
--- 1759,1770 ----
  			json_categorize_type(tupdesc->attrs[i]->atttypid,
  								 &tcategory, &outfuncoid);
  
! 		datum_to_json(val, isnull, out, tcategory,
! 					  tupdesc->attrs[i]->atttypid, outfuncoid, false);
  	}
  
! 	out->object_end(out);
! 
  	ReleaseTupleDesc(tupdesc);
  }
  
*************** composite_to_json(Datum composite, Strin
*** 1695,1701 ****
   * lookups only once.
   */
  static void
! add_json(Datum val, bool is_null, StringInfo result,
  		 Oid val_type, bool key_scalar)
  {
  	JsonTypeCategory tcategory;
--- 1776,1782 ----
   * lookups only once.
   */
  static void
! add_json(Datum val, bool is_null, JsonOutContext *out,
  		 Oid val_type, bool key_scalar)
  {
  	JsonTypeCategory tcategory;
*************** add_json(Datum val, bool is_null, String
*** 1715,1721 ****
  		json_categorize_type(val_type,
  							 &tcategory, &outfuncoid);
  
! 	datum_to_json(val, is_null, result, tcategory, outfuncoid, key_scalar);
  }
  
  /*
--- 1796,1802 ----
  		json_categorize_type(val_type,
  							 &tcategory, &outfuncoid);
  
! 	datum_to_json(val, is_null, out, tcategory, val_type, outfuncoid, key_scalar);
  }
  
  /*
*************** extern Datum
*** 1725,1737 ****
  array_to_json(PG_FUNCTION_ARGS)
  {
  	Datum		array = PG_GETARG_DATUM(0);
! 	StringInfo	result;
! 
! 	result = makeStringInfo();
  
! 	array_to_json_internal(array, result, false);
  
! 	PG_RETURN_TEXT_P(cstring_to_text_with_len(result->data, result->len));
  }
  
  /*
--- 1806,1817 ----
  array_to_json(PG_FUNCTION_ARGS)
  {
  	Datum		array = PG_GETARG_DATUM(0);
! 	JsonOutContext	out;
  
! 	json_out_init_context(&out, 0);
! 	array_to_json_internal(array, &out);
  
! 	PG_RETURN_TEXT_P(cstring_to_text_with_len(out.result.data, out.result.len));
  }
  
  /*
*************** array_to_json_pretty(PG_FUNCTION_ARGS)
*** 1742,1754 ****
  {
  	Datum		array = PG_GETARG_DATUM(0);
  	bool		use_line_feeds = PG_GETARG_BOOL(1);
! 	StringInfo	result;
! 
! 	result = makeStringInfo();
  
! 	array_to_json_internal(array, result, use_line_feeds);
  
! 	PG_RETURN_TEXT_P(cstring_to_text_with_len(result->data, result->len));
  }
  
  /*
--- 1822,1833 ----
  {
  	Datum		array = PG_GETARG_DATUM(0);
  	bool		use_line_feeds = PG_GETARG_BOOL(1);
! 	JsonOutContext	out;
  
! 	json_out_init_context(&out, use_line_feeds ? JSON_OUT_USE_LINE_FEEDS : 0);
! 	array_to_json_internal(array, &out);
  
! 	PG_RETURN_TEXT_P(cstring_to_text_with_len(out.result.data, out.result.len));
  }
  
  /*
*************** extern Datum
*** 1758,1770 ****
  row_to_json(PG_FUNCTION_ARGS)
  {
  	Datum		array = PG_GETARG_DATUM(0);
! 	StringInfo	result;
! 
! 	result = makeStringInfo();
  
! 	composite_to_json(array, result, false);
  
! 	PG_RETURN_TEXT_P(cstring_to_text_with_len(result->data, result->len));
  }
  
  /*
--- 1837,1848 ----
  row_to_json(PG_FUNCTION_ARGS)
  {
  	Datum		array = PG_GETARG_DATUM(0);
! 	JsonOutContext	out;
  
! 	json_out_init_context(&out, 0);
! 	composite_to_json(array, &out);
  
! 	PG_RETURN_TEXT_P(cstring_to_text_with_len(out.result.data, out.result.len));
  }
  
  /*
*************** row_to_json_pretty(PG_FUNCTION_ARGS)
*** 1775,1787 ****
  {
  	Datum		array = PG_GETARG_DATUM(0);
  	bool		use_line_feeds = PG_GETARG_BOOL(1);
! 	StringInfo	result;
! 
! 	result = makeStringInfo();
  
! 	composite_to_json(array, result, use_line_feeds);
  
! 	PG_RETURN_TEXT_P(cstring_to_text_with_len(result->data, result->len));
  }
  
  /*
--- 1853,1864 ----
  {
  	Datum		array = PG_GETARG_DATUM(0);
  	bool		use_line_feeds = PG_GETARG_BOOL(1);
! 	JsonOutContext	out;
  
! 	json_out_init_context(&out, use_line_feeds ? JSON_OUT_USE_LINE_FEEDS : 0);
! 	composite_to_json(array, &out);
  
! 	PG_RETURN_TEXT_P(cstring_to_text_with_len(out.result.data, out.result.len));
  }
  
  /*
*************** to_json(PG_FUNCTION_ARGS)
*** 1792,1800 ****
  {
  	Datum		val = PG_GETARG_DATUM(0);
  	Oid			val_type = get_fn_expr_argtype(fcinfo->flinfo, 0);
- 	StringInfo	result;
  	JsonTypeCategory tcategory;
  	Oid			outfuncoid;
  
  	if (val_type == InvalidOid)
  		ereport(ERROR,
--- 1869,1879 ----
  {
  	Datum		val = PG_GETARG_DATUM(0);
  	Oid			val_type = get_fn_expr_argtype(fcinfo->flinfo, 0);
  	JsonTypeCategory tcategory;
  	Oid			outfuncoid;
+ 	JsonOutContext	out;
+ 
+ 	json_out_init_context(&out, 0);
  
  	if (val_type == InvalidOid)
  		ereport(ERROR,
*************** to_json(PG_FUNCTION_ARGS)
*** 1804,1814 ****
  	json_categorize_type(val_type,
  						 &tcategory, &outfuncoid);
  
! 	result = makeStringInfo();
! 
! 	datum_to_json(val, false, result, tcategory, outfuncoid, false);
  
! 	PG_RETURN_TEXT_P(cstring_to_text_with_len(result->data, result->len));
  }
  
  /*
--- 1883,1891 ----
  	json_categorize_type(val_type,
  						 &tcategory, &outfuncoid);
  
! 	datum_to_json(val, false, &out, tcategory, val_type, outfuncoid, false);
  
! 	PG_RETURN_TEXT_P(cstring_to_text_with_len(out.result.data, out.result.len));
  }
  
  /*
*************** json_agg_transfn(PG_FUNCTION_ARGS)
*** 1822,1831 ****
  	Oid			val_type = get_fn_expr_argtype(fcinfo->flinfo, 1);
  	MemoryContext aggcontext,
  				oldcontext;
! 	StringInfo	state;
  	Datum		val;
- 	JsonTypeCategory tcategory;
- 	Oid			outfuncoid;
  
  	if (val_type == InvalidOid)
  		ereport(ERROR,
--- 1899,1906 ----
  	Oid			val_type = get_fn_expr_argtype(fcinfo->flinfo, 1);
  	MemoryContext aggcontext,
  				oldcontext;
! 	JsonOutContext	*out = NULL;
  	Datum		val;
  
  	if (val_type == InvalidOid)
  		ereport(ERROR,
*************** json_agg_transfn(PG_FUNCTION_ARGS)
*** 1847,1891 ****
  		 * use the right context to enlarge the object if necessary.
  		 */
  		oldcontext = MemoryContextSwitchTo(aggcontext);
! 		state = makeStringInfo();
  		MemoryContextSwitchTo(oldcontext);
  
! 		appendStringInfoChar(state, '[');
  	}
  	else
! 	{
! 		state = (StringInfo) PG_GETARG_POINTER(0);
! 		appendStringInfoString(state, ", ");
! 	}
  
  	/* fast path for NULLs */
  	if (PG_ARGISNULL(1))
  	{
! 		datum_to_json((Datum) 0, true, state, JSONTYPE_NULL, InvalidOid, false);
! 		PG_RETURN_POINTER(state);
  	}
  
  	val = PG_GETARG_DATUM(1);
  
! 	/* 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 == JSONTYPE_ARRAY || tcategory == JSONTYPE_COMPOSITE))
! 	{
! 		appendStringInfoString(state, "\n ");
! 	}
  
! 	datum_to_json(val, false, state, tcategory, outfuncoid, false);
  
  	/*
  	 * The transition type for array_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 ArrayBuildState pointer through nodeAgg.c's machinations.
  	 */
! 	PG_RETURN_POINTER(state);
  }
  
  /*
--- 1922,1959 ----
  		 * use the right context to enlarge the object if necessary.
  		 */
  		oldcontext = MemoryContextSwitchTo(aggcontext);
! 		out = palloc(sizeof(JsonOutContext));
! 		json_out_init_context(out, JSON_OUT_USE_LINE_FEEDS);
  		MemoryContextSwitchTo(oldcontext);
  
! 		out->array_start(out);
  	}
  	else
! 		out = (JsonOutContext *) PG_GETARG_POINTER(0);
  
  	/* fast path for NULLs */
  	if (PG_ARGISNULL(1))
  	{
! 		datum_to_json((Datum) 0, true, out,
! 					  JSONTYPE_NULL, InvalidOid, InvalidOid, false);
! 		PG_RETURN_POINTER(out);
  	}
  
  	val = PG_GETARG_DATUM(1);
  
! 	if (out->agg_outfuncoid == InvalidOid)
! 		json_categorize_type(val_type,
! 							 &out->agg_tcategory, &out->agg_outfuncoid);
  
! 	datum_to_json(val, false, out, out->agg_tcategory,
! 				  val_type, out->agg_outfuncoid, false);
  
  	/*
  	 * The transition type for array_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 ArrayBuildOut pointer through nodeAgg.c's machinations.
  	 */
! 	PG_RETURN_POINTER(out);
  }
  
  /*
*************** json_agg_transfn(PG_FUNCTION_ARGS)
*** 1894,1912 ****
  Datum
  json_agg_finalfn(PG_FUNCTION_ARGS)
  {
! 	StringInfo	state;
  
  	/* cannot be called directly because of internal-type argument */
  	Assert(AggCheckCallContext(fcinfo, NULL));
  
! 	state = PG_ARGISNULL(0) ? NULL : (StringInfo) PG_GETARG_POINTER(0);
  
  	/* NULL result for no rows in, as is standard with aggregates */
! 	if (state == NULL)
  		PG_RETURN_NULL();
  
! 	/* Else return state with appropriate array terminator added */
! 	PG_RETURN_TEXT_P(catenate_stringinfo_string(state, "]"));
  }
  
  /*
--- 1962,1981 ----
  Datum
  json_agg_finalfn(PG_FUNCTION_ARGS)
  {
! 	JsonOutContext	*out;
  
  	/* cannot be called directly because of internal-type argument */
  	Assert(AggCheckCallContext(fcinfo, NULL));
  
! 	out = PG_ARGISNULL(0) ? NULL : (JsonOutContext *) PG_GETARG_POINTER(0);
  
  	/* NULL result for no rows in, as is standard with aggregates */
! 	if (out == NULL)
  		PG_RETURN_NULL();
  
! 	out->array_end(out);
! 
! 	PG_RETURN_TEXT_P(cstring_to_text_with_len(out->result.data, out->result.len));
  }
  
  /*
*************** json_object_agg_transfn(PG_FUNCTION_ARGS
*** 1920,1926 ****
  	Oid			val_type;
  	MemoryContext aggcontext,
  				oldcontext;
! 	StringInfo	state;
  	Datum		arg;
  
  	if (!AggCheckCallContext(fcinfo, &aggcontext))
--- 1989,1995 ----
  	Oid			val_type;
  	MemoryContext aggcontext,
  				oldcontext;
! 	JsonOutContext	*out = NULL;
  	Datum		arg;
  
  	if (!AggCheckCallContext(fcinfo, &aggcontext))
*************** json_object_agg_transfn(PG_FUNCTION_ARGS
*** 1938,1953 ****
  		 * use the right context to enlarge the object if necessary.
  		 */
  		oldcontext = MemoryContextSwitchTo(aggcontext);
! 		state = makeStringInfo();
  		MemoryContextSwitchTo(oldcontext);
  
! 		appendStringInfoString(state, "{ ");
  	}
  	else
! 	{
! 		state = (StringInfo) PG_GETARG_POINTER(0);
! 		appendStringInfoString(state, ", ");
! 	}
  
  	/*
  	 * Note: since json_object_agg() is declared as taking type "any", the
--- 2007,2020 ----
  		 * use the right context to enlarge the object if necessary.
  		 */
  		oldcontext = MemoryContextSwitchTo(aggcontext);
! 		out = palloc(sizeof(JsonOutContext));
! 		json_out_init_context(out, JSON_OUT_USE_SPACES);
  		MemoryContextSwitchTo(oldcontext);
  
! 		out->object_start(out);
  	}
  	else
! 		out = (JsonOutContext *) PG_GETARG_POINTER(0);
  
  	/*
  	 * Note: since json_object_agg() is declared as taking type "any", the
*************** json_object_agg_transfn(PG_FUNCTION_ARGS
*** 1970,1978 ****
  
  	arg = PG_GETARG_DATUM(1);
  
! 	add_json(arg, false, state, val_type, true);
! 
! 	appendStringInfoString(state, " : ");
  
  	val_type = get_fn_expr_argtype(fcinfo->flinfo, 2);
  
--- 2037,2043 ----
  
  	arg = PG_GETARG_DATUM(1);
  
! 	add_json(arg, false, out, val_type, true);
  
  	val_type = get_fn_expr_argtype(fcinfo->flinfo, 2);
  
*************** json_object_agg_transfn(PG_FUNCTION_ARGS
*** 1986,1994 ****
  	else
  		arg = PG_GETARG_DATUM(2);
  
! 	add_json(arg, PG_ARGISNULL(2), state, val_type, false);
  
! 	PG_RETURN_POINTER(state);
  }
  
  /*
--- 2051,2059 ----
  	else
  		arg = PG_GETARG_DATUM(2);
  
! 	add_json(arg, PG_ARGISNULL(2), out, val_type, false);
  
! 	PG_RETURN_POINTER(out);
  }
  
  /*
*************** json_object_agg_transfn(PG_FUNCTION_ARGS
*** 1997,2035 ****
  Datum
  json_object_agg_finalfn(PG_FUNCTION_ARGS)
  {
! 	StringInfo	state;
  
  	/* cannot be called directly because of internal-type argument */
  	Assert(AggCheckCallContext(fcinfo, NULL));
  
! 	state = PG_ARGISNULL(0) ? NULL : (StringInfo) PG_GETARG_POINTER(0);
  
  	/* NULL result for no rows in, as is standard with aggregates */
! 	if (state == NULL)
  		PG_RETURN_NULL();
  
! 	/* Else return state with appropriate object terminator added */
! 	PG_RETURN_TEXT_P(catenate_stringinfo_string(state, " }"));
! }
! 
! /*
!  * Helper function for aggregates: return given StringInfo's contents plus
!  * specified trailing string, as a text datum.  We need this because aggregate
!  * final functions are not allowed to modify the aggregate state.
!  */
! static text *
! catenate_stringinfo_string(StringInfo buffer, const char *addon)
! {
! 	/* custom version of cstring_to_text_with_len */
! 	int			buflen = buffer->len;
! 	int			addlen = strlen(addon);
! 	text	   *result = (text *) palloc(buflen + addlen + VARHDRSZ);
! 
! 	SET_VARSIZE(result, buflen + addlen + VARHDRSZ);
! 	memcpy(VARDATA(result), buffer->data, buflen);
! 	memcpy(VARDATA(result) + buflen, addon, addlen);
  
! 	return result;
  }
  
  /*
--- 2062,2081 ----
  Datum
  json_object_agg_finalfn(PG_FUNCTION_ARGS)
  {
! 	JsonOutContext	*out;
  
  	/* cannot be called directly because of internal-type argument */
  	Assert(AggCheckCallContext(fcinfo, NULL));
  
! 	out = PG_ARGISNULL(0) ? NULL : (JsonOutContext *) PG_GETARG_POINTER(0);
  
  	/* NULL result for no rows in, as is standard with aggregates */
! 	if (out == NULL)
  		PG_RETURN_NULL();
  
! 	out->object_end(out);
  
! 	PG_RETURN_TEXT_P(cstring_to_text_with_len(out->result.data, out->result.len));
  }
  
  /*
*************** json_build_object(PG_FUNCTION_ARGS)
*** 2041,2048 ****
  	int			nargs = PG_NARGS();
  	int			i;
  	Datum		arg;
! 	const char *sep = "";
! 	StringInfo	result;
  	Oid			val_type;
  
  	if (nargs % 2 != 0)
--- 2087,2093 ----
  	int			nargs = PG_NARGS();
  	int			i;
  	Datum		arg;
! 	JsonOutContext	out;
  	Oid			val_type;
  
  	if (nargs % 2 != 0)
*************** json_build_object(PG_FUNCTION_ARGS)
*** 2051,2059 ****
  				 errmsg("argument list must have even number of elements"),
  				 errhint("The arguments of json_build_object() must consist of alternating keys and values.")));
  
! 	result = makeStringInfo();
! 
! 	appendStringInfoChar(result, '{');
  
  	for (i = 0; i < nargs; i += 2)
  	{
--- 2096,2103 ----
  				 errmsg("argument list must have even number of elements"),
  				 errhint("The arguments of json_build_object() must consist of alternating keys and values.")));
  
! 	json_out_init_context(&out, JSON_OUT_USE_SPACES);
! 	out.object_start(&out);
  
  	for (i = 0; i < nargs; i += 2)
  	{
*************** json_build_object(PG_FUNCTION_ARGS)
*** 2064,2071 ****
  		 * 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);
--- 2108,2113 ----
*************** json_build_object(PG_FUNCTION_ARGS)
*** 2084,2092 ****
  
  		arg = PG_GETARG_DATUM(i);
  
! 		add_json(arg, false, result, val_type, true);
! 
! 		appendStringInfoString(result, " : ");
  
  		/* process value */
  		val_type = get_fn_expr_argtype(fcinfo->flinfo, i + 1);
--- 2126,2132 ----
  
  		arg = PG_GETARG_DATUM(i);
  
! 		add_json(arg, false, &out, val_type, true);
  
  		/* process value */
  		val_type = get_fn_expr_argtype(fcinfo->flinfo, i + 1);
*************** json_build_object(PG_FUNCTION_ARGS)
*** 2102,2113 ****
  		else
  			arg = PG_GETARG_DATUM(i + 1);
  
! 		add_json(arg, PG_ARGISNULL(i + 1), result, val_type, false);
  	}
  
! 	appendStringInfoChar(result, '}');
  
! 	PG_RETURN_TEXT_P(cstring_to_text_with_len(result->data, result->len));
  }
  
  /*
--- 2142,2153 ----
  		else
  			arg = PG_GETARG_DATUM(i + 1);
  
! 		add_json(arg, PG_ARGISNULL(i + 1), &out, val_type, false);
  	}
  
! 	out.object_end(&out);
  
! 	PG_RETURN_TEXT_P(cstring_to_text_with_len(out.result.data, out.result.len));
  }
  
  /*
*************** json_build_array(PG_FUNCTION_ARGS)
*** 2128,2140 ****
  	int			nargs = PG_NARGS();
  	int			i;
  	Datum		arg;
! 	const char *sep = "";
! 	StringInfo	result;
  	Oid			val_type;
  
! 	result = makeStringInfo();
! 
! 	appendStringInfoChar(result, '[');
  
  	for (i = 0; i < nargs; i++)
  	{
--- 2168,2178 ----
  	int			nargs = PG_NARGS();
  	int			i;
  	Datum		arg;
! 	JsonOutContext	out;
  	Oid			val_type;
  
! 	json_out_init_context(&out, JSON_OUT_USE_SPACES);
! 	out.array_start(&out);
  
  	for (i = 0; i < nargs; i++)
  	{
*************** json_build_array(PG_FUNCTION_ARGS)
*** 2145,2153 ****
  		 * 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)
--- 2183,2188 ----
*************** json_build_array(PG_FUNCTION_ARGS)
*** 2161,2172 ****
  		else
  			arg = PG_GETARG_DATUM(i);
  
! 		add_json(arg, PG_ARGISNULL(i), result, val_type, false);
  	}
  
! 	appendStringInfoChar(result, ']');
  
! 	PG_RETURN_TEXT_P(cstring_to_text_with_len(result->data, result->len));
  }
  
  /*
--- 2196,2207 ----
  		else
  			arg = PG_GETARG_DATUM(i);
  
! 		add_json(arg, PG_ARGISNULL(i), &out, val_type, false);
  	}
  
! 	out.array_end(&out);
  
! 	PG_RETURN_TEXT_P(cstring_to_text_with_len(out.result.data, out.result.len));
  }
  
  /*
*************** json_object(PG_FUNCTION_ARGS)
*** 2189,2202 ****
  {
  	ArrayType  *in_array = PG_GETARG_ARRAYTYPE_P(0);
  	int			ndims = ARR_NDIM(in_array);
! 	StringInfoData result;
  	Datum	   *in_datums;
  	bool	   *in_nulls;
  	int			in_count,
  				count,
  				i;
- 	text	   *rval;
- 	char	   *v;
  
  	switch (ndims)
  	{
--- 2224,2235 ----
  {
  	ArrayType  *in_array = PG_GETARG_ARRAYTYPE_P(0);
  	int			ndims = ARR_NDIM(in_array);
! 	JsonOutContext	out;
  	Datum	   *in_datums;
  	bool	   *in_nulls;
  	int			in_count,
  				count,
  				i;
  
  	switch (ndims)
  	{
*************** json_object(PG_FUNCTION_ARGS)
*** 2230,2238 ****
  
  	count = in_count / 2;
  
! 	initStringInfo(&result);
! 
! 	appendStringInfoChar(&result, '{');
  
  	for (i = 0; i < count; ++i)
  	{
--- 2263,2270 ----
  
  	count = in_count / 2;
  
! 	json_out_init_context(&out, JSON_OUT_USE_SPACES);
! 	out.object_start(&out);
  
  	for (i = 0; i < count; ++i)
  	{
*************** json_object(PG_FUNCTION_ARGS)
*** 2241,2272 ****
  					(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
  					 errmsg("null value not allowed for object key")));
  
! 		v = TextDatumGetCString(in_datums[i * 2]);
! 		if (i > 0)
! 			appendStringInfoString(&result, ", ");
! 		escape_json(&result, v);
! 		appendStringInfoString(&result, " : ");
! 		pfree(v);
  		if (in_nulls[i * 2 + 1])
! 			appendStringInfoString(&result, "null");
  		else
! 		{
! 			v = TextDatumGetCString(in_datums[i * 2 + 1]);
! 			escape_json(&result, v);
! 			pfree(v);
! 		}
  	}
  
! 	appendStringInfoChar(&result, '}');
  
  	pfree(in_datums);
  	pfree(in_nulls);
  
! 	rval = cstring_to_text_with_len(result.data, result.len);
! 	pfree(result.data);
! 
! 	PG_RETURN_TEXT_P(rval);
! 
  }
  
  /*
--- 2273,2295 ----
  					(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
  					 errmsg("null value not allowed for object key")));
  
! 		out.value(&out, in_datums[i * 2], JSONTYPE_OTHER,
! 				  TEXTOID, 47 /* textout */, true);
! 
  		if (in_nulls[i * 2 + 1])
! 			out.value(&out, (Datum) 0, JSONTYPE_NULL,
! 					  InvalidOid, InvalidOid, false);
  		else
! 			out.value(&out, in_datums[i * 2 + 1], JSONTYPE_OTHER,
! 					  TEXTOID, 47 /* textout */, false);
  	}
  
! 	out.object_end(&out);
  
  	pfree(in_datums);
  	pfree(in_nulls);
  
! 	PG_RETURN_TEXT_P(cstring_to_text_with_len(out.result.data, out.result.len));
  }
  
  /*
*************** json_object_two_arg(PG_FUNCTION_ARGS)
*** 2282,2288 ****
  	ArrayType  *val_array = PG_GETARG_ARRAYTYPE_P(1);
  	int			nkdims = ARR_NDIM(key_array);
  	int			nvdims = ARR_NDIM(val_array);
! 	StringInfoData result;
  	Datum	   *key_datums,
  			   *val_datums;
  	bool	   *key_nulls,
--- 2305,2311 ----
  	ArrayType  *val_array = PG_GETARG_ARRAYTYPE_P(1);
  	int			nkdims = ARR_NDIM(key_array);
  	int			nvdims = ARR_NDIM(val_array);
! 	JsonOutContext	out;
  	Datum	   *key_datums,
  			   *val_datums;
  	bool	   *key_nulls,
*************** json_object_two_arg(PG_FUNCTION_ARGS)
*** 2290,2297 ****
  	int			key_count,
  				val_count,
  				i;
- 	text	   *rval;
- 	char	   *v;
  
  	if (nkdims > 1 || nkdims != nvdims)
  		ereport(ERROR,
--- 2313,2318 ----
*************** json_object_two_arg(PG_FUNCTION_ARGS)
*** 2314,2322 ****
  				(errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
  				 errmsg("mismatched array dimensions")));
  
! 	initStringInfo(&result);
! 
! 	appendStringInfoChar(&result, '{');
  
  	for (i = 0; i < key_count; ++i)
  	{
--- 2335,2342 ----
  				(errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
  				 errmsg("mismatched array dimensions")));
  
! 	json_out_init_context(&out, JSON_OUT_USE_SPACES);
! 	out.object_start(&out);
  
  	for (i = 0; i < key_count; ++i)
  	{
*************** json_object_two_arg(PG_FUNCTION_ARGS)
*** 2325,2357 ****
  					(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
  					 errmsg("null value not allowed for object key")));
  
! 		v = TextDatumGetCString(key_datums[i]);
! 		if (i > 0)
! 			appendStringInfoString(&result, ", ");
! 		escape_json(&result, v);
! 		appendStringInfoString(&result, " : ");
! 		pfree(v);
  		if (val_nulls[i])
! 			appendStringInfoString(&result, "null");
  		else
! 		{
! 			v = TextDatumGetCString(val_datums[i]);
! 			escape_json(&result, v);
! 			pfree(v);
! 		}
  	}
  
! 	appendStringInfoChar(&result, '}');
  
  	pfree(key_datums);
  	pfree(key_nulls);
  	pfree(val_datums);
  	pfree(val_nulls);
  
! 	rval = cstring_to_text_with_len(result.data, result.len);
! 	pfree(result.data);
! 
! 	PG_RETURN_TEXT_P(rval);
  }
  
  
--- 2345,2369 ----
  					(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
  					 errmsg("null value not allowed for object key")));
  
! 		out.value(&out, key_datums[i], JSONTYPE_OTHER,
! 				  TEXTOID, 47 /* textout */, true);
! 
  		if (val_nulls[i])
! 			out.value(&out, (Datum) 0, JSONTYPE_NULL,
! 					  InvalidOid, InvalidOid, false);
  		else
! 			out.value(&out, val_datums[i], JSONTYPE_OTHER,
! 					  TEXTOID, 47 /* textout */, false);
  	}
  
! 	out.object_end(&out);
  
  	pfree(key_datums);
  	pfree(key_nulls);
  	pfree(val_datums);
  	pfree(val_nulls);
  
! 	PG_RETURN_TEXT_P(cstring_to_text_with_len(out.result.data, out.result.len));
  }
  
  
diff --git a/src/include/utils/jsonapi.h b/src/include/utils/jsonapi.h
new file mode 100644
index 296d20a..8b9af6c
*** a/src/include/utils/jsonapi.h
--- b/src/include/utils/jsonapi.h
*************** extern JsonLexContext *makeJsonLexContex
*** 124,127 ****
--- 124,194 ----
   */
  extern bool IsValidJsonNumber(const char *str, int len);
  
+ 
+ /*
+  * Generalized structures for producing JSON output.
+  */
+ 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_DATE,				/* we use special formatting for datetimes */
+ 	JSONTYPE_TIMESTAMP,
+ 	JSONTYPE_TIMESTAMPTZ,
+ 	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;
+ 
+ struct JsonOutContext;
+ 
+ typedef void (*json_out_struct_action)(struct JsonOutContext *out);
+ typedef void (*json_out_value_action)(struct JsonOutContext *out,
+ 			  Datum val, JsonTypeCategory tcategory,
+ 			  Oid typoid, Oid outfuncoid, bool key_scalar);
+ typedef void (*json_out_post_action)(struct JsonOutContext *out,
+ 			  bool key_scalar);
+ 
+ #define JSON_OUT_USE_LINE_FEEDS		1
+ #define JSON_OUT_USE_SPACES			2
+ 
+ typedef struct JsonOutContext {
+ 	json_out_struct_action	object_start;
+ 	json_out_struct_action	object_end;
+ 	json_out_struct_action	array_start;
+ 	json_out_struct_action	array_end;
+ 	json_out_struct_action	before_value;
+ 	json_out_value_action	value;
+ 	json_out_post_action	after_value;
+ 
+ 	StringInfoData	result;
+ 	int		flags;
+ 	bool	need_comma;
+ 	int		depth;
+ 	/* these are used in json_agg to cache the type information */
+ 	JsonTypeCategory	agg_tcategory;
+ 	Oid					agg_outfuncoid;
+ } JsonOutContext;
+ 
+ extern void json_out_init_context(JsonOutContext *out, int flags);
+ extern void json_out_before_value(JsonOutContext *out);
+ extern void json_out_after_value(JsonOutContext *out, bool key_scalar);
+ extern void json_out_object_start(JsonOutContext *out);
+ extern void json_out_object_end(JsonOutContext *out);
+ extern void json_out_array_start(JsonOutContext *out);
+ extern void json_out_array_end(JsonOutContext *out);
+ extern void json_out_value(JsonOutContext *out, Datum val,
+ 				JsonTypeCategory tcategory, Oid typoid, Oid outfuncoid,
+ 				bool key_scalar);
+ extern void json_out_cstring(JsonOutContext *out, const char *str,
+ 				bool key_scalar);
+ 
+ extern void composite_to_json(Datum composite, JsonOutContext *out);
+ extern void datum_to_json(Datum val, bool is_null, JsonOutContext *out,
+ 			  JsonTypeCategory tcategory, Oid typoid, Oid outfuncoid,
+ 			  bool key_scalar);
+ 
  #endif   /* JSONAPI_H */
diff --git a/src/test/regress/expected/json.out b/src/test/regress/expected/json.out
new file mode 100644
index 3942c3b..fbc9b5b
*** a/src/test/regress/expected/json.out
--- b/src/test/regress/expected/json.out
*************** SELECT json_agg(q)
*** 457,476 ****
                 ROW(y.*,ARRAY[4,5,6])] AS z
           FROM generate_series(1,2) x,
                generate_series(4,5) y) q;
!                                json_agg                                
! -----------------------------------------------------------------------
!  [{"b":"a1","c":4,"z":[{"f1":1,"f2":[1,2,3]},{"f1":4,"f2":[4,5,6]}]}, +
!   {"b":"a1","c":5,"z":[{"f1":1,"f2":[1,2,3]},{"f1":5,"f2":[4,5,6]}]}, +
!   {"b":"a2","c":4,"z":[{"f1":2,"f2":[1,2,3]},{"f1":4,"f2":[4,5,6]}]}, +
    {"b":"a2","c":5,"z":[{"f1":2,"f2":[1,2,3]},{"f1":5,"f2":[4,5,6]}]}]
  (1 row)
  
  SELECT json_agg(q)
    FROM rows q;
!        json_agg        
! -----------------------
!  [{"x":1,"y":"txt1"}, +
!   {"x":2,"y":"txt2"}, +
    {"x":3,"y":"txt3"}]
  (1 row)
  
--- 457,476 ----
                 ROW(y.*,ARRAY[4,5,6])] AS z
           FROM generate_series(1,2) x,
                generate_series(4,5) y) q;
!                                json_agg                               
! ----------------------------------------------------------------------
!  [{"b":"a1","c":4,"z":[{"f1":1,"f2":[1,2,3]},{"f1":4,"f2":[4,5,6]}]},+
!   {"b":"a1","c":5,"z":[{"f1":1,"f2":[1,2,3]},{"f1":5,"f2":[4,5,6]}]},+
!   {"b":"a2","c":4,"z":[{"f1":2,"f2":[1,2,3]},{"f1":4,"f2":[4,5,6]}]},+
    {"b":"a2","c":5,"z":[{"f1":2,"f2":[1,2,3]},{"f1":5,"f2":[4,5,6]}]}]
  (1 row)
  
  SELECT json_agg(q)
    FROM rows q;
!        json_agg       
! ----------------------
!  [{"x":1,"y":"txt1"},+
!   {"x":2,"y":"txt2"},+
    {"x":3,"y":"txt3"}]
  (1 row)
  
*************** SELECT json_build_array('a',1,'b',1.2,'c
*** 1505,1522 ****
  (1 row)
  
  SELECT json_build_object('a',1,'b',1.2,'c',true,'d',null,'e',json '{"x": 3, "y": [1,2,3]}');
!                              json_build_object                              
! ----------------------------------------------------------------------------
!  {"a" : 1, "b" : 1.2, "c" : true, "d" : null, "e" : {"x": 3, "y": [1,2,3]}}
  (1 row)
  
  SELECT json_build_object(
         'a', json_build_object('b',false,'c',99),
         'd', json_build_object('e',array[9,8,7]::int[],
             'f', (select row_to_json(r) from ( select relkind, oid::regclass as name from pg_class where relname = 'pg_class') r)));
!                                         json_build_object                                        
! -------------------------------------------------------------------------------------------------
!  {"a" : {"b" : false, "c" : 99}, "d" : {"e" : [9,8,7], "f" : {"relkind":"r","name":"pg_class"}}}
  (1 row)
  
  -- empty objects/arrays
--- 1505,1522 ----
  (1 row)
  
  SELECT json_build_object('a',1,'b',1.2,'c',true,'d',null,'e',json '{"x": 3, "y": [1,2,3]}');
!                            json_build_object                           
! -----------------------------------------------------------------------
!  {"a": 1, "b": 1.2, "c": true, "d": null, "e": {"x": 3, "y": [1,2,3]}}
  (1 row)
  
  SELECT json_build_object(
         'a', json_build_object('b',false,'c',99),
         'd', json_build_object('e',array[9,8,7]::int[],
             'f', (select row_to_json(r) from ( select relkind, oid::regclass as name from pg_class where relname = 'pg_class') r)));
!                                      json_build_object                                     
! -------------------------------------------------------------------------------------------
!  {"a": {"b": false, "c": 99}, "d": {"e": [9,8,7], "f": {"relkind":"r","name":"pg_class"}}}
  (1 row)
  
  -- empty objects/arrays
*************** SELECT json_build_object();
*** 1536,1542 ****
  SELECT json_build_object(1,2);
   json_build_object 
  -------------------
!  {"1" : 2}
  (1 row)
  
  -- keys must be scalar and not null
--- 1536,1542 ----
  SELECT json_build_object(1,2);
   json_build_object 
  -------------------
!  {"1": 2}
  (1 row)
  
  -- keys must be scalar and not null
*************** INSERT INTO foo VALUES (847002,'t16','GE
*** 1555,1578 ****
  INSERT INTO foo VALUES (847003,'sub-alpha','GESS90');
  SELECT json_build_object('turbines',json_object_agg(serial_num,json_build_object('name',name,'type',type)))
  FROM foo;
!                                                                             json_build_object                                                                            
! -------------------------------------------------------------------------------------------------------------------------------------------------------------------------
!  {"turbines" : { "847001" : {"name" : "t15", "type" : "GE1043"}, "847002" : {"name" : "t16", "type" : "GE1043"}, "847003" : {"name" : "sub-alpha", "type" : "GESS90"} }}
  (1 row)
  
  -- json_object
  -- one dimension
  SELECT json_object('{a,1,b,2,3,NULL,"d e f","a b c"}');
!                       json_object                      
! -------------------------------------------------------
!  {"a" : "1", "b" : "2", "3" : null, "d e f" : "a b c"}
  (1 row)
  
  -- same but with two dimensions
  SELECT json_object('{{a,1},{b,2},{3,NULL},{"d e f","a b c"}}');
!                       json_object                      
! -------------------------------------------------------
!  {"a" : "1", "b" : "2", "3" : null, "d e f" : "a b c"}
  (1 row)
  
  -- odd number error
--- 1555,1578 ----
  INSERT INTO foo VALUES (847003,'sub-alpha','GESS90');
  SELECT json_build_object('turbines',json_object_agg(serial_num,json_build_object('name',name,'type',type)))
  FROM foo;
!                                                                       json_build_object                                                                      
! -------------------------------------------------------------------------------------------------------------------------------------------------------------
!  {"turbines": {"847001": {"name": "t15", "type": "GE1043"}, "847002": {"name": "t16", "type": "GE1043"}, "847003": {"name": "sub-alpha", "type": "GESS90"}}}
  (1 row)
  
  -- json_object
  -- one dimension
  SELECT json_object('{a,1,b,2,3,NULL,"d e f","a b c"}');
!                     json_object                    
! ---------------------------------------------------
!  {"a": "1", "b": "2", "3": null, "d e f": "a b c"}
  (1 row)
  
  -- same but with two dimensions
  SELECT json_object('{{a,1},{b,2},{3,NULL},{"d e f","a b c"}}');
!                     json_object                    
! ---------------------------------------------------
!  {"a": "1", "b": "2", "3": null, "d e f": "a b c"}
  (1 row)
  
  -- odd number error
*************** SELECT json_object('{{{a,b},{c,d}},{{b,c
*** 1589,1597 ****
  ERROR:  wrong number of array subscripts
  --two argument form of json_object
  select json_object('{a,b,c,"d e f"}','{1,2,3,"a b c"}');
!                      json_object                      
! ------------------------------------------------------
!  {"a" : "1", "b" : "2", "c" : "3", "d e f" : "a b c"}
  (1 row)
  
  -- too many dimensions
--- 1589,1597 ----
  ERROR:  wrong number of array subscripts
  --two argument form of json_object
  select json_object('{a,b,c,"d e f"}','{1,2,3,"a b c"}');
!                    json_object                    
! --------------------------------------------------
!  {"a": "1", "b": "2", "c": "3", "d e f": "a b c"}
  (1 row)
  
  -- too many dimensions
*************** select json_object('{a,b,NULL,"d e f"}',
*** 1607,1615 ****
  ERROR:  null value not allowed for object key
  -- empty key is allowed
  select json_object('{a,b,"","d e f"}','{1,2,3,"a b c"}');
!                      json_object                     
! -----------------------------------------------------
!  {"a" : "1", "b" : "2", "" : "3", "d e f" : "a b c"}
  (1 row)
  
  -- json_to_record and json_to_recordset
--- 1607,1615 ----
  ERROR:  null value not allowed for object key
  -- empty key is allowed
  select json_object('{a,b,"","d e f"}','{1,2,3,"a b c"}');
!                    json_object                   
! -------------------------------------------------
!  {"a": "1", "b": "2", "": "3", "d e f": "a b c"}
  (1 row)
  
  -- json_to_record and json_to_recordset
-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

Reply via email to