Commit 352a56ba authored by Tom Lane's avatar Tom Lane

Allow assignment to array elements not contiguous with those already

present; intervening positions are filled with nulls.  This behavior
is required by SQL99 but was not implementable before 8.2 due to lack
of support for nulls in arrays.  I have only made it work for the
one-dimensional case, which is all that SQL99 requires.  It seems quite
complex to get it right in higher dimensions, and since we never allowed
extension at all in higher dimensions, I think that must count as a
future feature addition not a bug fix.
parent 673a573d
<!-- $PostgreSQL: pgsql/doc/src/sgml/array.sgml,v 1.51 2006/05/09 23:12:54 momjian Exp $ -->
<!-- $PostgreSQL: pgsql/doc/src/sgml/array.sgml,v 1.52 2006/09/29 21:22:21 tgl Exp $ -->
<sect1 id="arrays">
<title>Arrays</title>
......@@ -350,11 +350,12 @@ UPDATE sal_emp SET pay_by_quarter[1:2] = '{27000,27000}'
</para>
<para>
A stored array value can be enlarged by assigning to an element adjacent to
those already present, or by assigning to a slice that is adjacent
to or overlaps the data already present. For example, if array
<literal>myarray</> currently has 4 elements, it will have five
elements after an update that assigns to <literal>myarray[5]</>.
A stored array value can be enlarged by assigning to element(s) not already
present. Any positions between those previously present and the newly
assigned element(s) will be filled with nulls. For example, if array
<literal>myarray</> currently has 4 elements, it will have six
elements after an update that assigns to <literal>myarray[6]</>,
and <literal>myarray[5]</> will contain a null.
Currently, enlargement in this fashion is only allowed for one-dimensional
arrays, not multidimensional arrays.
</para>
......
......@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/utils/adt/arrayfuncs.c,v 1.131 2006/09/10 20:14:20 tgl Exp $
* $PostgreSQL: pgsql/src/backend/utils/adt/arrayfuncs.c,v 1.132 2006/09/29 21:22:21 tgl Exp $
*
*-------------------------------------------------------------------------
*/
......@@ -1925,8 +1925,9 @@ array_get_slice(ArrayType *array,
* modified entry. The original array object is not changed.
*
* 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.
* (XXX we could be more flexible: perhaps allow NULL fill?)
* by assigning to a position outside the existing subscript range; any
* positions between the existing elements and the new one are set to NULLs.
* (XXX TODO: allow a corresponding behavior for multidimensional arrays)
*
* NOTE: For assignments, we throw an error for invalid subscripts etc,
* rather than returning a NULL as the fetch operations do.
......@@ -1949,17 +1950,18 @@ array_set(ArrayType *array,
lb[MAXDIM],
offset;
char *elt_ptr;
bool extendbefore = false;
bool extendafter = false;
bool newhasnulls;
bits8 *oldnullbitmap;
int oldnitems,
newnitems,
olddatasize,
newsize,
olditemlen,
newitemlen,
overheadlen,
oldoverheadlen,
addedbefore,
addedafter,
lenbefore,
lenafter;
......@@ -1972,12 +1974,12 @@ array_set(ArrayType *array,
if (nSubscripts != 1)
ereport(ERROR,
(errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
errmsg("invalid array subscripts")));
errmsg("wrong number of array subscripts")));
if (indx[0] < 0 || indx[0] * elmlen >= arraytyplen)
ereport(ERROR,
(errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
errmsg("invalid array subscripts")));
errmsg("array subscript out of range")));
if (isNull)
ereport(ERROR,
......@@ -1994,7 +1996,7 @@ array_set(ArrayType *array,
if (nSubscripts <= 0 || nSubscripts > MAXDIM)
ereport(ERROR,
(errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
errmsg("invalid array subscripts")));
errmsg("wrong number of array subscripts")));
/* make sure item to be inserted is not toasted */
if (elmlen == -1 && !isNull)
......@@ -2028,70 +2030,72 @@ array_set(ArrayType *array,
if (ndim != nSubscripts)
ereport(ERROR,
(errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
errmsg("invalid array subscripts")));
errmsg("wrong number of array subscripts")));
/* copy dim/lb since we may modify them */
memcpy(dim, ARR_DIMS(array), ndim * sizeof(int));
memcpy(lb, ARR_LBOUND(array), ndim * sizeof(int));
newhasnulls = (ARR_HASNULL(array) || isNull);
addedbefore = addedafter = 0;
/*
* Check subscripts
*/
for (i = 0; i < ndim; i++)
if (ndim == 1)
{
if (indx[i] < lb[i])
if (indx[0] < lb[0])
{
if (ndim == 1 && indx[i] == lb[i] - 1)
{
dim[i]++;
lb[i]--;
extendbefore = true;
}
else
ereport(ERROR,
(errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
errmsg("invalid array subscripts")));
addedbefore = lb[0] - indx[0];
dim[0] += addedbefore;
lb[0] = indx[0];
if (addedbefore > 1)
newhasnulls = true; /* will insert nulls */
}
if (indx[i] >= (dim[i] + lb[i]))
if (indx[0] >= (dim[0] + lb[0]))
{
if (ndim == 1 && indx[i] == (dim[i] + lb[i]))
{
dim[i]++;
extendafter = true;
}
else
addedafter = indx[0] - (dim[0] + lb[0]) + 1;
dim[0] += addedafter;
if (addedafter > 1)
newhasnulls = true; /* will insert nulls */
}
}
else
{
/*
* XXX currently we do not support extending multi-dimensional
* arrays during assignment
*/
for (i = 0; i < ndim; i++)
{
if (indx[i] < lb[i] ||
indx[i] >= (dim[i] + lb[i]))
ereport(ERROR,
(errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
errmsg("invalid array subscripts")));
errmsg("array subscript out of range")));
}
}
/*
* Compute sizes of items and areas to copy
*/
if (ARR_HASNULL(array) || isNull)
{
newhasnulls = true;
overheadlen = ARR_OVERHEAD_WITHNULLS(ndim,
ArrayGetNItems(ndim, dim));
}
newnitems = ArrayGetNItems(ndim, dim);
if (newhasnulls)
overheadlen = ARR_OVERHEAD_WITHNULLS(ndim, newnitems);
else
{
newhasnulls = false;
overheadlen = ARR_OVERHEAD_NONULLS(ndim);
}
oldnitems = ArrayGetNItems(ndim, ARR_DIMS(array));
oldnullbitmap = ARR_NULLBITMAP(array);
oldoverheadlen = ARR_DATA_OFFSET(array);
olddatasize = ARR_SIZE(array) - oldoverheadlen;
if (extendbefore)
if (addedbefore)
{
offset = 0;
lenbefore = 0;
olditemlen = 0;
lenafter = olddatasize;
}
else if (extendafter)
else if (addedafter)
{
offset = oldnitems;
lenbefore = olddatasize;
......@@ -2158,9 +2162,16 @@ array_set(ArrayType *array,
{
bits8 *newnullbitmap = ARR_NULLBITMAP(newarray);
array_set_isnull(newnullbitmap, offset, isNull);
if (extendbefore)
array_bitmap_copy(newnullbitmap, 1,
/* Zero the bitmap to take care of marking inserted positions null */
MemSet(newnullbitmap, 0, (newnitems + 7) / 8);
/* Fix the inserted value */
if (addedafter)
array_set_isnull(newnullbitmap, newnitems - 1, isNull);
else
array_set_isnull(newnullbitmap, offset, isNull);
/* Fix the copied range(s) */
if (addedbefore)
array_bitmap_copy(newnullbitmap, addedbefore,
oldnullbitmap, 0,
oldnitems);
else
......@@ -2168,7 +2179,7 @@ array_set(ArrayType *array,
array_bitmap_copy(newnullbitmap, 0,
oldnullbitmap, 0,
offset);
if (!extendafter)
if (addedafter == 0)
array_bitmap_copy(newnullbitmap, offset + 1,
oldnullbitmap, offset + 1,
oldnitems - offset - 1);
......@@ -2202,6 +2213,11 @@ array_set(ArrayType *array,
* A new array is returned, just like the old except for the
* modified range. The original array object is not changed.
*
* For one-dimensional arrays only, we allow the array to be extended
* by assigning to positions outside the existing subscript range; any
* positions between the existing elements and the new ones are set to NULLs.
* (XXX TODO: allow a corresponding behavior for multidimensional arrays)
*
* NOTE: we assume it is OK to scribble on the provided index arrays
* lowerIndx[] and upperIndx[]. These are generally just temporaries.
*
......@@ -2235,6 +2251,8 @@ array_set_slice(ArrayType *array,
newitemsize,
overheadlen,
oldoverheadlen,
addedbefore,
addedafter,
lenbefore,
lenafter,
itemsbefore,
......@@ -2298,55 +2316,70 @@ array_set_slice(ArrayType *array,
if (ndim < nSubscripts || ndim <= 0 || ndim > MAXDIM)
ereport(ERROR,
(errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
errmsg("invalid array subscripts")));
errmsg("wrong number of array subscripts")));
/* copy dim/lb since we may modify them */
memcpy(dim, ARR_DIMS(array), ndim * sizeof(int));
memcpy(lb, ARR_LBOUND(array), ndim * sizeof(int));
newhasnulls = (ARR_HASNULL(array) || ARR_HASNULL(srcArray));
addedbefore = addedafter = 0;
/*
* 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.
* Check subscripts
*/
for (i = 0; i < nSubscripts; i++)
if (ndim == 1)
{
if (lowerIndx[i] > upperIndx[i])
Assert(nSubscripts == 1);
if (lowerIndx[0] > upperIndx[0])
ereport(ERROR,
(errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
errmsg("invalid array subscripts")));
if (lowerIndx[i] < lb[i])
errmsg("upper bound cannot be less than lower bound")));
if (lowerIndx[0] < lb[0])
{
if (ndim == 1 && upperIndx[i] >= lb[i] - 1)
{
dim[i] += lb[i] - lowerIndx[i];
lb[i] = lowerIndx[i];
}
else
if (upperIndx[0] < lb[0] - 1)
newhasnulls = true; /* will insert nulls */
addedbefore = lb[0] - lowerIndx[0];
dim[0] += addedbefore;
lb[0] = lowerIndx[0];
}
if (upperIndx[0] >= (dim[0] + lb[0]))
{
if (lowerIndx[0] > (dim[0] + lb[0]))
newhasnulls = true; /* will insert nulls */
addedafter = upperIndx[0] - (dim[0] + lb[0]) + 1;
dim[0] += addedafter;
}
}
else
{
/*
* XXX currently we do not support extending multi-dimensional
* arrays during assignment
*/
for (i = 0; i < nSubscripts; i++)
{
if (lowerIndx[i] > upperIndx[i])
ereport(ERROR,
(errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
errmsg("upper bound cannot be less than lower bound")));
if (lowerIndx[i] < lb[i] ||
upperIndx[i] >= (dim[i] + lb[i]))
ereport(ERROR,
(errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
errmsg("invalid array subscripts")));
errmsg("array subscript out of range")));
}
if (upperIndx[i] >= (dim[i] + lb[i]))
/* fill any missing subscript positions with full array range */
for (; i < ndim; i++)
{
if (ndim == 1 && lowerIndx[i] <= (dim[i] + lb[i]))
dim[i] = upperIndx[i] - lb[i] + 1;
else
lowerIndx[i] = lb[i];
upperIndx[i] = dim[i] + lb[i] - 1;
if (lowerIndx[i] > upperIndx[i])
ereport(ERROR,
(errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
errmsg("invalid array subscripts")));
errmsg("upper bound cannot be less than lower bound")));
}
}
/* fill any missing subscript positions with full array range */
for (; i < ndim; i++)
{
lowerIndx[i] = lb[i];
upperIndx[i] = dim[i] + lb[i] - 1;
if (lowerIndx[i] > upperIndx[i])
ereport(ERROR,
(errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
errmsg("invalid array subscripts")));
}
/* Do this mainly to check for overflow */
nitems = ArrayGetNItems(ndim, dim);
......@@ -2366,16 +2399,10 @@ array_set_slice(ArrayType *array,
* Compute space occupied by new entries, space occupied by replaced
* entries, and required space for new array.
*/
if (ARR_HASNULL(array) || ARR_HASNULL(srcArray))
{
newhasnulls = true;
if (newhasnulls)
overheadlen = ARR_OVERHEAD_WITHNULLS(ndim, nitems);
}
else
{
newhasnulls = false;
overheadlen = ARR_OVERHEAD_NONULLS(ndim);
}
newitemsize = array_nelems_size(ARR_DATA_PTR(srcArray), 0,
ARR_NULLBITMAP(srcArray), nsrcitems,
elmlen, elmbyval, elmalign);
......@@ -2407,7 +2434,7 @@ array_set_slice(ArrayType *array,
char *oldarraydata = ARR_DATA_PTR(array);
bits8 *oldarraybitmap = ARR_NULLBITMAP(array);
itemsbefore = slicelb - oldlb;
itemsbefore = Min(slicelb, oldub + 1) - oldlb;
lenbefore = array_nelems_size(oldarraydata, 0, oldarraybitmap,
itemsbefore,
elmlen, elmbyval, elmalign);
......@@ -2467,13 +2494,15 @@ array_set_slice(ArrayType *array,
bits8 *newnullbitmap = ARR_NULLBITMAP(newarray);
bits8 *oldnullbitmap = ARR_NULLBITMAP(array);
array_bitmap_copy(newnullbitmap, 0,
/* Zero the bitmap to handle marking inserted positions null */
MemSet(newnullbitmap, 0, (nitems + 7) / 8);
array_bitmap_copy(newnullbitmap, addedbefore,
oldnullbitmap, 0,
itemsbefore);
array_bitmap_copy(newnullbitmap, itemsbefore,
array_bitmap_copy(newnullbitmap, lowerIndx[0] - lb[0],
ARR_NULLBITMAP(srcArray), 0,
nsrcitems);
array_bitmap_copy(newnullbitmap, itemsbefore + nsrcitems,
array_bitmap_copy(newnullbitmap, addedbefore + itemsbefore + nolditems,
oldnullbitmap, itemsbefore + nolditems,
itemsafter);
}
......
......@@ -143,6 +143,116 @@ SELECT a,b,c FROM arrtest;
[4:4]={NULL} | {3,4} | {foo,new_word}
(3 rows)
--
-- test array extension
--
CREATE TEMP TABLE arrtest1 (i int[], t text[]);
insert into arrtest1 values(array[1,2,null,4], array['one','two',null,'four']);
select * from arrtest1;
i | t
--------------+---------------------
{1,2,NULL,4} | {one,two,NULL,four}
(1 row)
update arrtest1 set i[2] = 22, t[2] = 'twenty-two';
select * from arrtest1;
i | t
---------------+----------------------------
{1,22,NULL,4} | {one,twenty-two,NULL,four}
(1 row)
update arrtest1 set i[5] = 5, t[5] = 'five';
select * from arrtest1;
i | t
-----------------+---------------------------------
{1,22,NULL,4,5} | {one,twenty-two,NULL,four,five}
(1 row)
update arrtest1 set i[8] = 8, t[8] = 'eight';
select * from arrtest1;
i | t
-----------------------------+-------------------------------------------------
{1,22,NULL,4,5,NULL,NULL,8} | {one,twenty-two,NULL,four,five,NULL,NULL,eight}
(1 row)
update arrtest1 set i[0] = 0, t[0] = 'zero';
select * from arrtest1;
i | t
-------------------------------------+------------------------------------------------------------
[0:8]={0,1,22,NULL,4,5,NULL,NULL,8} | [0:8]={zero,one,twenty-two,NULL,four,five,NULL,NULL,eight}
(1 row)
update arrtest1 set i[-3] = -3, t[-3] = 'minus-three';
select * from arrtest1;
i | t
---------------------------------------------------+-----------------------------------------------------------------------------------
[-3:8]={-3,NULL,NULL,0,1,22,NULL,4,5,NULL,NULL,8} | [-3:8]={minus-three,NULL,NULL,zero,one,twenty-two,NULL,four,five,NULL,NULL,eight}
(1 row)
update arrtest1 set i[0:2] = array[10,11,12], t[0:2] = array['ten','eleven','twelve'];
select * from arrtest1;
i | t
-----------------------------------------------------+---------------------------------------------------------------------------------
[-3:8]={-3,NULL,NULL,10,11,12,NULL,4,5,NULL,NULL,8} | [-3:8]={minus-three,NULL,NULL,ten,eleven,twelve,NULL,four,five,NULL,NULL,eight}
(1 row)
update arrtest1 set i[8:10] = array[18,null,20], t[8:10] = array['p18',null,'p20'];
select * from arrtest1;
i | t
---------------------------------------------------------------+-----------------------------------------------------------------------------------------
[-3:10]={-3,NULL,NULL,10,11,12,NULL,4,5,NULL,NULL,18,NULL,20} | [-3:10]={minus-three,NULL,NULL,ten,eleven,twelve,NULL,four,five,NULL,NULL,p18,NULL,p20}
(1 row)
update arrtest1 set i[11:12] = array[null,22], t[11:12] = array[null,'p22'];
select * from arrtest1;
i | t
-----------------------------------------------------------------------+--------------------------------------------------------------------------------------------------
[-3:12]={-3,NULL,NULL,10,11,12,NULL,4,5,NULL,NULL,18,NULL,20,NULL,22} | [-3:12]={minus-three,NULL,NULL,ten,eleven,twelve,NULL,four,five,NULL,NULL,p18,NULL,p20,NULL,p22}
(1 row)
update arrtest1 set i[15:16] = array[null,26], t[15:16] = array[null,'p26'];
select * from arrtest1;
i | t
-----------------------------------------------------------------------------------------+---------------------------------------------------------------------------------------------------------------------
[-3:16]={-3,NULL,NULL,10,11,12,NULL,4,5,NULL,NULL,18,NULL,20,NULL,22,NULL,NULL,NULL,26} | [-3:16]={minus-three,NULL,NULL,ten,eleven,twelve,NULL,four,five,NULL,NULL,p18,NULL,p20,NULL,p22,NULL,NULL,NULL,p26}
(1 row)
update arrtest1 set i[-5:-3] = array[-15,-14,-13], t[-5:-3] = array['m15','m14','m13'];
select * from arrtest1;
i | t
--------------------------------------------------------------------------------------------------+---------------------------------------------------------------------------------------------------------------------
[-5:16]={-15,-14,-13,NULL,NULL,10,11,12,NULL,4,5,NULL,NULL,18,NULL,20,NULL,22,NULL,NULL,NULL,26} | [-5:16]={m15,m14,m13,NULL,NULL,ten,eleven,twelve,NULL,four,five,NULL,NULL,p18,NULL,p20,NULL,p22,NULL,NULL,NULL,p26}
(1 row)
update arrtest1 set i[-7:-6] = array[-17,null], t[-7:-6] = array['m17',null];
select * from arrtest1;
i | t
-----------------------------------------------------------------------------------------------------------+------------------------------------------------------------------------------------------------------------------------------
[-7:16]={-17,NULL,-15,-14,-13,NULL,NULL,10,11,12,NULL,4,5,NULL,NULL,18,NULL,20,NULL,22,NULL,NULL,NULL,26} | [-7:16]={m17,NULL,m15,m14,m13,NULL,NULL,ten,eleven,twelve,NULL,four,five,NULL,NULL,p18,NULL,p20,NULL,p22,NULL,NULL,NULL,p26}
(1 row)
update arrtest1 set i[-12:-10] = array[-22,null,-20], t[-12:-10] = array['m22',null,'m20'];
select * from arrtest1;
i | t
-----------------------------------------------------------------------------------------------------------------------------------+------------------------------------------------------------------------------------------------------------------------------------------------------
[-12:16]={-22,NULL,-20,NULL,NULL,-17,NULL,-15,-14,-13,NULL,NULL,10,11,12,NULL,4,5,NULL,NULL,18,NULL,20,NULL,22,NULL,NULL,NULL,26} | [-12:16]={m22,NULL,m20,NULL,NULL,m17,NULL,m15,m14,m13,NULL,NULL,ten,eleven,twelve,NULL,four,five,NULL,NULL,p18,NULL,p20,NULL,p22,NULL,NULL,NULL,p26}
(1 row)
delete from arrtest1;
insert into arrtest1 values(array[1,2,null,4], array['one','two',null,'four']);
select * from arrtest1;
i | t
--------------+---------------------
{1,2,NULL,4} | {one,two,NULL,four}
(1 row)
update arrtest1 set i[0:5] = array[0,1,2,null,4,5], t[0:5] = array['z','p1','p2',null,'p4','p5'];
select * from arrtest1;
i | t
------------------------+----------------------------
[0:5]={0,1,2,NULL,4,5} | [0:5]={z,p1,p2,NULL,p4,p5}
(1 row)
--
-- array expressions and operators
--
......
......@@ -90,6 +90,42 @@ SELECT a FROM arrtest WHERE a[2] IS NULL;
DELETE FROM arrtest WHERE a[2] IS NULL AND b IS NULL;
SELECT a,b,c FROM arrtest;
--
-- test array extension
--
CREATE TEMP TABLE arrtest1 (i int[], t text[]);
insert into arrtest1 values(array[1,2,null,4], array['one','two',null,'four']);
select * from arrtest1;
update arrtest1 set i[2] = 22, t[2] = 'twenty-two';
select * from arrtest1;
update arrtest1 set i[5] = 5, t[5] = 'five';
select * from arrtest1;
update arrtest1 set i[8] = 8, t[8] = 'eight';
select * from arrtest1;
update arrtest1 set i[0] = 0, t[0] = 'zero';
select * from arrtest1;
update arrtest1 set i[-3] = -3, t[-3] = 'minus-three';
select * from arrtest1;
update arrtest1 set i[0:2] = array[10,11,12], t[0:2] = array['ten','eleven','twelve'];
select * from arrtest1;
update arrtest1 set i[8:10] = array[18,null,20], t[8:10] = array['p18',null,'p20'];
select * from arrtest1;
update arrtest1 set i[11:12] = array[null,22], t[11:12] = array[null,'p22'];
select * from arrtest1;
update arrtest1 set i[15:16] = array[null,26], t[15:16] = array[null,'p26'];
select * from arrtest1;
update arrtest1 set i[-5:-3] = array[-15,-14,-13], t[-5:-3] = array['m15','m14','m13'];
select * from arrtest1;
update arrtest1 set i[-7:-6] = array[-17,null], t[-7:-6] = array['m17',null];
select * from arrtest1;
update arrtest1 set i[-12:-10] = array[-22,null,-20], t[-12:-10] = array['m22',null,'m20'];
select * from arrtest1;
delete from arrtest1;
insert into arrtest1 values(array[1,2,null,4], array['one','two',null,'four']);
select * from arrtest1;
update arrtest1 set i[0:5] = array[0,1,2,null,4,5], t[0:5] = array['z','p1','p2',null,'p4','p5'];
select * from arrtest1;
--
-- array expressions and operators
--
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment