Skip to content

Commit 16762b5

Browse files
committed
Speed up array element assignment in plpgsql by caching type information.
Cache assorted data in the PLpgSQL_arrayelem struct to avoid repetitive catalog lookups over multiple executions of the same statement. Pavel Stehule
1 parent 821fd90 commit 16762b5

File tree

5 files changed

+179
-38
lines changed

5 files changed

+179
-38
lines changed

src/pl/plpgsql/src/gram.y

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -998,6 +998,8 @@ assign_var : T_DATUM
998998
new->dtype = PLPGSQL_DTYPE_ARRAYELEM;
999999
new->subscript = $3;
10001000
new->arrayparentno = $1;
1001+
/* initialize cached type data to "not valid" */
1002+
new->parenttypoid = InvalidOid;
10011003

10021004
plpgsql_adddatum((PLpgSQL_datum *) new);
10031005

src/pl/plpgsql/src/pl_exec.c

Lines changed: 64 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -874,7 +874,8 @@ copy_plpgsql_datum(PLpgSQL_datum *datum)
874874

875875
/*
876876
* These datum records are read-only at runtime, so no need to
877-
* copy them
877+
* copy them (well, ARRAYELEM contains some cached type data,
878+
* but we'd just as soon centralize the caching anyway)
878879
*/
879880
result = datum;
880881
break;
@@ -3986,20 +3987,16 @@ exec_assign_value(PLpgSQL_execstate *estate,
39863987
/*
39873988
* Target is an element of an array
39883989
*/
3990+
PLpgSQL_arrayelem *arrayelem;
39893991
int nsubscripts;
39903992
int i;
39913993
PLpgSQL_expr *subscripts[MAXDIM];
39923994
int subscriptvals[MAXDIM];
3993-
bool oldarrayisnull;
3994-
Oid arraytypeid,
3995-
arrayelemtypeid;
3996-
int32 arraytypmod;
3997-
int16 arraytyplen,
3998-
elemtyplen;
3999-
bool elemtypbyval;
4000-
char elemtypalign;
40013995
Datum oldarraydatum,
40023996
coerced_value;
3997+
bool oldarrayisnull;
3998+
Oid parenttypoid;
3999+
int32 parenttypmod;
40034000
ArrayType *oldarrayval;
40044001
ArrayType *newarrayval;
40054002
SPITupleTable *save_eval_tuptable;
@@ -4020,13 +4017,14 @@ exec_assign_value(PLpgSQL_execstate *estate,
40204017
* back to find the base array datum, and save the subscript
40214018
* expressions as we go. (We are scanning right to left here,
40224019
* but want to evaluate the subscripts left-to-right to
4023-
* minimize surprises.)
4020+
* minimize surprises.) Note that arrayelem is left pointing
4021+
* to the leftmost arrayelem datum, where we will cache the
4022+
* array element type data.
40244023
*/
40254024
nsubscripts = 0;
40264025
do
40274026
{
4028-
PLpgSQL_arrayelem *arrayelem = (PLpgSQL_arrayelem *) target;
4029-
4027+
arrayelem = (PLpgSQL_arrayelem *) target;
40304028
if (nsubscripts >= MAXDIM)
40314029
ereport(ERROR,
40324030
(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
@@ -4038,24 +4036,51 @@ exec_assign_value(PLpgSQL_execstate *estate,
40384036

40394037
/* Fetch current value of array datum */
40404038
exec_eval_datum(estate, target,
4041-
&arraytypeid, &arraytypmod,
4039+
&parenttypoid, &parenttypmod,
40424040
&oldarraydatum, &oldarrayisnull);
40434041

4044-
/* If target is domain over array, reduce to base type */
4045-
arraytypeid = getBaseTypeAndTypmod(arraytypeid, &arraytypmod);
4046-
4047-
/* ... and identify the element type */
4048-
arrayelemtypeid = get_element_type(arraytypeid);
4049-
if (!OidIsValid(arrayelemtypeid))
4050-
ereport(ERROR,
4051-
(errcode(ERRCODE_DATATYPE_MISMATCH),
4052-
errmsg("subscripted object is not an array")));
4053-
4054-
get_typlenbyvalalign(arrayelemtypeid,
4055-
&elemtyplen,
4056-
&elemtypbyval,
4057-
&elemtypalign);
4058-
arraytyplen = get_typlen(arraytypeid);
4042+
/* Update cached type data if necessary */
4043+
if (arrayelem->parenttypoid != parenttypoid ||
4044+
arrayelem->parenttypmod != parenttypmod)
4045+
{
4046+
Oid arraytypoid;
4047+
int32 arraytypmod = parenttypmod;
4048+
int16 arraytyplen;
4049+
Oid elemtypoid;
4050+
int16 elemtyplen;
4051+
bool elemtypbyval;
4052+
char elemtypalign;
4053+
4054+
/* If target is domain over array, reduce to base type */
4055+
arraytypoid = getBaseTypeAndTypmod(parenttypoid,
4056+
&arraytypmod);
4057+
4058+
/* ... and identify the element type */
4059+
elemtypoid = get_element_type(arraytypoid);
4060+
if (!OidIsValid(elemtypoid))
4061+
ereport(ERROR,
4062+
(errcode(ERRCODE_DATATYPE_MISMATCH),
4063+
errmsg("subscripted object is not an array")));
4064+
4065+
/* Collect needed data about the types */
4066+
arraytyplen = get_typlen(arraytypoid);
4067+
4068+
get_typlenbyvalalign(elemtypoid,
4069+
&elemtyplen,
4070+
&elemtypbyval,
4071+
&elemtypalign);
4072+
4073+
/* Now safe to update the cached data */
4074+
arrayelem->parenttypoid = parenttypoid;
4075+
arrayelem->parenttypmod = parenttypmod;
4076+
arrayelem->arraytypoid = arraytypoid;
4077+
arrayelem->arraytypmod = arraytypmod;
4078+
arrayelem->arraytyplen = arraytyplen;
4079+
arrayelem->elemtypoid = elemtypoid;
4080+
arrayelem->elemtyplen = elemtyplen;
4081+
arrayelem->elemtypbyval = elemtypbyval;
4082+
arrayelem->elemtypalign = elemtypalign;
4083+
}
40594084

40604085
/*
40614086
* Evaluate the subscripts, switch into left-to-right order.
@@ -4093,8 +4118,8 @@ exec_assign_value(PLpgSQL_execstate *estate,
40934118
/* Coerce source value to match array element type. */
40944119
coerced_value = exec_simple_cast_value(value,
40954120
valtype,
4096-
arrayelemtypeid,
4097-
arraytypmod,
4121+
arrayelem->elemtypoid,
4122+
arrayelem->arraytypmod,
40984123
*isNull);
40994124

41004125
/*
@@ -4107,12 +4132,12 @@ exec_assign_value(PLpgSQL_execstate *estate,
41074132
* array, either, so that's a no-op too. This is all ugly but
41084133
* corresponds to the current behavior of ExecEvalArrayRef().
41094134
*/
4110-
if (arraytyplen > 0 && /* fixed-length array? */
4135+
if (arrayelem->arraytyplen > 0 && /* fixed-length array? */
41114136
(oldarrayisnull || *isNull))
41124137
return;
41134138

41144139
if (oldarrayisnull)
4115-
oldarrayval = construct_empty_array(arrayelemtypeid);
4140+
oldarrayval = construct_empty_array(arrayelem->elemtypoid);
41164141
else
41174142
oldarrayval = (ArrayType *) DatumGetPointer(oldarraydatum);
41184143

@@ -4124,16 +4149,17 @@ exec_assign_value(PLpgSQL_execstate *estate,
41244149
subscriptvals,
41254150
coerced_value,
41264151
*isNull,
4127-
arraytyplen,
4128-
elemtyplen,
4129-
elemtypbyval,
4130-
elemtypalign);
4152+
arrayelem->arraytyplen,
4153+
arrayelem->elemtyplen,
4154+
arrayelem->elemtypbyval,
4155+
arrayelem->elemtypalign);
41314156

41324157
/*
41334158
* Avoid leaking the result of exec_simple_cast_value, if it
41344159
* performed a conversion to a pass-by-ref type.
41354160
*/
4136-
if (!*isNull && coerced_value != value && !elemtypbyval)
4161+
if (!*isNull && coerced_value != value &&
4162+
!arrayelem->elemtypbyval)
41374163
pfree(DatumGetPointer(coerced_value));
41384164

41394165
/*
@@ -4145,7 +4171,7 @@ exec_assign_value(PLpgSQL_execstate *estate,
41454171
*isNull = false;
41464172
exec_assign_value(estate, target,
41474173
PointerGetDatum(newarrayval),
4148-
arraytypeid, isNull);
4174+
arrayelem->arraytypoid, isNull);
41494175

41504176
/*
41514177
* Avoid leaking the modified array value, too.

src/pl/plpgsql/src/plpgsql.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -299,6 +299,16 @@ typedef struct
299299
int dno;
300300
PLpgSQL_expr *subscript;
301301
int arrayparentno; /* dno of parent array variable */
302+
/* Remaining fields are cached info about the array variable's type */
303+
Oid parenttypoid; /* type of array variable; 0 if not yet set */
304+
int32 parenttypmod; /* typmod of array variable */
305+
Oid arraytypoid; /* OID of actual array type */
306+
int32 arraytypmod; /* typmod of array (and its elements too) */
307+
int16 arraytyplen; /* typlen of array type */
308+
Oid elemtypoid; /* OID of array element type */
309+
int16 elemtyplen; /* typlen of element type */
310+
bool elemtypbyval; /* element type is pass-by-value? */
311+
char elemtypalign; /* typalign of element type */
302312
} PLpgSQL_arrayelem;
303313

304314

src/test/regress/expected/plpgsql.out

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4509,3 +4509,65 @@ NOTICE: {"(35,78)","(88,76)"}
45094509

45104510
drop function foreach_test(anyarray);
45114511
drop type xy_tuple;
4512+
--
4513+
-- Assorted tests for array subscript assignment
4514+
--
4515+
create temp table rtype (id int, ar text[]);
4516+
create function arrayassign1() returns text[] language plpgsql as $$
4517+
declare
4518+
r record;
4519+
begin
4520+
r := row(12, '{foo,bar,baz}')::rtype;
4521+
r.ar[2] := 'replace';
4522+
return r.ar;
4523+
end$$;
4524+
select arrayassign1();
4525+
arrayassign1
4526+
-------------------
4527+
{foo,replace,baz}
4528+
(1 row)
4529+
4530+
select arrayassign1(); -- try again to exercise internal caching
4531+
arrayassign1
4532+
-------------------
4533+
{foo,replace,baz}
4534+
(1 row)
4535+
4536+
create domain orderedarray as int[2]
4537+
constraint sorted check (value[1] < value[2]);
4538+
select '{1,2}'::orderedarray;
4539+
orderedarray
4540+
--------------
4541+
{1,2}
4542+
(1 row)
4543+
4544+
select '{2,1}'::orderedarray; -- fail
4545+
ERROR: value for domain orderedarray violates check constraint "sorted"
4546+
create function testoa(x1 int, x2 int, x3 int) returns orderedarray
4547+
language plpgsql as $$
4548+
declare res orderedarray;
4549+
begin
4550+
res := array[x1, x2];
4551+
res[2] := x3;
4552+
return res;
4553+
end$$;
4554+
select testoa(1,2,3);
4555+
testoa
4556+
--------
4557+
{1,3}
4558+
(1 row)
4559+
4560+
select testoa(1,2,3); -- try again to exercise internal caching
4561+
testoa
4562+
--------
4563+
{1,3}
4564+
(1 row)
4565+
4566+
select testoa(2,1,3); -- fail at initial assign
4567+
ERROR: value for domain orderedarray violates check constraint "sorted"
4568+
CONTEXT: PL/pgSQL function "testoa" line 4 at assignment
4569+
select testoa(1,2,1); -- fail at update
4570+
ERROR: value for domain orderedarray violates check constraint "sorted"
4571+
CONTEXT: PL/pgSQL function "testoa" line 5 at assignment
4572+
drop function arrayassign1();
4573+
drop function testoa(x1 int, x2 int, x3 int);

src/test/regress/sql/plpgsql.sql

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3559,3 +3559,44 @@ select foreach_test(ARRAY[[(10,20),(40,69)],[(35,78),(88,76)]]::xy_tuple[]);
35593559
35603560
drop function foreach_test(anyarray);
35613561
drop type xy_tuple;
3562+
3563+
--
3564+
-- Assorted tests for array subscript assignment
3565+
--
3566+
3567+
create temp table rtype (id int, ar text[]);
3568+
3569+
create function arrayassign1() returns text[] language plpgsql as $$
3570+
declare
3571+
r record;
3572+
begin
3573+
r := row(12, '{foo,bar,baz}')::rtype;
3574+
r.ar[2] := 'replace';
3575+
return r.ar;
3576+
end$$;
3577+
3578+
select arrayassign1();
3579+
select arrayassign1(); -- try again to exercise internal caching
3580+
3581+
create domain orderedarray as int[2]
3582+
constraint sorted check (value[1] < value[2]);
3583+
3584+
select '{1,2}'::orderedarray;
3585+
select '{2,1}'::orderedarray; -- fail
3586+
3587+
create function testoa(x1 int, x2 int, x3 int) returns orderedarray
3588+
language plpgsql as $$
3589+
declare res orderedarray;
3590+
begin
3591+
res := array[x1, x2];
3592+
res[2] := x3;
3593+
return res;
3594+
end$$;
3595+
3596+
select testoa(1,2,3);
3597+
select testoa(1,2,3); -- try again to exercise internal caching
3598+
select testoa(2,1,3); -- fail at initial assign
3599+
select testoa(1,2,1); -- fail at update
3600+
3601+
drop function arrayassign1();
3602+
drop function testoa(x1 int, x2 int, x3 int);

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy