diff --git a/src/pl/plpgsql/src/gram.y b/src/pl/plpgsql/src/gram.y index f8adc55dce..23dc894029 100644 --- a/src/pl/plpgsql/src/gram.y +++ b/src/pl/plpgsql/src/gram.y @@ -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 decl_aliasitem %type decl_stmts decl_stmt -%type expr_until_semi expr_until_then expr_until_loop +%type expr_until_semi expr_until_rightbracket +%type expr_until_then expr_until_loop %type opt_exitcond %type 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; diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c index 984be78eaf..d105c0e8dc 100644 --- a/src/pl/plpgsql/src/pl_exec.c +++ b/src/pl/plpgsql/src/pl_exec.c @@ -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 diff --git a/src/pl/plpgsql/src/pl_funcs.c b/src/pl/plpgsql/src/pl_funcs.c index 6ad1a3b0f3..c19399e336 100644 --- a/src/pl/plpgsql/src/pl_funcs.c +++ b/src/pl/plpgsql/src/pl_funcs.c @@ -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); diff --git a/src/pl/plpgsql/src/plpgsql.h b/src/pl/plpgsql/src/plpgsql.h index 2661228162..9706db602c 100644 --- a/src/pl/plpgsql/src/plpgsql.h +++ b/src/pl/plpgsql/src/plpgsql.h @@ -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;