Index: doc/src/sgml/errcodes.sgml
===================================================================
RCS file: /Users/neilc/local/cvs/pgsql-server/doc/src/sgml/errcodes.sgml,v
retrieving revision 1.3
diff -c -r1.3 errcodes.sgml
*** doc/src/sgml/errcodes.sgml	4 Mar 2004 21:47:18 -0000	1.3
--- doc/src/sgml/errcodes.sgml	25 Apr 2004 02:31:27 -0000
***************
*** 306,311 ****
--- 306,316 ----
  </row>
  
  <row>
+ <entry><literal>2201G</literal></entry>
+ <entry>INVALID ARGUMENT FOR WIDTH BUCKET FUNCTION</entry>
+ </row>
+ 
+ <row>
  <entry><literal>22018</literal></entry>
  <entry>INVALID CHARACTER VALUE FOR CAST</entry>
  </row>
Index: src/backend/utils/adt/float.c
===================================================================
RCS file: /Users/neilc/local/cvs/pgsql-server/src/backend/utils/adt/float.c,v
retrieving revision 1.103
diff -c -r1.103 float.c
*** src/backend/utils/adt/float.c	1 Apr 2004 23:52:18 -0000	1.103
--- src/backend/utils/adt/float.c	24 Apr 2004 23:51:19 -0000
***************
*** 1825,1830 ****
--- 1825,1835 ----
  	PG_RETURN_INT32(iseed);
  }
  
+ Datum
+ width_bucket_float8(PG_FUNCTION_ARGS)
+ {
+ 	;
+ }
  
  
  /*
Index: src/backend/utils/adt/numeric.c
===================================================================
RCS file: /Users/neilc/local/cvs/pgsql-server/src/backend/utils/adt/numeric.c,v
retrieving revision 1.72
diff -c -r1.72 numeric.c
*** src/backend/utils/adt/numeric.c	15 Mar 2004 03:29:22 -0000	1.72
--- src/backend/utils/adt/numeric.c	25 Apr 2004 03:49:04 -0000
***************
*** 252,257 ****
--- 252,258 ----
  
  static void apply_typmod(NumericVar *var, int32 typmod);
  
+ static int32 numericvar_to_int4(NumericVar *var);
  static bool numericvar_to_int8(NumericVar *var, int64 *result);
  static void int8_to_numericvar(int64 val, NumericVar *var);
  static double numeric_to_double_no_overflow(Numeric num);
***************
*** 285,290 ****
--- 286,293 ----
  static void round_var(NumericVar *var, int rscale);
  static void trunc_var(NumericVar *var, int rscale);
  static void strip_var(NumericVar *var);
+ static void compute_bucket(Numeric operand, Numeric bound1, Numeric bound2,
+ 						   NumericVar *count_var, NumericVar *result_var);
  
  
  /* ----------------------------------------------------------------------
***************
*** 803,808 ****
--- 806,930 ----
  	PG_RETURN_NUMERIC(res);
  }
  
+ /*
+  * width_bucket_numeric() -
+  *
+  * 'bound1' and 'bound2' are the lower and upper bounds of the
+  * histogram's range, respectively. 'count' is the number of buckets
+  * in the histogram. width_bucket() returns an integer indicating the
+  * bucket number that 'operand' belongs in for an equiwidth histogram
+  * with the specified characteristics. An operand smaller than the
+  * lower bound is assigned to bucket 0. An operand greater than the
+  * upper bound is assigned to an additional bucket (with number
+  * count+1).
+  */
+ Datum
+ width_bucket_numeric(PG_FUNCTION_ARGS)
+ {
+ 	Numeric		operand = PG_GETARG_NUMERIC(0);
+ 	Numeric		bound1 = PG_GETARG_NUMERIC(1);
+ 	Numeric		bound2 = PG_GETARG_NUMERIC(2);
+ 	int32		count = PG_GETARG_INT32(3);
+ 	NumericVar	count_var;
+ 	NumericVar	result_var;
+ 	int32		result;
+ 
+ 	if (count <= 0)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_INVALID_ARGUMENT_FOR_WIDTH_BUCKET_FUNCTION),
+ 				 errmsg("count must be greater than zero")));
+ 
+ 	init_var(&result_var);
+ 	init_var(&count_var);
+ 
+ 	/* Convert 'count' to a numeric, for ease of use later */
+ 	int8_to_numericvar((int64) count, &count_var);
+ 
+ 	switch (cmp_numerics(bound1, bound2))
+ 	{
+ 		case 0:
+ 			ereport(ERROR,
+ 					(errcode(ERRCODE_INVALID_ARGUMENT_FOR_WIDTH_BUCKET_FUNCTION),
+ 					 errmsg("lower bound cannot equal upper bound")));
+ 
+ 		/* bound1 < bound2 */
+ 		case -1:
+ 			if (cmp_numerics(operand, bound1) < 0)
+ 				set_var_from_var(&const_zero, &result_var);
+ 			else if (cmp_numerics(operand, bound2) >= 0)
+ 				add_var(&count_var, &const_one, &result_var);
+ 			else
+ 				compute_bucket(operand, bound1, bound2,
+ 							   &count_var, &result_var);
+ 			break;
+ 
+ 		/* bound1 > bound2 */
+ 		case 1:
+ 			if (cmp_numerics(operand, bound1) > 0)
+ 				set_var_from_var(&const_zero, &result_var);
+ 			else if (cmp_numerics(operand, bound2) <= 0)
+ 				add_var(&count_var, &const_one, &result_var);
+ 			else
+ 				compute_bucket(operand, bound1, bound2,
+ 							   &count_var, &result_var);
+ 			break;
+ 	}
+ 
+ 	result = numericvar_to_int4(&result_var);
+ 
+ 	free_var(&count_var);
+ 	free_var(&result_var);
+ 
+ 	PG_RETURN_INT32(result);
+ }
+ 
+ /*
+  * compute_bucket() -
+  *
+  * If 'operand' is not outside the bucket range, determine the correct
+  * bucket for it to go. The calculations performed by this function
+  * are derived directly from the SQL2003 spec.
+  */
+ static void
+ compute_bucket(Numeric operand, Numeric bound1, Numeric bound2,
+ 			   NumericVar *count_var, NumericVar *result_var)
+ {
+ 	NumericVar bound1_var;
+ 	NumericVar bound2_var;
+ 	NumericVar operand_var;
+ 
+ 	init_var(&bound1_var);
+ 	init_var(&bound2_var);
+ 	init_var(&operand_var);
+ 
+ 	set_var_from_num(bound1, &bound1_var);
+ 	set_var_from_num(bound2, &bound2_var);
+ 	set_var_from_num(operand, &operand_var);
+ 
+ 	if (cmp_var(&bound1_var, &bound2_var) < 0)
+ 	{
+ 		sub_var(&operand_var, &bound1_var, &operand_var);
+ 		sub_var(&bound2_var, &bound1_var, &bound2_var);
+ 		div_var(&operand_var, &bound2_var, result_var,
+ 				select_div_scale(&operand_var, &bound2_var));
+ 	}
+ 	else
+ 	{
+ 		sub_var(&bound1_var, &operand_var, &operand_var);
+ 		sub_var(&bound1_var, &bound2_var, &bound1_var);
+ 		div_var(&operand_var, &bound1_var, result_var,
+ 				select_div_scale(&operand_var, &bound1_var));
+ 	}
+ 
+ 	mul_var(result_var, count_var, result_var,
+ 			result_var->dscale + count_var->dscale);
+ 	add_var(result_var, &const_one, result_var);
+ 	floor_var(result_var, result_var);
+ 
+ 	free_var(&bound1_var);
+ 	free_var(&bound2_var);
+ 	free_var(&operand_var);
+ }	
  
  /* ----------------------------------------------------------------------
   *
***************
*** 1612,1618 ****
  {
  	Numeric		num = PG_GETARG_NUMERIC(0);
  	NumericVar	x;
- 	int64		val;
  	int32		result;
  
  	/* XXX would it be better to return NULL? */
--- 1734,1739 ----
***************
*** 1621,1637 ****
  				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
  				 errmsg("cannot convert NaN to integer")));
  
