Hi all,

I needed introspection capabilities for record types to write more generic
trigger procedures in PL/PGSQL.

With the following patch it's possible to
- extract all field names of a record into an array
- extract field count of a record
- address a single field of a record with a variable
  containing the field name (additional to the usual record.fieldname
  notation where the fieldname is hardcoded).

The syntax is
- record%NFIELDS gives the number of fields in the record
- record%FIELDNAMES gives the array of the field names
- record%scalarvariable extracts the field whose name
  is equal to the contents of scalarvariable

------------

The patch is nonintrusive in the sense that it only adds things
with one exception:
In function exec_eval_datum(), file pl_exec.c, line 3557 (after the patch)
I chose to convert the record field values to TEXT if the caller does
not require a certain type (expectedtypeid == InvalidOid).
Additionally, I cast the value from the record field type if
the destination Datum is of different type.

As far as I can see, this does no harm because in most cases
the expectedtypeid is correctly set. I just wanted to avoid that
if it is not set, the returned datum is of a more restrictive type
than TEXT.

------------

The patch is against a HEAD checkout from 07/12/05
The output comes from difforig.

Test code for the patch can be extracted from an example I put into
plpgsql.sgml

------------

Here is a summary of things that get patched by the file:
- add three new special parsing functions to pl_comp.c
  (plpgsql_parse_wordpercentword, plpgsql_parse_wordnfields,
  plpgsql_parse_wordfieldnames).
- modify PLpgSQL_recfield in plpgsql.h to either hold
  a conventional field name (record.fieldname) or a dno
  for the variable (record%variable).
- add two PLPGSQL_DTYPEs for the two new % notations
- modify "case PLPGSQL_DTYPE_RECFIELD:"
  in exec_eval_datum() and exec_assign_value()
  to deal with index strings from a variable
- add "case PLPGSQL_DTYPE_RECFIELDNAMES"
  and "case PLPGSQL_DTYPE_NRECFIELD"
  to exec_eval_datum() to evaluate  %FIELDNAMES
  and %NFIELDS expressions.
- update plpgsql.sgml in the docs directory

------------

Please notify me if I can be of further assistance.

Regards
Titus
*** ./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 ----
     </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 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:
+ <programlisting>
+ 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 := '<NULL>';
+                       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;
+ 

+ 
+ </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>
      function is declared to return type <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 00:25:42 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 = strdup(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 = strdup(cp[2]);
!                               new->fieldindex_flag = RECFIELD_USE_FIELDNAME;
                                new->recparentno = ns->itemno;
  
                                plpgsql_adddatum((PLpgSQL_datum *) new);
***************
*** 1551,1556 ****
--- 1553,1736 ----
        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 = malloc(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;
+                               } /* IF VAR */
+                               break;
+                       }
+               default:
+                       break;
+       }
+       return ret;
+ } /* plpgsql_parse_wordpercentword */
+ 
+ /* ----------
+  * 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 = 
malloc(sizeof(PLpgSQL_recfieldproperties));
+                               new->dtype = PLPGSQL_DTYPE_NRECFIELD;
+                               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 */
+ 
+ 
+ /* ----------
+  * 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 = 
malloc(sizeof(PLpgSQL_recfieldproperties));
+                               new->dtype = PLPGSQL_DTYPE_RECFIELDNAMES;
+                               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_wordfieldnames */
+ 
+ 
  /*
   * 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 00:42:51 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:
***************
*** 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;
  
--- 3158,3192 ----
                                 * 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;
                        }
  
--- 3524,3670 ----
                                           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 = TEXTOID;       /* TEXT 
should be able to cope with anything */
!                               /* Want to be able store the value in any 
compatible variable */
!                               if ( expectedtypeid != 
SPI_gettypeid(rec->tupdesc, fno) ) {
!                                       Datum cval = SPI_getbinval(rec->tup, 
rec->tupdesc, fno, isnull);
!                                       /* elog(WARNING, "casting record field 
value to expected value!"); */
!                                       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)));
!                                       */
!                               } /* IF expected typeid is valid but does not 
match! */
!                               else { /* expected typeid matches */
!                                       *value = SPI_getbinval(rec->tup, 
rec->tupdesc, fno, isnull);
!                                       *typeid = SPI_gettypeid(rec->tupdesc, 
fno);
!                               } /* ELSE */
!                               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_rec             *rec = (PLpgSQL_rec *) 
(estate->datums[((PLpgSQL_recfieldproperties *) datum)->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;
!                                       } /* IF */
!                               } /* FOR */
! 
!                               arrayval = construct_array(arrayelems, tfc,
!                                                        arrayelemtypeid,
!                                                        elemtyplen,
!                                                        elemtypbyval,
!                                                        elemtypalign);
! 
! 
!                               /* construct_array copies data; free temp elem 
array */
! #if   0
!                               for ( fc = 0; fc < tfc; ++fc )
!                                       pfree(DatumGetPointer(arrayelems[fc]);
!                               pfree(arrayelems);
! #endif
!                               *value = PointerGetDatum(arrayval);
!                               *typeid = arraytypeid;
!                               *isnull = false;
!                               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;
                        }
  
*** ./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      Wed Jul 13 19:19:30 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,294 ----
  {                                                             /* 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 */
+ } PLpgSQL_recfieldproperties;
+ 
  
  typedef struct
  {                                                             /* Element of 
array variable */
***************
*** 678,683 ****
--- 694,702 ----
  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;                      }
  
---------------------------(end of broadcast)---------------------------
TIP 6: explain analyze is your friend

Reply via email to