Hi Dean,
Here's v2 of the patch. How's this look?
.m
*** a/doc/src/sgml/func.sgml
--- b/doc/src/sgml/func.sgml
***************
*** 12635,12640 **** NULL baz</literallayout>(3 rows)</entry>
--- 12635,12660 ----
<row>
<entry>
<indexterm>
+ <primary>single_value</primary>
+ </indexterm>
+ <function>
+ single_value(<replaceable class="parameter">expression</replaceable>)
+ </function>
+ </entry>
+ <entry>
+ any type for which the equality operator has been defined
+ </entry>
+ <entry>
+ same as argument type
+ </entry>
+ <entry>returns the single distinct value from the input values;
+ if more than one distinct value exists (while considering NULLs equal),
+ an exception is raised</entry>
+ </row>
+
+ <row>
+ <entry>
+ <indexterm>
<primary>string_agg</primary>
</indexterm>
<function>
*** a/src/backend/utils/adt/misc.c
--- b/src/backend/utils/adt/misc.c
***************
*** 40,45 ****
--- 40,46 ----
#include "utils/acl.h"
#include "utils/builtins.h"
#include "utils/timestamp.h"
+ #include "utils/typcache.h"
#define atooid(x) ((Oid) strtoul((x), NULL, 10))
***************
*** 598,600 **** pg_column_is_updatable(PG_FUNCTION_ARGS)
--- 599,702 ----
PG_RETURN_BOOL((events & REQ_EVENTS) == REQ_EVENTS);
}
+
+ struct single_value_agg_stype
+ {
+ FunctionCallInfoData fcinfo;
+ Datum datum;
+ bool isnull;
+ };
+
+ Datum
+ single_value_agg_transfn(PG_FUNCTION_ARGS)
+ {
+ MemoryContext aggcontext;
+ struct single_value_agg_stype *state;
+
+ if (!AggCheckCallContext(fcinfo, &aggcontext))
+ {
+ /* cannot be called directly because of internal-type argument */
+ elog(ERROR, "single_value_agg_transfn called in non-aggregate context");
+ }
+
+ if (PG_ARGISNULL(0))
+ {
+ TypeCacheEntry *typentry;
+ Oid arg1_typeid = get_fn_expr_argtype(fcinfo->flinfo, 1);
+
+ if (arg1_typeid == InvalidOid)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("could not determine input data type")));
+
+ typentry = lookup_type_cache(arg1_typeid,
+ TYPECACHE_EQ_OPR_FINFO);
+ if (!OidIsValid(typentry->eq_opr_finfo.fn_oid))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_FUNCTION),
+ errmsg("could not identify an equality operator for type %s",
+ format_type_be(arg1_typeid))));
+
+ state = (struct single_value_agg_stype *) MemoryContextAlloc(aggcontext, sizeof(struct single_value_agg_stype));
+
+ if (PG_ARGISNULL(1))
+ {
+ state->datum = (Datum) 0;
+ state->isnull = true;
+ memset(&state->fcinfo, 0, sizeof(state->fcinfo));
+ }
+ else
+ {
+ state->datum = PG_GETARG_DATUM(1);
+ state->isnull = false;
+ InitFunctionCallInfoData(state->fcinfo, &typentry->eq_opr_finfo, 2,
+ InvalidOid, NULL, NULL);
+ }
+ }
+ else
+ {
+ bool oprresult;
+
+ state = (struct single_value_agg_stype *) PG_GETARG_POINTER(0);
+
+ if (state->isnull)
+ oprresult = PG_ARGISNULL(1);
+ else
+ {
+ state->fcinfo.argnull[0] = false;
+ state->fcinfo.argnull[1] = false;
+ state->fcinfo.arg[0] = state->datum;
+ state->fcinfo.arg[1] = PG_GETARG_DATUM(1);
+ state->fcinfo.isnull = false;
+ oprresult = DatumGetBool(FunctionCallInvoke(&state->fcinfo));
+ }
+ if (!oprresult)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("more than one distinct value passed to single_value")));
+ }
+
+ /*
+ * The transition type for single_value() is declared to be "internal",
+ * which is a pass-by-value type the same size as a pointer. So we can
+ * safely pass the pointer through nodeAgg.c's machinations.
+ */
+ PG_RETURN_POINTER(state);
+ }
+
+ Datum
+ single_value_agg_finalfn(PG_FUNCTION_ARGS)
+ {
+ struct single_value_agg_stype *state;
+
+ if (PG_ARGISNULL(0))
+ PG_RETURN_NULL();
+
+ /* cannot be called directly because of internal-type argument */
+ Assert(AggCheckCallContext(fcinfo, NULL));
+
+ state = (struct single_value_agg_stype *) PG_GETARG_POINTER(0);
+ if (state->isnull)
+ PG_RETURN_NULL();
+ PG_RETURN_DATUM(state->datum);
+ }
*** a/src/include/catalog/pg_aggregate.h
--- b/src/include/catalog/pg_aggregate.h
***************
*** 292,297 **** DATA(insert ( 3197 n 0 json_object_agg_transfn json_object_agg_finalfn - -
--- 292,300 ----
DATA(insert ( 3267 n 0 jsonb_agg_transfn jsonb_agg_finalfn - - - f f 0 2281 0 0 0 _null_ _null_ ));
DATA(insert ( 3270 n 0 jsonb_object_agg_transfn jsonb_object_agg_finalfn - - - f f 0 2281 0 0 0 _null_ _null_ ));
+ /* single_value */
+ DATA(insert ( 4202 n 0 single_value_agg_transfn single_value_agg_finalfn - - - t f 0 2281 0 0 0 _null_ _null_ ));
+
/* ordered-set and hypothetical-set aggregates */
DATA(insert ( 3972 o 1 ordered_set_transition percentile_disc_final - - - t f 0 2281 0 0 0 _null_ _null_ ));
DATA(insert ( 3974 o 1 ordered_set_transition percentile_cont_float8_final - - - f f 0 2281 0 0 0 _null_ _null_ ));
*** a/src/include/catalog/pg_proc.h
--- b/src/include/catalog/pg_proc.h
***************
*** 945,950 **** DATA(insert OID = 4053 ( array_agg PGNSP PGUID 12 1 0 0 0 t f f f f f i s 1
--- 945,956 ----
DESCR("concatenate aggregate input into an array");
DATA(insert OID = 3218 ( width_bucket PGNSP PGUID 12 1 0 0 0 f f f f t f i s 2 0 23 "2283 2277" _null_ _null_ _null_ _null_ _null_ width_bucket_array _null_ _null_ _null_ ));
DESCR("bucket number of operand given a sorted array of bucket lower bounds");
+ DATA(insert OID = 4200 ( single_value_agg_transfn PGNSP PGUID 12 1 0 0 0 f f f f f f i s 2 0 2281 "2281 2283" _null_ _null_ _null_ _null_ _null_ single_value_agg_transfn _null_ _null_ _null_ ));
+ DESCR("aggregate transition function");
+ DATA(insert OID = 4201 ( single_value_agg_finalfn PGNSP PGUID 12 1 0 0 0 f f f f f f i s 2 0 2283 "2281 2283" _null_ _null_ _null_ _null_ _null_ single_value_agg_finalfn _null_ _null_ _null_ ));
+ DESCR("aggregate final function");
+ DATA(insert OID = 4202 ( single_value PGNSP PGUID 12 1 0 0 0 t f f f f f i s 1 0 2283 "2283" _null_ _null_ _null_ _null_ _null_ aggregate_dummy _null_ _null_ _null_ ));
+ DESCR("return the single distinct value from aggregate input");
DATA(insert OID = 3816 ( array_typanalyze PGNSP PGUID 12 1 0 0 0 f f f f t f s s 1 0 16 "2281" _null_ _null_ _null_ _null_ _null_ array_typanalyze _null_ _null_ _null_ ));
DESCR("array typanalyze");
DATA(insert OID = 3817 ( arraycontsel PGNSP PGUID 12 1 0 0 0 f f f f t f s s 4 0 701 "2281 26 2281 23" _null_ _null_ _null_ _null_ _null_ arraycontsel _null_ _null_ _null_ ));
*** a/src/include/utils/builtins.h
--- b/src/include/utils/builtins.h
***************
*** 495,500 **** extern Datum pg_typeof(PG_FUNCTION_ARGS);
--- 495,502 ----
extern Datum pg_collation_for(PG_FUNCTION_ARGS);
extern Datum pg_relation_is_updatable(PG_FUNCTION_ARGS);
extern Datum pg_column_is_updatable(PG_FUNCTION_ARGS);
+ extern Datum single_value_agg_transfn(PG_FUNCTION_ARGS);
+ extern Datum single_value_agg_finalfn(PG_FUNCTION_ARGS);
/* oid.c */
extern Datum oidin(PG_FUNCTION_ARGS);
*** a/src/test/regress/expected/aggregates.out
--- b/src/test/regress/expected/aggregates.out
***************
*** 1784,1786 **** NOTICE: sum_transfn called with 4
--- 1784,1827 ----
(1 row)
rollback;
+ /* single_value */
+ select single_value(vv.v) from (values (1), (1)) vv(v);
+ single_value
+ --------------
+ 1
+ (1 row)
+
+ select single_value(vv.v) from (values (1), (2)) vv(v);
+ ERROR: more than one distinct value passed to single_value
+ select single_value(vv.v) from (values (1), (NULL)) vv(v);
+ ERROR: more than one distinct value passed to single_value
+ select single_value(vv.v) from (values (NULL), (NULL)) vv(v);
+ single_value
+ --------------
+
+ (1 row)
+
+ select single_value(vv.v) from (values (json '{}')) vv(v);
+ ERROR: could not identify an equality operator for type json
+ select single_value(vv.v1) from (values (1,1), (2,2), (3,3)) vv(v1, v2);
+ ERROR: more than one distinct value passed to single_value
+ select vv.v2, single_value(vv.v1) from (values (1,1), (2,2), (2,2), (3,3)) vv(v1, v2) group by vv.v2;
+ v2 | single_value
+ ----+--------------
+ 1 | 1
+ 2 | 2
+ 3 | 3
+ (3 rows)
+
+ select single_value(vv.v1) FILTER (WHERE vv.v2 = 1) from (values (1,1), (2,2), (3,3)) vv(v1, v2);
+ single_value
+ --------------
+ 1
+ (1 row)
+
+ select single_value(vv.v) from (select 1) vv(V) where false;
+ single_value
+ --------------
+
+ (1 row)
+
*** a/src/test/regress/sql/aggregates.sql
--- b/src/test/regress/sql/aggregates.sql
***************
*** 755,757 **** create aggregate my_half_sum(int4)
--- 755,768 ----
select my_sum(one),my_half_sum(one) from (values(1),(2),(3),(4)) t(one);
rollback;
+
+ /* single_value */
+ select single_value(vv.v) from (values (1), (1)) vv(v);
+ select single_value(vv.v) from (values (1), (2)) vv(v);
+ select single_value(vv.v) from (values (1), (NULL)) vv(v);
+ select single_value(vv.v) from (values (NULL), (NULL)) vv(v);
+ select single_value(vv.v) from (values (json '{}')) vv(v);
+ select single_value(vv.v1) from (values (1,1), (2,2), (3,3)) vv(v1, v2);
+ select vv.v2, single_value(vv.v1) from (values (1,1), (2,2), (2,2), (3,3)) vv(v1, v2) group by vv.v2;
+ select single_value(vv.v1) FILTER (WHERE vv.v2 = 1) from (values (1,1), (2,2), (3,3)) vv(v1, v2);
+ select single_value(vv.v) from (select 1) vv(V) where false;
--
Sent via pgsql-hackers mailing list ([email protected])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers