Skip to content

Commit 428c345

Browse files
committed
fix processing of out parameters of procedures
1 parent 08a9fe8 commit 428c345

File tree

5 files changed

+108
-31
lines changed

5 files changed

+108
-31
lines changed

expected/plpgsql_check_active-15.out

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -550,3 +550,41 @@ select * from plpgsql_check_function('prtest'); --ok
550550
(0 rows)
551551

552552
drop procedure prtest();
553+
create function return_constant_refcursor() returns refcursor as $$
554+
declare
555+
rc constant refcursor;
556+
begin
557+
open rc for select a from rc_test;
558+
return rc;
559+
end
560+
$$ language plpgsql;
561+
create table rc_test(a int);
562+
select * from plpgsql_check_function('return_constant_refcursor');
563+
plpgsql_check_function
564+
-------------------------------------------------------
565+
error:22005:5:OPEN:variable "rc" is declared CONSTANT
566+
(1 row)
567+
568+
drop table rc_test;
569+
drop function return_constant_refcursor();
570+
create procedure p1(a int, out b int)
571+
as $$
572+
begin
573+
b := a + 10;
574+
end;
575+
$$ language plpgsql;
576+
create function f1()
577+
returns void as $$
578+
declare b constant int;
579+
begin
580+
call p1(10, b);
581+
end;
582+
$$ language plpgsql;
583+
select * from plpgsql_check_function('f1');
584+
plpgsql_check_function
585+
------------------------------------------------------
586+
error:22005:4:CALL:variable "b" is declared CONSTANT
587+
(1 row)
588+
589+
drop function f1();
590+
drop procedure p1(int, int);

sql/plpgsql_check_active-15.sql

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -325,3 +325,39 @@ $$ language plpgsql;
325325
select * from plpgsql_check_function('prtest'); --ok
326326

327327
drop procedure prtest();
328+
329+
create function return_constant_refcursor() returns refcursor as $$
330+
declare
331+
rc constant refcursor;
332+
begin
333+
open rc for select a from rc_test;
334+
return rc;
335+
end
336+
$$ language plpgsql;
337+
338+
create table rc_test(a int);
339+
340+
select * from plpgsql_check_function('return_constant_refcursor');
341+
342+
drop table rc_test;
343+
drop function return_constant_refcursor();
344+
345+
create procedure p1(a int, out b int)
346+
as $$
347+
begin
348+
b := a + 10;
349+
end;
350+
$$ language plpgsql;
351+
352+
create function f1()
353+
returns void as $$
354+
declare b constant int;
355+
begin
356+
call p1(10, b);
357+
end;
358+
$$ language plpgsql;
359+
360+
select * from plpgsql_check_function('f1');
361+
362+
drop function f1();
363+
drop procedure p1(int, int);

src/assign.c

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -95,8 +95,8 @@ plpgsql_check_row_or_rec(PLpgSQL_checkstate *cstate, PLpgSQL_row *row, PLpgSQL_r
9595
}
9696
}
9797

