Make the world safe (more or less) for dropped columns in plpgsql rowtypes.

This commit is contained in:
Tom Lane 2003-09-25 23:02:12 +00:00
parent a039148cad
commit 2848dc5fea
7 changed files with 259 additions and 196 deletions

View File

@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/executor/execQual.c,v 1.145 2003/09/25 06:57:59 petere Exp $
* $Header: /cvsroot/pgsql/src/backend/executor/execQual.c,v 1.146 2003/09/25 23:02:11 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -315,23 +315,6 @@ ExecEvalAggref(AggrefExprState *aggref, ExprContext *econtext, bool *isNull)
*
* Returns a Datum whose value is the value of a range
* variable with respect to given expression context.
*
*
* As an entry condition, we expect that the datatype the
* plan expects to get (as told by our "variable" argument) is in
* fact the datatype of the attribute the plan says to fetch (as
* seen in the current context, identified by our "econtext"
* argument).
*
* If we fetch a Type A attribute and Caller treats it as if it
* were Type B, there will be undefined results (e.g. crash).
* One way these might mismatch now is that we're accessing a
* catalog class and the type information in the pg_attribute
* class does not match the hardcoded pg_attribute information
* (in pg_attribute.h) for the class in question.
*
* We have an Assert to make sure this entry condition is met.
*
* ---------------------------------------------------------------- */
static Datum
ExecEvalVar(Var *variable, ExprContext *econtext, bool *isNull)
@ -369,11 +352,40 @@ ExecEvalVar(Var *variable, ExprContext *econtext, bool *isNull)
attnum = variable->varattno;
/* (See prolog for explanation of this Assert) */
Assert(attnum <= 0 ||
(attnum - 1 <= tuple_type->natts - 1 &&
tuple_type->attrs[attnum - 1] != NULL &&
variable->vartype == tuple_type->attrs[attnum - 1]->atttypid));
/*
* Some checks that are only applied for user attribute numbers
* (bogus system attnums will be caught inside heap_getattr).
*/
if (attnum > 0)
{
/*
* This assert checks that the attnum is valid.
*/
Assert(attnum <= tuple_type->natts &&
tuple_type->attrs[attnum - 1] != NULL);
/*
* If the attribute's column has been dropped, we force a NULL result.
* This case should not happen in normal use, but it could happen if
* we are executing a plan cached before the column was dropped.
*/
if (tuple_type->attrs[attnum - 1]->attisdropped)
{
*isNull = true;
return (Datum) 0;
}
/*
* This assert checks that the datatype the plan expects to get (as
* told by our "variable" argument) is in fact the datatype of the
* attribute being fetched (as seen in the current context, identified
* by our "econtext" argument). Otherwise crashes are likely.
*
* Note that we can't check dropped columns, since their atttypid
* has been zeroed.
*/
Assert(variable->vartype == tuple_type->attrs[attnum - 1]->atttypid);
}
/*
* If the attribute number is invalid, then we are supposed to return

View File

@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/executor/nodeFunctionscan.c,v 1.21 2003/09/25 06:57:59 petere Exp $
* $Header: /cvsroot/pgsql/src/backend/executor/nodeFunctionscan.c,v 1.22 2003/09/25 23:02:12 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -35,7 +35,7 @@
static TupleTableSlot *FunctionNext(FunctionScanState *node);
static bool tupledesc_mismatch(TupleDesc tupdesc1, TupleDesc tupdesc2);
static bool tupledesc_match(TupleDesc dst_tupdesc, TupleDesc src_tupdesc);
/* ----------------------------------------------------------------
* Scan Support
@ -86,8 +86,7 @@ FunctionNext(FunctionScanState *node)
* need to do this for functions returning RECORD, but might as
* well do it always.
*/
if (funcTupdesc &&
tupledesc_mismatch(node->tupdesc, funcTupdesc))
if (funcTupdesc && !tupledesc_match(node->tupdesc, funcTupdesc))
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("query-specified return row and actual function return row do not match")));
@ -364,26 +363,36 @@ ExecFunctionReScan(FunctionScanState *node, ExprContext *exprCtxt)
tuplestore_rescan(node->tuplestorestate);
}
/*
* Check that function result tuple type (src_tupdesc) matches or can be
* considered to match what the query expects (dst_tupdesc).
*
* We really only care about number of attributes and data type.
* Also, we can ignore type mismatch on columns that are dropped in the
* destination type, so long as the physical storage matches. This is
* helpful in some cases involving out-of-date cached plans.
*/
static bool
tupledesc_mismatch(TupleDesc tupdesc1, TupleDesc tupdesc2)
tupledesc_match(TupleDesc dst_tupdesc, TupleDesc src_tupdesc)
{
int i;
if (tupdesc1->natts != tupdesc2->natts)
return true;
if (dst_tupdesc->natts != src_tupdesc->natts)
return false;
for (i = 0; i < tupdesc1->natts; i++)
for (i = 0; i < dst_tupdesc->natts; i++)
{
Form_pg_attribute attr1 = tupdesc1->attrs[i];
Form_pg_attribute attr2 = tupdesc2->attrs[i];
Form_pg_attribute dattr = dst_tupdesc->attrs[i];
Form_pg_attribute sattr = src_tupdesc->attrs[i];
/*
* We really only care about number of attributes and data type
*/
if (attr1->atttypid != attr2->atttypid)
return true;
if (dattr->atttypid == sattr->atttypid)
continue; /* no worries */
if (!dattr->attisdropped)
return false;
if (dattr->attlen != sattr->attlen ||
dattr->attalign != sattr->attalign)
return false;
}
return false;
return true;
}

View File

@ -4,7 +4,7 @@
* procedural language
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/pl/plpgsql/src/gram.y,v 1.46 2003/07/27 21:49:54 tgl Exp $
* $Header: /cvsroot/pgsql/src/pl/plpgsql/src/gram.y,v 1.47 2003/09/25 23:02:12 tgl Exp $
*
* This software is copyrighted by Jan Wieck - Hamburg.
*
@ -47,7 +47,6 @@ static PLpgSQL_expr *read_sql_stmt(const char *sqlstart);
static PLpgSQL_type *read_datatype(int tok);
static PLpgSQL_stmt *make_select_stmt(void);
static PLpgSQL_stmt *make_fetch_stmt(void);
static PLpgSQL_expr *make_tupret_expr(PLpgSQL_row *row);
static void check_assignable(PLpgSQL_datum *datum);
%}
@ -493,7 +492,7 @@ decl_cursor_arglist : decl_cursor_arg
new->dtype = PLPGSQL_DTYPE_ROW;
new->refname = strdup("*internal*");
new->lineno = plpgsql_scanner_lineno();
new->rowtypeclass = InvalidOid;
new->rowtupdesc = NULL;
/*
* We make temporary fieldnames/varnos arrays that
* are much bigger than necessary. We will resize
@ -1176,24 +1175,24 @@ stmt_return : K_RETURN lno
new = malloc(sizeof(PLpgSQL_stmt_return));
memset(new, 0, sizeof(PLpgSQL_stmt_return));
new->expr = NULL;
new->retrecno = -1;
new->retrowno = -1;
if (plpgsql_curr_compile->fn_retistuple &&
!plpgsql_curr_compile->fn_retset)
{
new->retrecno = -1;
switch (yylex())
{
case K_NULL:
new->expr = NULL;
break;
case T_ROW:
new->expr = make_tupret_expr(yylval.row);
new->retrowno = yylval.row->rowno;
break;
case T_RECORD:
new->retrecno = yylval.rec->recno;
new->expr = NULL;
break;
default:
@ -1874,7 +1873,7 @@ make_select_stmt(void)
row->dtype = PLPGSQL_DTYPE_ROW;
row->refname = strdup("*internal*");
row->lineno = plpgsql_scanner_lineno();
row->rowtypeclass = InvalidOid;
row->rowtupdesc = NULL;
row->nfields = nfields;
row->fieldnames = malloc(sizeof(char *) * nfields);
row->varnos = malloc(sizeof(int) * nfields);
@ -2007,7 +2006,7 @@ make_fetch_stmt(void)
row->dtype = PLPGSQL_DTYPE_ROW;
row->refname = strdup("*internal*");
row->lineno = plpgsql_scanner_lineno();
row->rowtypeclass = InvalidOid;
row->rowtupdesc = NULL;
row->nfields = nfields;
row->fieldnames = malloc(sizeof(char *) * nfields);
row->varnos = malloc(sizeof(int) * nfields);
@ -2041,36 +2040,6 @@ make_fetch_stmt(void)
}
static PLpgSQL_expr *
make_tupret_expr(PLpgSQL_row *row)
{
PLpgSQL_dstring ds;
PLpgSQL_expr *expr;
int i;
char buf[16];
expr = malloc(sizeof(PLpgSQL_expr) + sizeof(int) * (row->nfields - 1));
expr->dtype = PLPGSQL_DTYPE_EXPR;
plpgsql_dstring_init(&ds);
plpgsql_dstring_append(&ds, "SELECT ");
for (i = 0; i < row->nfields; i++)
{
sprintf(buf, "%s$%d", (i > 0) ? "," : "", i + 1);
plpgsql_dstring_append(&ds, buf);
expr->params[i] = row->varnos[i];
}
expr->query = strdup(plpgsql_dstring_get(&ds));
expr->plan = NULL;
expr->plan_argtypes = NULL;
expr->nparams = row->nfields;
plpgsql_dstring_free(&ds);
return expr;
}
static void
check_assignable(PLpgSQL_datum *datum)
{

View File

@ -3,7 +3,7 @@
* procedural language
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/pl/plpgsql/src/pl_comp.c,v 1.67 2003/08/18 19:16:02 tgl Exp $
* $Header: /cvsroot/pgsql/src/pl/plpgsql/src/pl_comp.c,v 1.68 2003/09/25 23:02:12 tgl Exp $
*
* This software is copyrighted by Jan Wieck - Hamburg.
*
@ -899,7 +899,8 @@ plpgsql_parse_dblword(char *word)
row = (PLpgSQL_row *) (plpgsql_Datums[ns->itemno]);
for (i = 0; i < row->nfields; i++)
{
if (strcmp(row->fieldnames[i], cp[1]) == 0)
if (row->fieldnames[i] &&
strcmp(row->fieldnames[i], cp[1]) == 0)
{
plpgsql_yylval.var = (PLpgSQL_var *) (plpgsql_Datums[row->varnos[i]]);
pfree(cp[0]);
@ -1005,7 +1006,8 @@ plpgsql_parse_tripword(char *word)
row = (PLpgSQL_row *) (plpgsql_Datums[ns->itemno]);
for (i = 0; i < row->nfields; i++)
{
if (strcmp(row->fieldnames[i], cp[2]) == 0)
if (row->fieldnames[i] &&
strcmp(row->fieldnames[i], cp[2]) == 0)
{
plpgsql_yylval.var = (PLpgSQL_var *) (plpgsql_Datums[row->varnos[i]]);
pfree(cp[0]);
@ -1396,6 +1398,8 @@ plpgsql_parse_wordrowtype(char *word)
*/
plpgsql_yylval.row = plpgsql_build_rowtype(classOid);
plpgsql_adddatum((PLpgSQL_datum *) plpgsql_yylval.row);
pfree(cp[0]);
pfree(cp[1]);
@ -1439,6 +1443,8 @@ plpgsql_parse_dblwordrowtype(char *word)
*/
plpgsql_yylval.row = plpgsql_build_rowtype(classOid);
plpgsql_adddatum((PLpgSQL_datum *) plpgsql_yylval.row);
pfree(cp);
return T_ROW;
@ -1451,23 +1457,20 @@ PLpgSQL_row *
plpgsql_build_rowtype(Oid classOid)
{
PLpgSQL_row *row;
HeapTuple classtup;
Relation rel;
Form_pg_class classStruct;
const char *relname;
int i;
MemoryContext oldcxt;
/*
* Fetch the pg_class tuple.
* Open the relation to get info.
*/
classtup = SearchSysCache(RELOID,
ObjectIdGetDatum(classOid),
0, 0, 0);
if (!HeapTupleIsValid(classtup))
elog(ERROR, "cache lookup failed for relation %u", classOid);
classStruct = (Form_pg_class) GETSTRUCT(classtup);
relname = NameStr(classStruct->relname);
rel = heap_open(classOid, AccessShareLock);
classStruct = RelationGetForm(rel);
relname = RelationGetRelationName(rel);
/* accept relation, sequence, view, or type pg_class entries */
/* accept relation, sequence, view, or composite type entries */
if (classStruct->relkind != RELKIND_RELATION &&
classStruct->relkind != RELKIND_SEQUENCE &&
classStruct->relkind != RELKIND_VIEW &&
@ -1484,78 +1487,88 @@ plpgsql_build_rowtype(Oid classOid)
memset(row, 0, sizeof(PLpgSQL_row));
row->dtype = PLPGSQL_DTYPE_ROW;
/*
* This is a bit ugly --- need a permanent copy of the rel's tupdesc.
* Someday all these mallocs should go away in favor of a per-function
* memory context ...
*/
oldcxt = MemoryContextSwitchTo(TopMemoryContext);
row->rowtupdesc = CreateTupleDescCopy(RelationGetDescr(rel));
MemoryContextSwitchTo(oldcxt);
row->nfields = classStruct->relnatts;
row->rowtypeclass = classStruct->reltype;
row->fieldnames = malloc(sizeof(char *) * row->nfields);
row->varnos = malloc(sizeof(int) * row->nfields);
for (i = 0; i < row->nfields; i++)
{
HeapTuple attrtup;
Form_pg_attribute attrStruct;
HeapTuple typetup;
const char *attname;
PLpgSQL_var *var;
/*
* Get the attribute and it's type
* Get the attribute and check for dropped column
*/
attrtup = SearchSysCache(ATTNUM,
ObjectIdGetDatum(classOid),
Int16GetDatum(i + 1),
0, 0);
if (!HeapTupleIsValid(attrtup))
elog(ERROR, "cache lookup failed for attribute %d of relation %u",
i + 1, classOid);
attrStruct = (Form_pg_attribute) GETSTRUCT(attrtup);
attrStruct = RelationGetDescr(rel)->attrs[i];
attname = NameStr(attrStruct->attname);
if (!attrStruct->attisdropped)
{
const char *attname;
HeapTuple typetup;
PLpgSQL_var *var;
typetup = SearchSysCache(TYPEOID,
ObjectIdGetDatum(attrStruct->atttypid),
0, 0, 0);
if (!HeapTupleIsValid(typetup))
elog(ERROR, "cache lookup failed for type %u",
attrStruct->atttypid);
attname = NameStr(attrStruct->attname);
/*
* Create the internal variable
*
* We know if the table definitions contain a default value or if the
* field is declared in the table as NOT NULL. But it's possible
* to create a table field as NOT NULL without a default value and
* that would lead to problems later when initializing the
* variables due to entering a block at execution time. Thus we
* ignore this information for now.
*/
var = malloc(sizeof(PLpgSQL_var));
memset(var, 0, sizeof(PLpgSQL_var));
var->dtype = PLPGSQL_DTYPE_VAR;
var->refname = malloc(strlen(relname) + strlen(attname) + 2);
strcpy(var->refname, relname);
strcat(var->refname, ".");
strcat(var->refname, attname);
var->datatype = build_datatype(typetup, attrStruct->atttypmod);
var->isconst = false;
var->notnull = false;
var->default_val = NULL;
var->value = (Datum) 0;
var->isnull = true;
var->freeval = false;
typetup = SearchSysCache(TYPEOID,
ObjectIdGetDatum(attrStruct->atttypid),
0, 0, 0);
if (!HeapTupleIsValid(typetup))
elog(ERROR, "cache lookup failed for type %u",
attrStruct->atttypid);
plpgsql_adddatum((PLpgSQL_datum *) var);
/*
* Create the internal variable for the field
*
* We know if the table definitions contain a default value or if
* the field is declared in the table as NOT NULL. But it's
* possible to create a table field as NOT NULL without a default
* value and that would lead to problems later when initializing
* the variables due to entering a block at execution time. Thus
* we ignore this information for now.
*/
var = malloc(sizeof(PLpgSQL_var));
MemSet(var, 0, sizeof(PLpgSQL_var));
var->dtype = PLPGSQL_DTYPE_VAR;
var->refname = malloc(strlen(relname) + strlen(attname) + 2);
strcpy(var->refname, relname);
strcat(var->refname, ".");
strcat(var->refname, attname);
var->datatype = build_datatype(typetup, attrStruct->atttypmod);
var->isconst = false;
var->notnull = false;
var->default_val = NULL;
var->value = (Datum) 0;
var->isnull = true;
var->freeval = false;
/*
* Add the variable to the row.
*/
row->fieldnames[i] = strdup(attname);
row->varnos[i] = var->varno;
plpgsql_adddatum((PLpgSQL_datum *) var);
ReleaseSysCache(typetup);
ReleaseSysCache(attrtup);
/*
* Add the variable to the row.
*/
row->fieldnames[i] = strdup(attname);
row->varnos[i] = var->varno;
ReleaseSysCache(typetup);
}
else
{
/* Leave a hole in the row structure for the dropped col */
row->fieldnames[i] = NULL;
row->varnos[i] = -1;
}
}
ReleaseSysCache(classtup);
heap_close(rel, AccessShareLock);
return row;
}

View File

@ -3,7 +3,7 @@
* procedural language
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/pl/plpgsql/src/pl_exec.c,v 1.90 2003/08/04 00:43:33 momjian Exp $
* $Header: /cvsroot/pgsql/src/pl/plpgsql/src/pl_exec.c,v 1.91 2003/09/25 23:02:12 tgl Exp $
*
* This software is copyrighted by Jan Wieck - Hamburg.
*
@ -148,6 +148,9 @@ static void exec_move_row(PLpgSQL_execstate * estate,
PLpgSQL_rec * rec,
PLpgSQL_row * row,
HeapTuple tup, TupleDesc tupdesc);
static HeapTuple make_tuple_from_row(PLpgSQL_execstate * estate,
PLpgSQL_row * row,
TupleDesc tupdesc);
static Datum exec_cast_value(Datum value, Oid valtype,
Oid reqtype,
FmgrInfo *reqinput,
@ -1574,6 +1577,22 @@ exec_stmt_return(PLpgSQL_execstate * estate, PLpgSQL_stmt_return * stmt)
return PLPGSQL_RC_RETURN;
}
if (stmt->retrowno >= 0)
{
PLpgSQL_row *row = (PLpgSQL_row *) (estate->datums[stmt->retrowno]);
if (row->rowtupdesc) /* should always be true here */
{
estate->retval = (Datum) make_tuple_from_row(estate, row,
row->rowtupdesc);
if (estate->retval == (Datum) NULL) /* should not happen */
elog(ERROR, "row not compatible with its own tupdesc");
estate->rettupdesc = row->rowtupdesc;
estate->retisnull = false;
}
return PLPGSQL_RC_RETURN;
}
if (stmt->expr != NULL)
{
exec_run_select(estate, stmt->expr, 1, NULL);
@ -1650,37 +1669,11 @@ exec_stmt_return_next(PLpgSQL_execstate * estate,
}
else if (stmt->row)
{
Datum *dvalues;
char *nulls;
int i;
if (natts != stmt->row->nfields)
tuple = make_tuple_from_row(estate, stmt->row, tupdesc);
if (tuple == NULL)
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("wrong record type supplied in RETURN NEXT")));
dvalues = (Datum *) palloc0(natts * sizeof(Datum));
nulls = (char *) palloc(natts * sizeof(char));
MemSet(nulls, 'n', natts);
for (i = 0; i < natts; i++)
{
PLpgSQL_var *var;
var = (PLpgSQL_var *) (estate->datums[stmt->row->varnos[i]]);
if (var->datatype->typoid != tupdesc->attrs[i]->atttypid)
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("wrong record type supplied in RETURN NEXT")));
dvalues[i] = var->value;
if (!var->isnull)
nulls[i] = ' ';
}
tuple = heap_formtuple(tupdesc, dvalues, nulls);
pfree(dvalues);
pfree(nulls);
errmsg("wrong record type supplied in RETURN NEXT")));
free_tuple = true;
}
else if (stmt->expr)
@ -3412,7 +3405,8 @@ exec_move_row(PLpgSQL_execstate * estate,
* expected if it's from an inheritance-child table of the current
* table, or it might have fewer if the table has had columns added by
* ALTER TABLE. Ignore extra columns and assume NULL for missing
* columns, the same as heap_getattr would do.
* columns, the same as heap_getattr would do. We also have to skip
* over dropped columns in either the source or destination.
*
* If we have no tuple data at all, we'll assign NULL to all columns of
* the row variable.
@ -3420,25 +3414,35 @@ exec_move_row(PLpgSQL_execstate * estate,
if (row != NULL)
{
int t_natts;
int i;
int fnum;
int anum;
if (HeapTupleIsValid(tup))
t_natts = tup->t_data->t_natts;
else
t_natts = 0;
for (i = 0; i < row->nfields; i++)
anum = 0;
for (fnum = 0; fnum < row->nfields; fnum++)
{
PLpgSQL_var *var;
Datum value;
bool isnull;
Oid valtype;
var = (PLpgSQL_var *) (estate->datums[row->varnos[i]]);
if (i < t_natts)
if (row->varnos[fnum] < 0)
continue; /* skip dropped column in row struct */
var = (PLpgSQL_var *) (estate->datums[row->varnos[fnum]]);
while (anum < t_natts && tupdesc->attrs[anum]->attisdropped)
anum++; /* skip dropped column in tuple */
if (anum < t_natts)
{
value = SPI_getbinval(tup, tupdesc, i + 1, &isnull);
valtype = SPI_gettypeid(tupdesc, i + 1);
value = SPI_getbinval(tup, tupdesc, anum + 1, &isnull);
valtype = SPI_gettypeid(tupdesc, anum + 1);
anum++;
}
else
{
@ -3447,7 +3451,7 @@ exec_move_row(PLpgSQL_execstate * estate,
valtype = InvalidOid;
}
exec_assign_value(estate, estate->datums[row->varnos[i]],
exec_assign_value(estate, (PLpgSQL_datum *) var,
value, valtype, &isnull);
}
@ -3457,6 +3461,54 @@ exec_move_row(PLpgSQL_execstate * estate,
elog(ERROR, "unsupported target");
}
/* ----------
* make_tuple_from_row Make a tuple from the values of a row object
*
* A NULL return indicates rowtype mismatch; caller must raise suitable error
* ----------
*/
static HeapTuple
make_tuple_from_row(PLpgSQL_execstate * estate,
PLpgSQL_row * row,
TupleDesc tupdesc)
{
int natts = tupdesc->natts;
HeapTuple tuple;
Datum *dvalues;
char *nulls;
int i;
if (natts != row->nfields)
return NULL;
dvalues = (Datum *) palloc0(natts * sizeof(Datum));
nulls = (char *) palloc(natts * sizeof(char));
MemSet(nulls, 'n', natts);
for (i = 0; i < natts; i++)
{
PLpgSQL_var *var;
if (tupdesc->attrs[i]->attisdropped)
continue; /* leave the column as null */
if (row->varnos[i] < 0) /* should not happen */
elog(ERROR, "dropped rowtype entry for non-dropped column");
var = (PLpgSQL_var *) (estate->datums[row->varnos[i]]);
if (var->datatype->typoid != tupdesc->attrs[i]->atttypid)
return NULL;
dvalues[i] = var->value;
if (!var->isnull)
nulls[i] = ' ';
}
tuple = heap_formtuple(tupdesc, dvalues, nulls);
pfree(dvalues);
pfree(nulls);
return tuple;
}
/* ----------
* exec_cast_value Cast a value if required

View File

@ -3,7 +3,7 @@
* procedural language
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/pl/plpgsql/src/pl_funcs.c,v 1.29 2003/08/04 00:43:33 momjian Exp $
* $Header: /cvsroot/pgsql/src/pl/plpgsql/src/pl_funcs.c,v 1.30 2003/09/25 23:02:12 tgl Exp $
*
* This software is copyrighted by Jan Wieck - Hamburg.
*
@ -848,15 +848,14 @@ dump_return(PLpgSQL_stmt_return * stmt)
{
dump_ind();
printf("RETURN ");
if (stmt->retrecno > 0)
if (stmt->retrecno >= 0)
printf("record %d", stmt->retrecno);
else if (stmt->retrowno >= 0)
printf("row %d", stmt->retrowno);
else if (stmt->expr == NULL)
printf("NULL");
else
{
if (stmt->expr == NULL)
printf("NULL");
else
dump_expr(stmt->expr);
}
dump_expr(stmt->expr);
printf("\n");
}
@ -1031,8 +1030,9 @@ plpgsql_dumptree(PLpgSQL_function * func)
printf("ROW %-16s fields", row->refname);
for (i = 0; i < row->nfields; i++)
{
printf(" %s=var %d", row->fieldnames[i],
row->varnos[i]);
if (row->fieldnames[i])
printf(" %s=var %d", row->fieldnames[i],
row->varnos[i]);
}
printf("\n");
}

View File

@ -3,7 +3,7 @@
* procedural language
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/pl/plpgsql/src/plpgsql.h,v 1.40 2003/08/18 19:16:02 tgl Exp $
* $Header: /cvsroot/pgsql/src/pl/plpgsql/src/plpgsql.h,v 1.41 2003/09/25 23:02:12 tgl Exp $
*
* This software is copyrighted by Jan Wieck - Hamburg.
*
@ -207,8 +207,15 @@ typedef struct
int rowno;
char *refname;
int lineno;
Oid rowtypeclass;
TupleDesc rowtupdesc;
/*
* Note: TupleDesc is only set up for named rowtypes, else it is NULL.
*
* Note: if the underlying rowtype contains a dropped column, the
* corresponding fieldnames[] entry will be NULL, and there is no
* corresponding var (varnos[] will be -1).
*/
int nfields;
char **fieldnames;
int *varnos;
@ -449,6 +456,7 @@ typedef struct
int lineno;
PLpgSQL_expr *expr;
int retrecno;
int retrowno;
} PLpgSQL_stmt_return;
typedef struct