string_agg_20100128.patch

application/octet-stream

Filename: string_agg_20100128.patch
Type: application/octet-stream
Part: 0
Message: Re: Review: listagg aggregate

Patch

Same data as JSON: GET /api/v1/attachments/:id/patch the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes. API reference →
Format: context
File+
doc/src/sgml/func.sgml 21 0
src/backend/utils/adt/varlena.c 117 0
src/include/catalog/pg_aggregate.h 4 0
src/include/catalog/pg_proc.h 11 0
src/include/utils/builtins.h 7 0
src/test/regress/expected/aggregates.out 37 0
src/test/regress/sql/aggregates.sql 8 0
diff -cprN head/doc/src/sgml/func.sgml work/doc/src/sgml/func.sgml
*** head/doc/src/sgml/func.sgml	Tue Jan 26 11:47:08 2010
--- work/doc/src/sgml/func.sgml	Thu Jan 28 09:58:12 2010
***************
*** 1789,1794 ****
--- 1789,1798 ----
      </tgroup>
     </table>
  
+    <para>
+    See also <xref linkend="functions-aggregate"> about the aggregate
+    function <function>string_agg</function>.
+    </para>
  
     <table id="conversion-names">
      <title>Built-in Conversions</title>
*************** SELECT NULLIF(value, '(none)') ...
*** 9837,9842 ****
--- 9841,9863 ----
       </row>
  
       <row>
+       <entry>
+        <indexterm>
+         <primary>string_agg</primary>
+        </indexterm>
+        <function>string_agg(<replaceable class="parameter">expression</replaceable> 
+                      [, <replaceable class="parameter">expression</replaceable> ] )</function>
+       </entry>
+       <entry>
+        <type>text</type>
+       </entry>
+       <entry>
+        <type>text</type>
+       </entry>
+       <entry>input values concatenated into an string, optionally separated by the second argument</entry>
+      </row>
+ 
+      <row>
        <entry><function>sum(<replaceable class="parameter">expression</replaceable>)</function></entry>
        <entry>
         <type>smallint</type>, <type>int</type>,
diff -cprN head/src/backend/utils/adt/varlena.c work/src/backend/utils/adt/varlena.c
*** head/src/backend/utils/adt/varlena.c	Tue Jan 26 11:47:08 2010
--- work/src/backend/utils/adt/varlena.c	Thu Jan 28 11:29:55 2010
***************
*** 18,26 ****
--- 18,28 ----
  
  #include "access/tuptoaster.h"
  #include "catalog/pg_type.h"
+ #include "lib/stringinfo.h"
  #include "libpq/md5.h"
  #include "libpq/pqformat.h"
  #include "miscadmin.h"
+ #include "nodes/execnodes.h"
  #include "parser/scansup.h"
  #include "regex/regex.h"
  #include "utils/builtins.h"
*************** static bytea *bytea_substring(Datum str,
*** 73,78 ****
--- 75,81 ----
  				int L,
  				bool length_not_specified);
  static bytea *bytea_overlay(bytea *t1, bytea *t2, int sp, int sl);