! 	/* Convert to variable format and thence to int8 */
  	init_var(&x);
  	set_var_from_num(num, &x);
  
! 	if (!numericvar_to_int8(&x, &val))
  		ereport(ERROR,
  				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
  				 errmsg("integer out of range")));
  
- 	free_var(&x);
- 
  	/* Down-convert to int4 */
  	result = (int32) val;
  
--- 1742,1771 ----
  				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
  				 errmsg("cannot convert NaN to integer")));
  
! 	/* Convert to variable format, then convert to int4 */
  	init_var(&x);
  	set_var_from_num(num, &x);
+ 	result = numericvar_to_int4(&x);
+ 	free_var(&x);
+ 	PG_RETURN_INT32(result);
+ }
+ 
+ /*
+  * Given a NumericVar, convert it to an int32. If the NumericVar
+  * exceeds the range of an int32, raise the appropriate error via
+  * ereport(). The input NumericVar is *not* free'd.
+  */
+ static int32
+ numericvar_to_int4(NumericVar *var)
+ {
+ 	int32 result;
+ 	int64 val;
  
! 	if (!numericvar_to_int8(var, &val))
  		ereport(ERROR,
  				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
  				 errmsg("integer out of range")));
  
  	/* Down-convert to int4 */
  	result = (int32) val;
  
***************
*** 1641,1649 ****
  				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
  				 errmsg("integer out of range")));
  
! 	PG_RETURN_INT32(result);
  }
- 
  
  Datum
  int8_numeric(PG_FUNCTION_ARGS)
--- 1775,1782 ----
  				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
  				 errmsg("integer out of range")));
  
! 	return result;
  }
  
  Datum
  int8_numeric(PG_FUNCTION_ARGS)
Index: src/include/catalog/catversion.h
===================================================================
RCS file: /Users/neilc/local/cvs/pgsql-server/src/include/catalog/catversion.h,v
retrieving revision 1.224
diff -c -r1.224 catversion.h
*** src/include/catalog/catversion.h	23 Apr 2004 20:32:19 -0000	1.224
--- src/include/catalog/catversion.h	24 Apr 2004 23:51:19 -0000
***************
*** 53,58 ****
   */
  
  /*							yyyymmddN */
! #define CATALOG_VERSION_NO	200404220
  
  #endif
--- 53,58 ----
   */
  
  /*							yyyymmddN */
! #define CATALOG_VERSION_NO	200404230
  
  #endif
Index: src/include/catalog/pg_proc.h
===================================================================
RCS file: /Users/neilc/local/cvs/pgsql-server/src/include/catalog/pg_proc.h,v
retrieving revision 1.325
diff -c -r1.325 pg_proc.h
*** src/include/catalog/pg_proc.h	23 Apr 2004 20:32:19 -0000	1.325
--- src/include/catalog/pg_proc.h	25 Apr 2004 03:30:52 -0000
***************
*** 2508,2513 ****
--- 2508,2517 ----
  DESCR("(internal)");
  DATA(insert OID = 1746 ( float8					PGNSP PGUID 12 f f t f i 1 701 "1700" _null_  numeric_float8 - _null_ ));
  DESCR("(internal)");
+ DATA(insert OID = 2170 ( width_bucket			PGNSP PGUID 12 f f t f i 4 23 "1700 1700 1700 23" _null_  width_bucket_numeric - _null_ ));
+ DESCR("XXX: todo");
+ DATA(insert OID = 2171 ( width_bucket			PGNSP PGUID 12 f f t f i 4 23 "701 701 701 23" _null_  width_bucket_float8 - _null_ ));
+ DESCR("XXX: todo");
  
  DATA(insert OID = 1747 ( time_pl_interval		PGNSP PGUID 12 f f t f i 2 1083 "1083 1186" _null_  time_pl_interval - _null_ ));
  DESCR("plus");
