Re: PL/PGSQL: Dynamic Record Introspection

From: Bruce Momjian <pgman(at)candle(dot)pha(dot)pa(dot)us>
To: Titus von Boxberg <ut(at)bhi-hamburg(dot)de>
Cc: Tom Lane <tgl(at)sss(dot)pgh(dot)pa(dot)us>, Neil Conway <neilc(at)samurai(dot)com>, pgsql-patches(at)postgresql(dot)org
Subject: Re: PL/PGSQL: Dynamic Record Introspection
Date: 2005-07-31 02:36:05
Message-ID: 200507310236.j6V2a5g04335@candle.pha.pa.us
Views: Raw Message | Whole Thread | Download mbox | Resend email
Thread:
Lists: pgsql-patches


This has been saved for the 8.2 release:

http://momjian.postgresql.org/cgi-bin/pgpatches_hold

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

Titus von Boxberg wrote:
> Tom Lane schrieb:
> > "Titus von Boxberg" <ut(at)bhi-hamburg(dot)de> writes:
> > It works for me if we want to have an "NFIELDS" construct. Personally
> > I'm still not convinced that we need one --- what's the use-case?
> I have removed the NFIELDS construct
> >
> >
> > I'd prefer arbitrary expression, but I suppose there's no harm in doing
> > the simple case first and generalizing if there's demand.
> I took the "no harm" way.
>
> Attached please find the updated patch.
> The patch is against HEAD of 050721.
>
> I switched the syntax to your proposal, renamed the functions
> in pl_comp.c and updated the sgml doc source and regression
> test files accordingly.
>
> Regards
> Titus
>

> *** ./doc/src/sgml/plpgsql.sgml.orig Sat Jul 2 08:59:47 2005
> --- ./doc/src/sgml/plpgsql.sgml Sat Jul 23 17:24:54 2005
> ***************
> *** 867,872 ****
> --- 867,921 ----
> </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>
> function is declared to return type <type>record</>, this is not quite the
> *** ./src/pl/plpgsql/src/pl_comp.c.orig Wed Jul 6 16:42:10 2005
> --- ./src/pl/plpgsql/src/pl_comp.c Thu Jul 21 21:28:15 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);
> ***************
> *** 1550,1555 ****
> --- 1552,1683 ----
> MemoryContextSwitchTo(oldCxt);
> 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
> *** ./src/pl/plpgsql/src/pl_exec.c.orig Sun Jun 26 22:05:42 2005
> --- ./src/pl/plpgsql/src/pl_exec.c Sat Jul 23 17:13:32 2005
> ***************
> *** 716,721 ****
> --- 716,722 ----
> 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
> ***************
> *** 825,830 ****
> --- 826,832 ----
>
> case PLPGSQL_DTYPE_RECFIELD:
> case PLPGSQL_DTYPE_ARRAYELEM:
> + case PLPGSQL_DTYPE_RECFIELDNAMES:
> break;
>
> default:
> ***************
> *** 2146,2151 ****
> --- 2148,2155 ----
> 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 ****
> --- 2158,2171 ----
> /* 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;
>
> --- 3166,3200 ----
> * 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,3518 ****
> 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;
> ! }
> !
> case PLPGSQL_DTYPE_TRIGARG:
> {
> PLpgSQL_trigarg *trigarg = (PLpgSQL_trigarg *) datum;
> --- 3532,3656 ----
> 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_TRIGARG:
> {
> PLpgSQL_trigarg *trigarg = (PLpgSQL_trigarg *) datum;
> ***************
> *** 3610,3616 ****
> --- 3748,3790 ----
> */
> 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 */
> + /* Make sure expr is not in the list of active simple expressions
> + because exec_prepare_plan()/exec_simple_check_plan()...
> + will destroy the link to the next simple expression */
> + PLpgSQL_expr *e, *enext;
> + PLpgSQL_expr **eprevnext = &active_simple_exprs;
> + for (e = active_simple_exprs; e; e = enext)
> + {
> + enext = e->expr_simple_next;
> + if ( e == expr ) /* found us in the list */
> + *eprevnext = enext; /* discard us */
> + else
> + eprevnext = &(e->expr_simple_next);
> + }
>
> + /* 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
> *** ./src/pl/plpgsql/src/pl_funcs.c.orig Wed Jun 22 01:35:02 2005
> --- ./src/pl/plpgsql/src/pl_funcs.c Thu Jul 21 21:03:40 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 Thu Jul 21 21:00:57 2005
> --- ./src/pl/plpgsql/src/plpgsql.h Thu Jul 21 21:15:42 2005
> ***************
> *** 73,79 ****
> PLPGSQL_DTYPE_RECFIELD,
> PLPGSQL_DTYPE_ARRAYELEM,
> PLPGSQL_DTYPE_EXPR,
> ! PLPGSQL_DTYPE_TRIGARG
> };
>
> /* ----------
> --- 73,80 ----
> PLPGSQL_DTYPE_RECFIELD,
> PLPGSQL_DTYPE_ARRAYELEM,
> PLPGSQL_DTYPE_EXPR,
> ! PLPGSQL_DTYPE_TRIGARG,
> ! PLPGSQL_DTYPE_RECFIELDNAMES
> };
>
> /* ----------
> ***************
> *** 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 */
> --- 270,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 */
> + ArrayType * save_fieldnames;
> + } PLpgSQL_recfieldproperties;
> +
>
> typedef struct
> { /* Element of array variable */
> ***************
> *** 678,683 ****
> --- 694,701 ----
> 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,
> *** ./src/pl/plpgsql/src/scan.l.orig Sun Jun 26 19:16:07 2005
> --- ./src/pl/plpgsql/src/scan.l Thu Jul 21 21:28:20 2005
> ***************
> *** 243,248 ****
> --- 243,254 ----
> {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; }
>
> *** ./src/test/regress/expected/plpgsql.out.orig Sat Jul 2 08:59:48 2005
> --- ./src/test/regress/expected/plpgsql.out Thu Jul 21 22:32:52 2005
> ***************
> *** 2721,2723 ****
> --- 2721,2761 ----
> $$ 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();
> *** ./src/test/regress/sql/plpgsql.sql.orig Thu Jul 21 21:04:43 2005
> --- ./src/test/regress/sql/plpgsql.sql Thu Jul 21 21:36:11 2005
> ***************
> *** 2280,2282 ****
> --- 2280,2314 ----
> 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;
> + columns := r.(*);
> + RAISE NOTICE 'fieldnames=%', columns;
> + RETURN;
> + end;
> + $$ language plpgsql;
> + select test_record();
> + drop table ritest;
> + drop function test_record();
> +

>
> ---------------------------(end of broadcast)---------------------------
> TIP 9: In versions below 8.0, the planner will ignore your desire to
> choose an index scan if your joining column's datatypes do not
> match

--
Bruce Momjian | http://candle.pha.pa.us
pgman(at)candle(dot)pha(dot)pa(dot)us | (610) 359-1001
+ If your life is a hard drive, | 13 Roberts Road
+ Christ can be your backup. | Newtown Square, Pennsylvania 19073

In response to

Browse pgsql-patches by date

  From Date Subject
Next Message Bruce Momjian 2005-07-31 03:39:20 Remote administration functionality
Previous Message Tom Lane 2005-07-31 01:01:11 Re: per user/database connections limit again