Here is a WIP patch for enhancements to json generation.

First, there is the much_requested json_agg, which will aggregate rows directly to json. So the following will now work:

    select json_agg(my_table) from mytable;
    select json_agg(q) from (<myquery here>) q;

One open question regarding this feature is whether this should return NULL or '[]' for 0 rows. Currently it returns NULL but I could be convinced to return '[]', and the change would be very small.

Next is to_json(), which will turn any value into json, so we're no longer restricted to rows and arrays.

Non-builtin types are now searched for a cast to json, and if it exists it is used instead of the type's text representation. I didn't add a special type to look for a cast to, as was discussed before, as it seemed a bit funky and unnecessary. It can easily be added, but I'm still not convinced it's a good idea. Note that this is only done for types that aren't builtin - we know how to turn all of those into json without needing to look for a cast.

Along with this there is an hstore_to_json() function added to the hstore module, and a cast from hstore to json that uses it. This function treats every value in the hstore as a string. There is also a function with the working title of hstore_to_json_loose() that does a heuristic conversion that treats values of 't' and 'f' as booleans, and strings that look like numbers as numbers unless they start with a leading 0 followed by another digit (could be zip codes, phone numbers etc.) The difference between these is illustrated here (notice that quoted '"t"' becomes unquoted 'true' and quoted '"1"' becomes '1'):

   andrew=# select json_agg(q) from foo q;
                                json_agg
   -----------------------------------------------------------------
     [{"a":"a","b":1,"h":{"c": "t", "d": null, "q": "1", "x": "y"}}]
   (1 row)

   andrew=# select json_agg(q) from (select a, b, hstore_to_json_loose(h) as h 
from foo) q;
                                json_agg
   ----------------------------------------------------------------
     [{"a":"a","b":1,"h":{"c": true, "d": null, "q": 1, "x": "y"}}]
   (1 row)

Note: this patch will need a change in the oids used for the new functions if 
applied against git tip, as they have been overtaken by time.


Comments welcome.


cheers

andrew



*** a/contrib/hstore/hstore--1.1.sql
--- b/contrib/hstore/hstore--1.1.sql
***************
*** 234,239 **** LANGUAGE C IMMUTABLE STRICT;
--- 234,252 ----
  CREATE CAST (text[] AS hstore)
    WITH FUNCTION hstore(text[]);
  
+ CREATE FUNCTION hstore_to_json(hstore)
+ RETURNS json
+ AS 'MODULE_PATHNAME', 'hstore_to_json'
+ LANGUAGE C IMMUTABLE STRICT;
+ 
+ CREATE CAST (hstore AS json)
+   WITH FUNCTION hstore_to_json(hstore);
+ 
+ CREATE FUNCTION hstore_to_json_loose(hstore)
+ RETURNS json
+ AS 'MODULE_PATHNAME', 'hstore_to_json_loose'
+ LANGUAGE C IMMUTABLE STRICT;
+ 
  CREATE FUNCTION hstore(record)
  RETURNS hstore
  AS 'MODULE_PATHNAME', 'hstore_from_record'
*** a/contrib/hstore/hstore_io.c
--- b/contrib/hstore/hstore_io.c
***************
*** 8,14 ****
--- 8,17 ----
  #include "access/htup_details.h"
  #include "catalog/pg_type.h"
  #include "funcapi.h"
+ #include "lib/stringinfo.h"
  #include "libpq/pqformat.h"
+ #include "utils/builtins.h"
+ #include "utils/json.h"
  #include "utils/lsyscache.h"
  #include "utils/typcache.h"
  
***************
*** 1209,1211 **** hstore_send(PG_FUNCTION_ARGS)
--- 1212,1425 ----
  
  	PG_RETURN_BYTEA_P(pq_endtypsend(&buf));
  }
