1
0
mirror of https://github.com/postgres/postgres.git synced 2025-07-28 23:42:10 +03:00

Add dynamic record inspection to PL/PgSQL, useful for generic triggers:

tval2 := r.(cname);

or

  columns := r.(*);

Titus von Boxberg
This commit is contained in:
Bruce Momjian
2006-05-30 12:03:13 +00:00
parent 88ba64d396
commit 38c7700f56
8 changed files with 472 additions and 38 deletions

View File

@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_comp.c,v 1.104 2006/05/30 11:58:05 momjian Exp $
* $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_comp.c,v 1.105 2006/05/30 12:03:13 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

View File

@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_exec.c,v 1.167 2006/05/30 11:58:05 momjian Exp $
* $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_exec.c,v 1.168 2006/05/30 12:03:13 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

View File

@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_funcs.c,v 1.50 2006/05/30 11:58:05 momjian Exp $
* $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_funcs.c,v 1.51 2006/05/30 12:03:13 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 ",

View File

@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/pl/plpgsql/src/plpgsql.h,v 1.72 2006/05/30 11:58:05 momjian Exp $
* $PostgreSQL: pgsql/src/pl/plpgsql/src/plpgsql.h,v 1.73 2006/05/30 12:03:13 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,

View File

@ -9,7 +9,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/pl/plpgsql/src/scan.l,v 1.47 2006/05/30 11:58:05 momjian Exp $
* $PostgreSQL: pgsql/src/pl/plpgsql/src/scan.l,v 1.48 2006/05/30 12:03:13 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; }

View File

@ -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$
<<lbl>>declare a integer; b varchar; c varchar; r record;

View File

@ -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$
<<lbl>>declare a integer; b varchar; c varchar; r record;