Add dynamic record inspection to PL/PgSQL, useful for generic triggers:

tval2 := r.(cname);

or

  columns := r.(*);

Titus von Boxberg
This commit is contained in:
Bruce Momjian 2006-05-30 12:03:13 +00:00
parent 88ba64d396
commit 38c7700f56
8 changed files with 472 additions and 38 deletions

View File

@ -1,4 +1,4 @@
<!-- $PostgreSQL: pgsql/doc/src/sgml/plpgsql.sgml,v 1.92 2006/05/30 11:58:05 momjian Exp $ -->
<!-- $PostgreSQL: pgsql/doc/src/sgml/plpgsql.sgml,v 1.93 2006/05/30 12:03:12 momjian Exp $ -->
<chapter id="plpgsql">
<title><application>PL/pgSQL</application> - <acronym>SQL</acronym> Procedural Language</title>
@ -879,6 +879,55 @@ SELECT merge_fields(t.*) FROM table1 t WHERE ... ;
field in it will draw a run-time error.
</para>
<para>
To obtain the values of the fields the record is made up of,
the record variable can be qualified with the column or field
name. This can be done either by literally using the column name
or the column name for indexing the record can be taken out of a scalar
variable. The syntax for this notation is Record_variable.(IndexVariable).
To get information about the column field names of the record,
a special expression exists that returns all column names as an array:
RecordVariable.(*) .
Thus, the RECORD can be viewed
as an associative array that allows for introspection of it's contents.
This feature is especially useful for writing generic triggers that
operate on records with unknown structure.
Here is an example procedure that shows column names and values
of the predefined record NEW in a trigger procedure:
<programlisting>
CREATE OR REPLACE FUNCTION show_associative_records() RETURNS TRIGGER AS $$
DECLARE
colname TEXT;
colcontent TEXT;
colnames TEXT[];
coln INT4;
coli INT4;
BEGIN
-- obtain an array with all field names of the record
colnames := NEW.(*);
RAISE NOTICE 'All column names of test record: %', colnames;
-- show field names and contents of record
coli := 1;
coln := array_upper(colnames,1);
RAISE NOTICE 'Number of columns in NEW: %', coln;
FOR coli IN 1 .. coln LOOP
colname := colnames[coli];
colcontent := NEW.(colname);
RAISE NOTICE 'column % of NEW: %', quote_ident(colname), quote_literal(colcontent);
END LOOP;
-- Do it with a fixed field name:
-- will have to know the column name
RAISE NOTICE 'column someint of NEW: %', quote_literal(NEW.someint);
RETURN NULL;
END;
$$ LANGUAGE plpgsql;
--CREATE TABLE test_records (someint INT8, somestring TEXT);
--CREATE TRIGGER tr_test_record BEFORE INSERT ON test_records FOR EACH ROW EXECUTE PROCEDURE show_associative_records();
</programlisting>
</para>
<para>
Note that <literal>RECORD</> is not a true data type, only a placeholder.
One should also realize that when a <application>PL/pgSQL</application>

View File