+ 
+ 
+ /*
+  * hstore_to_json_loose
+  *
+  * This is a heuristic conversion to json which treats
+  * 't' and 'f' as booleans and strings that look like numbers as numbers,
+  * as long as they don't start with a leading zero followed by another digit
+  * (think zip codes or phone numbers starting with 0).
+  */
+ PG_FUNCTION_INFO_V1(hstore_to_json_loose);
+ Datum		hstore_to_json_loose(PG_FUNCTION_ARGS);
+ Datum
+ hstore_to_json_loose(PG_FUNCTION_ARGS)
+ {
+ 	HStore	   *in = PG_GETARG_HS(0);
+ 	int			buflen,
+ 				i;
+ 	int			count = HS_COUNT(in);
+ 	char	   *out,
+ 			   *ptr;
+ 	char	   *base = STRPTR(in);
+ 	HEntry	   *entries = ARRPTR(in);
+ 	bool        is_number;
+ 	StringInfo  src, dst;
+ 
+ 	if (count == 0)
+ 	{
+ 		out = palloc(1);
+ 		*out = '\0';
+ 		PG_RETURN_TEXT_P(cstring_to_text(out));
+ 	}
+ 
+ 	buflen = 3;
+ 
+ 	/*
+ 	 * Formula adjusted slightly from the logic in hstore_out.
+ 	 * We have to take account of out treatment of booleans
+ 	 * to be a bit more pessimistic about the length of values.
+ 	 */
+ 
+ 	for (i = 0; i < count; i++)
+ 	{
+ 		/* include "" and colon-space and comma-space */
+ 		buflen += 6 + 2 * HS_KEYLEN(entries, i);
+ 		/* include "" only if nonnull */
+ 		buflen += 3 + (HS_VALISNULL(entries, i)
+ 					   ? 1
+ 					   :  2 * HS_VALLEN(entries, i));
+ 	}
+ 
+ 	out = ptr = palloc(buflen);
+ 
+ 	src = makeStringInfo();
+ 	dst = makeStringInfo();
+ 
+ 	*ptr++ = '{';
+ 
+ 	for (i = 0; i < count; i++)
+ 	{
+ 	    resetStringInfo(src);
+ 		resetStringInfo(dst);
+ 		appendBinaryStringInfo(src, HS_KEY(entries, base, i), HS_KEYLEN(entries, i));
+ 		escape_json(dst, src->data);	
+ 		strncpy(ptr, dst->data, dst->len);
+ 		ptr += dst->len;
+ 		*ptr++ = ':';
+ 		*ptr++ = ' ';
+ 		resetStringInfo(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
+ 		{
+ 			is_number = false;
+ 			resetStringInfo(src);
+ 			appendBinaryStringInfo(src, HS_VAL(entries, base, i), HS_VALLEN(entries, i));
+ 			/* 
+ 			 * don't treat something with a leading zero followed by another digit as numeric - 
+ 			 * could be a zip code or similar 
+ 			 */
+ 			if (src->len > 0 && (src->data[0] != '0' || !isdigit(src->data[1])) &&
+ 				strspn(src->data,"+-0123456789Ee.") == src->len)
+ 			{
+ 				/* might be a number. See if we can input it as a numeric value*/
+ 				char * endptr;
+ 				long longres = strtol(src->data,&endptr,10);
+ 				if (*endptr == '\0')
+ 				{
+ 					/* strol man page says this means the whole string is valid */
+ 					is_number = true;
+ 				}
+ 				else
+ 				{
+ 					/* not an int - try a double */
+ 					double doubleres = strtod(src->data,&endptr);
+ 					if (*endptr == '\0')
+ 						is_number = true;
+ 					else if (false)
+ 					{
+ 						/* shut the compiler up about unused variables */
+ 						longres = (long) doubleres;
+ 						longres = longres / 2;
+ 					}
+ 				}
+ 			}
+ 			if (is_number)
+ 				appendBinaryStringInfo(dst,src->data, src->len);
+ 			else
+ 				escape_json(dst, src->data);
+ 		}
+ 		strncpy(ptr, dst->data, dst->len);
+ 		ptr += dst->len;
+ 		
+ 		if (i + 1 != count)
+ 		{
+ 			*ptr++ = ',';
+ 			*ptr++ = ' ';
+ 		}
+ 	}
+ 	*ptr++ = '}';
+ 	*ptr = '\0';
+ 
+ 	PG_RETURN_TEXT_P(cstring_to_text(out));
+ }
+ 
+ PG_FUNCTION_INFO_V1(hstore_to_json);
+ Datum		hstore_to_json(PG_FUNCTION_ARGS);
+ Datum
+ hstore_to_json(PG_FUNCTION_ARGS)
+ {
+ 	HStore	   *in = PG_GETARG_HS(0);
+ 	int			buflen,
+ 				i;
+ 	int			count = HS_COUNT(in);
+ 	char	   *out,
+ 			   *ptr;
+ 	char	   *base = STRPTR(in);
+ 	HEntry	   *entries = ARRPTR(in);
+ 	StringInfo  src, dst;
+ 
+ 	if (count == 0)
+ 	{
+ 		out = palloc(1);
+ 		*out = '\0';
+ 		PG_RETURN_TEXT_P(cstring_to_text(out));
+ 	}
+ 
+ 	buflen = 3;
+ 
+ 	/*
+ 	 * Formula adjusted slightly from the logic in hstore_out.
+ 	 * We have to take account of out treatment of booleans
+ 	 * to be a bit more pessimistic about the length of values.
+ 	 */
+ 
+ 	for (i = 0; i < count; i++)
+ 	{
+ 		/* include "" and colon-space and comma-space */
+ 		buflen += 6 + 2 * HS_KEYLEN(entries, i);
+ 		/* include "" only if nonnull */
+ 		buflen += 3 + (HS_VALISNULL(entries, i)
+ 					   ? 1
+ 					   :  2 * HS_VALLEN(entries, i));
+ 	}
+ 
+ 	out = ptr = palloc(buflen);
+ 
+ 	src = makeStringInfo();
+ 	dst = makeStringInfo();
+ 
+ 	*ptr++ = '{';
+ 
+ 	for (i = 0; i < count; i++)
+ 	{
+ 	    resetStringInfo(src);
+ 		resetStringInfo(dst);
+ 		appendBinaryStringInfo(src, HS_KEY(entries, base, i), HS_KEYLEN(entries, i));
+ 		escape_json(dst, src->data);	
+ 		strncpy(ptr, dst->data, dst->len);
+ 		ptr += dst->len;
+ 		*ptr++ = ':';
+ 		*ptr++ = ' ';
+ 		resetStringInfo(dst);
+ 		if (HS_VALISNULL(entries, i))
+ 			appendStringInfoString(dst,"null");
+ 		else
+ 		{
+ 			resetStringInfo(src);
+ 			appendBinaryStringInfo(src, HS_VAL(entries, base, i), HS_VALLEN(entries, i));
+ 			escape_json(dst, src->data);
+ 		}
+ 		strncpy(ptr, dst->data, dst->len);
+ 		ptr += dst->len;
+ 		
+ 		if (i + 1 != count)
+ 		{
+ 			*ptr++ = ',';
+ 			*ptr++ = ' ';
+ 		}
+ 	}
+ 	*ptr++ = '}';
+ 	*ptr = '\0';
+ 
+ 	PG_RETURN_TEXT_P(cstring_to_text(out));
+ }
+ 
*** a/src/backend/utils/adt/json.c
--- b/src/backend/utils/adt/json.c
***************
*** 14,19 ****
--- 14,21 ----
  #include "postgres.h"
  
  #include "access/htup_details.h"
+ #include "access/transam.h"
+ #include "catalog/pg_cast.h"
  #include "catalog/pg_type.h"
  #include "executor/spi.h"
  #include "lib/stringinfo.h"
***************
*** 24,29 ****
--- 26,32 ----
  #include "utils/builtins.h"
  #include "utils/lsyscache.h"
  #include "utils/json.h"
+ #include "utils/syscache.h"
  #include "utils/typcache.h"
  
  typedef enum					/* types of JSON values */
***************
*** 88,95 **** static void array_dim_to_json(StringInfo result, int dim, int ndims, int *dims,
--- 91,104 ----
  static void array_to_json_internal(Datum array, StringInfo result,
  								   bool use_line_feeds);
  
+ /* 
+  * 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'
  /* letters appearing in numeric output that aren't valid in a JSON number */
  #define NON_NUMERIC_LETTER "NnAaIiFfTtYy"
  /* chars to consider as part of an alphanumeric token */
***************
*** 820,825 **** datum_to_json(Datum val, bool is_null, StringInfo result,
--- 829,835 ----
  			  TYPCATEGORY tcategory, Oid typoutputfunc)
  {
  	char	   *outputstr;
+ 	text       *jsontext;
  
  	if (is_null)
  	{
***************
*** 862,867 **** datum_to_json(Datum val, bool is_null, StringInfo result,
--- 872,884 ----
  			appendStringInfoString(result, outputstr);
  			pfree(outputstr);
  			break;
+ 		case TYPCATEGORY_JSON_CAST:
+ 			jsontext = DatumGetTextP(OidFunctionCall1(typoutputfunc, val));
+ 			outputstr = text_to_cstring(jsontext);
+ 			appendStringInfoString(result, outputstr);
+ 			pfree(outputstr);
+ 			pfree(jsontext);
+ 			break;
  		default:
  			outputstr = OidOutputFunctionCall(typoutputfunc, val);
  			escape_json(result, outputstr);
***************
*** 935,940 **** array_to_json_internal(Datum array, StringInfo result, bool use_line_feeds)
--- 952,958 ----
  	Oid			typioparam;
  	Oid			typoutputfunc;
  	TYPCATEGORY tcategory;
+ 	Oid         castfunc = InvalidOid;
  
  	ndim = ARR_NDIM(v);
  	dim = ARR_DIMS(v);
***************
*** 950,960 **** array_to_json_internal(Datum array, StringInfo result, bool use_line_feeds)
  					 &typlen, &typbyval, &typalign,
  					 &typdelim, &typioparam, &typoutputfunc);
  
  	deconstruct_array(v, element_type, typlen, typbyval,
  					  typalign, &elements, &nulls,
  					  &nitems);
  
! 	if (element_type == RECORDOID)
  		tcategory = TYPCATEGORY_COMPOSITE;
  	else if (element_type == JSONOID)
  		tcategory = TYPCATEGORY_JSON;
--- 968,999 ----
  					 &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;
+ 
+ 				ReleaseSysCache(tuple);
+ 			}
+ 	}
+ 
  	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)
  		tcategory = TYPCATEGORY_JSON;
***************
*** 1009,1014 **** composite_to_json(Datum composite, StringInfo result, bool use_line_feeds)
--- 1048,1054 ----
  		TYPCATEGORY tcategory;
  		Oid			typoutput;
  		bool		typisvarlena;
+ 		Oid         castfunc = InvalidOid;
  
  		if (tupdesc->attrs[i]->attisdropped)
  			continue;
***************
*** 1023,1029 **** composite_to_json(Datum composite, StringInfo result, bool use_line_feeds)
  
  		origval = heap_getattr(tuple, i + 1, tupdesc, &isnull);
  
! 		if (tupdesc->attrs[i]->atttypid == RECORDARRAYOID)
  			tcategory = TYPCATEGORY_ARRAY;
  		else if (tupdesc->attrs[i]->atttypid == RECORDOID)
  			tcategory = TYPCATEGORY_COMPOSITE;
--- 1063,1093 ----
  
  		origval = heap_getattr(tuple, i + 1, tupdesc, &isnull);
  
! 		getTypeOutputInfo(tupdesc->attrs[i]->atttypid,
! 						  &typoutput, &typisvarlena);
! 
! 		if (tupdesc->attrs[i]->atttypid > FirstNormalObjectId)
! 		{
! 		    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);
! 			}
! 		}
! 
! 		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;
***************
*** 1032,1040 **** composite_to_json(Datum composite, StringInfo result, bool use_line_feeds)
  		else
  			tcategory = TypeCategory(tupdesc->attrs[i]->atttypid);
  
- 		getTypeOutputInfo(tupdesc->attrs[i]->atttypid,
- 						  &typoutput, &typisvarlena);
- 
  		/*
  		 * If we have a toasted datum, forcibly detoast it here to avoid
  		 * memory leakage inside the type's output routine.
--- 1096,1101 ----
***************
*** 1122,1127 **** row_to_json_pretty(PG_FUNCTION_ARGS)
--- 1183,1401 ----
  }
  
  /*
+  * SQL function to_json(anyvalue)
+  */
+ Datum
+ to_json(PG_FUNCTION_ARGS)
+ {
+     Oid         val_type = get_fn_expr_argtype(fcinfo->flinfo, 0);
+     StringInfo  result;
+     Datum       orig_val, val;
+ 	TYPCATEGORY tcategory;
+ 	Oid			typoutput;
+ 	bool		typisvarlena;
+ 	Oid         castfunc = InvalidOid;
+ 
+     if (val_type == InvalidOid)
+         ereport(ERROR,
+                 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+                  errmsg("could not determine input data type")));
+ 
+ 
+ 	result = makeStringInfo();
+ 
+ 	orig_val = PG_ARGISNULL(0) ? (Datum) 0 : PG_GETARG_DATUM(0);
+ 
+ 	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)
+ 		tcategory = TYPCATEGORY_JSON;
+ 	else
+ 		tcategory = TypeCategory(val_type);
+ 	
+ 	/*
+ 	 * If we have a toasted datum, forcibly detoast it here to avoid
+ 	 * memory leakage inside the type's output routine.
+ 	 */
+ 	if (typisvarlena && orig_val != (Datum) 0)
+ 		val = PointerGetDatum(PG_DETOAST_DATUM(orig_val));
+ 	else
+ 		val = orig_val;
+ 	
+ 	datum_to_json(val, false, result, tcategory, typoutput);
+ 
+ 	/* Clean up detoasted copy, if any */
+ 	if (val != orig_val)
+ 		pfree(DatumGetPointer(val));
+ 	
+ 	PG_RETURN_TEXT_P(cstring_to_text(result->data));
+ }
+ 
+ /*
+  * json_agg transition function
+  */
+ Datum
+ json_agg_transfn(PG_FUNCTION_ARGS)
+ {
+     Oid         val_type = get_fn_expr_argtype(fcinfo->flinfo, 1);
+     MemoryContext aggcontext, oldcontext;
+     StringInfo  state;
+     Datum       orig_val, val;
+ 	TYPCATEGORY tcategory;
+ 	Oid			typoutput;
+ 	bool		typisvarlena;
+ 	Oid         castfunc = InvalidOid;
+ 
+     if (val_type == InvalidOid)
+         ereport(ERROR,
+                 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+                  errmsg("could not determine input data type")));
+ 
+     if (!AggCheckCallContext(fcinfo, &aggcontext))
+     {
+         /* cannot be called directly because of internal-type argument */
+         elog(ERROR, "json_agg_transfn called in non-aggregate context");
+     }
+ 
+     if (PG_ARGISNULL(0))
+ 	{
+ 		/* 
+ 		 * 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.
+ 		 */
+ 		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))
+ 	{
+ 		orig_val = (Datum) 0;
+ 		datum_to_json(orig_val, true, state, 0, InvalidOid);
+ 		PG_RETURN_POINTER(state);
+ 	}
+ 
+ 
+ 	orig_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)
+ 		tcategory = TYPCATEGORY_JSON;
+ 	else
+ 		tcategory = TypeCategory(val_type);
+ 	
+ 	/*
+ 	 * If we have a toasted datum, forcibly detoast it here to avoid
+ 	 * memory leakage inside the type's output routine.
+ 	 */
+ 	if (typisvarlena)
+ 		val = PointerGetDatum(PG_DETOAST_DATUM(orig_val));
+ 	else
+ 		val = orig_val;
+ 
+ 	if (! PG_ARGISNULL(0) && 
+ 		(tcategory == TYPCATEGORY_ARRAY || tcategory == TYPCATEGORY_COMPOSITE))
+ 	{
+ 		appendStringInfoString(state,"\n ");
+ 	}
+ 	
+ 	datum_to_json(val, false, state, tcategory, typoutput);
+ 
+ 	/* Clean up detoasted copy, if any */
+ 	if (val != orig_val)
+ 		pfree(DatumGetPointer(val));
+ 	
+     /*
+      * 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);
+ }
+ 
+ /*
+  * json_agg final function
+  */
+ 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);
+ 
+ 	if (state == NULL)
+ 		PG_RETURN_NULL();
+ 
+ 	appendStringInfoChar(state,']');
+ 
+ 	PG_RETURN_TEXT_P(cstring_to_text(state->data));
+ }
+ 
+ /*
   * Produce a JSON string literal, properly escaping characters in the text.
   */
  void
*** a/src/include/catalog/pg_aggregate.h
--- b/src/include/catalog/pg_aggregate.h
***************
*** 232,237 **** DATA(insert ( 3538	string_agg_transfn	string_agg_finalfn		0	2281	_null_ ));
--- 232,240 ----
  /* bytea */
  DATA(insert ( 3545	bytea_string_agg_transfn	bytea_string_agg_finalfn		0	2281	_null_ ));
  
+ /* json */
+ DATA(insert ( 3172	json_agg_transfn	json_agg_finalfn		0	2281	_null_ ));
+ 
  /*
   * prototypes for functions in pg_aggregate.c
   */
*** a/src/include/catalog/pg_proc.h
--- b/src/include/catalog/pg_proc.h
***************
*** 4091,4096 **** DATA(insert OID = 3155 (  row_to_json	   PGNSP PGUID 12 1 0 0 0 f f f f t f s 1
--- 4091,4104 ----
  DESCR("map row to json");
  DATA(insert OID = 3156 (  row_to_json	   PGNSP PGUID 12 1 0 0 0 f f f f t f s 2 0 114 "2249 16" _null_ _null_ _null_ _null_ row_to_json_pretty _null_ _null_ _null_ ));
  DESCR("map row to json with optional pretty printing");
+ DATA(insert OID = 3170 (  json_agg_transfn   PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 2281 "2281 2283" _null_ _null_ _null_ _null_ json_agg_transfn _null_ _null_ _null_ ));
+ DESCR("json aggregate transition function");
+ DATA(insert OID = 3171 (  json_agg_finalfn   PGNSP PGUID 12 1 0 0 0 f f f f f f i 1 0 2277 "2281" _null_ _null_ _null_ _null_ json_agg_finalfn _null_ _null_ _null_ ));
+ DESCR("json aggregate final function");
+ DATA(insert OID = 3172 (  json_agg		   PGNSP PGUID 12 1 0 0 0 t f f f f f i 1 0 114 "2283" _null_ _null_ _null_ _null_ aggregate_dummy _null_ _null_ _null_ ));
+ DESCR("aggregate input into json");
+ DATA(insert OID = 3173 (  to_json	   PGNSP PGUID 12 1 0 0 0 f f f f t f s 1 0 114 "2283" _null_ _null_ _null_ _null_ to_json _null_ _null_ _null_ ));
+ DESCR("map input to json");
  
  /* uuid */
  DATA(insert OID = 2952 (  uuid_in		   PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 2950 "2275" _null_ _null_ _null_ _null_ uuid_in _null_ _null_ _null_ ));
*** a/src/include/utils/json.h
--- b/src/include/utils/json.h
***************
*** 25,30 **** extern Datum array_to_json(PG_FUNCTION_ARGS);
--- 25,35 ----
  extern Datum array_to_json_pretty(PG_FUNCTION_ARGS);
  extern Datum row_to_json(PG_FUNCTION_ARGS);
  extern Datum row_to_json_pretty(PG_FUNCTION_ARGS);
+ extern Datum to_json(PG_FUNCTION_ARGS);
+ 
+ extern Datum json_agg_transfn(PG_FUNCTION_ARGS);
+ extern Datum json_agg_finalfn(PG_FUNCTION_ARGS);
+ 
  extern void escape_json(StringInfo buf, const char *str);
  
  #endif   /* JSON_H */
-- 
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