Index: src/include/utils/builtins.h
===================================================================
RCS file: /Users/neilc/local/cvs/pgsql-server/src/include/utils/builtins.h,v
retrieving revision 1.236
diff -c -r1.236 builtins.h
*** src/include/utils/builtins.h	1 Apr 2004 21:28:46 -0000	1.236
--- src/include/utils/builtins.h	24 Apr 2004 23:51:19 -0000
***************
*** 347,352 ****
--- 347,353 ----
  extern Datum float84le(PG_FUNCTION_ARGS);
  extern Datum float84gt(PG_FUNCTION_ARGS);
  extern Datum float84ge(PG_FUNCTION_ARGS);
+ extern Datum width_bucket_float8(PG_FUNCTION_ARGS);
  
  /* misc.c */
  extern Datum nullvalue(PG_FUNCTION_ARGS);
***************
*** 756,761 ****
--- 757,763 ----
  extern Datum int2_avg_accum(PG_FUNCTION_ARGS);
  extern Datum int4_avg_accum(PG_FUNCTION_ARGS);
  extern Datum int8_avg(PG_FUNCTION_ARGS);
+ extern Datum width_bucket_numeric(PG_FUNCTION_ARGS);
  
  /* ri_triggers.c */
  extern Datum RI_FKey_check_ins(PG_FUNCTION_ARGS);
Index: src/include/utils/errcodes.h
===================================================================
RCS file: /Users/neilc/local/cvs/pgsql-server/src/include/utils/errcodes.h,v
retrieving revision 1.8
diff -c -r1.8 errcodes.h
*** src/include/utils/errcodes.h	4 Mar 2004 21:47:18 -0000	1.8
--- src/include/utils/errcodes.h	25 Apr 2004 02:21:35 -0000
***************
*** 100,105 ****
--- 100,106 ----
  #define ERRCODE_ESCAPE_CHARACTER_CONFLICT	MAKE_SQLSTATE('2','2', '0','0','B')
  #define ERRCODE_INDICATOR_OVERFLOW			MAKE_SQLSTATE('2','2', '0','2','2')
  #define ERRCODE_INTERVAL_FIELD_OVERFLOW		MAKE_SQLSTATE('2','2', '0','1','5')
+ #define ERRCODE_INVALID_ARGUMENT_FOR_WIDTH_BUCKET_FUNCTION		MAKE_SQLSTATE('2','2', '0', '1', 'G')
  #define ERRCODE_INVALID_CHARACTER_VALUE_FOR_CAST		MAKE_SQLSTATE('2','2', '0','1','8')
  #define ERRCODE_INVALID_DATETIME_FORMAT		MAKE_SQLSTATE('2','2', '0','0','7')
  #define ERRCODE_INVALID_ESCAPE_CHARACTER	MAKE_SQLSTATE('2','2', '0','1','9')
Index: src/test/regress/expected/numeric.out
===================================================================
RCS file: /Users/neilc/local/cvs/pgsql-server/src/test/regress/expected/numeric.out,v
retrieving revision 1.14
diff -c -r1.14 numeric.out
*** src/test/regress/expected/numeric.out	23 Apr 2004 20:32:20 -0000	1.14
--- src/test/regress/expected/numeric.out	25 Apr 2004 03:58:35 -0000
***************
*** 730,735 ****
--- 730,786 ----
  (7 rows)
  
  DROP TABLE ceil_floor_round;
