Re: PL/PGSQL: Dynamic Record Introspection
От | Bruce Momjian |
---|---|
Тема | Re: PL/PGSQL: Dynamic Record Introspection |
Дата | |
Msg-id | 200507310236.j6V2a5g04335@candle.pha.pa.us обсуждение исходный текст |
Ответ на | Re: PL/PGSQL: Dynamic Record Introspection (Titus von Boxberg <ut@bhi-hamburg.de>) |
Список | 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@bhi-hamburg.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@candle.pha.pa.us | (610) 359-1001 + If your life is a hard drive, | 13 Roberts Road + Christ can be your backup. | Newtown Square, Pennsylvania 19073
В списке pgsql-patches по дате отправления: