mirror of
https://git.postgresql.org/git/postgresql.git
synced 2025-01-24 18:55:04 +08:00
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:
parent
9e29b32e78
commit
2c19928301
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
Loading…
Reference in New Issue
Block a user