+ -- Testing for width_bucket()
+ -- NULL result
+ SELECT width_bucket(NULL, NULL, NULL, NULL);
+  width_bucket 
+ --------------
+              
+ (1 row)
+ 
+ -- errors
+ SELECT width_bucket(5.0, 3.0, 4.0, 0);
+ ERROR:  count must be greater than zero
+ SELECT width_bucket(5.0, 3.0, 4.0, -5);
+ ERROR:  count must be greater than zero
+ SELECT width_bucket(3.0, 3.0, 3.0, 888);
+ ERROR:  lower bound cannot equal upper bound
+ -- normal operation
+ CREATE TABLE width_bucket_test (operand numeric);
+ COPY width_bucket_test FROM stdin;
+ SELECT
+     operand,
+     width_bucket(operand, 0, 10, 5) AS wb_1,
+     width_bucket(operand, 10, 0, 5) AS wb_2,
+     width_bucket(operand, 2, 8, 4) AS wb_3,
+     width_bucket(operand, 5.0, 5.5, 20) AS wb_4,
+     width_bucket(operand, -25, 25, 10) AS wb_5
+     FROM width_bucket_test;
+      operand      | wb_1 | wb_2 | wb_3 | wb_4 | wb_5 
+ ------------------+------+------+------+------+------
+              -5.2 |    0 |    6 |    0 |    0 |    4
+  -0.0000000000001 |    0 |    6 |    0 |    0 |    5
+   0.0000000000001 |    1 |    5 |    0 |    0 |    6
+                 1 |    1 |    5 |    0 |    0 |    6
+  1.99999999999999 |    1 |    5 |    0 |    0 |    6
+                 2 |    2 |    5 |    1 |    0 |    6
+  2.00000000000001 |    2 |    4 |    1 |    0 |    6
+                 3 |    2 |    4 |    1 |    0 |    6
+                 4 |    3 |    4 |    2 |    0 |    6
+               4.5 |    3 |    3 |    2 |    0 |    6
+                 5 |    3 |    3 |    3 |    1 |    7
+               5.5 |    3 |    3 |    3 |   21 |    7
+                 6 |    4 |    3 |    3 |   21 |    7
+                 7 |    4 |    2 |    4 |   21 |    7
+                 8 |    5 |    2 |    5 |   21 |    7
+                 9 |    5 |    1 |    5 |   21 |    7
+  9.99999999999999 |    5 |    1 |    5 |   21 |    7
+                10 |    6 |    1 |    5 |   21 |    8
+  10.0000000000001 |    6 |    0 |    5 |   21 |    8
+               NaN |    6 |    0 |    5 |   21 |   11
+ (20 rows)
+ 
+ DROP TABLE width_bucket_test;
  -- TO_CHAR()
  --
  SELECT '' AS to_char_1, to_char(val, '9G999G999G999G999G999') 
Index: src/test/regress/sql/numeric.sql
===================================================================
RCS file: /Users/neilc/local/cvs/pgsql-server/src/test/regress/sql/numeric.sql,v
retrieving revision 1.9
diff -c -r1.9 numeric.sql
*** src/test/regress/sql/numeric.sql	23 Apr 2004 20:32:20 -0000	1.9
--- src/test/regress/sql/numeric.sql	25 Apr 2004 03:53:50 -0000
***************
*** 667,672 ****
--- 667,718 ----
  SELECT a, ceil(a), ceiling(a), floor(a), round(a) FROM ceil_floor_round;
  DROP TABLE ceil_floor_round;
  
+ -- Testing for width_bucket()
+ -- NULL result
+ SELECT width_bucket(NULL, NULL, NULL, NULL);
+ 
+ -- errors
+ SELECT width_bucket(5.0, 3.0, 4.0, 0);
+ SELECT width_bucket(5.0, 3.0, 4.0, -5);
+ SELECT width_bucket(3.0, 3.0, 3.0, 888);
+ 
+ -- normal operation
+ CREATE TABLE width_bucket_test (operand numeric);
+ 
+ COPY width_bucket_test FROM stdin;
+ -5.2
+ -0.0000000000001
+ 0.0000000000001
+ 1
+ 1.99999999999999
+ 2
+ 2.00000000000001
+ 3
+ 4
+ 4.5
+ 5
+ 5.5
+ 6
+ 7
+ 8
+ 9
+ 9.99999999999999
+ 10
+ 10.0000000000001
+ NaN
+ \.
+ 
+ SELECT
+     operand,
+     width_bucket(operand, 0, 10, 5) AS wb_1,
+     width_bucket(operand, 10, 0, 5) AS wb_2,
+     width_bucket(operand, 2, 8, 4) AS wb_3,
+     width_bucket(operand, 5.0, 5.5, 20) AS wb_4,
+     width_bucket(operand, -25, 25, 10) AS wb_5
+     FROM width_bucket_test;
+ 
+ DROP TABLE width_bucket_test;
+ 
  -- TO_CHAR()
  --
  SELECT '' AS to_char_1, to_char(val, '9G999G999G999G999G999') 
