*** ./doc/src/sgml/func.sgml.orig 2010-01-19 06:50:18.000000000 +0100 --- ./doc/src/sgml/func.sgml 2010-01-25 15:48:05.050382968 +0100 *************** *** 1789,1794 **** --- 1789,1798 ---- + + See also about the aggregate + function string_agg. + Built-in Conversions *************** *** 9811,9816 **** --- 9815,9837 ---- + + + string_agg + + string_agg(expression + [, expression ] ) + + + text + + + text + + input values concatenated into an string, optionally separated by the second argument + + + sum(expression) smallint, int, *** ./src/backend/utils/adt/varlena.c.orig 2010-01-02 17:57:55.000000000 +0100 --- ./src/backend/utils/adt/varlena.c 2010-01-25 15:34:13.259507851 +0100 *************** *** 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" *************** *** 48,53 **** --- 50,67 ---- int skiptable[256]; /* skip distance for given mismatched char */ } TextPositionState; + /* type for state data of string_agg function */ + typedef struct + { + StringInfo strInfo; + char delimiter[1]; /* separator string - one or more chars */ + } StringAggState; + + static StringAggState *accumStringResult(StringAggState *state, + text *elem, + text *delimiter, + MemoryContext aggcontext); + #define DatumGetUnknownP(X) ((unknown *) PG_DETOAST_DATUM(X)) #define DatumGetUnknownPCopy(X) ((unknown *) PG_DETOAST_DATUM_COPY(X)) #define PG_GETARG_UNKNOWN_P(n) DatumGetUnknownP(PG_GETARG_DATUM(n)) *************** *** 3143,3145 **** --- 3157,3311 ---- 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 StringAggState * + accumStringResult(StringAggState *state, text *elem, text *delimiter, + MemoryContext aggcontext) + { + MemoryContext oldcontext; + + /* + * when state is NULL, create new state value. + */ + if (state == NULL) + { + if (delimiter != NULL) + { + char *dstr = text_to_cstring(delimiter); + int len = strlen(dstr); + + oldcontext = MemoryContextSwitchTo(aggcontext); + state = palloc(sizeof(StringAggState) + len); + + /* copy delimiter to state var */ + memcpy(&state->delimiter, dstr, len + 1); + } + else + { + oldcontext = MemoryContextSwitchTo(aggcontext); + state = palloc(sizeof(StringAggState)); + state->delimiter[0] = '\0'; + } + + /* Initialise StringInfo */ + state->strInfo = NULL; + + MemoryContextSwitchTo(oldcontext); + } + + /* only when element isn't null */ + if (elem != NULL) + { + char *value = text_to_cstring(elem); + + oldcontext = MemoryContextSwitchTo(aggcontext); + if (state->strInfo != NULL) + appendStringInfoString(state->strInfo, state->delimiter); + else + state->strInfo = makeStringInfo(); + + appendStringInfoString(state->strInfo, value); + MemoryContextSwitchTo(oldcontext); + } + + return state; + } + + Datum + string_agg1_transfn(PG_FUNCTION_ARGS) + { + MemoryContext aggcontext; + StringAggState *state = NULL; + text *elem; + + if (fcinfo->context && IsA(fcinfo->context, AggState)) + aggcontext = ((AggState *) fcinfo->context)->aggcontext; + else if (fcinfo->context && IsA(fcinfo->context, WindowAggState)) + aggcontext = ((WindowAggState *) fcinfo->context)->wincontext; + else + { + /* cannot be called directly because of internal-type argument */ + elog(ERROR, "string_agg1_transfn called in non-aggregate context"); + aggcontext = NULL; /* keep compiler quiet */ + } + + state = PG_ARGISNULL(0) ? NULL : (StringAggState *) PG_GETARG_POINTER(0); + elem = PG_ARGISNULL(1) ? NULL : PG_GETARG_TEXT_PP(1); + + state = accumStringResult(state, + elem, + NULL, + aggcontext); + + /* + * 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) + { + MemoryContext aggcontext; + StringAggState *state = NULL; + text *elem; + text *delimiter = NULL; + + if (fcinfo->context && IsA(fcinfo->context, AggState)) + aggcontext = ((AggState *) fcinfo->context)->aggcontext; + else if (fcinfo->context && IsA(fcinfo->context, WindowAggState)) + aggcontext = ((WindowAggState *) fcinfo->context)->wincontext; + else + { + /* cannot be called directly because of internal-type argument */ + elog(ERROR, "string_agg2_transfn called in non-aggregate context"); + aggcontext = NULL; /* keep compiler quiet */ + } + + state = PG_ARGISNULL(0) ? NULL : (StringAggState *) PG_GETARG_POINTER(0); + elem = PG_ARGISNULL(1) ? NULL : PG_GETARG_TEXT_PP(1); + delimiter = PG_ARGISNULL(2) ? NULL : PG_GETARG_TEXT_PP(2); + + state = accumStringResult(state, + elem, + delimiter, + aggcontext); + + /* + * 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) + { + StringAggState *state = NULL; + + 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 = (StringAggState *) PG_GETARG_POINTER(0); + if (state->strInfo != NULL) + PG_RETURN_TEXT_P(cstring_to_text(state->strInfo->data)); + else + PG_RETURN_NULL(); + } *** ./src/include/catalog/pg_aggregate.h.orig 2010-01-05 02:06:56.000000000 +0100 --- ./src/include/catalog/pg_aggregate.h 2010-01-25 15:14:08.953574327 +0100 *************** *** 223,228 **** --- 223,232 ---- /* array */ DATA(insert ( 2335 array_agg_transfn array_agg_finalfn 0 2281 _null_ )); + /* text */ + DATA(insert (3034 string_agg1_transfn string_agg_finalfn 0 2281 _null_ )); + DATA(insert (3035 string_agg2_transfn string_agg_finalfn 0 2281 _null_ )); + /* * prototypes for functions in pg_aggregate.c */ *** ./src/include/catalog/pg_proc.h.orig 2010-01-19 15:11:32.000000000 +0100 --- ./src/include/catalog/pg_proc.h 2010-01-25 15:19:30.631574450 +0100 *************** *** 2818,2823 **** --- 2818,2834 ---- 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 = 3031 ( 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 = 3032 ( 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 = 3033 ( 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 = 3034 ( 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 = 3035 ( 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"); *** ./src/include/utils/builtins.h.orig 2010-01-19 06:50:18.000000000 +0100 --- ./src/include/utils/builtins.h 2010-01-25 15:22:44.784448955 +0100 *************** *** 722,727 **** --- 722,731 ---- 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); *************** *** 770,775 **** --- 774,782 ---- 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, *** ./src/test/regress/expected/aggregates.out.orig 2009-12-15 18:57:47.000000000 +0100 --- ./src/test/regress/expected/aggregates.out 2010-01-25 15:54:06.000000000 +0100 *************** *** 799,801 **** --- 799,832 ---- 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) + *** ./src/test/regress/sql/aggregates.sql.orig 2009-12-15 18:57:48.000000000 +0100 --- ./src/test/regress/sql/aggregates.sql 2010-01-25 15:53:48.406291403 +0100 *************** *** 355,357 **** --- 355,364 ---- 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);