diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml index 0a9a62b759..5ce49880b5 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -1,4 +1,4 @@ - + Functions and Operators @@ -1112,7 +1112,9 @@ substring(string from pattern) text - Extract substring matching POSIX regular expression + Extract substring matching POSIX regular expression. See + for more information on pattern + matching. substring('Thomas' from '...$') mas @@ -1122,8 +1124,9 @@ substring(string from pattern for escape) text - Extract substring matching SQL regular - expression + Extract substring matching SQL regular expression. + See for more information on + pattern matching. substring('Thomas' from '%#"o_a#"_' for '#') oma @@ -1420,6 +1423,18 @@ 'O''Reilly' + + regexp_replace(string text, pattern text, replacement text [,flags text]) + text + + Replace substring matching POSIX regular expression. See + for more information on pattern + matching. + + regexp_replace('Thomas', '.[mN]a.', 'M') + ThM + + repeat(string text, number int) text diff --git a/doc/src/sgml/plpgsql.sgml b/doc/src/sgml/plpgsql.sgml index 2fc998ee90..ef5e9e3ece 100644 --- a/doc/src/sgml/plpgsql.sgml +++ b/doc/src/sgml/plpgsql.sgml @@ -1,4 +1,4 @@ - + <application>PL/pgSQL</application> - <acronym>SQL</acronym> Procedural Language @@ -879,6 +879,55 @@ SELECT merge_fields(t.*) FROM table1 t WHERE ... ; field in it will draw a run-time error. + + 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: + + +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(); + + + + Note that RECORD is not a true data type, only a placeholder. One should also realize that when a PL/pgSQL diff --git a/src/pl/plpgsql/src/pl_comp.c b/src/pl/plpgsql/src/pl_comp.c index 673ae54ca6..566379da42 100644 --- a/src/pl/plpgsql/src/pl_comp.c +++ b/src/pl/plpgsql/src/pl_comp.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_comp.c,v 1.102 2006/05/28 03:03:17 adunstan Exp $ + * $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_comp.c,v 1.103 2006/05/30 11:54:51 momjian Exp $ * *------------------------------------------------------------------------- */ @@ -884,7 +884,8 @@ plpgsql_parse_dblword(char *word) new = palloc(sizeof(PLpgSQL_recfield)); new->dtype = PLPGSQL_DTYPE_RECFIELD; - new->fieldname = pstrdup(cp[1]); + new->fieldindex.fieldname = pstrdup(cp[1]); + new->fieldindex_flag = RECFIELD_USE_FIELDNAME; new->recparentno = ns->itemno; plpgsql_adddatum((PLpgSQL_datum *) new); @@ -990,7 +991,8 @@ plpgsql_parse_tripword(char *word) new = palloc(sizeof(PLpgSQL_recfield)); new->dtype = PLPGSQL_DTYPE_RECFIELD; - new->fieldname = pstrdup(cp[2]); + new->fieldindex.fieldname = pstrdup(cp[2]); + new->fieldindex_flag = RECFIELD_USE_FIELDNAME; new->recparentno = ns->itemno; plpgsql_adddatum((PLpgSQL_datum *) new); @@ -1438,6 +1440,132 @@ plpgsql_parse_dblwordrowtype(char *word) 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 * datatype diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c index d2ff1fffd5..63ed3f8523 100644 --- a/src/pl/plpgsql/src/pl_exec.c +++ b/src/pl/plpgsql/src/pl_exec.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_exec.c,v 1.165 2006/05/28 03:03:17 adunstan Exp $ + * $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_exec.c,v 1.166 2006/05/30 11:54:51 momjian Exp $ * *------------------------------------------------------------------------- */ @@ -741,7 +741,7 @@ copy_plpgsql_datum(PLpgSQL_datum *datum) 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 @@ -851,6 +851,7 @@ exec_stmt_block(PLpgSQL_execstate *estate, PLpgSQL_stmt_block *block) case PLPGSQL_DTYPE_RECFIELD: case PLPGSQL_DTYPE_ARRAYELEM: + case PLPGSQL_DTYPE_RECFIELDNAMES: break; default: @@ -2179,6 +2180,8 @@ plpgsql_estate_setup(PLpgSQL_execstate *estate, 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); @@ -2187,6 +2190,14 @@ exec_eval_cleanup(PLpgSQL_execstate *estate) /* 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; + } + } } @@ -3156,7 +3167,7 @@ exec_assign_value(PLpgSQL_execstate *estate, */ PLpgSQL_recfield *recfield = (PLpgSQL_recfield *) target; PLpgSQL_rec *rec; - int fno; + int fno = 0; HeapTuple newtup; int natts; int i; @@ -3185,12 +3196,35 @@ exec_assign_value(PLpgSQL_execstate *estate, * 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) + 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\" has no field \"%s\"", - rec->refname, recfield->fieldname))); + (errcode(ERRCODE_UNDEFINED_COLUMN), + errmsg("record \"%s\": internal error", + rec->refname))); fno--; natts = rec->tupdesc->natts; @@ -3510,7 +3544,7 @@ exec_eval_datum(PLpgSQL_execstate *estate, { PLpgSQL_recfield *recfield = (PLpgSQL_recfield *) datum; PLpgSQL_rec *rec; - int fno; + int fno = 0; rec = (PLpgSQL_rec *) (estate->datums[recfield->recparentno]); if (!HeapTupleIsValid(rec->tup)) @@ -3519,22 +3553,125 @@ exec_eval_datum(PLpgSQL_execstate *estate, 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; - } - + 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; @@ -3632,7 +3769,29 @@ exec_eval_expr(PLpgSQL_execstate *estate, */ 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 */ + /* 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 diff --git a/src/pl/plpgsql/src/pl_funcs.c b/src/pl/plpgsql/src/pl_funcs.c index a6cf2bd32e..8eafb3e61e 100644 --- a/src/pl/plpgsql/src/pl_funcs.c +++ b/src/pl/plpgsql/src/pl_funcs.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_funcs.c,v 1.48 2006/03/09 21:29:37 momjian Exp $ + * $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_funcs.c,v 1.49 2006/05/30 11:54:51 momjian Exp $ * *------------------------------------------------------------------------- */ @@ -1044,9 +1044,13 @@ plpgsql_dumptree(PLpgSQL_function *func) 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); + 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 ", diff --git a/src/pl/plpgsql/src/plpgsql.h b/src/pl/plpgsql/src/plpgsql.h index 50fe382b32..652f7b7bde 100644 --- a/src/pl/plpgsql/src/plpgsql.h +++ b/src/pl/plpgsql/src/plpgsql.h @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/pl/plpgsql/src/plpgsql.h,v 1.70 2006/05/28 03:03:17 adunstan Exp $ + * $PostgreSQL: pgsql/src/pl/plpgsql/src/plpgsql.h,v 1.71 2006/05/30 11:54:51 momjian Exp $ * *------------------------------------------------------------------------- */ @@ -52,7 +52,8 @@ enum PLPGSQL_DTYPE_RECFIELD, PLPGSQL_DTYPE_ARRAYELEM, PLPGSQL_DTYPE_EXPR, - PLPGSQL_DTYPE_TRIGARG + PLPGSQL_DTYPE_TRIGARG, + PLPGSQL_DTYPE_RECFIELDNAMES }; /* ---------- @@ -251,10 +252,25 @@ typedef struct { /* Field in record */ int dtype; int rfno; - char *fieldname; + 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 */ @@ -661,6 +677,8 @@ extern int plpgsql_parse_dblwordtype(char *word); 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, diff --git a/src/pl/plpgsql/src/scan.l b/src/pl/plpgsql/src/scan.l index 835162445d..c537fba96e 100644 --- a/src/pl/plpgsql/src/scan.l +++ b/src/pl/plpgsql/src/scan.l @@ -9,7 +9,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/pl/plpgsql/src/scan.l,v 1.45 2006/03/09 21:29:38 momjian Exp $ + * $PostgreSQL: pgsql/src/pl/plpgsql/src/scan.l,v 1.46 2006/05/30 11:54:51 momjian Exp $ * *------------------------------------------------------------------------- */ @@ -222,6 +222,12 @@ dump { return O_DUMP; } {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; } diff --git a/src/test/regress/expected/plpgsql.out b/src/test/regress/expected/plpgsql.out index 6e6597dadb..e20d76c0ae 100644 --- a/src/test/regress/expected/plpgsql.out +++ b/src/test/regress/expected/plpgsql.out @@ -2725,6 +2725,44 @@ end; $$ 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(); -- using list of scalars in fori and fore stmts create function for_vect() returns void as $proc$ <>declare a integer; b varchar; c varchar; r record; diff --git a/src/test/regress/sql/plpgsql.sql b/src/test/regress/sql/plpgsql.sql index 19e145be65..0546062986 100644 --- a/src/test/regress/sql/plpgsql.sql +++ b/src/test/regress/sql/plpgsql.sql @@ -2281,6 +2281,38 @@ begin 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(); + + -- using list of scalars in fori and fore stmts create function for_vect() returns void as $proc$ <>declare a integer; b varchar; c varchar; r record;