Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
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)