From 2c773296f88fe800315ca1bf131287662ecef999 Mon Sep 17 00:00:00 2001
From: Bruce Momjian <bruce@momjian.us>
Date: Wed, 16 Jul 2008 00:48:54 +0000
Subject: [PATCH] Add array_fill() to create arrays initialized with a value.

Pavel Stehule
---
 doc/src/sgml/func.sgml               |  15 +-
 src/backend/utils/adt/arrayfuncs.c   | 276 ++++++++++++++++++++++++++-
 src/include/catalog/catversion.h     |   4 +-
 src/include/catalog/pg_proc.h        |   8 +-
 src/include/utils/array.h            |   4 +-
 src/test/regress/expected/arrays.out |  58 ++++++
 src/test/regress/sql/arrays.sql      |  14 ++
 7 files changed, 371 insertions(+), 8 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 9bc051583dc..acd2b92918e 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -1,4 +1,4 @@
-<!-- $PostgreSQL: pgsql/doc/src/sgml/func.sgml,v 1.440 2008/07/15 18:24:59 momjian Exp $ -->
+<!-- $PostgreSQL: pgsql/doc/src/sgml/func.sgml,v 1.441 2008/07/16 00:48:53 momjian Exp $ -->
 
  <chapter id="functions">
   <title>Functions and Operators</title>
@@ -9371,6 +9371,19 @@ SELECT NULLIF(value, '(none)') ...
         <entry><literal>array_dims(ARRAY[[1,2,3], [4,5,6]])</literal></entry>
         <entry><literal>[1:2][1:3]</literal></entry>
        </row>
+       <row>
+        <entry>
+         <literal>
+          <function>array_fill</function>(<type>anyelement</type>, <type>anyarray</type>,
+          <optional>, <type>anyarray</type></optional>)
+         </literal>
+        </entry>
+        <entry><type>anyarray</type></entry>
+        <entry>returns an array initialized with supplied value,
+        dimensions, and lower bounds</entry>
+        <entry><literal>array_fill(7, ARRAY[3], ARRAY[2])</literal></entry>
+        <entry><literal>[2:4]={7,7,7}</literal></entry>
+       </row>
        <row>
         <entry>
          <literal>
diff --git a/src/backend/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c
index b3a2ce86579..6c810025e5e 100644
--- a/src/backend/utils/adt/arrayfuncs.c
+++ b/src/backend/utils/adt/arrayfuncs.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/utils/adt/arrayfuncs.c,v 1.145 2008/05/12 00:00:51 alvherre Exp $
+ *	  $PostgreSQL: pgsql/src/backend/utils/adt/arrayfuncs.c,v 1.146 2008/07/16 00:48:53 momjian Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -95,6 +95,11 @@ static void array_insert_slice(ArrayType *destArray, ArrayType *origArray,
 				   int *st, int *endp,
 				   int typlen, bool typbyval, char typalign);
 static int	array_cmp(FunctionCallInfo fcinfo);
+static ArrayType *create_array_envelope(int ndims, int *dimv, int *lbv, int nbytes,
+			    Oid elmtype, int dataoffset);
+static ArrayType *array_fill_internal(ArrayType *dims, ArrayType *lbs, Datum value, 
+					    Oid elmtype, bool isnull, 
+					    FunctionCallInfo fcinfo);
 
 
 /*
@@ -4314,3 +4319,272 @@ generate_subscripts_nodir(PG_FUNCTION_ARGS)
 	/* just call the other one -- it can handle both cases */
 	return generate_subscripts(fcinfo);
 }
