plpgsql can assign to subscripted variables now, e.g.

x[42] := whatever;
The facility is pretty primitive because it doesn't do array slicing and
it has the same semantics as array update in SQL (array must already
be non-null, etc).  But it's a start.
This commit is contained in:
Tom Lane 2003-03-25 03:16:41 +00:00
parent 9e29b32e78
commit 2c19928301
4 changed files with 253 additions and 47 deletions

View File

@ -4,7 +4,7 @@
* procedural language * procedural language
* *
* IDENTIFICATION * IDENTIFICATION
* $Header: /cvsroot/pgsql/src/pl/plpgsql/src/gram.y,v 1.40 2002/11/10 00:35:58 momjian Exp $ * $Header: /cvsroot/pgsql/src/pl/plpgsql/src/gram.y,v 1.41 2003/03/25 03:16:40 tgl Exp $
* *
* This software is copyrighted by Jan Wieck - Hamburg. * This software is copyrighted by Jan Wieck - Hamburg.
* *
@ -105,7 +105,8 @@ static void check_assignable(PLpgSQL_datum *datum);
%type <nsitem> decl_aliasitem %type <nsitem> decl_aliasitem
%type <str> decl_stmts decl_stmt %type <str> decl_stmts decl_stmt
%type <expr> expr_until_semi expr_until_then expr_until_loop %type <expr> expr_until_semi expr_until_rightbracket
%type <expr> expr_until_then expr_until_loop
%type <expr> opt_exitcond %type <expr> opt_exitcond
%type <ival> assign_var cursor_variable %type <ival> assign_var cursor_variable
@ -822,6 +823,21 @@ assign_var : T_VARIABLE
check_assignable(yylval.variable); check_assignable(yylval.variable);
$$ = yylval.variable->dno; $$ = yylval.variable->dno;
} }
| assign_var '[' expr_until_rightbracket
{
PLpgSQL_arrayelem *new;
new = malloc(sizeof(PLpgSQL_arrayelem));
memset(new, 0, sizeof(PLpgSQL_arrayelem));
new->dtype = PLPGSQL_DTYPE_ARRAYELEM;
new->subscript = $3;
new->arrayparentno = $1;
plpgsql_adddatum((PLpgSQL_datum *)new);
$$ = new->dno;
}
; ;
stmt_if : K_IF lno expr_until_then proc_sect stmt_else K_END K_IF ';' stmt_if : K_IF lno expr_until_then proc_sect stmt_else K_END K_IF ';'
@ -1491,6 +1507,10 @@ expr_until_semi :
{ $$ = plpgsql_read_expression(';', ";"); } { $$ = plpgsql_read_expression(';', ";"); }
; ;
expr_until_rightbracket :
{ $$ = plpgsql_read_expression(']', "]"); }
;
expr_until_then : expr_until_then :
{ $$ = plpgsql_read_expression(K_THEN, "THEN"); } { $$ = plpgsql_read_expression(K_THEN, "THEN"); }
; ;
@ -1577,16 +1597,16 @@ read_sql_construct(int until,
for (;;) for (;;)
{ {
tok = yylex(); tok = yylex();
if (tok == '(') if (tok == until && parenlevel == 0)
break;
if (tok == '(' || tok == '[')
parenlevel++; parenlevel++;
else if (tok == ')') else if (tok == ')' || tok == ']')
{ {
parenlevel--; parenlevel--;
if (parenlevel < 0) if (parenlevel < 0)
elog(ERROR, "mismatched parentheses"); elog(ERROR, "mismatched parentheses");
} }
else if (parenlevel == 0 && tok == until)
break;
/* /*
* End of function definition is an error, and we don't expect to * End of function definition is an error, and we don't expect to
* hit a semicolon either (unless it's the until symbol, in which * hit a semicolon either (unless it's the until symbol, in which
@ -1988,6 +2008,9 @@ check_assignable(PLpgSQL_datum *datum)
case PLPGSQL_DTYPE_RECFIELD: case PLPGSQL_DTYPE_RECFIELD:
/* always assignable? */ /* always assignable? */
break; break;
case PLPGSQL_DTYPE_ARRAYELEM:
/* always assignable? */
break;
case PLPGSQL_DTYPE_TRIGARG: case PLPGSQL_DTYPE_TRIGARG:
yyerror("cannot assign to tg_argv"); yyerror("cannot assign to tg_argv");
break; break;

View File

@ -3,7 +3,7 @@
* procedural language * procedural language
* *
* IDENTIFICATION * IDENTIFICATION
* $Header: /cvsroot/pgsql/src/pl/plpgsql/src/pl_exec.c,v 1.82 2003/03/25 00:34:23 tgl Exp $ * $Header: /cvsroot/pgsql/src/pl/plpgsql/src/pl_exec.c,v 1.83 2003/03/25 03:16:40 tgl Exp $
* *
* This software is copyrighted by Jan Wieck - Hamburg. * This software is copyrighted by Jan Wieck - Hamburg.
* *
@ -49,6 +49,7 @@
#include "optimizer/clauses.h" #include "optimizer/clauses.h"
#include "parser/parse_expr.h" #include "parser/parse_expr.h"
#include "tcop/tcopprot.h" #include "tcop/tcopprot.h"
#include "utils/array.h"
#include "utils/builtins.h" #include "utils/builtins.h"
#include "utils/lsyscache.h" #include "utils/lsyscache.h"
#include "utils/syscache.h" #include "utils/syscache.h"
@ -136,6 +137,9 @@ static void exec_eval_datum(PLpgSQL_execstate *estate,
Oid *typeid, Oid *typeid,
Datum *value, Datum *value,
bool *isnull); bool *isnull);
static int exec_eval_subscript(PLpgSQL_execstate * estate,
PLpgSQL_expr * expr,
bool *isNull);
static Datum exec_eval_expr(PLpgSQL_execstate * estate, static Datum exec_eval_expr(PLpgSQL_execstate * estate,
PLpgSQL_expr * expr, PLpgSQL_expr * expr,
bool *isNull, bool *isNull,
@ -152,6 +156,9 @@ static Datum exec_cast_value(Datum value, Oid valtype,
Oid reqtypelem, Oid reqtypelem,
int32 reqtypmod, int32 reqtypmod,
bool *isnull); bool *isnull);
static Datum exec_simple_cast_value(Datum value, Oid valtype,
Oid reqtype, int32 reqtypmod,
bool *isnull);
static void exec_init_tuple_store(PLpgSQL_execstate * estate); static void exec_init_tuple_store(PLpgSQL_execstate * estate);
static bool compatible_tupdesc(TupleDesc td1, TupleDesc td2); static bool compatible_tupdesc(TupleDesc td1, TupleDesc td2);
static void exec_set_found(PLpgSQL_execstate * estate, bool state); static void exec_set_found(PLpgSQL_execstate * estate, bool state);
@ -237,6 +244,7 @@ plpgsql_exec_function(PLpgSQL_function * func, FunctionCallInfo fcinfo)
case PLPGSQL_DTYPE_ROW: case PLPGSQL_DTYPE_ROW:
case PLPGSQL_DTYPE_RECFIELD: case PLPGSQL_DTYPE_RECFIELD:
case PLPGSQL_DTYPE_ARRAYELEM:
estate.datums[i] = func->datums[i]; estate.datums[i] = func->datums[i];
break; break;
@ -308,6 +316,7 @@ plpgsql_exec_function(PLpgSQL_function * func, FunctionCallInfo fcinfo)
case PLPGSQL_DTYPE_ROW: case PLPGSQL_DTYPE_ROW:
case PLPGSQL_DTYPE_REC: case PLPGSQL_DTYPE_REC:
case PLPGSQL_DTYPE_RECFIELD: case PLPGSQL_DTYPE_RECFIELD:
case PLPGSQL_DTYPE_ARRAYELEM:
break; break;
default: default:
@ -507,6 +516,7 @@ plpgsql_exec_trigger(PLpgSQL_function * func,
case PLPGSQL_DTYPE_ROW: case PLPGSQL_DTYPE_ROW:
case PLPGSQL_DTYPE_RECFIELD: case PLPGSQL_DTYPE_RECFIELD:
case PLPGSQL_DTYPE_ARRAYELEM:
case PLPGSQL_DTYPE_TRIGARG: case PLPGSQL_DTYPE_TRIGARG:
estate.datums[i] = func->datums[i]; estate.datums[i] = func->datums[i];
break; break;
@ -658,6 +668,7 @@ plpgsql_exec_trigger(PLpgSQL_function * func,
case PLPGSQL_DTYPE_ROW: case PLPGSQL_DTYPE_ROW:
case PLPGSQL_DTYPE_REC: case PLPGSQL_DTYPE_REC:
case PLPGSQL_DTYPE_RECFIELD: case PLPGSQL_DTYPE_RECFIELD:
case PLPGSQL_DTYPE_ARRAYELEM:
case PLPGSQL_DTYPE_TRIGARG: case PLPGSQL_DTYPE_TRIGARG:
break; break;
@ -822,6 +833,7 @@ exec_stmt_block(PLpgSQL_execstate * estate, PLpgSQL_stmt_block * block)
break; break;
case PLPGSQL_DTYPE_RECFIELD: case PLPGSQL_DTYPE_RECFIELD:
case PLPGSQL_DTYPE_ARRAYELEM:
break; break;
default: default:
@ -1693,24 +1705,11 @@ exec_stmt_return_next(PLpgSQL_execstate * estate,
&rettype); &rettype);
/* coerce type if needed */ /* coerce type if needed */
if (!isNull && rettype != tupdesc->attrs[0]->atttypid) retval = exec_simple_cast_value(retval,
{
Oid targType = tupdesc->attrs[0]->atttypid;
Oid typInput;
Oid typElem;
FmgrInfo finfo_input;
getTypeInputInfo(targType, &typInput, &typElem);
fmgr_info(typInput, &finfo_input);
retval = exec_cast_value(retval,
rettype, rettype,
targType, tupdesc->attrs[0]->atttypid,
&finfo_input,
typElem,
tupdesc->attrs[0]->atttypmod, tupdesc->attrs[0]->atttypmod,
&isNull); &isNull);
}
nullflag = isNull ? 'n' : ' '; nullflag = isNull ? 'n' : ' ';
@ -2709,10 +2708,21 @@ exec_assign_value(PLpgSQL_execstate * estate,
bool attisnull; bool attisnull;
Oid atttype; Oid atttype;
int32 atttypmod; int32 atttypmod;
int nsubscripts;
PLpgSQL_expr *subscripts[MAXDIM];
int subscriptvals[MAXDIM];
bool havenullsubscript,
oldarrayisnull;
Oid arraytypeid,
arrayelemtypeid,
arrayInputFn;
int16 elemtyplen;
bool elemtypbyval;
char elemtypalign;
Datum oldarrayval,
coerced_value;
ArrayType *newarrayval;
HeapTuple newtup; HeapTuple newtup;
Oid typInput;
Oid typElem;
FmgrInfo finfo_input;
switch (target->dtype) switch (target->dtype)
{ {
@ -2812,15 +2822,10 @@ exec_assign_value(PLpgSQL_execstate * estate,
*/ */
atttype = SPI_gettypeid(rec->tupdesc, fno + 1); atttype = SPI_gettypeid(rec->tupdesc, fno + 1);
atttypmod = rec->tupdesc->attrs[fno]->atttypmod; atttypmod = rec->tupdesc->attrs[fno]->atttypmod;
getTypeInputInfo(atttype, &typInput, &typElem);
fmgr_info(typInput, &finfo_input);
attisnull = *isNull; attisnull = *isNull;
values[fno] = exec_cast_value(value, values[fno] = exec_simple_cast_value(value,
valtype, valtype,
atttype, atttype,
&finfo_input,
typElem,
atttypmod, atttypmod,
&attisnull); &attisnull);
if (attisnull) if (attisnull)
@ -2829,7 +2834,7 @@ exec_assign_value(PLpgSQL_execstate * estate,
nulls[fno] = ' '; nulls[fno] = ' ';
/* /*
* Avoid leaking the result of exec_cast_value, if it * Avoid leaking the result of exec_simple_cast_value, if it
* performed a conversion to a pass-by-ref type. * performed a conversion to a pass-by-ref type.
*/ */
if (!attisnull && values[fno] != value && !get_typbyval(atttype)) if (!attisnull && values[fno] != value && !get_typbyval(atttype))
@ -2856,6 +2861,103 @@ exec_assign_value(PLpgSQL_execstate * estate,
break; break;
case PLPGSQL_DTYPE_ARRAYELEM:
/*
* Target is an element of an array
*
* To handle constructs like x[1][2] := something, we have to
* be prepared to deal with a chain of arrayelem datums.
* Chase 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.)
*/
nsubscripts = 0;
do {
PLpgSQL_arrayelem *arrayelem = (PLpgSQL_arrayelem *) target;
if (nsubscripts >= MAXDIM)
elog(ERROR, "Too many subscripts");
subscripts[nsubscripts++] = arrayelem->subscript;
target = estate->datums[arrayelem->arrayparentno];
} while (target->dtype == PLPGSQL_DTYPE_ARRAYELEM);
/* Fetch current value of array datum */
exec_eval_datum(estate, target, InvalidOid,
&arraytypeid, &oldarrayval, &oldarrayisnull);
getTypeInputInfo(arraytypeid, &arrayInputFn, &arrayelemtypeid);
if (!OidIsValid(arrayelemtypeid))
elog(ERROR, "Subscripted item is not an array");
/* Evaluate the subscripts, switch into left-to-right order */
havenullsubscript = false;
for (i = 0; i < nsubscripts; i++)
{
bool subisnull;
subscriptvals[i] =
exec_eval_subscript(estate,
subscripts[nsubscripts-1-i],
&subisnull);
havenullsubscript |= subisnull;
}
/*
* Skip the assignment if we have any nulls, either in the
* original array value, the subscripts, or the righthand side.
* This is pretty bogus but it corresponds to the current
* behavior of ExecEvalArrayRef().
*/
if (oldarrayisnull || havenullsubscript || *isNull)
return;
/* Coerce source value to match array element type. */
coerced_value = exec_simple_cast_value(value,
valtype,
arrayelemtypeid,
-1,
isNull);
/*
* Build the modified array value.
*/
get_typlenbyvalalign(arrayelemtypeid,
&elemtyplen,
&elemtypbyval,
&elemtypalign);
newarrayval = array_set((ArrayType *) DatumGetPointer(oldarrayval),
nsubscripts,
subscriptvals,
coerced_value,
get_typlen(arraytypeid),
elemtyplen,
elemtypbyval,
elemtypalign,
isNull);
/*
* Assign it to the base variable.
*/
exec_assign_value(estate, target,
PointerGetDatum(newarrayval),
arraytypeid, isNull);
/*
* 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)
pfree(DatumGetPointer(coerced_value));
/*
* Avoid leaking the modified array value, too.
*/
pfree(newarrayval);
break;
default: default:
elog(ERROR, "unknown dtype %d in exec_assign_value()", elog(ERROR, "unknown dtype %d in exec_assign_value()",
target->dtype); target->dtype);
@ -2888,7 +2990,6 @@ exec_eval_datum(PLpgSQL_execstate *estate,
PLpgSQL_recfield *recfield; PLpgSQL_recfield *recfield;
PLpgSQL_trigarg *trigarg; PLpgSQL_trigarg *trigarg;
int tgargno; int tgargno;
Oid tgargoid;
int fno; int fno;
switch (datum->dtype) switch (datum->dtype)
@ -2922,9 +3023,7 @@ exec_eval_datum(PLpgSQL_execstate *estate,
case PLPGSQL_DTYPE_TRIGARG: case PLPGSQL_DTYPE_TRIGARG:
trigarg = (PLpgSQL_trigarg *) datum; trigarg = (PLpgSQL_trigarg *) datum;
*typeid = TEXTOID; *typeid = TEXTOID;
tgargno = (int) exec_eval_expr(estate, trigarg->argnum, tgargno = exec_eval_subscript(estate, trigarg->argnum, isnull);
isnull, &tgargoid);
exec_eval_cleanup(estate);
if (*isnull || tgargno < 0 || tgargno >= estate->trig_nargs) if (*isnull || tgargno < 0 || tgargno >= estate->trig_nargs)
{ {
*value = (Datum) 0; *value = (Datum) 0;
@ -2946,6 +3045,36 @@ exec_eval_datum(PLpgSQL_execstate *estate,
} }
} }
/* ----------
* exec_eval_subscript Hack to allow subscripting of result variables.
*
* The caller may already have an open eval_econtext, which we have to
* save and restore around the call of exec_eval_expr.
* ----------
*/
static int
exec_eval_subscript(PLpgSQL_execstate * estate,
PLpgSQL_expr * expr,
bool *isNull)
{
ExprContext *save_econtext;
Datum subscriptdatum;
Oid subscripttypeid;
int result;
save_econtext = estate->eval_econtext;
estate->eval_econtext = NULL;
subscriptdatum = exec_eval_expr(estate, expr, isNull, &subscripttypeid);
subscriptdatum = exec_simple_cast_value(subscriptdatum,
subscripttypeid,
INT4OID, -1,
isNull);
result = DatumGetInt32(subscriptdatum);
exec_eval_cleanup(estate);
estate->eval_econtext = save_econtext;
return result;
}
/* ---------- /* ----------
* exec_eval_expr Evaluate an expression and return * exec_eval_expr Evaluate an expression and return
* the result Datum. * the result Datum.
@ -3323,6 +3452,43 @@ exec_cast_value(Datum value, Oid valtype,
return value; return value;
} }
/* ----------
* exec_simple_cast_value Cast a value if required
*
* As above, but need not supply details about target type. Note that this
* is slower than exec_cast_value with cached type info, and so should be
* avoided in heavily used code paths.
* ----------
*/
static Datum
exec_simple_cast_value(Datum value, Oid valtype,
Oid reqtype, int32 reqtypmod,
bool *isnull)
{
if (!*isnull)
{
if (valtype != reqtype || reqtypmod != -1)
{
Oid typInput;
Oid typElem;
FmgrInfo finfo_input;
getTypeInputInfo(reqtype, &typInput, &typElem);
fmgr_info(typInput, &finfo_input);
value = exec_cast_value(value,
valtype,
reqtype,
&finfo_input,
typElem,
reqtypmod,
isnull);
}
}
return value;
}
/* ---------- /* ----------
* exec_simple_check_node - Recursively check if an expression * exec_simple_check_node - Recursively check if an expression

View File

@ -3,7 +3,7 @@
* procedural language * procedural language
* *
* IDENTIFICATION * IDENTIFICATION
* $Header: /cvsroot/pgsql/src/pl/plpgsql/src/pl_funcs.c,v 1.24 2003/03/25 00:34:23 tgl Exp $ * $Header: /cvsroot/pgsql/src/pl/plpgsql/src/pl_funcs.c,v 1.25 2003/03/25 03:16:41 tgl Exp $
* *
* This software is copyrighted by Jan Wieck - Hamburg. * This software is copyrighted by Jan Wieck - Hamburg.
* *
@ -1023,6 +1023,12 @@ plpgsql_dumptree(PLpgSQL_function * func)
((PLpgSQL_recfield *) d)->fieldname, ((PLpgSQL_recfield *) d)->fieldname,
((PLpgSQL_recfield *) d)->recparentno); ((PLpgSQL_recfield *) d)->recparentno);
break; break;
case PLPGSQL_DTYPE_ARRAYELEM:
printf("ARRAYELEM of VAR %d subscript ",
((PLpgSQL_arrayelem *) d)->arrayparentno);
dump_expr(((PLpgSQL_arrayelem *) d)->subscript);
printf("\n");
break;
case PLPGSQL_DTYPE_TRIGARG: case PLPGSQL_DTYPE_TRIGARG:
printf("TRIGARG "); printf("TRIGARG ");
dump_expr(((PLpgSQL_trigarg *) d)->argnum); dump_expr(((PLpgSQL_trigarg *) d)->argnum);

View File

@ -3,7 +3,7 @@
* procedural language * procedural language
* *
* IDENTIFICATION * IDENTIFICATION
* $Header: /cvsroot/pgsql/src/pl/plpgsql/src/plpgsql.h,v 1.32 2003/03/25 00:34:24 tgl Exp $ * $Header: /cvsroot/pgsql/src/pl/plpgsql/src/plpgsql.h,v 1.33 2003/03/25 03:16:41 tgl Exp $
* *
* This software is copyrighted by Jan Wieck - Hamburg. * This software is copyrighted by Jan Wieck - Hamburg.
* *
@ -72,6 +72,7 @@ enum
PLPGSQL_DTYPE_ROW, PLPGSQL_DTYPE_ROW,
PLPGSQL_DTYPE_REC, PLPGSQL_DTYPE_REC,
PLPGSQL_DTYPE_RECFIELD, PLPGSQL_DTYPE_RECFIELD,
PLPGSQL_DTYPE_ARRAYELEM,
PLPGSQL_DTYPE_EXPR, PLPGSQL_DTYPE_EXPR,
PLPGSQL_DTYPE_TRIGARG PLPGSQL_DTYPE_TRIGARG
}; };
@ -154,7 +155,8 @@ typedef struct
/* /*
* PLpgSQL_datum is the common supertype for PLpgSQL_expr, PLpgSQL_var, * PLpgSQL_datum is the common supertype for PLpgSQL_expr, PLpgSQL_var,
* PLpgSQL_row, PLpgSQL_rec, PLpgSQL_recfield, PLpgSQL_trigarg * PLpgSQL_row, PLpgSQL_rec, PLpgSQL_recfield, PLpgSQL_arrayelem, and
* PLpgSQL_trigarg
*/ */
typedef struct typedef struct
{ /* Generic datum array item */ { /* Generic datum array item */
@ -231,10 +233,19 @@ typedef struct
int dtype; int dtype;
int rfno; int rfno;
char *fieldname; char *fieldname;
int recparentno; /* recno of parent record */ int recparentno; /* dno of parent record */
} PLpgSQL_recfield; } PLpgSQL_recfield;
typedef struct
{ /* Element of array variable */
int dtype;
int dno;
PLpgSQL_expr *subscript;
int arrayparentno; /* dno of parent array variable */
} PLpgSQL_arrayelem;
typedef struct typedef struct
{ /* Positional argument to trigger */ { /* Positional argument to trigger */
int dtype; int dtype;