*** ./doc/src/sgml/plpgsql.sgml.orig Thu Jul 14 00:49:33 2005
--- ./doc/src/sgml/plpgsql.sgml Thu Jul 14 01:15:41 2005
***************
*** 867,872 ****
--- 867,941 ----
+ 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 literally with the usual dot notation
+ 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 fields, two special
+ notations exist that return all column names as an array (RecordVariable%FIELDNAMES)
+ and the count of fields stored in a record (RecordVariable%NFIELDS).
+ 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 that operates on the predefined record NEW in
+ a trigger procedure:
+
+ CREATE OR REPLACE FUNCTION show_associative_records() RETURNS TRIGGER AS $ztt_u$
+ DECLARE
+ wmsg TEXT;
+ column TEXT;
+ colcontent TEXT;
+ colnames TEXT[];
+ coln INT4;
+ coli INT4;
+ testint8 INT8;
+ BEGIN
+ -- get the number of fields
+ coln := NEW%NFIELDS;
+ RAISE NOTICE 'Record type has % fields', coln;
+
+ -- obtain an array with all field names of the record
+ colnames := NEW%FIELDNAMES;
+ RAISE NOTICE 'All column names of test record: %', colnames;
+
+ -- show field names and contents of record
+ coli := 1;
+ LOOP
+ column := colnames[coli];
+ colcontent := NEW%column;
+ IF colcontent IS NULL
+ THEN
+ colcontent := '';
+ END IF;
+ RAISE NOTICE 'column name % of NEW: value %', column, colcontent;
+ coli := coli + 1;
+ EXIT WHEN coli > coln;
+ END LOOP;
+
+ -- get a single record field value indexed by the contents of the variable "column"
+ -- raises an error if the record does not have the field name from "column"
+ column := 'SomeFieldNameInYourRecord';
+ -- testint8 will contain NULL if the value of this column
+ -- has a different type and cannot be casted to int8!
+ -- use a TEXT scalar variable to avoid this problem.
+ testint8 := NEW%column;
+ RAISE WARNING 'Column name = %, Column content = %', column, testint8;
+
+ -- the "normal" way with fixed field name with dot notation:
+ wmsg := NEW.SomeFieldNameInYourRecord;
+ RAISE NOTICE 'Column content NEW.SomeFieldNameInYourRecord = %', wmsg;
+
+ RETURN NULL;
+ END;
+ $ztt_u$ LANGUAGE plpgsql;
+
+
+
+
+
+
Note that RECORD> is not a true data type, only a placeholder.
One should also realize that when a PL/pgSQL
function is declared to return type record>, this is not quite the
*** ./src/pl/plpgsql/src/pl_comp.c.orig Wed Jul 13 19:22:43 2005
--- ./src/pl/plpgsql/src/pl_comp.c Thu Jul 14 23:54:26 2005
***************
*** 995,1001 ****
new = palloc(sizeof(PLpgSQL_recfield));
new->dtype = PLPGSQL_DTYPE_RECFIELD;
! new->fieldname = pstrdup(cp[1]);
new->recparentno = ns->itemno;
plpgsql_adddatum((PLpgSQL_datum *) new);
--- 995,1002 ----
new = palloc(sizeof(PLpgSQL_recfield));
new->dtype = PLPGSQL_DTYPE_RECFIELD;
! new->fieldindex.fieldname = pstrdup(cp[1]);
! new->fieldindex_flag = RECFIELD_USE_FIELDNAME;
new->recparentno = ns->itemno;
plpgsql_adddatum((PLpgSQL_datum *) new);
***************
*** 1101,1107 ****
new = palloc(sizeof(PLpgSQL_recfield));
new->dtype = PLPGSQL_DTYPE_RECFIELD;
! new->fieldname = pstrdup(cp[2]);
new->recparentno = ns->itemno;
plpgsql_adddatum((PLpgSQL_datum *) new);
--- 1102,1109 ----
new = palloc(sizeof(PLpgSQL_recfield));
new->dtype = PLPGSQL_DTYPE_RECFIELD;
! new->fieldindex.fieldname = pstrdup(cp[2]);
! new->fieldindex_flag = RECFIELD_USE_FIELDNAME;
new->recparentno = ns->itemno;
plpgsql_adddatum((PLpgSQL_datum *) new);
***************
*** 1551,1556 ****
--- 1553,1738 ----
return T_DTYPE;
}
+ /* ----------
+ * plpgsql_parse_wordpercentword
+ * lookup associative index into record
+ * ----------
+ */
+ int
+ plpgsql_parse_wordpercentword(char *word)
+ {
+ PLpgSQL_nsitem *ns1, *ns2;
+ char *cp[2];
+ int ret = T_ERROR;
+
+ /* convert % to . for plpgsql_convert_ident */
+ char * percl = strchr(word, '%');
+ if ( percl == NULL )
+ return T_ERROR;
+ *percl = '.';
+
+ /* Do case conversion and word separation */
+ plpgsql_convert_ident(word, cp, 2);
+ *percl = '%';
+
+ /*
+ * 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(cp[1], 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_wordnfields
+ * create # of fields in a record
+ * ----------
+ */
+ int
+ plpgsql_parse_wordnfields(char *word)
+ {
+ PLpgSQL_nsitem *ns1;
+ char *cp[2];
+ int ret = T_ERROR;
+
+ /* convert % to . for plpgsql_convert_ident */
+ int i = strlen(word) - 8;
+ Assert(word[i] == '%');
+ word[i] = '.';
+ /* Do case conversion and word separation */
+ plpgsql_convert_ident(word, cp, 2);
+ word[i] = '%';
+
+ /*
+ * 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_NRECFIELD;
+ 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_parse_wordfieldnames
+ * create fieldnames of a record
+ * ----------
+ */
+ int
+ plpgsql_parse_wordfieldnames(char *word)
+ {
+ PLpgSQL_nsitem *ns1;
+ char *cp[2];
+ int ret = T_ERROR;
+
+ /* convert % to . for plpgsql_convert_ident */
+ int i = strlen(word) - 11;
+ Assert(word[i] == '%');
+ word[i] = '.';
+ /* Do case conversion and word separation */
+ plpgsql_convert_ident(word, cp, 2);
+ word[i] = '%';
+
+ /*
+ * 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
*** ./src/pl/plpgsql/src/pl_exec.c.orig Wed Jul 13 19:22:08 2005
--- ./src/pl/plpgsql/src/pl_exec.c Thu Jul 14 23:54:28 2005
***************
*** 716,721 ****
--- 716,723 ----
case PLPGSQL_DTYPE_RECFIELD:
case PLPGSQL_DTYPE_ARRAYELEM:
case PLPGSQL_DTYPE_TRIGARG:
+ case PLPGSQL_DTYPE_NRECFIELD:
+ case PLPGSQL_DTYPE_RECFIELDNAMES:
/*
* These datum records are read-only at runtime, so no need
* to copy them
***************
*** 825,830 ****
--- 827,834 ----
case PLPGSQL_DTYPE_RECFIELD:
case PLPGSQL_DTYPE_ARRAYELEM:
+ case PLPGSQL_DTYPE_NRECFIELD:
+ case PLPGSQL_DTYPE_RECFIELDNAMES:
break;
default:
***************
*** 2146,2151 ****
--- 2150,2157 ----
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);
***************
*** 2154,2159 ****
--- 2160,2173 ----
/* 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;
+ }
+ }
}
***************
*** 3154,3165 ****
* 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)
ereport(ERROR,
! (errcode(ERRCODE_UNDEFINED_COLUMN),
! errmsg("record \"%s\" has no field \"%s\"",
! rec->refname, recfield->fieldname)));
fno--;
natts = rec->tupdesc->natts;
--- 3168,3202 ----
* Get the number of the records field to change and the
* number of attributes in the tuple.
*/
! 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)));
fno--;
natts = rec->tupdesc->natts;
***************
*** 3497,3515 ****
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;
}
--- 3534,3677 ----
errmsg("record \"%s\" is not assigned yet",
rec->refname),
errdetail("The tuple structure of a not-yet-assigned record is indeterminate.")));
! 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_NRECFIELD:
! /* Get # of fields in a record */
! {
! PLpgSQL_rec *rec = (PLpgSQL_rec *) (estate->datums[((PLpgSQL_recfieldproperties *) datum)->recparentno]);
!
! 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.")));
! if ( expectedtypeid != InvalidOid && expectedtypeid != INT4OID )
! ereport(ERROR,
! (errcode(ERRCODE_DATATYPE_MISMATCH),
! errmsg("type of \"%s\" does not match scalar type",
! rec->refname)));
! *value = Int32GetDatum(rec->tupdesc->natts);
! *typeid = INT4OID;
! *isnull = false;
break;
}
***************
*** 3610,3616 ****
*/
if (expr->plan == NULL)
exec_prepare_plan(estate, expr);
!
/*
* If this is a simple expression, bypass SPI and use the executor
* directly
--- 3772,3799 ----
*/
if (expr->plan == NULL)
exec_prepare_plan(estate, expr);
! else {
! /*
! * check for any subexpressions with varying type in the expression
! * currently, 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 ) {
! /* discard the plan and get new one */
! SPI_freeplan(expr->plan);
! exec_prepare_plan(estate, expr);
! }
! }
/*
* If this is a simple expression, bypass SPI and use the executor
* directly
*** ./src/pl/plpgsql/src/pl_funcs.c.orig Wed Jul 13 19:23:08 2005
--- ./src/pl/plpgsql/src/pl_funcs.c Wed Jul 13 19:32:17 2005
***************
*** 1066,1074 ****
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);
break;
case PLPGSQL_DTYPE_ARRAYELEM:
printf("ARRAYELEM of VAR %d subscript ",
--- 1066,1078 ----
printf("REC %s\n", ((PLpgSQL_rec *) d)->refname);
break;
case PLPGSQL_DTYPE_RECFIELD:
! 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 ",
*** ./src/pl/plpgsql/src/plpgsql.h.orig Wed Jul 13 19:21:32 2005
--- ./src/pl/plpgsql/src/plpgsql.h Thu Jul 14 17:14:46 2005
***************
*** 73,79 ****
PLPGSQL_DTYPE_RECFIELD,
PLPGSQL_DTYPE_ARRAYELEM,
PLPGSQL_DTYPE_EXPR,
! PLPGSQL_DTYPE_TRIGARG
};
/* ----------
--- 73,81 ----
PLPGSQL_DTYPE_RECFIELD,
PLPGSQL_DTYPE_ARRAYELEM,
PLPGSQL_DTYPE_EXPR,
! PLPGSQL_DTYPE_TRIGARG,
! PLPGSQL_DTYPE_RECFIELDNAMES,
! PLPGSQL_DTYPE_NRECFIELD
};
/* ----------
***************
*** 269,278 ****
{ /* Field in record */
int dtype;
int rfno;
! char *fieldname;
int recparentno; /* dno of parent record */
} PLpgSQL_recfield;
typedef struct
{ /* Element of array variable */
--- 271,295 ----
{ /* Field in record */
int dtype;
int rfno;
! 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 */
***************
*** 678,683 ****
--- 695,703 ----
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_wordnfields(char *word);
+ extern int plpgsql_parse_wordfieldnames(char *word);
+ extern int plpgsql_parse_wordpercentword(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,
*** ./src/pl/plpgsql/src/scan.l.orig Wed Jul 13 19:21:59 2005
--- ./src/pl/plpgsql/src/scan.l Wed Jul 13 19:33:30 2005
***************
*** 243,248 ****
--- 243,257 ----
{param}{space}*\.{space}*{identifier}{space}*%ROWTYPE {
plpgsql_error_lineno = plpgsql_scanner_lineno();
return plpgsql_parse_dblwordrowtype(yytext); }
+ {identifier}{space}*%NFIELDS {
+ plpgsql_error_lineno = plpgsql_scanner_lineno();
+ return plpgsql_parse_wordnfields(yytext); }
+ {identifier}{space}*%FIELDNAMES {
+ plpgsql_error_lineno = plpgsql_scanner_lineno();
+ return plpgsql_parse_wordfieldnames(yytext); }
+ {identifier}{space}*%{identifier} {
+ plpgsql_error_lineno = plpgsql_scanner_lineno();
+ return plpgsql_parse_wordpercentword(yytext); }
{digit}+ { return T_NUMBER; }
*** ./src/test/regress/expected/plpgsql.out.orig Thu Jul 14 12:03:36 2005
--- ./src/test/regress/expected/plpgsql.out Thu Jul 14 16:28:39 2005
***************
*** 2721,2723 ****
--- 2721,2764 ----
$$ 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;
+ ival := r%NFIELDS;
+ RAISE NOTICE 'fieldcount=%', ival;
+ columns := r%FIELDNAMES;
+ RAISE NOTICE 'fieldnames=%', columns;
+ RETURN;
+ end;
+ $$ language plpgsql;
+ select test_record();
+ NOTICE: ival=1, tval=sometext
+ NOTICE: ival2=1, tval2=sometext
+ NOTICE: fieldcount=2
+ NOTICE: fieldnames={i,t}
+ test_record
+ -------------
+
+ (1 row)
+
+ drop table ritest;
+ drop function test_record();
*** ./src/test/regress/sql/plpgsql.sql.orig Thu Jul 14 10:57:17 2005
--- ./src/test/regress/sql/plpgsql.sql Thu Jul 14 12:17:26 2005
***************
*** 2280,2282 ****
--- 2280,2316 ----
end loop outer_label;
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;
+ ival := r%NFIELDS;
+ RAISE NOTICE 'fieldcount=%', ival;
+ columns := r%FIELDNAMES;
+ RAISE NOTICE 'fieldnames=%', columns;
+ RETURN;
+ end;
+ $$ language plpgsql;
+ select test_record();
+ drop table ritest;
+ drop function test_record();
+