+
+/*
+ * array_fill_with_lower_bounds
+ *		Create and fill array with defined lower bounds.
+ */
+Datum
+array_fill_with_lower_bounds(PG_FUNCTION_ARGS)
+{
+	ArrayType	*dims;
+	ArrayType	*lbs;
+	ArrayType		*result;
+	Oid			elmtype;
+	Datum 	value;
+	bool	isnull;
+
+	if (PG_ARGISNULL(1) || PG_ARGISNULL(2))
+		ereport(ERROR, 
+			    (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+			     errmsg("dimension array or low bound array cannot be NULL")));
+
+	dims = PG_GETARG_ARRAYTYPE_P(1);
+	lbs  = PG_GETARG_ARRAYTYPE_P(2);
+
+	if (!PG_ARGISNULL(0))
+	{
+		value = PG_GETARG_DATUM(0);
+		isnull = false;
+	}
+	else
+	{
+		value = 0;
+		isnull = true;
+	}
+
+	elmtype = get_fn_expr_argtype(fcinfo->flinfo, 0); 
+	if (!OidIsValid(elmtype)) 
+		elog(ERROR, "could not determine data type of input"); 
+
+	result = array_fill_internal(dims, lbs, value, elmtype, isnull, fcinfo);
+	PG_RETURN_ARRAYTYPE_P(result);
+}
+
+/*
+ * array_fill
+ *		Create and fill array with default lower bounds.
+ */
+Datum
+array_fill(PG_FUNCTION_ARGS)
+{
+	ArrayType	*dims;
+	ArrayType		*result;
+	Oid			elmtype;
+	Datum 	value;
+	bool	isnull;
+
+	if (PG_ARGISNULL(1))
+		ereport(ERROR, 
+			    (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+			     errmsg("dimension array or low bound array cannot be NULL")));
+
+	dims = PG_GETARG_ARRAYTYPE_P(1);
+
+	if (!PG_ARGISNULL(0))
+	{
+		value = PG_GETARG_DATUM(0);
+		isnull = false;
+	}
+	else
+	{
+		value = 0;
+		isnull = true;
+	}
+
+	elmtype = get_fn_expr_argtype(fcinfo->flinfo, 0); 
+	if (!OidIsValid(elmtype)) 
+		elog(ERROR, "could not determine data type of input"); 
+
+	result = array_fill_internal(dims, NULL, value, elmtype, isnull, fcinfo);
+	PG_RETURN_ARRAYTYPE_P(result);
+}
+
+static ArrayType *
+create_array_envelope(int ndims, int *dimv, int *lbsv, int nbytes,
+			    Oid elmtype, int dataoffset)
+{
+	ArrayType *result;
+
+	result = (ArrayType *) palloc0(nbytes);
+	SET_VARSIZE(result, nbytes);
+	result->ndim = ndims;
+	result->dataoffset = dataoffset;
+	result->elemtype = elmtype;
+	memcpy(ARR_DIMS(result), dimv, ndims * sizeof(int));
+	memcpy(ARR_LBOUND(result), lbsv, ndims * sizeof(int));
+
+	return result;
+}
+
+static ArrayType *
+array_fill_internal(ArrayType *dims, ArrayType *lbs, Datum value, 
+					    Oid elmtype, bool isnull,
+					    FunctionCallInfo fcinfo)
+{
+	ArrayType	*result;
+	int	*dimv;
+	int	*lbsv;
+	int	ndims;
+	int	nitems;
+	int 		deflbs[MAXDIM];
+	int16 elmlen; 
+	bool elmbyval; 
+	char elmalign;
+	ArrayMetaState 		*my_extra;
+
+	/* 
+	 * Params checks
+	 */
+	if (ARR_NDIM(dims) != 1)
+		ereport(ERROR,
+			    (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
+			     errmsg("wrong number of array subscripts"),
+			     errhint("Dimension array must be one dimensional.")));
+
+	if (ARR_LBOUND(dims)[0] != 1)
+		ereport(ERROR,
+			    (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
+			     errmsg("wrong range of array_subscripts"),
+			     errhint("Lower bound of dimension array must be one.")));
+	
+	if (ARR_HASNULL(dims))
+		ereport(ERROR, 
+			    (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+			     errmsg("dimension values cannot be null")));
+
+	dimv = (int *) ARR_DATA_PTR(dims);
+	ndims = ARR_DIMS(dims)[0];
+	
+	if (ndims < 0)				/* we do allow zero-dimension arrays */
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("invalid number of dimensions: %d", ndims)));
+	if (ndims > MAXDIM)
+		ereport(ERROR,
+				(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
+				 errmsg("number of array dimensions (%d) exceeds the maximum allowed (%d)",
+						ndims, MAXDIM)));
+	
+	if (lbs != NULL)
+	{
+		if (ARR_NDIM(lbs) != 1)
+			ereport(ERROR,
+				    (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
+			    	     errmsg("wrong number of array subscripts"),
+			    	     errhint("Dimension array must be one dimensional.")));
+
+		if (ARR_LBOUND(lbs)[0] != 1)
+			ereport(ERROR,
+				(errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
+			         errmsg("wrong range of array_subscripts"),
+			    	 errhint("Lower bound of dimension array must be one.")));
+	
+		if (ARR_HASNULL(lbs))
+			ereport(ERROR, 
+				(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+				 errmsg("dimension values cannot be null")));
+
+		if (ARR_DIMS(lbs)[0] != ndims)
+			ereport(ERROR,
+				    (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
+				     errmsg("wrong number of array_subscripts"),
+				     errhint("Low bound array has different size than dimensions array.")));
+				     
+		lbsv = (int *) ARR_DATA_PTR(lbs);
+	}
+	else	
+	{
+		int	i;
+	
+		for (i = 0; i < MAXDIM; i++)
+			deflbs[i] = 1;
+
+		lbsv = deflbs;
+	}
+
+	/* fast track for empty array */
+	if (ndims == 0)
+		return construct_empty_array(elmtype);
+	
+	nitems = ArrayGetNItems(ndims, dimv);
+
+
+	/*
+	 * We arrange to look up info about element type only once per series of
+	 * calls, assuming the element type doesn't change underneath us.
+	 */
+	my_extra = (ArrayMetaState *) fcinfo->flinfo->fn_extra;
+	if (my_extra == NULL)
+	{
+		fcinfo->flinfo->fn_extra = MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
+													  sizeof(ArrayMetaState));
+		my_extra = (ArrayMetaState *) fcinfo->flinfo->fn_extra;
+		my_extra->element_type = InvalidOid;
+	}
+
+	if (my_extra->element_type != elmtype)
+	{
+		/* Get info about element type */
+		get_typlenbyvalalign(elmtype,
+							 &my_extra->typlen,
+							 &my_extra->typbyval,
+							 &my_extra->typalign);
+		my_extra->element_type = elmtype;
+	}
+
+	elmlen = my_extra->typlen;
+	elmbyval = my_extra->typbyval;
+	elmalign = my_extra->typalign;
+
+	/* compute required space */
+	if (!isnull)
+	{
+		int 	i;
+		char		*p;
+		int			nbytes;
+		Datum	aux_value = value;
+
+		/* make sure data is not toasted */
+		if (elmlen == -1)
+			value = PointerGetDatum(PG_DETOAST_DATUM(value));
+
+		nbytes = att_addlength_datum(0, elmlen, value);
+		nbytes = att_align_nominal(nbytes, elmalign);
+
+		nbytes *= nitems;
+		/* check for overflow of total request */
+		if (!AllocSizeIsValid(nbytes))
+			ereport(ERROR,
+					(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
+					 errmsg("array size exceeds the maximum allowed (%d)",
+							(int) MaxAllocSize)));
+
+		nbytes += ARR_OVERHEAD_NONULLS(ndims);
+		result = create_array_envelope(ndims, dimv, lbsv, nbytes,
+							elmtype, 0);
+		p = ARR_DATA_PTR(result);
+		for (i = 0; i < nitems; i++)
+			p += ArrayCastAndSet(value, elmlen, elmbyval, elmalign, p);
+
+		/* cleaning up detoasted copies of datum */
+		if (aux_value != value)
+			pfree((Pointer) value);
+	}
+	else
+	{
+		int	nbytes;
+		int	dataoffset;
+		bits8	*bitmap;
+
+		dataoffset = ARR_OVERHEAD_WITHNULLS(ndims, nitems);
+		nbytes = dataoffset;
+
+		result = create_array_envelope(ndims, dimv, lbsv, nbytes,
+							elmtype, dataoffset);
+		bitmap = ARR_NULLBITMAP(result);
+		MemSet(bitmap, 0, (nitems + 7) / 8);
+	}
+		
+	return result;
+}
diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h
index 247833801c7..07f66e80a7f 100644
--- a/src/include/catalog/catversion.h
+++ b/src/include/catalog/catversion.h
@@ -37,7 +37,7 @@
  * Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/catalog/catversion.h,v 1.467 2008/07/14 00:51:45 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/catalog/catversion.h,v 1.468 2008/07/16 00:48:53 momjian Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -53,6 +53,6 @@
  */
 
 /*							yyyymmddN */
-#define CATALOG_VERSION_NO	200807131
+#define CATALOG_VERSION_NO	200807151
 
 #endif
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index 310f571290c..63f1cc10d2e 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/catalog/pg_proc.h,v 1.505 2008/07/14 00:51:45 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/catalog/pg_proc.h,v 1.506 2008/07/16 00:48:53 momjian Exp $
  *
  * NOTES
  *	  The script catalog/genbki.sh reads this file and generates .bki
@@ -1010,8 +1010,10 @@ DATA(insert OID = 1191 (  generate_subscripts PGNSP PGUID 12 1 1000 f f t t i 3
 DESCR("array subscripts generator");
 DATA(insert OID = 1192 (  generate_subscripts PGNSP PGUID 12 1 1000 f f t t i 2 23 "2277 23" _null_ _null_ _null_ generate_subscripts_nodir - _null_ _null_ ));
 DESCR("array subscripts generator");
-
-
+DATA(insert OID = 1193 (  array_fill PGNSP PGUID 12 1 0 f f f f i 2 2277 "2283 1007" _null_ _null_ _null_ array_fill - _null_ _null_ ));
+DESCR("array constructor with value");
+DATA(insert OID = 1286 (  array_fill PGNSP PGUID 12 1 0 f f f f i 3 2277 "2283 1007 1007" _null_ _null_ _null_ array_fill_with_lower_bounds - _null_ _null_ ));
+DESCR("array constructor with value");
 DATA(insert OID = 760 (  smgrin			   PGNSP PGUID 12 1 0 f f t f s 1 210 "2275" _null_ _null_ _null_  smgrin - _null_ _null_ ));
 DESCR("I/O");
 DATA(insert OID = 761 (  smgrout		   PGNSP PGUID 12 1 0 f f t f s 1 2275 "210" _null_ _null_ _null_  smgrout - _null_ _null_ ));
diff --git a/src/include/utils/array.h b/src/include/utils/array.h
index f8595d908b1..9efa78e6f3e 100644
--- a/src/include/utils/array.h
+++ b/src/include/utils/array.h
@@ -49,7 +49,7 @@
  * Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/utils/array.h,v 1.67 2008/04/28 14:48:57 alvherre Exp $
+ * $PostgreSQL: pgsql/src/include/utils/array.h,v 1.68 2008/07/16 00:48:54 momjian Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -202,6 +202,8 @@ extern Datum array_larger(PG_FUNCTION_ARGS);
 extern Datum array_smaller(PG_FUNCTION_ARGS);
 extern Datum generate_subscripts(PG_FUNCTION_ARGS);
 extern Datum generate_subscripts_nodir(PG_FUNCTION_ARGS);
+extern Datum array_fill(PG_FUNCTION_ARGS);
+extern Datum array_fill_with_lower_bounds(PG_FUNCTION_ARGS);
 
 extern Datum array_ref(ArrayType *array, int nSubscripts, int *indx,
 		  int arraytyplen, int elmlen, bool elmbyval, char elmalign,
diff --git a/src/test/regress/expected/arrays.out b/src/test/regress/expected/arrays.out
index 9ab372d15a5..7b7a01694ac 100644
--- a/src/test/regress/expected/arrays.out
+++ b/src/test/regress/expected/arrays.out
@@ -933,3 +933,61 @@ select * from unnest2(array[[1,2,3],[4,5,6]]);
 
 drop function unnest1(anyarray);
 drop function unnest2(anyarray);
+select array_fill(null::integer, array[3,3],array[2,2]);
+                           array_fill                            
+-----------------------------------------------------------------
+ [2:4][2:4]={{NULL,NULL,NULL},{NULL,NULL,NULL},{NULL,NULL,NULL}}
+(1 row)
+
+select array_fill(null::integer, array[3,3]);
+                      array_fill                      
+------------------------------------------------------
+ {{NULL,NULL,NULL},{NULL,NULL,NULL},{NULL,NULL,NULL}}
+(1 row)
+
+select array_fill(null::text, array[3,3],array[2,2]);
+                           array_fill                            
+-----------------------------------------------------------------
+ [2:4][2:4]={{NULL,NULL,NULL},{NULL,NULL,NULL},{NULL,NULL,NULL}}
+(1 row)
+
+select array_fill(null::text, array[3,3]);
+                      array_fill                      
+------------------------------------------------------
+ {{NULL,NULL,NULL},{NULL,NULL,NULL},{NULL,NULL,NULL}}
+(1 row)
+
+select array_fill(7, array[3,3],array[2,2]);
+              array_fill              
+--------------------------------------
+ [2:4][2:4]={{7,7,7},{7,7,7},{7,7,7}}
+(1 row)
+
+select array_fill(7, array[3,3]);
+        array_fill         
+---------------------------
+ {{7,7,7},{7,7,7},{7,7,7}}
+(1 row)
+
+select array_fill('juhu'::text, array[3,3],array[2,2]);
+                           array_fill                            
+-----------------------------------------------------------------
+ [2:4][2:4]={{juhu,juhu,juhu},{juhu,juhu,juhu},{juhu,juhu,juhu}}
+(1 row)
+
+select array_fill('juhu'::text, array[3,3]);
+                      array_fill                      
+------------------------------------------------------
+ {{juhu,juhu,juhu},{juhu,juhu,juhu},{juhu,juhu,juhu}}
+(1 row)
+
+-- raise exception
+select array_fill(1, null, array[2,2]);
+ERROR:  dimension array or low bound array cannot be NULL
+select array_fill(1, array[2,2], null);
+ERROR:  dimension array or low bound array cannot be NULL
+select array_fill(1, array[3,3], array[1,1,1]);
+ERROR:  wrong number of array_subscripts
+HINT:  Low bound array has different size than dimensions array.
+select array_fill(1, array[1,2,null]);
+ERROR:  dimension values cannot be null
diff --git a/src/test/regress/sql/arrays.sql b/src/test/regress/sql/arrays.sql
index 6590cad36c4..868ee4afda7 100644
--- a/src/test/regress/sql/arrays.sql
+++ b/src/test/regress/sql/arrays.sql
@@ -357,3 +357,17 @@ select * from unnest2(array[[1,2,3],[4,5,6]]);
 
 drop function unnest1(anyarray);
 drop function unnest2(anyarray);
+
+select array_fill(null::integer, array[3,3],array[2,2]);
+select array_fill(null::integer, array[3,3]);
+select array_fill(null::text, array[3,3],array[2,2]);
+select array_fill(null::text, array[3,3]);
+select array_fill(7, array[3,3],array[2,2]);
+select array_fill(7, array[3,3]);
+select array_fill('juhu'::text, array[3,3],array[2,2]);
+select array_fill('juhu'::text, array[3,3]);
+-- raise exception
+select array_fill(1, null, array[2,2]);
+select array_fill(1, array[2,2], null);
+select array_fill(1, array[3,3], array[1,1,1]);
+select array_fill(1, array[1,2,null]);
-- 
GitLab