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 (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

Reply via email to