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
*
* 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.
*
@ -105,7 +105,8 @@ static void check_assignable(PLpgSQL_datum *datum);
%type <nsitem> decl_aliasitem
%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 <ival> assign_var cursor_variable
@ -822,6 +823,21 @@ assign_var : T_VARIABLE
check_assignable(yylval.variable);
$$ = 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 ';'
@ -1491,6 +1507,10 @@ expr_until_semi :
{ $$ = plpgsql_read_expression(';', ";"); }
;
expr_until_rightbracket :
{ $$ = plpgsql_read_expression(']', "]"); }
;
expr_until_then :
{ $$ = plpgsql_read_expression(K_THEN, "THEN"); }
;
@ -1577,16 +1597,16 @@ read_sql_construct(int until,
for (;;)
{
tok = yylex();
if (tok == '(')
if (tok == until && parenlevel == 0)
break;
if (tok == '(' || tok == '[')
parenlevel++;
else if (tok == ')')
else if (tok == ')' || tok == ']')
{
parenlevel--;
if (parenlevel < 0)
elog(ERROR, "mismatched parentheses");
}
else if (parenlevel == 0 && tok == until)
break;
/*
* 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
@ -1988,6 +2008,9 @@ check_assignable(PLpgSQL_datum *datum)
case PLPGSQL_DTYPE_RECFIELD:
/* always assignable? */
break;
case PLPGSQL_DTYPE_ARRAYELEM:
/* always assignable? */
break;
case PLPGSQL_DTYPE_TRIGARG:
yyerror("cannot assign to tg_argv");
break;

View File

@ -3,7 +3,7 @@
* procedural language
*
* 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.
*
@ -49,6 +49,7 @@
#include "optimizer/clauses.h"
#include "parser/parse_expr.h"
#include "tcop/tcopprot.h"
#include "utils/array.h"
#include "utils/builtins.h"
#include "utils/lsyscache.h"
#include "utils/syscache.h"
@ -136,6 +137,9 @@ static void exec_eval_datum(PLpgSQL_execstate *estate,
Oid *typeid,
Datum *value,
bool *isnull);
static int exec_eval_subscript(PLpgSQL_execstate * estate,
PLpgSQL_expr * expr,
bool *isNull);
static Datum exec_eval_expr(PLpgSQL_execstate * estate,
PLpgSQL_expr * expr,
bool *isNull,
@ -152,6 +156,9 @@ static Datum exec_cast_value(Datum value, Oid valtype,
Oid reqtypelem,
int32 reqtypmod,
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 bool compatible_tupdesc(TupleDesc td1, TupleDesc td2);
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_RECFIELD:
case PLPGSQL_DTYPE_ARRAYELEM:
estate.datums[i] = func->datums[i];
break;
@ -308,6 +316,7 @@ plpgsql_exec_function(PLpgSQL_function * func, FunctionCallInfo fcinfo)
case PLPGSQL_DTYPE_ROW:
case PLPGSQL_DTYPE_REC:
case PLPGSQL_DTYPE_RECFIELD:
case PLPGSQL_DTYPE_ARRAYELEM:
break;
default:
@ -507,6 +516,7 @@ plpgsql_exec_trigger(PLpgSQL_function * func,
case PLPGSQL_DTYPE_ROW:
case PLPGSQL_DTYPE_RECFIELD:
case PLPGSQL_DTYPE_ARRAYELEM:
case PLPGSQL_DTYPE_TRIGARG:
estate.datums[i] = func->datums[i];
break;
@ -658,6 +668,7 @@ plpgsql_exec_trigger(PLpgSQL_function * func,
case PLPGSQL_DTYPE_ROW:
case PLPGSQL_DTYPE_REC:
case PLPGSQL_DTYPE_RECFIELD:
case PLPGSQL_DTYPE_ARRAYELEM:
case PLPGSQL_DTYPE_TRIGARG:
break;
@ -822,6 +833,7 @@ exec_stmt_block(PLpgSQL_execstate * estate, PLpgSQL_stmt_block * block)
break;
case PLPGSQL_DTYPE_RECFIELD:
case PLPGSQL_DTYPE_ARRAYELEM:
break;
default:
@ -1693,24 +1705,11 @@ exec_stmt_return_next(PLpgSQL_execstate * estate,
&rettype);
/* coerce type if needed */
if (!isNull && rettype != tupdesc->attrs[0]->atttypid)
{
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,
targType,
&finfo_input,
typElem,
tupdesc->attrs[0]->atttypmod,
&isNull);
}
retval = exec_simple_cast_value(retval,
rettype,
tupdesc->attrs[0]->atttypid,
tupdesc->attrs[0]->atttypmod,
&isNull);
nullflag = isNull ? 'n' : ' ';
@ -2709,10 +2708,21 @@ exec_assign_value(PLpgSQL_execstate * estate,
bool attisnull;
Oid atttype;
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;
Oid typInput;
Oid typElem;
FmgrInfo finfo_input;
switch (target->dtype)
{
@ -2812,24 +2822,19 @@ exec_assign_value(PLpgSQL_execstate * estate,
*/
atttype = SPI_gettypeid(rec->tupdesc, fno + 1);
atttypmod = rec->tupdesc->attrs[fno]->atttypmod;
getTypeInputInfo(atttype, &typInput, &typElem);
fmgr_info(typInput, &finfo_input);
attisnull = *isNull;
values[fno] = exec_cast_value(value,
valtype,
atttype,
&finfo_input,
typElem,
atttypmod,
&attisnull);
values[fno] = exec_simple_cast_value(value,
valtype,
atttype,
atttypmod,
&attisnull);
if (attisnull)
nulls[fno] = 'n';
else
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.
*/
if (!attisnull && values[fno] != value && !get_typbyval(atttype))
@ -2856,6 +2861,103 @@ exec_assign_value(PLpgSQL_execstate * estate,
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:
elog(ERROR, "unknown dtype %d in exec_assign_value()",
target->dtype);
@ -2888,7 +2990,6 @@ exec_eval_datum(PLpgSQL_execstate *estate,
PLpgSQL_recfield *recfield;
PLpgSQL_trigarg *trigarg;
int tgargno;
Oid tgargoid;
int fno;
switch (datum->dtype)
@ -2922,9 +3023,7 @@ exec_eval_datum(PLpgSQL_execstate *estate,
case PLPGSQL_DTYPE_TRIGARG:
trigarg = (PLpgSQL_trigarg *) datum;
*typeid = TEXTOID;
tgargno = (int) exec_eval_expr(estate, trigarg->argnum,
isnull, &tgargoid);
exec_eval_cleanup(estate);
tgargno = exec_eval_subscript(estate, trigarg->argnum, isnull);
if (*isnull || tgargno < 0 || tgargno >= estate->trig_nargs)
{
*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
* the result Datum.
@ -3323,6 +3452,43 @@ exec_cast_value(Datum value, Oid valtype,
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

View File

@ -3,7 +3,7 @@
* procedural language
*
* 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.
*
@ -1023,6 +1023,12 @@ plpgsql_dumptree(PLpgSQL_function * func)
((PLpgSQL_recfield *) d)->fieldname,
((PLpgSQL_recfield *) d)->recparentno);
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:
printf("TRIGARG ");
dump_expr(((PLpgSQL_trigarg *) d)->argnum);

View File

@ -3,7 +3,7 @@
* procedural language
*
* 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.
*
@ -72,6 +72,7 @@ enum
PLPGSQL_DTYPE_ROW,
PLPGSQL_DTYPE_REC,
PLPGSQL_DTYPE_RECFIELD,
PLPGSQL_DTYPE_ARRAYELEM,
PLPGSQL_DTYPE_EXPR,
PLPGSQL_DTYPE_TRIGARG
};
@ -154,7 +155,8 @@ typedef struct
/*
* 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
{ /* Generic datum array item */
@ -231,10 +233,19 @@ typedef struct
int dtype;
int rfno;
char *fieldname;
int recparentno; /* recno of parent record */
int recparentno; /* dno of parent record */
} 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
{ /* Positional argument to trigger */
int dtype;