+ static StringInfo makeStringAggState(fmNodePtr context);
  
  
  /*****************************************************************************
*************** pg_column_size(PG_FUNCTION_ARGS)
*** 3315,3317 ****
--- 3318,3434 ----
  
  	PG_RETURN_INT32(result);
  }
+ 
+ /*
+  * string_agg
+  *  
+  * Concates values and returns string.
+  *
+  * Syntax:
+  *     FUNCTION string_agg(string text, delimiter text = '')
+  *      RETURNS text;
+  *
+  * Note: any NULL value is ignored.
+  */
+ static StringInfo
+ makeStringAggState(fmNodePtr context)
+ {
+ 	StringInfo		state;
+ 	MemoryContext	aggcontext;
+ 	MemoryContext	oldcontext;
+ 
+ 	if (context && IsA(context, AggState))
+ 		aggcontext = ((AggState *) context)->aggcontext;
+ 	else if (context && IsA(context, WindowAggState))
+ 		aggcontext = ((WindowAggState *) context)->wincontext;
+ 	else
+ 	{
+ 		/* cannot be called directly because of internal-type argument */
+ 		elog(ERROR, "string_agg_transfn called in non-aggregate context");
+ 		aggcontext = NULL;		/* keep compiler quiet */
+ 	}
+ 
+ 	/* Create state in aggregate context */
+ 	oldcontext = MemoryContextSwitchTo(aggcontext);
+ 	state = makeStringInfo();
+ 	MemoryContextSwitchTo(oldcontext);
+ 
+ 	return state;
+ }
+ 
+ Datum
+ string_agg1_transfn(PG_FUNCTION_ARGS)
+ {
+ 	StringInfo state;
+ 
+ 	state = PG_ARGISNULL(0) ? NULL : (StringInfo) PG_GETARG_POINTER(0);
+ 
+ 	/* Append the element only when it isn't null */
+ 	if (!PG_ARGISNULL(1))
+ 	{
+ 		text *elem = PG_GETARG_TEXT_PP(1);
+ 
+ 		if (state == NULL)
+ 			state = makeStringAggState(fcinfo->context);
+ 		appendBinaryStringInfo(state, VARDATA_ANY(elem),
+ 							   VARSIZE_ANY_EXHDR(elem));
+ 	}
+ 
+ 	/*
+ 	 * The transition type for string_agg() is declared to be "internal", which
+ 	 * is a pass-by-value type the same size as a pointer.	
+ 	 */
+ 	PG_RETURN_POINTER(state);
+ }
+ 
+ Datum 
+ string_agg2_transfn(PG_FUNCTION_ARGS)
+ {
+ 	StringInfo state;
+ 
+ 	state = PG_ARGISNULL(0) ? NULL : (StringInfo) PG_GETARG_POINTER(0);
+ 
+ 	/* Append the element only when it isn't null */
+ 	if (!PG_ARGISNULL(1))
+ 	{
+ 		text *elem = PG_GETARG_TEXT_PP(1);
+ 
+ 		if (state == NULL)
+ 			state = makeStringAggState(fcinfo->context);
+ 		else if (!PG_ARGISNULL(2))
+ 		{
+ 			text *delim = PG_GETARG_TEXT_PP(2);
+ 			appendBinaryStringInfo(state, VARDATA_ANY(delim),
+ 								   VARSIZE_ANY_EXHDR(delim));
+ 		}
+ 
+ 		appendBinaryStringInfo(state, VARDATA_ANY(elem),
+ 							   VARSIZE_ANY_EXHDR(elem));
+ 	}
+ 
+ 	/*
+ 	 * The transition type for string_agg() is declared to be "internal", which
+ 	 * is a pass-by-value type the same size as a pointer.
+ 	 */
+ 	PG_RETURN_POINTER(state);
+ }
+ 
+ Datum
+ string_agg_finalfn(PG_FUNCTION_ARGS)
+ {
+ 	StringInfo state;
+ 
+ 	if (PG_ARGISNULL(0))
+ 		PG_RETURN_NULL();
+ 
+ 	/* cannot be called directly because of internal-type argument */
+ 	Assert(fcinfo->context &&
+ 		    (IsA(fcinfo->context, AggState) ||
+ 			    IsA(fcinfo->context, WindowAggState)));
+ 
+ 	state = (StringInfo) PG_GETARG_POINTER(0);
+ 	if (state != NULL)
+ 		PG_RETURN_TEXT_P(cstring_to_text(state->data));
+ 	else
+ 		PG_RETURN_NULL();
+ }
diff -cprN head/src/include/catalog/pg_aggregate.h work/src/include/catalog/pg_aggregate.h
*** head/src/include/catalog/pg_aggregate.h	Tue Jan  5 10:25:20 2010
--- work/src/include/catalog/pg_aggregate.h	Thu Jan 28 09:58:12 2010
*************** DATA(insert ( 2901 xmlconcat2	  -					0	
*** 223,228 ****
--- 223,232 ----
  /* array */
  DATA(insert ( 2335	array_agg_transfn	array_agg_finalfn		0	2281	_null_ ));
  
+ /* text */
+ DATA(insert (3537	string_agg1_transfn	string_agg_finalfn			0	2281	_null_ ));
+ DATA(insert (3538	string_agg2_transfn	string_agg_finalfn			0	2281	_null_ ));
+ 
  /*
   * prototypes for functions in pg_aggregate.c
   */
diff -cprN head/src/include/catalog/pg_proc.h work/src/include/catalog/pg_proc.h
*** head/src/include/catalog/pg_proc.h	Tue Jan 26 11:47:08 2010
--- work/src/include/catalog/pg_proc.h	Thu Jan 28 09:58:12 2010
*************** DESCR("COVAR_SAMP(double, double) aggreg
*** 2830,2835 ****
--- 2830,2846 ----
  DATA(insert OID = 2817 (  float8_corr				PGNSP PGUID 12 1 0 0 f f f t f i 1 0 701 "1022" _null_ _null_ _null_ _null_ float8_corr _null_ _null_ _null_ ));
  DESCR("CORR(double, double) aggregate final function");
  
+ DATA(insert OID = 3534 (  string_agg1_transfn   PGNSP PGUID 12 1 0 0 f f f f f i 2 0 2281 "2281 25" _null_ _null_ _null_ _null_ string_agg1_transfn _null_ _null_ _null_ ));
+ DESCR("string_agg one param transition function");
+ DATA(insert OID = 3535 (  string_agg2_transfn   PGNSP PGUID 12 1 0 0 f f f f f i 3 0 2281 "2281 25 25" _null_ _null_ _null_ _null_ string_agg2_transfn _null_ _null_ _null_ ));
+ DESCR("string_agg two params transition function");
+ DATA(insert OID = 3536 (  string_agg_finalfn   PGNSP PGUID 12 1 0 0 f f f f f i 1 0 25 "2281" _null_ _null_ _null_ _null_ string_agg_finalfn _null_ _null_ _null_ ));
+ DESCR("string_agg final function");
+ DATA(insert OID = 3537 (  string_agg		   PGNSP PGUID 12 1 0 0 t f f f f i 1 0 25 "25" _null_ _null_ _null_ _null_ aggregate_dummy _null_ _null_ _null_ ));
+ DESCR("concatenate aggregate input into an string");
+ DATA(insert OID = 3538 (  string_agg		   PGNSP PGUID 12 1 0 0 t f f f f i 2 0 25 "25 25" _null_ _null_ _null_ _null_ aggregate_dummy _null_ _null_ _null_ ));
+ DESCR("concatenate aggregate input into an string with delimiter");
+ 
  /* To ASCII conversion */
  DATA(insert OID = 1845 ( to_ascii	PGNSP PGUID 12 1 0 0 f f f t f i 1 0 25 "25" _null_ _null_ _null_ _null_	to_ascii_default _null_ _null_ _null_ ));
  DESCR("encode text from DB encoding to ASCII text");
diff -cprN head/src/include/utils/builtins.h work/src/include/utils/builtins.h
*** head/src/include/utils/builtins.h	Tue Jan 26 11:47:08 2010
--- work/src/include/utils/builtins.h	Thu Jan 28 09:58:12 2010
*************** extern Datum unknownsend(PG_FUNCTION_ARG
*** 724,729 ****
--- 724,733 ----
  
  extern Datum pg_column_size(PG_FUNCTION_ARGS);
  
+ extern Datum string_agg1_transfn(PG_FUNCTION_ARGS);
+ extern Datum string_agg2_transfn(PG_FUNCTION_ARGS);
+ extern Datum string_agg_finalfn(PG_FUNCTION_ARGS);
+ 
  /* version.c */
  extern Datum pgsql_version(PG_FUNCTION_ARGS);
  
*************** extern Datum translate(PG_FUNCTION_ARGS)
*** 772,777 ****
--- 776,784 ----
  extern Datum chr (PG_FUNCTION_ARGS);
  extern Datum repeat(PG_FUNCTION_ARGS);
  extern Datum ascii(PG_FUNCTION_ARGS);
+ extern Datum string_agg1_transfn(PG_FUNCTION_ARGS);
+ extern Datum string_agg2_transfn(PG_FUNCTION_ARGS);
+ extern Datum string_agg_finalfn(PG_FUNCTION_ARGS);
  
  /* inet_net_ntop.c */
  extern char *inet_net_ntop(int af, const void *src, int bits,
diff -cprN head/src/test/regress/expected/aggregates.out work/src/test/regress/expected/aggregates.out
*** head/src/test/regress/expected/aggregates.out	Wed Dec 16 09:17:22 2009
--- work/src/test/regress/expected/aggregates.out	Thu Jan 28 11:41:07 2010
*************** select aggfns(distinct a,a,c order by a,
*** 799,801 ****
--- 799,838 ----
  ERROR:  in an aggregate with DISTINCT, ORDER BY expressions must appear in argument list
  LINE 1: select aggfns(distinct a,a,c order by a,b)
                                                  ^
+ -- string_agg tests
+ select string_agg(a) from (values('aaaa'),('bbbb'),('cccc')) g(a);
+   string_agg  
+ --------------
+  aaaabbbbcccc
+ (1 row)
+ 
+ select string_agg(a,',') from (values('aaaa'),('bbbb'),('cccc')) g(a);
+    string_agg   
+ ----------------
+  aaaa,bbbb,cccc
+ (1 row)
+ 
+ select string_agg(a,',') from (values('aaaa'),(null),('bbbb'),('cccc')) g(a);
+    string_agg   
+ ----------------
+  aaaa,bbbb,cccc
+ (1 row)
+ 
+ select string_agg(a,',') from (values(null),(null),('bbbb'),('cccc')) g(a);
+  string_agg 
+ ------------
+  bbbb,cccc
+ (1 row)
+ 
+ select string_agg(a,',') from (values(null),(null)) g(a);
+  string_agg 
+ ------------
+  
+ (1 row)
+ 
+ select string_agg(elem, delim) from (values('A', ','), ('B', '+'), ('C', '*')) t(elem, delim);
+  string_agg 
+ ------------
+  A+B*C
+ (1 row)
+ 
diff -cprN head/src/test/regress/sql/aggregates.sql work/src/test/regress/sql/aggregates.sql
*** head/src/test/regress/sql/aggregates.sql	Wed Dec 16 09:17:22 2009
--- work/src/test/regress/sql/aggregates.sql	Thu Jan 28 11:40:15 2010
*************** select aggfns(distinct a,b,c order by a,
*** 355,357 ****
--- 355,365 ----
    from (values (1,1,'foo')) v(a,b,c), generate_series(1,2) i;
  select aggfns(distinct a,a,c order by a,b)
    from (values (1,1,'foo')) v(a,b,c), generate_series(1,2) i;
+ 
+ -- string_agg tests
+ select string_agg(a) from (values('aaaa'),('bbbb'),('cccc')) g(a);
+ select string_agg(a,',') from (values('aaaa'),('bbbb'),('cccc')) g(a);
+ select string_agg(a,',') from (values('aaaa'),(null),('bbbb'),('cccc')) g(a);
+ select string_agg(a,',') from (values(null),(null),('bbbb'),('cccc')) g(a);
+ select string_agg(a,',') from (values(null),(null)) g(a);
+ select string_agg(elem, delim) from (values('A', ','), ('B', '+'), ('C', '*')) t(elem, delim);