/*
* These datum records are read-only at runtime, so no need to
- * copy them
+ * copy them (well, ARRAYELEM contains some cached type data,
+ * but we'd just as soon centralize the caching anyway)
*/
result = datum;
break;
/*
* Target is an element of an array
*/
+ PLpgSQL_arrayelem *arrayelem;
int nsubscripts;
int i;
PLpgSQL_expr *subscripts[MAXDIM];
int subscriptvals[MAXDIM];
- bool oldarrayisnull;
- Oid arraytypeid,
- arrayelemtypeid;
- int32 arraytypmod;
- int16 arraytyplen,
- elemtyplen;
- bool elemtypbyval;
- char elemtypalign;
Datum oldarraydatum,
coerced_value;
+ bool oldarrayisnull;
+ Oid parenttypoid;
+ int32 parenttypmod;
ArrayType *oldarrayval;
ArrayType *newarrayval;
SPITupleTable *save_eval_tuptable;
* back to find the base array datum, and save the subscript
* expressions as we go. (We are scanning right to left here,
* but want to evaluate the subscripts left-to-right to
- * minimize surprises.)
+ * minimize surprises.) Note that arrayelem is left pointing
+ * to the leftmost arrayelem datum, where we will cache the
+ * array element type data.
*/
nsubscripts = 0;
do
{
- PLpgSQL_arrayelem *arrayelem = (PLpgSQL_arrayelem *) target;
-
+ arrayelem = (PLpgSQL_arrayelem *) target;
if (nsubscripts >= MAXDIM)
ereport(ERROR,
(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
/* Fetch current value of array datum */
exec_eval_datum(estate, target,
- &arraytypeid, &arraytypmod,
+ &parenttypoid, &parenttypmod,
&oldarraydatum, &oldarrayisnull);
- /* If target is domain over array, reduce to base type */
- arraytypeid = getBaseTypeAndTypmod(arraytypeid, &arraytypmod);
-
- /* ... and identify the element type */
- arrayelemtypeid = get_element_type(arraytypeid);
- if (!OidIsValid(arrayelemtypeid))
- ereport(ERROR,
- (errcode(ERRCODE_DATATYPE_MISMATCH),
- errmsg("subscripted object is not an array")));
-
- get_typlenbyvalalign(arrayelemtypeid,
- &elemtyplen,
- &elemtypbyval,
- &elemtypalign);
- arraytyplen = get_typlen(arraytypeid);
+ /* Update cached type data if necessary */
+ if (arrayelem->parenttypoid != parenttypoid ||
+ arrayelem->parenttypmod != parenttypmod)
+ {
+ Oid arraytypoid;
+ int32 arraytypmod = parenttypmod;
+ int16 arraytyplen;
+ Oid elemtypoid;
+ int16 elemtyplen;
+ bool elemtypbyval;
+ char elemtypalign;
+
+ /* If target is domain over array, reduce to base type */
+ arraytypoid = getBaseTypeAndTypmod(parenttypoid,
+ &arraytypmod);
+
+ /* ... and identify the element type */
+ elemtypoid = get_element_type(arraytypoid);
+ if (!OidIsValid(elemtypoid))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("subscripted object is not an array")));
+
+ /* Collect needed data about the types */
+ arraytyplen = get_typlen(arraytypoid);
+
+ get_typlenbyvalalign(elemtypoid,
+ &elemtyplen,
+ &elemtypbyval,
+ &elemtypalign);
+
+ /* Now safe to update the cached data */
+ arrayelem->parenttypoid = parenttypoid;
+ arrayelem->parenttypmod = parenttypmod;
+ arrayelem->arraytypoid = arraytypoid;
+ arrayelem->arraytypmod = arraytypmod;
+ arrayelem->arraytyplen = arraytyplen;
+ arrayelem->elemtypoid = elemtypoid;
+ arrayelem->elemtyplen = elemtyplen;
+ arrayelem->elemtypbyval = elemtypbyval;
+ arrayelem->elemtypalign = elemtypalign;
+ }
/*
* Evaluate the subscripts, switch into left-to-right order.
/* Coerce source value to match array element type. */
coerced_value = exec_simple_cast_value(value,
valtype,
- arrayelemtypeid,
- arraytypmod,
+ arrayelem->elemtypoid,
+ arrayelem->arraytypmod,
*isNull);
/*
* array, either, so that's a no-op too. This is all ugly but
* corresponds to the current behavior of ExecEvalArrayRef().
*/
- if (arraytyplen > 0 && /* fixed-length array? */
+ if (arrayelem->arraytyplen > 0 && /* fixed-length array? */
(oldarrayisnull || *isNull))
return;
if (oldarrayisnull)
- oldarrayval = construct_empty_array(arrayelemtypeid);
+ oldarrayval = construct_empty_array(arrayelem->elemtypoid);
else
oldarrayval = (ArrayType *) DatumGetPointer(oldarraydatum);
subscriptvals,
coerced_value,
*isNull,
- arraytyplen,
- elemtyplen,
- elemtypbyval,
- elemtypalign);
+ arrayelem->arraytyplen,
+ arrayelem->elemtyplen,
+ arrayelem->elemtypbyval,
+ arrayelem->elemtypalign);
/*
* Avoid leaking the result of exec_simple_cast_value, if it
* performed a conversion to a pass-by-ref type.
*/
- if (!*isNull && coerced_value != value && !elemtypbyval)
+ if (!*isNull && coerced_value != value &&
+ !arrayelem->elemtypbyval)
pfree(DatumGetPointer(coerced_value));
/*
*isNull = false;
exec_assign_value(estate, target,
PointerGetDatum(newarrayval),
- arraytypeid, isNull);
+ arrayelem->arraytypoid, isNull);
/*
* Avoid leaking the modified array value, too.
drop function foreach_test(anyarray);
drop type xy_tuple;
+--
+-- Assorted tests for array subscript assignment
+--
+create temp table rtype (id int, ar text[]);
+create function arrayassign1() returns text[] language plpgsql as $$
+declare
+ r record;
+begin
+ r := row(12, '{foo,bar,baz}')::rtype;
+ r.ar[2] := 'replace';
+ return r.ar;
+end$$;
+select arrayassign1();
+ arrayassign1
+-------------------
+ {foo,replace,baz}
+(1 row)
+
+select arrayassign1(); -- try again to exercise internal caching
+ arrayassign1
+-------------------
+ {foo,replace,baz}
+(1 row)
+
+create domain orderedarray as int[2]
+ constraint sorted check (value[1] < value[2]);
+select '{1,2}'::orderedarray;
+ orderedarray
+--------------
+ {1,2}
+(1 row)
+
+select '{2,1}'::orderedarray; -- fail
+ERROR: value for domain orderedarray violates check constraint "sorted"
+create function testoa(x1 int, x2 int, x3 int) returns orderedarray
+language plpgsql as $$
+declare res orderedarray;
+begin
+ res := array[x1, x2];
+ res[2] := x3;
+ return res;
+end$$;
+select testoa(1,2,3);
+ testoa
+--------
+ {1,3}
+(1 row)
+
+select testoa(1,2,3); -- try again to exercise internal caching
+ testoa
+--------
+ {1,3}
+(1 row)
+
+select testoa(2,1,3); -- fail at initial assign
+ERROR: value for domain orderedarray violates check constraint "sorted"
+CONTEXT: PL/pgSQL function "testoa" line 4 at assignment
+select testoa(1,2,1); -- fail at update
+ERROR: value for domain orderedarray violates check constraint "sorted"
+CONTEXT: PL/pgSQL function "testoa" line 5 at assignment
+drop function arrayassign1();
+drop function testoa(x1 int, x2 int, x3 int);
drop function foreach_test(anyarray);
drop type xy_tuple;
+
+--
+-- Assorted tests for array subscript assignment
+--
+
+create temp table rtype (id int, ar text[]);
+
+create function arrayassign1() returns text[] language plpgsql as $$
+declare
+ r record;
+begin
+ r := row(12, '{foo,bar,baz}')::rtype;
+ r.ar[2] := 'replace';
+ return r.ar;
+end$$;
+
+select arrayassign1();
+select arrayassign1(); -- try again to exercise internal caching
+
+create domain orderedarray as int[2]
+ constraint sorted check (value[1] < value[2]);
+
+select '{1,2}'::orderedarray;
+select '{2,1}'::orderedarray; -- fail
+
+create function testoa(x1 int, x2 int, x3 int) returns orderedarray
+language plpgsql as $$
+declare res orderedarray;
+begin
+ res := array[x1, x2];
+ res[2] := x3;
+ return res;
+end$$;
+
+select testoa(1,2,3);
+select testoa(1,2,3); -- try again to exercise internal caching
+select testoa(2,1,3); -- fail at initial assign
+select testoa(1,2,1); -- fail at update
+
+drop function arrayassign1();
+drop function testoa(x1 int, x2 int, x3 int);