@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_comp.c,v 1.104 2006/05/30 11:58:05 momjian Exp $
* $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_comp.c,v 1.105 2006/05/30 12:03:13 momjian Exp $
*
*-------------------------------------------------------------------------
*/
@ -884,7 +884,8 @@ plpgsql_parse_dblword(char *word)
new = palloc(sizeof(PLpgSQL_recfield));
new->dtype = PLPGSQL_DTYPE_RECFIELD;
new->fieldname = pstrdup(cp[1]);
new->fieldindex.fieldname = pstrdup(cp[1]);
new->fieldindex_flag = RECFIELD_USE_FIELDNAME;
new->recparentno = ns->itemno;
plpgsql_adddatum((PLpgSQL_datum *) new);
@ -990,7 +991,8 @@ plpgsql_parse_tripword(char *word)
new = palloc(sizeof(PLpgSQL_recfield));
new->dtype = PLPGSQL_DTYPE_RECFIELD;
new->fieldname = pstrdup(cp[2]);
new->fieldindex.fieldname = pstrdup(cp[2]);
new->fieldindex_flag = RECFIELD_USE_FIELDNAME;
new->recparentno = ns->itemno;
plpgsql_adddatum((PLpgSQL_datum *) new);
@ -1438,6 +1440,132 @@ plpgsql_parse_dblwordrowtype(char *word)
return T_DTYPE;
}
/* ----------
* plpgsql_parse_recindex
* lookup associative index into record
* ----------
*/
int
plpgsql_parse_recindex(char *word)
{
PLpgSQL_nsitem *ns1, *ns2;
char *cp[2];
int ret = T_ERROR;
char *fieldvar;
int fl;
/* Do case conversion and word separation */
plpgsql_convert_ident(word, cp, 2);
Assert(cp[1] != NULL);
/* cleanup the "(identifier)" string to "identifier" */
fieldvar = cp[1];
Assert(*fieldvar == '(');
++fieldvar; /* get rid of ( */
fl = strlen(fieldvar);
Assert(fieldvar[fl-1] == ')');
fieldvar[fl-1] = 0; /* get rid of ) */
/*
* Lookup the first word
*/
ns1 = plpgsql_ns_lookup(cp[0], NULL);
if ( ns1 == NULL )
{
pfree(cp[0]);
pfree(cp[1]);
return T_ERROR;
}
ns2 = plpgsql_ns_lookup(fieldvar, NULL);
pfree(cp[0]);
pfree(cp[1]);
if ( ns2 == NULL ) /* name lookup failed */
return T_ERROR;
switch (ns1->itemtype)
{
case PLPGSQL_NSTYPE_REC:
{
/*
* First word is a record name, so second word must be an
* variable holding the field name in this record.
*/
if ( ns2->itemtype == PLPGSQL_NSTYPE_VAR ) {
PLpgSQL_recfield *new;
new = palloc(sizeof(PLpgSQL_recfield));
new->dtype = PLPGSQL_DTYPE_RECFIELD;
new->fieldindex.indexvar_no = ns2->itemno;
new->fieldindex_flag = RECFIELD_USE_INDEX_VAR;
new->recparentno = ns1->itemno;
plpgsql_adddatum((PLpgSQL_datum *) new);
plpgsql_yylval.scalar = (PLpgSQL_datum *) new;
ret = T_SCALAR;
}
break;
}
default:
break;
}
return ret;
}
/* ----------
* plpgsql_parse_recfieldnames
* create fieldnames of a record
* ----------
*/
int
plpgsql_parse_recfieldnames(char *word)
{
PLpgSQL_nsitem *ns1;
char *cp[2];
int ret = T_ERROR;
/* Do case conversion and word separation */
plpgsql_convert_ident(word, cp, 2);
/*
* Lookup the first word
*/
ns1 = plpgsql_ns_lookup(cp[0], NULL);
if ( ns1 == NULL )
{
pfree(cp[0]);
pfree(cp[1]);
return T_ERROR;
}
pfree(cp[0]);
pfree(cp[1]);
switch (ns1->itemtype)
{
case PLPGSQL_NSTYPE_REC:
{
PLpgSQL_recfieldproperties *new;
new = palloc(sizeof(PLpgSQL_recfieldproperties));
new->dtype = PLPGSQL_DTYPE_RECFIELDNAMES;
new->recparentno = ns1->itemno;
new->save_fieldnames = NULL;
plpgsql_adddatum((PLpgSQL_datum *) new);
plpgsql_yylval.scalar = (PLpgSQL_datum *) new;
ret = T_SCALAR; /* ??? */
break;
}
default:
break;
}
return ret;
}
/*
* plpgsql_build_variable - build a datum-array entry of a given
* datatype

View File

@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_exec.c,v 1.167 2006/05/30 11:58:05 momjian Exp $
* $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_exec.c,v 1.168 2006/05/30 12:03:13 momjian Exp $
*
*-------------------------------------------------------------------------
*/
@ -741,7 +741,7 @@ copy_plpgsql_datum(PLpgSQL_datum *datum)
case PLPGSQL_DTYPE_RECFIELD:
case PLPGSQL_DTYPE_ARRAYELEM:
case PLPGSQL_DTYPE_TRIGARG:
case PLPGSQL_DTYPE_RECFIELDNAMES:
/*
* These datum records are read-only at runtime, so no need to
* copy them
@ -851,6 +851,7 @@ exec_stmt_block(PLpgSQL_execstate *estate, PLpgSQL_stmt_block *block)
case PLPGSQL_DTYPE_RECFIELD:
case PLPGSQL_DTYPE_ARRAYELEM:
case PLPGSQL_DTYPE_RECFIELDNAMES:
break;
default:
@ -2179,6 +2180,8 @@ plpgsql_estate_setup(PLpgSQL_execstate *estate,
static void
exec_eval_cleanup(PLpgSQL_execstate *estate)
{
int i;
ArrayType *a;
/* Clear result of a full SPI_execute */
if (estate->eval_tuptable != NULL)
SPI_freetuptable(estate->eval_tuptable);
@ -2187,6 +2190,14 @@ exec_eval_cleanup(PLpgSQL_execstate *estate)
/* Clear result of exec_eval_simple_expr (but keep the econtext) */
if (estate->eval_econtext != NULL)
ResetExprContext(estate->eval_econtext);
for ( i = 0; i < estate->ndatums; ++i ) {
if ( estate->datums[i]->dtype == PLPGSQL_DTYPE_RECFIELDNAMES ) {
a = ((PLpgSQL_recfieldproperties *)(estate->datums[i]))->save_fieldnames;
if ( a )
pfree(a);
((PLpgSQL_recfieldproperties *)(estate->datums[i]))->save_fieldnames = NULL;
}
}
}
@ -3156,7 +3167,7 @@ exec_assign_value(PLpgSQL_execstate *estate,
*/
PLpgSQL_recfield *recfield = (PLpgSQL_recfield *) target;
PLpgSQL_rec *rec;
int fno;
int fno = 0;
HeapTuple newtup;
int natts;
int i;
@ -3185,12 +3196,35 @@ exec_assign_value(PLpgSQL_execstate *estate,
* Get the number of the records field to change and the
* number of attributes in the tuple.
*/
fno = SPI_fnumber(rec->tupdesc, recfield->fieldname);
if (fno == SPI_ERROR_NOATTRIBUTE)
if ( recfield->fieldindex_flag == RECFIELD_USE_FIELDNAME ) {
fno = SPI_fnumber(rec->tupdesc, recfield->fieldindex.fieldname);
if (fno == SPI_ERROR_NOATTRIBUTE)
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_COLUMN),
errmsg("record \"%s\" has no field \"%s\"",
rec->refname, recfield->fieldindex.fieldname)));
}
else if ( recfield->fieldindex_flag == RECFIELD_USE_INDEX_VAR ) {
PLpgSQL_var * idxvar = (PLpgSQL_var *) (estate->datums[recfield->fieldindex.indexvar_no]);
char * fname = convert_value_to_string(idxvar->value, idxvar->datatype->typoid);
if ( fname == NULL )
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_COLUMN),
errmsg("record \"%s\": cannot evaluate variable to record index string",
rec->refname)));
fno = SPI_fnumber(rec->tupdesc, fname);
pfree(fname);
if (fno == SPI_ERROR_NOATTRIBUTE)
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_COLUMN),
errmsg("record \"%s\" has no field \"%s\"",
rec->refname, fname)));
}
else
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_COLUMN),
errmsg("record \"%s\" has no field \"%s\"",
rec->refname, recfield->fieldname)));
(errcode(ERRCODE_UNDEFINED_COLUMN),
errmsg("record \"%s\": internal error",
rec->refname)));
fno--;
natts = rec->tupdesc->natts;
@ -3510,7 +3544,7 @@ exec_eval_datum(PLpgSQL_execstate *estate,
{
PLpgSQL_recfield *recfield = (PLpgSQL_recfield *) datum;
PLpgSQL_rec *rec;
int fno;
int fno = 0;
rec = (PLpgSQL_rec *) (estate->datums[recfield->recparentno]);
if (!HeapTupleIsValid(rec->tup))
@ -3519,22 +3553,125 @@ exec_eval_datum(PLpgSQL_execstate *estate,
errmsg("record \"%s\" is not assigned yet",
rec->refname),
errdetail("The tuple structure of a not-yet-assigned record is indeterminate.")));
fno = SPI_fnumber(rec->tupdesc, recfield->fieldname);
if (fno == SPI_ERROR_NOATTRIBUTE)
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_COLUMN),
errmsg("record \"%s\" has no field \"%s\"",
rec->refname, recfield->fieldname)));
*typeid = SPI_gettypeid(rec->tupdesc, fno);
*value = SPI_getbinval(rec->tup, rec->tupdesc, fno, isnull);
if (expectedtypeid != InvalidOid && expectedtypeid != *typeid)
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("type of \"%s.%s\" does not match that when preparing the plan",
rec->refname, recfield->fieldname)));
break;
}
if ( recfield->fieldindex_flag == RECFIELD_USE_FIELDNAME ) {
fno = SPI_fnumber(rec->tupdesc, recfield->fieldindex.fieldname);
if (fno == SPI_ERROR_NOATTRIBUTE)
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_COLUMN),
errmsg("record \"%s\" has no field \"%s\"",
rec->refname, recfield->fieldindex.fieldname)));
}
else if ( recfield->fieldindex_flag == RECFIELD_USE_INDEX_VAR ) {
PLpgSQL_var * idxvar = (PLpgSQL_var *) (estate->datums[recfield->fieldindex.indexvar_no]);
char * fname = convert_value_to_string(idxvar->value, idxvar->datatype->typoid);
if ( fname == NULL )
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_COLUMN),
errmsg("record \"%s\": cannot evaluate variable to record index string",
rec->refname)));
fno = SPI_fnumber(rec->tupdesc, fname);
pfree(fname);
if (fno == SPI_ERROR_NOATTRIBUTE)
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_COLUMN),
errmsg("record \"%s\" has no field \"%s\"",
rec->refname, fname)));
}
else
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_COLUMN),
errmsg("record \"%s\": internal error",
rec->refname)));
/* Do not allow typeids to become "narrowed" by InvalidOids
causing specialized typeids from the tuple restricting the destination */
if ( expectedtypeid != InvalidOid && expectedtypeid != SPI_gettypeid(rec->tupdesc, fno) ) {
Datum cval = SPI_getbinval(rec->tup, rec->tupdesc, fno, isnull);
cval = exec_simple_cast_value(cval,
SPI_gettypeid(rec->tupdesc, fno),
expectedtypeid,
-1,
*isnull);
*value = cval;
*typeid = expectedtypeid;
/* ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("type of \"%s\" does not match that when preparing the plan",
rec->refname)));
*/
}
else { /* expected typeid matches */
*value = SPI_getbinval(rec->tup, rec->tupdesc, fno, isnull);
*typeid = SPI_gettypeid(rec->tupdesc, fno);
}
break;
}
case PLPGSQL_DTYPE_RECFIELDNAMES:
/* Construct array datum from record field names */
{
Oid arraytypeid,
arrayelemtypeid = TEXTOID;
int16 arraytyplen,
elemtyplen;
bool elemtypbyval;
char elemtypalign;
ArrayType *arrayval;
PLpgSQL_recfieldproperties * recfp = (PLpgSQL_recfieldproperties *) datum;
PLpgSQL_rec *rec = (PLpgSQL_rec *) (estate->datums[recfp->recparentno]);
int fc, tfc = 0;
Datum *arrayelems;
char *fieldname;
if (!HeapTupleIsValid(rec->tup))
ereport(ERROR,
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
errmsg("record \"%s\" is not assigned yet",
rec->refname),
errdetail("The tuple structure of a not-yet-assigned record is indeterminate.")));
arrayelems = palloc(sizeof(Datum) * rec->tupdesc->natts);
arraytypeid = get_array_type(arrayelemtypeid);
arraytyplen = get_typlen(arraytypeid);
get_typlenbyvalalign(arrayelemtypeid,
&elemtyplen,
&elemtypbyval,
&elemtypalign);
if ( expectedtypeid != InvalidOid && expectedtypeid != arraytypeid )
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("type of \"%s\" does not match array type when preparing the plan",
rec->refname)));
for ( fc = 0; fc < rec->tupdesc->natts; ++fc ) {
fieldname = SPI_fname(rec->tupdesc, fc+1);
if ( fieldname ) {
arrayelems[fc] = DirectFunctionCall1(textin, CStringGetDatum(fieldname));
pfree(fieldname);
++tfc;
}
}
arrayval = construct_array(arrayelems, tfc,
arrayelemtypeid,
elemtyplen,
elemtypbyval,
elemtypalign);
/* construct_array copies data; free temp elem array */
for ( fc = 0; fc < tfc; ++fc )
pfree(DatumGetPointer(arrayelems[fc]));
pfree(arrayelems);
*value = PointerGetDatum(arrayval);
*typeid = arraytypeid;
*isnull = false;
/* need to save the pointer because otherwise it does not get freed */
if ( recfp->save_fieldnames )
pfree(recfp->save_fieldnames);
recfp->save_fieldnames = arrayval;
break;
}
case PLPGSQL_DTYPE_TRIGARG:
{
PLpgSQL_trigarg *trigarg = (PLpgSQL_trigarg *) datum;
@ -3632,7 +3769,29 @@ exec_eval_expr(PLpgSQL_execstate *estate,
*/
if (expr->plan == NULL)
exec_prepare_plan(estate, expr);
else {
/*
* check for any subexpressions with varying type in the expression
* currently (July 05), this is a record field of a record indexed by a variable
*/
int i;
PLpgSQL_datum *d;
PLpgSQL_recfield *rf;
for ( i = 0; i < expr->nparams; ++i ) {
d = estate->datums[expr->params[i]];
if ( d->dtype == PLPGSQL_DTYPE_RECFIELD ) {
rf = (PLpgSQL_recfield *)d;
if ( rf->fieldindex_flag == RECFIELD_USE_INDEX_VAR )
break;
}
}
if ( i < expr->nparams ) { /* expr may change it's type */
/* now discard the plan and get new one */
SPI_freeplan(expr->plan);
expr->plan = NULL;
exec_prepare_plan(estate, expr);
}
}
/*
* If this is a simple expression, bypass SPI and use the executor
* directly

View File

@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_funcs.c,v 1.50 2006/05/30 11:58:05 momjian Exp $
* $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_funcs.c,v 1.51 2006/05/30 12:03:13 momjian Exp $
*
*-------------------------------------------------------------------------
*/
@ -1044,9 +1044,13 @@ plpgsql_dumptree(PLpgSQL_function *func)
printf("REC %s\n", ((PLpgSQL_rec *) d)->refname);
break;
case PLPGSQL_DTYPE_RECFIELD:
printf("RECFIELD %-16s of REC %d\n",
((PLpgSQL_recfield *) d)->fieldname,
((PLpgSQL_recfield *) d)->recparentno);
if ( ((PLpgSQL_recfield *) d)->fieldindex_flag == RECFIELD_USE_FIELDNAME )
printf("RECFIELD %-16s of REC %d\n",
((PLpgSQL_recfield *) d)->fieldindex.fieldname,
((PLpgSQL_recfield *) d)->recparentno);
else
printf("RECFIELD Variable of REC %d\n",
((PLpgSQL_recfield *) d)->recparentno);
break;
case PLPGSQL_DTYPE_ARRAYELEM:
printf("ARRAYELEM of VAR %d subscript ",

View File

@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/pl/plpgsql/src/plpgsql.h,v 1.72 2006/05/30 11:58:05 momjian Exp $
* $PostgreSQL: pgsql/src/pl/plpgsql/src/plpgsql.h,v 1.73 2006/05/30 12:03:13 momjian Exp $
*
*-------------------------------------------------------------------------
*/
@ -52,7 +52,8 @@ enum
PLPGSQL_DTYPE_RECFIELD,
PLPGSQL_DTYPE_ARRAYELEM,
PLPGSQL_DTYPE_EXPR,
PLPGSQL_DTYPE_TRIGARG
PLPGSQL_DTYPE_TRIGARG,
PLPGSQL_DTYPE_RECFIELDNAMES
};
/* ----------
@ -251,10 +252,25 @@ typedef struct
{ /* Field in record */
int dtype;
int rfno;
char *fieldname;
union {
char *fieldname;
int indexvar_no; /* dno of variable holding index string */
} fieldindex;
enum {
RECFIELD_USE_FIELDNAME,
RECFIELD_USE_INDEX_VAR,
} fieldindex_flag;
int recparentno; /* dno of parent record */
} PLpgSQL_recfield;
typedef struct
{ /* Field in record */
int dtype;
int rfno;
int recparentno; /* dno of parent record */
ArrayType * save_fieldnames;
} PLpgSQL_recfieldproperties;
typedef struct
{ /* Element of array variable */
@ -661,6 +677,8 @@ extern int plpgsql_parse_dblwordtype(char *word);
extern int plpgsql_parse_tripwordtype(char *word);
extern int plpgsql_parse_wordrowtype(char *word);
extern int plpgsql_parse_dblwordrowtype(char *word);
extern int plpgsql_parse_recfieldnames(char *word);
extern int plpgsql_parse_recindex(char *word);
extern PLpgSQL_type *plpgsql_parse_datatype(const char *string);
extern PLpgSQL_type *plpgsql_build_datatype(Oid typeOid, int32 typmod);
extern PLpgSQL_variable *plpgsql_build_variable(const char *refname, int lineno,

View File

@ -9,7 +9,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/pl/plpgsql/src/scan.l,v 1.47 2006/05/30 11:58:05 momjian Exp $
* $PostgreSQL: pgsql/src/pl/plpgsql/src/scan.l,v 1.48 2006/05/30 12:03:13 momjian Exp $
*
*-------------------------------------------------------------------------
*/
@ -222,6 +222,12 @@ dump { return O_DUMP; }
{param}{space}*\.{space}*{identifier}{space}*%ROWTYPE {
plpgsql_error_lineno = plpgsql_scanner_lineno();
return plpgsql_parse_dblwordrowtype(yytext); }
{identifier}{space}*\.\(\*\) {
plpgsql_error_lineno = plpgsql_scanner_lineno();
return plpgsql_parse_recfieldnames(yytext); }
{identifier}{space}*\.\({identifier}\) {
plpgsql_error_lineno = plpgsql_scanner_lineno();
return plpgsql_parse_recindex(yytext); }
{digit}+ { return T_NUMBER; }

View File

@ -2725,6 +2725,44 @@ end;
$$ language plpgsql;
ERROR: end label "outer_label" specified for unlabelled block
CONTEXT: compile of PL/pgSQL function "end_label4" near line 5
-- check introspective records
create table ritest (i INT4, t TEXT);
insert into ritest (i, t) VALUES (1, 'sometext');
create function test_record() returns void as $$
declare
cname text;
tval text;
ival int4;
tval2 text;
ival2 int4;
columns text[];
r RECORD;
begin
SELECT INTO r * FROM ritest WHERE i = 1;
ival := r.i;
tval := r.t;
RAISE NOTICE 'ival=%, tval=%', ival, tval;
cname := 'i';
ival2 := r.(cname);
cname :='t';
tval2 := r.(cname);
RAISE NOTICE 'ival2=%, tval2=%', ival2, tval2;
columns := r.(*);
RAISE NOTICE 'fieldnames=%', columns;
RETURN;
end;
$$ language plpgsql;
select test_record();
NOTICE: ival=1, tval=sometext
NOTICE: ival2=1, tval2=sometext
NOTICE: fieldnames={i,t}
test_record
-------------
(1 row)
drop table ritest;
drop function test_record();
-- using list of scalars in fori and fore stmts
create function for_vect() returns void as $proc$
<<lbl>>declare a integer; b varchar; c varchar; r record;

View File

@ -2281,6 +2281,38 @@ begin
end;
$$ language plpgsql;
-- check introspective records
create table ritest (i INT4, t TEXT);
insert into ritest (i, t) VALUES (1, 'sometext');
create function test_record() returns void as $$
declare
cname text;
tval text;
ival int4;
tval2 text;
ival2 int4;
columns text[];
r RECORD;
begin
SELECT INTO r * FROM ritest WHERE i = 1;
ival := r.i;
tval := r.t;
RAISE NOTICE 'ival=%, tval=%', ival, tval;
cname := 'i';
ival2 := r.(cname);
cname :='t';
tval2 := r.(cname);
RAISE NOTICE 'ival2=%, tval2=%', ival2, tval2;
columns := r.(*);
RAISE NOTICE 'fieldnames=%', columns;
RETURN;
end;
$$ language plpgsql;
select test_record();
drop table ritest;
drop function test_record();
-- using list of scalars in fori and fore stmts
create function for_vect() returns void as $proc$
<<lbl>>declare a integer; b varchar; c varchar; r record;