98-
static void
99-
exec_check_assignable(PLpgSQL_execstate *estate, int dno)
98+
void
99+
plpgsql_check_is_assignable(PLpgSQL_execstate *estate, int dno)
100100
{
101101
PLpgSQL_datum *datum;
102102

@@ -121,7 +121,7 @@ exec_check_assignable(PLpgSQL_execstate *estate, int dno)
121121
break;
122122
case PLPGSQL_DTYPE_RECFIELD:
123123
/* assignable if parent record is */
124-
exec_check_assignable(estate,
124+
plpgsql_check_is_assignable(estate,
125125
((PLpgSQL_recfield *) datum)->recparentno);
126126
break;
127127
default:
@@ -131,9 +131,6 @@ exec_check_assignable(PLpgSQL_execstate *estate, int dno)
131131

132132
#else
133133

134-
elog(NOTICE, "kuku");
135-
136-
137134
if (datum->dtype == PLPGSQL_DTYPE_VAR)
138135
if (((PLpgSQL_var *) datum)->isconst)
139136
ereport(ERROR,
@@ -154,7 +151,7 @@ plpgsql_check_target(PLpgSQL_checkstate *cstate, int varno, Oid *expected_typoid
154151
{
155152
PLpgSQL_datum *target = cstate->estate->datums[varno];
156153

157-
exec_check_assignable(cstate->estate, varno);
154+
plpgsql_check_is_assignable(cstate->estate, varno);
158155
plpgsql_check_record_variable_usage(cstate, varno, true);
159156

160157
switch (target->dtype)

src/plpgsql_check.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,8 @@ extern void plpgsql_check_assign_tupdesc_row_or_rec(PLpgSQL_checkstate *cstate,
181181
extern void plpgsql_check_recval_assign_tupdesc(PLpgSQL_checkstate *cstate, PLpgSQL_rec *rec, TupleDesc tupdesc, bool is_null);
182182
extern void plpgsql_check_recval_init(PLpgSQL_rec *rec);
183183
extern void plpgsql_check_recval_release(PLpgSQL_rec *rec);
184+
extern void plpgsql_check_is_assignable(PLpgSQL_execstate *estate, int dno);
185+
184186

185187
/*
186188
* functions from format.c

src/typdesc.c

Lines changed: 28 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -46,14 +46,14 @@ plpgsql_check_CallExprGetRowTarget(PLpgSQL_checkstate *cstate, PLpgSQL_expr *Cal
4646
{
4747
PLpgSQL_row *row;
4848
CachedPlanSource *plansource;
49-
HeapTuple tuple;
50-
List *funcargs;
49+
CallStmt *stmt;
50+
HeapTuple tuple;
5151
Oid *argtypes;
5252
char **argnames;
5353
char *argmodes;
54-
ListCell *lc;
5554
int i;
5655
int nfields = 0;
56+
int numargs;
5757

5858
plansource = plpgsql_check_get_plan_source(cstate, CallExpr->plan);
5959

@@ -64,7 +64,8 @@ plpgsql_check_CallExprGetRowTarget(PLpgSQL_checkstate *cstate, PLpgSQL_expr *Cal
6464
if (!IsA(node, CallStmt))
6565
elog(ERROR, "returned row from not a CallStmt");
6666

67-
funcexpr = castNode(CallStmt, node)->funcexpr;
67+
stmt = castNode(CallStmt, node);
68+
funcexpr = stmt->funcexpr;
6869

6970
/*
7071
* Get the argument modes
@@ -73,43 +74,45 @@ plpgsql_check_CallExprGetRowTarget(PLpgSQL_checkstate *cstate, PLpgSQL_expr *Cal
7374
if (!HeapTupleIsValid(tuple))
7475
elog(ERROR, "cache lookup failed for function %u", funcexpr->funcid);
7576

76-
/* Extract function arguments, and expand any named-arg notation */
77-
funcargs = expand_function_arguments(funcexpr->args,
78-
#if PG_VERSION_NUM >= 140000
79-
true,
80-
#endif
81-
funcexpr->funcresulttype,
82-
tuple);
83-
84-
get_func_arg_info(tuple, &argtypes, &argnames, &argmodes);
77+
/*
78+
* Get the argument names and modes, so that we can deliver on-point error
79+
* messages when something is wrong.
80+
*/
81+
numargs = get_func_arg_info(tuple, &argtypes, &argnames, &argmodes);
8582

8683
ReleaseSysCache(tuple);
8784

88-
row = palloc0(sizeof(PLpgSQL_row));
85+
row = (PLpgSQL_row *) palloc0(sizeof(PLpgSQL_row));
8986
row->dtype = PLPGSQL_DTYPE_ROW;
90-
row->dno = -1;
9187
row->refname = NULL;
92-
row->lineno = 0;
93-
row->varnos = palloc(sizeof(int) * list_length(funcargs));
88+
row->dno = -1;
89+
row->lineno = -1;
90+
row->varnos = (int *) palloc(numargs * sizeof(int));
9491

9592
/*
96-
* Construct row
93+
* Examine procedure's argument list. Each output arg position should be
94+
* an unadorned plpgsql variable (Datum), which we can insert into the row
95+
* Datum.
9796
*/
98-
i = 0;
99-
foreach(lc, funcargs)
97+
nfields = 0;
98+
for (i = 0; i < numargs; i++)
10099
{
101-
Node *n = lfirst(lc);
102-
103100
if (argmodes &&
104101
(argmodes[i] == PROARGMODE_INOUT ||
105102
argmodes[i] == PROARGMODE_OUT))
106103
{
104+
Node *n = list_nth(stmt->outargs, nfields);
105+
107106
if (IsA(n, Param))
108107
{
109108
Param *param = (Param *) n;
109+
int dno;
110110

111111
/* paramid is offset by 1 (see make_datum_param()) */
112-
row->varnos[nfields++] = param->paramid - 1;
112+
dno = param->paramid - 1;
113+
/* must check assignability now, because grammar can't */
114+
plpgsql_check_is_assignable(cstate->estate, dno);
115+
row->varnos[nfields++] = dno;
113116
}
114117
else
115118
{
@@ -126,9 +129,10 @@ plpgsql_check_CallExprGetRowTarget(PLpgSQL_checkstate *cstate, PLpgSQL_expr *Cal
126129
i + 1)));
127130
}
128131
}
129-
i++;
130132
}
131133

134+
Assert(nfields == list_length(stmt->outargs));
135+
132136
row->nfields = nfields;
133137

134138
/* Don't return empty row variable */

0 commit comments

Comments
 (0)