Re: PL/PGSQL: Dynamic Record Introspection - Mailing list pgsql-patches
From | Titus von Boxberg |
---|---|
Subject | Re: PL/PGSQL: Dynamic Record Introspection |
Date | |
Msg-id | 42D6E6A3.3050108@bhi-hamburg.de Whole thread Raw |
In response to | Re: PL/PGSQL: Dynamic Record Introspection (Neil Conway <neilc@samurai.com>) |
List | pgsql-patches |
Neil Conway schrieb: > Titus von Boxberg wrote: > > Can you supply some proper regression tests, please? i.e. patch sql/plpgsql.sql and expected/plpgsql.out in src/test/regress In sql/plpgsql.sql I have added a function testing the new features and altered expected/plpgsql.out > > A few minor comments from skimming the patch: Done. ------------------ The modifications to the first one are the following: - restored type evaluation in RECFIELD value evaluation in exec_eval_datum to the previous semantics. The addition should now be completely invisible to existing code. - instead do a check in exec_eval_expr() if the expression contains datums of the type record%variable. For these the type cannot be stored in a plan because the type might change between evaluations of this expr. --> free and reprepare the plan. - storage allocated by exec_eval_expr() for the expression record%FIELDNAMES (the array containing the names) is freed in exec_eval_cleanup ------------------ regression tests are ok on my system. Do you agree with the mods and the patch? 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 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(); +
pgsql-patches by date: