From e4e6459c0f34f102cf998661ed5619f3b9031b68 Mon Sep 17 00:00:00 2001
From: Tom Lane <tgl@sss.pgh.pa.us>
Date: Sun, 23 Jul 2000 01:36:05 +0000
Subject: [PATCH] Further cleanup of array behavior.  Slice assignments to
 arrays with varlena elements work now.  Allow assignment to
 previously-nonexistent subscript position to extend array, but only for 1-D
 arrays and only if adjacent to existing positions (could do more if we had a
 way to represent nulls in arrays, but I don't want to tackle that now).
 Arrange for assignment of NULL to an array element in UPDATE to be a no-op,
 rather than setting the entire array to NULL as it used to. (Throwing an
 error would be a reasonable alternative, but it's never done that...)  Update
 regress test accordingly.

---
 src/backend/executor/execQual.c      |  61 ++-
 src/backend/utils/adt/arrayfuncs.c   | 734 +++++++++++++++++++--------
 src/test/regress/expected/arrays.out |  46 +-
 src/test/regress/sql/arrays.sql      |  20 +-
 4 files changed, 612 insertions(+), 249 deletions(-)

diff --git a/src/backend/executor/execQual.c b/src/backend/executor/execQual.c
index 1aeb07a7a9f..a445a57d9cf 100644
--- a/src/backend/executor/execQual.c
+++ b/src/backend/executor/execQual.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/executor/execQual.c,v 1.75 2000/07/22 03:34:27 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/executor/execQual.c,v 1.76 2000/07/23 01:35:58 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -64,18 +64,30 @@ static Datum ExecEvalVar(Var *variable, ExprContext *econtext, bool *isNull);
 static Datum ExecMakeFunctionResult(Node *node, List *arguments,
 					   ExprContext *econtext, bool *isNull, bool *isDone);
 
-/*
+/*----------
  *	  ExecEvalArrayRef
  *
  *	   This function takes an ArrayRef and returns the extracted Datum
  *	   if it's a simple reference, or the modified array value if it's
- *	   an array assignment (read array element insertion).
+ *	   an array assignment (i.e., array element or slice insertion).
+ *
+ * NOTE: if we get a NULL result from a subexpression, we return NULL when
+ * it's an array reference, or the unmodified source array when it's an
+ * array assignment.  This may seem peculiar, but if we return NULL (as was
+ * done in versions up through 7.0) then an assignment like
+ *			UPDATE table SET arrayfield[4] = NULL
+ * will result in setting the whole array to NULL, which is certainly not
+ * very desirable.  By returning the source array we make the assignment
+ * into a no-op, instead.  (Eventually we need to redesign arrays so that
+ * individual elements can be NULL, but for now, let's try to protect users
+ * from shooting themselves in the foot.)
  *
  * NOTE: we deliberately refrain from applying DatumGetArrayTypeP() here,
  * even though that might seem natural, because this code needs to support
  * both varlena arrays and fixed-length array types.  DatumGetArrayTypeP()
  * only works for the varlena kind.  The routines we call in arrayfuncs.c
  * have to know the difference (that's what they need refattrlength for).
+ *----------
  */
 static Datum
 ExecEvalArrayRef(ArrayRef *arrayRef,
@@ -85,6 +97,7 @@ ExecEvalArrayRef(ArrayRef *arrayRef,
 {
 	ArrayType  *array_source;
 	ArrayType  *resultArray;
+	bool		isAssignment = (arrayRef->refassgnexpr != NULL);
 	List	   *elt;
 	int			i = 0,
 				j = 0;
@@ -102,7 +115,11 @@ ExecEvalArrayRef(ArrayRef *arrayRef,
 										 econtext,
 										 isNull,
 										 isDone));
-		/* If refexpr yields NULL, result is always NULL, for now anyway */
+		/*
+		 * If refexpr yields NULL, result is always NULL, for now anyway.
+		 * (This means you cannot assign to an element or slice of an array
+		 * that's NULL; it'll just stay NULL.)
+		 */
 		if (*isNull)
 			return (Datum) NULL;
 	}
@@ -110,7 +127,7 @@ ExecEvalArrayRef(ArrayRef *arrayRef,
 	{
 
 		/*
-		 * Null refexpr indicates we are doing an INSERT into an array
+		 * Empty refexpr indicates we are doing an INSERT into an array
 		 * column. For now, we just take the refassgnexpr (which the
 		 * parser will have ensured is an array value) and return it
 		 * as-is, ignoring any subscripts that may have been supplied in
@@ -130,9 +147,14 @@ ExecEvalArrayRef(ArrayRef *arrayRef,
 													 econtext,
 													 isNull,
 													 &dummy));
-		/* If any index expr yields NULL, result is NULL */
+		/* If any index expr yields NULL, result is NULL or source array */
 		if (*isNull)
-			return (Datum) NULL;
+		{
+			if (! isAssignment || array_source == NULL)
+				return (Datum) NULL;
+			*isNull = false;
+			return PointerGetDatum(array_source);
+		}
 	}
 
 	if (arrayRef->reflowerindexpr != NIL)
@@ -147,9 +169,14 @@ ExecEvalArrayRef(ArrayRef *arrayRef,
 														 econtext,
 														 isNull,
 														 &dummy));
-			/* If any index expr yields NULL, result is NULL */
+			/* If any index expr yields NULL, result is NULL or source array */
 			if (*isNull)
-				return (Datum) NULL;
+			{
+				if (! isAssignment || array_source == NULL)
+					return (Datum) NULL;
+				*isNull = false;
+				return PointerGetDatum(array_source);
+			}
 		}
 		if (i != j)
 			elog(ERROR,
@@ -159,18 +186,26 @@ ExecEvalArrayRef(ArrayRef *arrayRef,
 	else
 		lIndex = NULL;
 
-	if (arrayRef->refassgnexpr != NULL)
+	if (isAssignment)
 	{
 		Datum		sourceData = ExecEvalExpr(arrayRef->refassgnexpr,
 											  econtext,
 											  isNull,
 											  &dummy);
-		/* For now, can't cope with inserting NULL into an array */
+		/*
+		 * For now, can't cope with inserting NULL into an array,
+		 * so make it a no-op per discussion above...
+		 */
 		if (*isNull)
-			return (Datum) NULL;
+		{
+			if (array_source == NULL)
+				return (Datum) NULL;
+			*isNull = false;
+			return PointerGetDatum(array_source);
+		}
 
 		if (array_source == NULL)
-			return sourceData;	/* XXX do something else? */
+			return sourceData;		/* XXX do something else? */
 
 		if (lIndex == NULL)
 			resultArray = array_set(array_source, i,
diff --git a/src/backend/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c
index b4bc71f349a..d9a210592c5 100644
--- a/src/backend/utils/adt/arrayfuncs.c
+++ b/src/backend/utils/adt/arrayfuncs.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/utils/adt/arrayfuncs.c,v 1.62 2000/07/22 03:34:43 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/utils/adt/arrayfuncs.c,v 1.63 2000/07/23 01:35:58 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -23,10 +23,6 @@
 #include "utils/memutils.h"
 #include "utils/syscache.h"
 
-#define ASSGN	 "="
-
-#define RETURN_NULL(type)  do { *isNull = true; return (type) 0; } while (0)
-
 
 /*
  * An array has the following internal structure:
@@ -39,6 +35,23 @@
  * The actual data starts on a MAXALIGN boundary.
  */
 
+
+/* ----------
+ * Local definitions
+ * ----------
+ */
+#ifndef MIN
+#define MIN(a,b) (((a)<(b)) ? (a) : (b))
+#endif
+#ifndef MAX
+#define MAX(a,b) (((a)>(b)) ? (a) : (b))
+#endif
+
+#define ASSGN	 "="
+
+#define RETURN_NULL(type)  do { *isNull = true; return (type) 0; } while (0)
+
+
 static int	ArrayCount(char *str, int *dim, int typdelim);
 static Datum *ReadArrayStr(char *arrayStr, int nitems, int ndim, int *dim,
 			  FmgrInfo *inputproc, Oid typelem, int32 typmod,
@@ -48,16 +61,22 @@ static void CopyArrayEls(char *p, Datum *values, int nitems,
 						 bool typbyval, int typlen, char typalign,
 						 bool freedata);
 static void system_cache_lookup(Oid element_type, bool input, int *typlen,
-				 bool *typbyval, char *typdelim, Oid *typelem, Oid *proc,
-					char *typalign);
+								bool *typbyval, char *typdelim, Oid *typelem,
+								Oid *proc, char *typalign);
 static Datum ArrayCast(char *value, bool byval, int len);
-static void ArrayClipCopy(int *st, int *endp, int bsize, char *destPtr,
-						  ArrayType *array, bool from);
-static int	ArrayClipCount(int *st, int *endp, ArrayType *array);
 static int	ArrayCastAndSet(Datum src, bool typbyval, int typlen, char *dest);
-static bool SanityCheckInput(int ndim, int n, int *dim, int *lb, int *indx);
-static int	array_read(char *destptr, int eltsize, int nitems, char *srcptr);
+static int	array_nelems_size(char *ptr, int eltsize, int nitems);
 static char *array_seek(char *ptr, int eltsize, int nitems);
+static int	array_copy(char *destptr, int eltsize, int nitems, char *srcptr);
+static int	array_slice_size(int ndim, int *dim, int *lb, char *arraydataptr,
+							 int eltsize, int *st, int *endp);
+static void array_extract_slice(int ndim, int *dim, int *lb,
+								char *arraydataptr, int eltsize,
+								int *st, int *endp, char *destPtr);
+static void array_insert_slice(int ndim, int *dim, int *lb,
+							   char *origPtr, int origdatasize,
+							   char *destPtr, int eltsize,
+							   int *st, int *endp, char *srcPtr);
 
 
 /*---------------------------------------------------------------------
@@ -153,7 +172,7 @@ array_in(PG_FUNCTION_ARGS)
 	{
 		while (isspace((int) *p))
 			p++;
-		if (strncmp(p, ASSGN, strlen(ASSGN)))
+		if (strncmp(p, ASSGN, strlen(ASSGN)) != 0)
 			elog(ERROR, "array_in: missing assignment operator");
 		p += strlen(ASSGN);
 		while (isspace((int) *p))
@@ -172,6 +191,7 @@ array_in(PG_FUNCTION_ARGS)
 	nitems = ArrayGetNItems(ndim, dim);
 	if (nitems == 0)
 	{
+		/* Return empty array */
 		retval = (ArrayType *) palloc(sizeof(ArrayType));
 		MemSet(retval, 0, sizeof(ArrayType));
 		retval->size = sizeof(ArrayType);
@@ -677,6 +697,10 @@ array_dims(PG_FUNCTION_ARGS)
 	int		   *dimv,
 			   *lb;
 
+	/* Sanity check: does it look like an array at all? */
+	if (ARR_NDIM(v) <= 0 || ARR_NDIM(v) > MAXDIM)
+		PG_RETURN_NULL();
+
 	nbytes = ARR_NDIM(v) * 33 + 1;
 	/*
 	 * 33 since we assume 15 digits per number + ':' +'[]'
@@ -716,11 +740,15 @@ array_ref(ArrayType *array,
 		  int arraylen,
 		  bool *isNull)
 {
-	int			ndim,
+	int			i,
+				ndim,
 			   *dim,
 			   *lb,
-				offset;
-	char	   *retptr;
+				offset,
+				fixedDim[1],
+				fixedLb[1];
+	char	   *arraydataptr,
+			   *retptr;
 
 	if (array == (ArrayType *) NULL)
 		RETURN_NULL(Datum);
@@ -730,27 +758,39 @@ array_ref(ArrayType *array,
 		/*
 		 * fixed-length arrays -- these are assumed to be 1-d, 0-based
 		 */
-		if (nSubscripts != 1)
-			RETURN_NULL(Datum);
-		if (indx[0] < 0 || indx[0] * elmlen >= arraylen)
-			RETURN_NULL(Datum);
-		retptr = (char *) array + indx[0] * elmlen;
-		return ArrayCast(retptr, elmbyval, elmlen);
+		ndim = 1;
+		fixedDim[0] = arraylen / elmlen;
+		fixedLb[0] = 0;
+		dim = fixedDim;
+		lb = fixedLb;
+		arraydataptr = (char *) array;
 	}
+	else
+	{
+		/* detoast input if necessary */
+		array = DatumGetArrayTypeP(PointerGetDatum(array));
 
-	/* detoast input if necessary */
-	array = DatumGetArrayTypeP(PointerGetDatum(array));
-
-	ndim = ARR_NDIM(array);
-	dim = ARR_DIMS(array);
-	lb = ARR_LBOUND(array);
+		ndim = ARR_NDIM(array);
+		dim = ARR_DIMS(array);
+		lb = ARR_LBOUND(array);
+		arraydataptr = ARR_DATA_PTR(array);
+	}
 
-	if (!SanityCheckInput(ndim, nSubscripts, dim, lb, indx))
+	/*
+	 * Return NULL for invalid subscript
+	 */
+	if (ndim != nSubscripts || ndim <= 0 || ndim > MAXDIM)
 		RETURN_NULL(Datum);
+	for (i = 0; i < ndim; i++)
+		if (indx[i] < lb[i] || indx[i] >= (dim[i] + lb[i]))
+			RETURN_NULL(Datum);
 
+	/*
+	 * OK, get the element
+	 */
 	offset = ArrayGetOffset(nSubscripts, dim, lb, indx);
 
-	retptr = array_seek(ARR_DATA_PTR(array), elmlen, offset);
+	retptr = array_seek(arraydataptr, elmlen, offset);
 
 	return ArrayCast(retptr, elmbyval, elmlen);
 }
@@ -760,6 +800,9 @@ array_ref(ArrayType *array,
  *		   This routine takes an array and a range of indices (upperIndex and
  *		   lowerIndx), creates a new array structure for the referred elements
  *		   and returns a pointer to it.
+ *
+ * NOTE: we assume it is OK to scribble on the provided index arrays
+ * lowerIndx[] and upperIndx[].  These are generally just temporaries.
  *-----------------------------------------------------------------------------
  */
 ArrayType *
@@ -776,7 +819,10 @@ array_get_slice(ArrayType *array,
 				ndim,
 			   *dim,
 			   *lb;
-	ArrayType  *newArr;
+	int			fixedDim[1],
+				fixedLb[1];
+	char	   *arraydataptr;
+	ArrayType  *newarray;
 	int			bytes,
 				span[MAXDIM];
 
@@ -786,44 +832,68 @@ array_get_slice(ArrayType *array,
 	if (arraylen > 0)
 	{
 		/*
-		 * fixed-length arrays -- no can do slice...
+		 * fixed-length arrays -- currently, cannot slice these because
+		 * parser labels output as being of the fixed-length array type!
+		 * Code below shows how we could support it if the parser were
+		 * changed to label output as a suitable varlena array type.
 		 */
 		elog(ERROR, "Slices of fixed-length arrays not implemented");
-	}
 
-	/* detoast input if necessary */
-	array = DatumGetArrayTypeP(PointerGetDatum(array));
+		/*
+		 * fixed-length arrays -- these are assumed to be 1-d, 0-based
+		 */
+		ndim = 1;
+		fixedDim[0] = arraylen / elmlen;
+		fixedLb[0] = 0;
+		dim = fixedDim;
+		lb = fixedLb;
+		arraydataptr = (char *) array;
+	}
+	else
+	{
+		/* detoast input if necessary */
+		array = DatumGetArrayTypeP(PointerGetDatum(array));
 
-	ndim = ARR_NDIM(array);
-	dim = ARR_DIMS(array);
-	lb = ARR_LBOUND(array);
+		ndim = ARR_NDIM(array);
+		dim = ARR_DIMS(array);
+		lb = ARR_LBOUND(array);
+		arraydataptr = ARR_DATA_PTR(array);
+	}
 
-	if (!SanityCheckInput(ndim, nSubscripts, dim, lb, upperIndx) ||
-		!SanityCheckInput(ndim, nSubscripts, dim, lb, lowerIndx))
+	/*
+	 * Check provided subscripts.  A slice exceeding the current array
+	 * limits is silently truncated to the array limits.  If we end up with
+	 * an empty slice, return NULL (should it be an empty array instead?)
+	 */
+	if (ndim != nSubscripts || ndim <= 0 || ndim > MAXDIM)
 		RETURN_NULL(ArrayType *);
 
-	for (i = 0; i < nSubscripts; i++)
+	for (i = 0; i < ndim; i++)
+	{
+		if (lowerIndx[i] < lb[i])
+			lowerIndx[i] = lb[i];
+		if (upperIndx[i] >= (dim[i] + lb[i]))
+			upperIndx[i] = dim[i] + lb[i] - 1;
 		if (lowerIndx[i] > upperIndx[i])
 			RETURN_NULL(ArrayType *);
+	}
 
 	mda_get_range(nSubscripts, span, lowerIndx, upperIndx);
 
-	if (elmlen > 0)
-		bytes = ArrayGetNItems(nSubscripts, span) * elmlen;
-	else
-		bytes = ArrayClipCount(lowerIndx, upperIndx, array);
-	bytes += ARR_OVERHEAD(nSubscripts);
-
-	newArr = (ArrayType *) palloc(bytes);
-	newArr->size = bytes;
-	newArr->ndim = array->ndim;
-	newArr->flags = array->flags;
-	memcpy(ARR_DIMS(newArr), span, nSubscripts * sizeof(int));
-	memcpy(ARR_LBOUND(newArr), lowerIndx, nSubscripts * sizeof(int));
-	ArrayClipCopy(lowerIndx, upperIndx, elmlen, ARR_DATA_PTR(newArr),
-				  array, true);
-
-	return newArr;
+	bytes = array_slice_size(ndim, dim, lb, arraydataptr,
+							 elmlen, lowerIndx, upperIndx);
+	bytes += ARR_OVERHEAD(ndim);
+
+	newarray = (ArrayType *) palloc(bytes);
+	newarray->size = bytes;
+	newarray->ndim = ndim;
+	newarray->flags = 0;
+	memcpy(ARR_DIMS(newarray), span, ndim * sizeof(int));
+	memcpy(ARR_LBOUND(newarray), lowerIndx, ndim * sizeof(int));
+	array_extract_slice(ndim, dim, lb, arraydataptr, elmlen,
+						lowerIndx, upperIndx, ARR_DATA_PTR(newarray));
+
+	return newarray;
 }
 
 /*-----------------------------------------------------------------------------
@@ -834,7 +904,11 @@ array_get_slice(ArrayType *array,
  *		  A new array is returned, just like the old except for the one
  *		  modified entry.
  *
- * NOTE: For assignments, we throw an error for silly subscripts etc,
+ * For one-dimensional arrays only, we allow the array to be extended
+ * by assigning to the position one above or one below the existing range.
+ * (We could be more flexible if we had a way to represent NULL elements.)
+ *
+ * NOTE: For assignments, we throw an error for invalid subscripts etc,
  * rather than returning a NULL as the fetch operations do.  The reasoning
  * is that returning a NULL would cause the user's whole array to be replaced
  * with NULL, which will probably not make him happy.
@@ -850,19 +924,22 @@ array_set(ArrayType *array,
 		  int arraylen,
 		  bool *isNull)
 {
-	int			ndim,
-			   *dim,
-			   *lb,
+	int			i,
+				ndim,
+				dim[MAXDIM],
+				lb[MAXDIM],
 				offset;
 	ArrayType  *newarray;
 	char	   *elt_ptr;
-	int			oldsize,
+	bool		extendbefore = false;
+	bool		extendafter = false;
+	int			olddatasize,
 				newsize,
-				oldlen,
-				newlen,
-				lth0,
-				lth1,
-				lth2;
+				olditemlen,
+				newitemlen,
+				overheadlen,
+				lenbefore,
+				lenafter;
 
 	if (array == (ArrayType *) NULL)
 		RETURN_NULL(ArrayType *);
@@ -870,7 +947,8 @@ array_set(ArrayType *array,
 	if (arraylen > 0)
 	{
 		/*
-		 * fixed-length arrays -- these are assumed to be 1-d, 0-based
+		 * fixed-length arrays -- these are assumed to be 1-d, 0-based.
+		 * We cannot extend them, either.
 		 */
 		if (nSubscripts != 1)
 			elog(ERROR, "Invalid array subscripts");
@@ -887,40 +965,99 @@ array_set(ArrayType *array,
 	array = DatumGetArrayTypeP(PointerGetDatum(array));
 
 	ndim = ARR_NDIM(array);
-	dim = ARR_DIMS(array);
-	lb = ARR_LBOUND(array);
-
-	if (!SanityCheckInput(ndim, nSubscripts, dim, lb, indx))
+	if (ndim != nSubscripts || ndim <= 0 || ndim > MAXDIM)
 		elog(ERROR, "Invalid array subscripts");
 
-	offset = ArrayGetOffset(nSubscripts, dim, lb, indx);
+	/* copy dim/lb since we may modify them */
+	memcpy(dim, ARR_DIMS(array), ndim * sizeof(int));
+	memcpy(lb, ARR_LBOUND(array), ndim * sizeof(int));
 
-	elt_ptr = array_seek(ARR_DATA_PTR(array), elmlen, offset);
+	/*
+	 * Check subscripts
+	 */
+	for (i = 0; i < ndim; i++)
+	{
+		if (indx[i] < lb[i])
+		{
+			if (ndim == 1 && indx[i] == lb[i] - 1)
+			{
+				dim[i]++;
+				lb[i]--;
+				extendbefore = true;
+			}
+			else
+			{
+				elog(ERROR, "Invalid array subscripts");
+			}
+		}
+		if (indx[i] >= (dim[i] + lb[i]))
+		{
+			if (ndim == 1 && indx[i] == (dim[i] + lb[i]))
+			{
+				dim[i]++;
+				extendafter = true;
+			}
+			else
+			{
+				elog(ERROR, "Invalid array subscripts");
+			}
+		}
+	}
 
-	if (elmlen > 0)
+	/*
+	 * Compute sizes of items and areas to copy
+	 */
+	overheadlen = ARR_OVERHEAD(ndim);
+	olddatasize = ARR_SIZE(array) - overheadlen;
+	if (extendbefore)
+	{
+		lenbefore = 0;
+		olditemlen = 0;
+		lenafter = olddatasize;
+	}
+	else if (extendafter)
 	{
-		oldlen = newlen = elmlen;
+		lenbefore = olddatasize;
+		olditemlen = 0;
+		lenafter = 0;
 	}
 	else
 	{
-		/* varlena type */
-		oldlen = INTALIGN(*(int32 *) elt_ptr);
-		newlen = INTALIGN(*(int32 *) DatumGetPointer(dataValue));
+		offset = ArrayGetOffset(nSubscripts, dim, lb, indx);
+		elt_ptr = array_seek(ARR_DATA_PTR(array), elmlen, offset);
+		lenbefore = (int) (elt_ptr - ARR_DATA_PTR(array));
+		if (elmlen > 0)
+			olditemlen = elmlen;
+		else
+			olditemlen = INTALIGN(*(int32 *) elt_ptr);
+		lenafter = (int) (olddatasize - lenbefore - olditemlen);
 	}
 
-	oldsize = ARR_SIZE(array);
-	lth0 = ARR_OVERHEAD(ndim);
-	lth1 = (int) (elt_ptr - ARR_DATA_PTR(array));
-	lth2 = (int) (oldsize - lth0 - lth1 - oldlen);
-	newsize = lth0 + lth1 + newlen + lth2;
+	if (elmlen > 0)
+		newitemlen = elmlen;
+	else
+		newitemlen = INTALIGN(*(int32 *) DatumGetPointer(dataValue));
+
+	newsize = overheadlen + lenbefore + newitemlen + lenafter;
 
+	/*
+	 * OK, do the assignment
+	 */
 	newarray = (ArrayType *) palloc(newsize);
-	memcpy((char *) newarray, (char *) array, lth0 + lth1);
-	memcpy((char *) newarray + lth0 + lth1 + newlen,
-		   (char *) array + lth0 + lth1 + oldlen, lth2);
 	newarray->size = newsize;
-	newlen = ArrayCastAndSet(dataValue, elmbyval, elmlen,
-							 (char *) newarray + lth0 + lth1);
+	newarray->ndim = ndim;
+	newarray->flags = 0;
+	memcpy(ARR_DIMS(newarray), dim, ndim * sizeof(int));
+	memcpy(ARR_LBOUND(newarray), lb, ndim * sizeof(int));
+	memcpy((char *) newarray + overheadlen,
+		   (char *) array + overheadlen,
+		   lenbefore);
+	memcpy((char *) newarray + overheadlen + lenbefore + newitemlen,
+		   (char *) array + overheadlen + lenbefore + olditemlen,
+		   lenafter);
+
+	ArrayCastAndSet(dataValue, elmbyval, elmlen,
+					(char *) newarray + overheadlen + lenbefore);
 
 	return newarray;
 }
@@ -953,55 +1090,162 @@ array_set_slice(ArrayType *array,
 {
 	int			i,
 				ndim,
-			   *dim,
-			   *lb;
-	int			span[MAXDIM];
+				dim[MAXDIM],
+				lb[MAXDIM],
+				span[MAXDIM];
+	ArrayType  *newarray;
+	int			nsrcitems,
+				olddatasize,
+				newsize,
+				olditemsize,
+				newitemsize,
+				overheadlen,
+				lenbefore,
+				lenafter;
 
 	if (array == (ArrayType *) NULL)
 		RETURN_NULL(ArrayType *);
 	if (srcArray == (ArrayType *) NULL)
-		RETURN_NULL(ArrayType *);
+		return array;
 
 	if (arraylen > 0)
 	{
 		/*
-		 * fixed-length arrays -- no can do slice...
+		 * fixed-length arrays -- not got round to doing this...
 		 */
 		elog(ERROR, "Updates on slices of fixed-length arrays not implemented");
 	}
 
-	/* detoast array, making sure we get an overwritable copy */
-	array = DatumGetArrayTypePCopy(PointerGetDatum(array));
-
-	/* detoast source array if necessary */
+	/* detoast arrays if necessary */
+	array = DatumGetArrayTypeP(PointerGetDatum(array));
 	srcArray = DatumGetArrayTypeP(PointerGetDatum(srcArray));
 
-	if (elmlen < 0)
-		elog(ERROR, "Updates on slices of arrays of variable length elements not implemented");
-
 	ndim = ARR_NDIM(array);
-	dim = ARR_DIMS(array);
-	lb = ARR_LBOUND(array);
-
-	if (!SanityCheckInput(ndim, nSubscripts, dim, lb, upperIndx) ||
-		!SanityCheckInput(ndim, nSubscripts, dim, lb, lowerIndx))
+	if (ndim != nSubscripts || ndim <= 0 || ndim > MAXDIM)
 		elog(ERROR, "Invalid array subscripts");
 
-	for (i = 0; i < nSubscripts; i++)
+	/* copy dim/lb since we may modify them */
+	memcpy(dim, ARR_DIMS(array), ndim * sizeof(int));
+	memcpy(lb, ARR_LBOUND(array), ndim * sizeof(int));
+
+	/*
+	 * Check provided subscripts.  A slice exceeding the current array
+	 * limits throws an error, *except* in the 1-D case where we will
+	 * extend the array as long as no hole is created.
+	 * An empty slice is an error, too.
+	 */
+	for (i = 0; i < ndim; i++)
+	{
 		if (lowerIndx[i] > upperIndx[i])
-		elog(ERROR, "Invalid array subscripts");
+			elog(ERROR, "Invalid array subscripts");
+		if (lowerIndx[i] < lb[i])
+		{
+			if (ndim == 1 && upperIndx[i] >= lb[i] - 1)
+			{
+				dim[i] += lb[i] - lowerIndx[i];
+				lb[i] = lowerIndx[i];
+			}
+			else
+			{
+				elog(ERROR, "Invalid array subscripts");
+			}
+		}
+		if (upperIndx[i] >= (dim[i] + lb[i]))
+		{
+			if (ndim == 1 && lowerIndx[i] <= (dim[i] + lb[i]))
+			{
+				dim[i] = upperIndx[i] - lb[i] + 1;
+			}
+			else
+			{
+				elog(ERROR, "Invalid array subscripts");
+			}
+		}
+	}
 
-	/* make sure source array has enough entries */
+	/*
+	 * Make sure source array has enough entries.  Note we ignore the shape
+	 * of the source array and just read entries serially.
+	 */
 	mda_get_range(ndim, span, lowerIndx, upperIndx);
-
-	if (ArrayGetNItems(ndim, span) >
-		ArrayGetNItems(ARR_NDIM(srcArray), ARR_DIMS(srcArray)))
+	nsrcitems = ArrayGetNItems(ndim, span);
+	if (nsrcitems > ArrayGetNItems(ARR_NDIM(srcArray), ARR_DIMS(srcArray)))
 		elog(ERROR, "Source array too small");
 
-	ArrayClipCopy(lowerIndx, upperIndx, elmlen, ARR_DATA_PTR(srcArray),
-				  array, false);
+	/*
+	 * Compute space occupied by new entries, space occupied by replaced
+	 * entries, and required space for new array.
+	 */
+	newitemsize = array_nelems_size(ARR_DATA_PTR(srcArray), elmlen,
+									nsrcitems);
+	overheadlen = ARR_OVERHEAD(ndim);
+	olddatasize = ARR_SIZE(array) - overheadlen;
+	if (ndim > 1)
+	{
+		/*
+		 * here we do not need to cope with extension of the array;
+		 * it would be a lot more complicated if we had to do so...
+		 */
+		olditemsize = array_slice_size(ndim, dim, lb, ARR_DATA_PTR(array),
+									   elmlen, lowerIndx, upperIndx);
+		lenbefore = lenafter = 0; /* keep compiler quiet */
+	}
+	else
+	{
+		/*
+		 * here we must allow for possibility of slice larger than orig array
+		 */
+		int		oldlb = ARR_LBOUND(array)[0];
+		int		oldub = oldlb + ARR_DIMS(array)[0] - 1;
+		int		slicelb = MAX(oldlb, lowerIndx[0]);
+		int		sliceub = MIN(oldub, upperIndx[0]);
+		char   *oldarraydata = ARR_DATA_PTR(array);
+
+		lenbefore = array_nelems_size(oldarraydata,
+									  elmlen,
+									  slicelb - oldlb);
+		if (slicelb > sliceub)
+			olditemsize = 0;
+		else
+			olditemsize = array_nelems_size(oldarraydata + lenbefore,
+											elmlen,
+											sliceub - slicelb + 1);
+		lenafter = olddatasize - lenbefore - olditemsize;
+	}
+
+	newsize = overheadlen + olddatasize - olditemsize + newitemsize;
+
+	newarray = (ArrayType *) palloc(newsize);
+	newarray->size = newsize;
+	newarray->ndim = ndim;
+	newarray->flags = 0;
+	memcpy(ARR_DIMS(newarray), dim, ndim * sizeof(int));
+	memcpy(ARR_LBOUND(newarray), lb, ndim * sizeof(int));
+
+	if (ndim > 1)
+	{
+		/*
+		 * here we do not need to cope with extension of the array;
+		 * it would be a lot more complicated if we had to do so...
+		 */
+		array_insert_slice(ndim, dim, lb, ARR_DATA_PTR(array), olddatasize,
+						   ARR_DATA_PTR(newarray), elmlen,
+						   lowerIndx, upperIndx, ARR_DATA_PTR(srcArray));
+	}
+	else
+	{
+		memcpy((char *) newarray + overheadlen,
+			   (char *) array + overheadlen,
+			   lenbefore);
+		memcpy((char *) newarray + overheadlen + lenbefore,
+			   ARR_DATA_PTR(srcArray),
+			   newitemsize);
+		memcpy((char *) newarray + overheadlen + lenbefore + newitemsize,
+			   (char *) array + overheadlen + lenbefore + olditemsize,
+			   lenafter);
+	}
 
-	return array;
+	return newarray;
 }
 
 /*
@@ -1326,7 +1570,9 @@ system_cache_lookup(Oid element_type,
 		*proc = typeStruct->typoutput;
 }
 
-/* Fetch array value at pointer, converted correctly to a Datum */
+/*
+ * Fetch array element at pointer, converted correctly to a Datum
+ */
 static Datum
 ArrayCast(char *value, bool byval, int len)
 {
@@ -1402,138 +1648,196 @@ ArrayCastAndSet(Datum src,
 	return inc;
 }
 
-/* Do Sanity check on input subscripting info */
-static bool
-SanityCheckInput(int ndim, int n, int *dim, int *lb, int *indx)
+/*
+ * Compute total size of the nitems array elements starting at *ptr
+ *
+ * XXX should consider alignment spec for fixed-length types
+ */
+static int
+array_nelems_size(char *ptr, int eltsize, int nitems)
 {
+	char	   *origptr;
 	int			i;
 
-	if (n != ndim || ndim <= 0 || ndim > MAXDIM)
-		return false;
-	for (i = 0; i < ndim; i++)
-		if ((lb[i] > indx[i]) || (indx[i] >= (dim[i] + lb[i])))
-			return false;
-	return true;
+	/* fixed-size elements? */
+	if (eltsize > 0)
+		return eltsize * nitems;
+	/* else assume they are varlena items */
+	origptr = ptr;
+	for (i = 0; i < nitems; i++)
+		ptr += INTALIGN(*(int32 *) ptr);
+	return ptr - origptr;
 }
 
-/* Copy an array slice into or out of an array */
-static void
-ArrayClipCopy(int *st,
-			  int *endp,
-			  int bsize,
-			  char *destPtr,
-			  ArrayType *array,
-			  bool from)
+/*
+ * Advance ptr over nitems array elements
+ */
+static char *
+array_seek(char *ptr, int eltsize, int nitems)
 {
-	int			n,
-			   *dim,
-			   *lb,
-				st_pos,
-				prod[MAXDIM];
-	int			span[MAXDIM],
-				dist[MAXDIM],
-				indx[MAXDIM];
-	int			i,
-				j,
-				inc;
-	char	   *srcPtr;
+	return ptr + array_nelems_size(ptr, eltsize, nitems);
+}
 
-	n = ARR_NDIM(array);
-	dim = ARR_DIMS(array);
-	lb = ARR_LBOUND(array);
-	st_pos = ArrayGetOffset(n, dim, lb, st);
-	srcPtr = array_seek(ARR_DATA_PTR(array), bsize, st_pos);
-	mda_get_prod(n, dim, prod);
-	mda_get_range(n, span, st, endp);
-	mda_get_offset_values(n, dist, prod, span);
-	for (i = 0; i < n; i++)
-		indx[i] = 0;
-	j = n - 1;
-	do
-	{
-		srcPtr = array_seek(srcPtr, bsize, dist[j]);
-		if (from)
-			inc = array_read(destPtr, bsize, 1, srcPtr);
-		else
-			inc = array_read(srcPtr, bsize, 1, destPtr);
-		destPtr += inc;
-		srcPtr += inc;
-	} while ((j = mda_next_tuple(n, indx, span)) != -1);
+/*
+ * Copy nitems array elements from srcptr to destptr
+ *
+ * Returns number of bytes copied
+ */
+static int
+array_copy(char *destptr, int eltsize, int nitems, char *srcptr)
+{
+	int			numbytes = array_nelems_size(srcptr, eltsize, nitems);
+
+	memmove(destptr, srcptr, numbytes);
+	return numbytes;
 }
 
-/* Compute space needed for an array slice of varlena items */
+/*
+ * Compute space needed for a slice of an array
+ *
+ * We assume the caller has verified that the slice coordinates are valid.
+ */
 static int
-ArrayClipCount(int *st, int *endp, ArrayType *array)
+array_slice_size(int ndim, int *dim, int *lb, char *arraydataptr,
+				 int eltsize, int *st, int *endp)
 {
-	int			n,
-			   *dim,
-			   *lb,
-				st_pos,
-				prod[MAXDIM];
-	int			span[MAXDIM],
+	int			st_pos,
+				span[MAXDIM],
+				prod[MAXDIM],
 				dist[MAXDIM],
 				indx[MAXDIM];
+	char	   *ptr;
 	int			i,
 				j,
 				inc;
 	int			count = 0;
-	char	   *ptr;
 
-	n = ARR_NDIM(array);
-	dim = ARR_DIMS(array);
-	lb = ARR_LBOUND(array);
-	st_pos = ArrayGetOffset(n, dim, lb, st);
-	ptr = array_seek(ARR_DATA_PTR(array), -1, st_pos);
-	mda_get_prod(n, dim, prod);
-	mda_get_range(n, span, st, endp);
-	mda_get_offset_values(n, dist, prod, span);
-	for (i = 0; i < n; i++)
+	mda_get_range(ndim, span, st, endp);
+
+	/* Pretty easy for fixed element length ... */
+	if (eltsize > 0)
+		return ArrayGetNItems(ndim, span) * eltsize;
+
+	/* Else gotta do it the hard way */
+	st_pos = ArrayGetOffset(ndim, dim, lb, st);
+	ptr = array_seek(arraydataptr, eltsize, st_pos);
+	mda_get_prod(ndim, dim, prod);
+	mda_get_offset_values(ndim, dist, prod, span);
+	for (i = 0; i < ndim; i++)
 		indx[i] = 0;
-	j = n - 1;
+	j = ndim - 1;
 	do
 	{
-		ptr = array_seek(ptr, -1, dist[j]);
+		ptr = array_seek(ptr, eltsize, dist[j]);
 		inc = INTALIGN(*(int32 *) ptr);
 		ptr += inc;
 		count += inc;
-	} while ((j = mda_next_tuple(n, indx, span)) != -1);
+	} while ((j = mda_next_tuple(ndim, indx, span)) != -1);
 	return count;
 }
 
-/* Advance over nitems array elements */
-static char *
-array_seek(char *ptr, int eltsize, int nitems)
+/*
+ * Extract a slice of an array into consecutive elements at *destPtr.
+ *
+ * We assume the caller has verified that the slice coordinates are valid
+ * and allocated enough storage at *destPtr.
+ */
+static void
+array_extract_slice(int ndim,
+					int *dim,
+					int *lb,
+					char *arraydataptr,
+					int eltsize,
+					int *st,
+					int *endp,
+					char *destPtr)
 {
-	int			i;
+	int			st_pos,
+				prod[MAXDIM],
+				span[MAXDIM],
+				dist[MAXDIM],
+				indx[MAXDIM];
+	char	   *srcPtr;
+	int			i,
+				j,
+				inc;
 
-	if (eltsize > 0)
-		return ptr + eltsize * nitems;
-	for (i = 0; i < nitems; i++)
-		ptr += INTALIGN(*(int32 *) ptr);
-	return ptr;
+	st_pos = ArrayGetOffset(ndim, dim, lb, st);
+	srcPtr = array_seek(arraydataptr, eltsize, st_pos);
+	mda_get_prod(ndim, dim, prod);
+	mda_get_range(ndim, span, st, endp);
+	mda_get_offset_values(ndim, dist, prod, span);
+	for (i = 0; i < ndim; i++)
+		indx[i] = 0;
+	j = ndim - 1;
+	do
+	{
+		srcPtr = array_seek(srcPtr, eltsize, dist[j]);
+		inc = array_copy(destPtr, eltsize, 1, srcPtr);
+		destPtr += inc;
+		srcPtr += inc;
+	} while ((j = mda_next_tuple(ndim, indx, span)) != -1);
 }
 
-/* Copy nitems array elements from srcptr to destptr */
-static int
-array_read(char *destptr, int eltsize, int nitems, char *srcptr)
+/*
+ * Insert a slice into an array.
+ *
+ * ndim/dim/lb are dimensions of the dest array, which has data area
+ * starting at origPtr.  A new array with those same dimensions is to
+ * be constructed; its data area starts at destPtr.
+ *
+ * Elements within the slice volume are taken from consecutive locations
+ * at srcPtr; elements outside it are copied from origPtr.
+ *
+ * We assume the caller has verified that the slice coordinates are valid
+ * and allocated enough storage at *destPtr.
+ */
+static void
+array_insert_slice(int ndim,
+				   int *dim,
+				   int *lb,
+				   char *origPtr,
+				   int origdatasize,
+				   char *destPtr,
+				   int eltsize,
+				   int *st,
+				   int *endp,
+				   char *srcPtr)
 {
+	int			st_pos,
+				prod[MAXDIM],
+				span[MAXDIM],
+				dist[MAXDIM],
+				indx[MAXDIM];
+	char	   *origEndpoint = origPtr + origdatasize;
 	int			i,
-				inc,
-				tmp;
+				j,
+				inc;
 
-	if (eltsize > 0)
-	{
-		memmove(destptr, srcptr, eltsize * nitems);
-		return eltsize * nitems;
-	}
-	inc = 0;
-	for (i = 0; i < nitems; i++)
+	st_pos = ArrayGetOffset(ndim, dim, lb, st);
+	inc = array_copy(destPtr, eltsize, st_pos, origPtr);
+	destPtr += inc;
+	origPtr += inc;
+	mda_get_prod(ndim, dim, prod);
+	mda_get_range(ndim, span, st, endp);
+	mda_get_offset_values(ndim, dist, prod, span);
+	for (i = 0; i < ndim; i++)
+		indx[i] = 0;
+	j = ndim - 1;
+	do
 	{
-		tmp = (INTALIGN(*(int32 *) srcptr));
-		memmove(destptr, srcptr, tmp);
-		srcptr += tmp;
-		destptr += tmp;
-		inc += tmp;
-	}
-	return inc;
+		/* Copy/advance over elements between here and next part of slice */
+		inc = array_copy(destPtr, eltsize, dist[j], origPtr);
+		destPtr += inc;
+		origPtr += inc;
+		/* Copy new element at this slice position */
+		inc = array_copy(destPtr, eltsize, 1, srcPtr);
+		destPtr += inc;
+		srcPtr += inc;
+		/* Advance over old element at this slice position */
+		origPtr = array_seek(origPtr, eltsize, 1);
+	} while ((j = mda_next_tuple(ndim, indx, span)) != -1);
+
+	/* don't miss any data at the end */
+	memcpy(destPtr, origPtr, origEndpoint - origPtr);
 }
diff --git a/src/test/regress/expected/arrays.out b/src/test/regress/expected/arrays.out
index c100ea6ce4b..b6878380640 100644
--- a/src/test/regress/expected/arrays.out
+++ b/src/test/regress/expected/arrays.out
@@ -39,17 +39,17 @@ SELECT a[1:3],
      a      |        b        |       c       |         d         
 ------------+-----------------+---------------+-------------------
  {1,2,3}    | {{{0,0},{1,2}}} |               | 
- {11,12,23} |                 |               | {{"elt1","elt2"}}
+ {11,12,23} |                 | {"foobar"}    | {{"elt1","elt2"}}
             |                 | {"foo","bar"} | 
 (3 rows)
 
--- returns three different results--
-SELECT array_dims(arrtest.b) AS x;
-        x        
------------------
- [1:1][1:2][1:2]
- [1:2][1:2]
- [1:2]
+SELECT array_dims(a) AS a,array_dims(b) AS b,array_dims(c) AS c
+   FROM arrtest;
+   a   |        b        |   c   
+-------+-----------------+-------
+ [1:5] | [1:1][1:2][1:2] | 
+ [1:3] | [1:2][1:2]      | [1:1]
+       | [1:2]           | [1:2]
 (3 rows)
 
 -- returns nothing 
@@ -62,18 +62,32 @@ SELECT *
 (0 rows)
 
 UPDATE arrtest
-  SET a[1:2] = '{16,25}',
-      b[1:1][1:1][1:2] = '{113, 117}', 
-      c[1:1] = '{"new_word"}';
+  SET a[1:2] = '{16,25}'
+  WHERE NOT a = '{}'::_int2;
+UPDATE arrtest
+  SET b[1:1][1:1][1:2] = '{113, 117}',
+      b[1:1][1:2][2:2] = '{142, 147}'
+  WHERE array_dims(b) = '[1:1][1:2][1:2]';
+UPDATE arrtest
+  SET c[2:2] = '{"new_word"}'
+  WHERE array_dims(c) is not null;
+SELECT a,b,c FROM arrtest;
+       a       |           b           |           c           
+---------------+-----------------------+-----------------------
+ {16,25,3,4,5} | {{{113,142},{1,147}}} | {}
+ {16,25,23}    | {{3,4},{4,5}}         | {"foobar","new_word"}
+ {}            | {3,4}                 | {"foo","new_word"}
+(3 rows)
+
 SELECT a[1:3],
           b[1:1][1:2][1:2],
           c[1:2], 
           d[1:1][2:2]
    FROM arrtest;
-     a      |          b          |         c          |     d      
-------------+---------------------+--------------------+------------
- {16,25,3}  | {{{113,117},{1,2}}} |                    | 
- {16,25,23} |                     |                    | {{"elt2"}}
-            |                     | {"new_word","bar"} | 
+     a      |           b           |           c           |     d      
+------------+-----------------------+-----------------------+------------
+ {16,25,3}  | {{{113,142},{1,147}}} |                       | 
+ {16,25,23} |                       | {"foobar","new_word"} | {{"elt2"}}
+            |                       | {"foo","new_word"}    | 
 (3 rows)
 
diff --git a/src/test/regress/sql/arrays.sql b/src/test/regress/sql/arrays.sql
index 58615a66b97..3b02acf6427 100644
--- a/src/test/regress/sql/arrays.sql
+++ b/src/test/regress/sql/arrays.sql
@@ -20,8 +20,8 @@ SELECT a[1:3],
           d[1:1][1:2]
    FROM arrtest;
 
--- returns three different results--
-SELECT array_dims(arrtest.b) AS x;
+SELECT array_dims(a) AS a,array_dims(b) AS b,array_dims(c) AS c
+   FROM arrtest;
 
 -- returns nothing 
 SELECT *
@@ -30,9 +30,19 @@ SELECT *
          c = '{"foobar"}'::_name;
 
 UPDATE arrtest
-  SET a[1:2] = '{16,25}',
-      b[1:1][1:1][1:2] = '{113, 117}', 
-      c[1:1] = '{"new_word"}';
+  SET a[1:2] = '{16,25}'
+  WHERE NOT a = '{}'::_int2;
+
+UPDATE arrtest
+  SET b[1:1][1:1][1:2] = '{113, 117}',
+      b[1:1][1:2][2:2] = '{142, 147}'
+  WHERE array_dims(b) = '[1:1][1:2][1:2]';
+
+UPDATE arrtest
+  SET c[2:2] = '{"new_word"}'
+  WHERE array_dims(c) is not null;
+
+SELECT a,b,c FROM arrtest;
 
 SELECT a[1:3],
           b[1:1][1:2][1:2],
-- 
GitLab