*
*
* 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 $
*
*-------------------------------------------------------------------------
*/
*
* 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)
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
*
*
* 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 $
*
*-------------------------------------------------------------------------
*/
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
* 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")));
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];
-
- /*
- * We really only care about number of attributes and data type
- */
- if (attr1->atttypid != attr2->atttypid)
- return true;
+ Form_pg_attribute dattr = dst_tupdesc->attrs[i];
+ Form_pg_attribute sattr = src_tupdesc->attrs[i];
+
+ 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;
}
* 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.
*
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);
%}
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
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:
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);
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);
}
-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)
{
* 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.
*
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]);
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]);
*/
plpgsql_yylval.row = plpgsql_build_rowtype(classOid);
+ plpgsql_adddatum((PLpgSQL_datum *) plpgsql_yylval.row);
+
pfree(cp[0]);
pfree(cp[1]);
*/
plpgsql_yylval.row = plpgsql_build_rowtype(classOid);
+ plpgsql_adddatum((PLpgSQL_datum *) plpgsql_yylval.row);
+
pfree(cp);
return T_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 &&
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);
-
- attname = NameStr(attrStruct->attname);
-
- typetup = SearchSysCache(TYPEOID,
- ObjectIdGetDatum(attrStruct->atttypid),
- 0, 0, 0);
- if (!HeapTupleIsValid(typetup))
- elog(ERROR, "cache lookup failed for type %u",
- attrStruct->atttypid);
+ attrStruct = RelationGetDescr(rel)->attrs[i];
- /*
- * 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;
-
- plpgsql_adddatum((PLpgSQL_datum *) var);
+ if (!attrStruct->attisdropped)
+ {
+ const char *attname;
+ HeapTuple typetup;
+ PLpgSQL_var *var;
- /*
- * Add the variable to the row.
- */
- row->fieldnames[i] = strdup(attname);
- row->varnos[i] = var->varno;
+ attname = NameStr(attrStruct->attname);
+
+ typetup = SearchSysCache(TYPEOID,
+ ObjectIdGetDatum(attrStruct->atttypid),
+ 0, 0, 0);
+ if (!HeapTupleIsValid(typetup))
+ elog(ERROR, "cache lookup failed for type %u",
+ attrStruct->atttypid);
+
+ /*
+ * 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;
- ReleaseSysCache(typetup);
- ReleaseSysCache(attrtup);
+ plpgsql_adddatum((PLpgSQL_datum *) var);
+
+ /*
+ * 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;
}
* 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.
*
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,
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);
}
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)
* 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.
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
{
valtype = InvalidOid;
}
- exec_assign_value(estate, estate->datums[row->varnos[i]],
+ exec_assign_value(estate, (PLpgSQL_datum *) var,
value, valtype, &isnull);
}
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
* 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.
*
{
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");
}
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");
}
* 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.
*
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;
int lineno;
PLpgSQL_expr *expr;
int retrecno;
+ int retrowno;
} PLpgSQL_stmt_return;
typedef struct