Revert patch, needs more work:
--------------------------------------------------------------------------- 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:
parent
fc079f8411
commit
4d06e86d04
@ -1,4 +1,4 @@
|
|||||||
<!-- $PostgreSQL: pgsql/doc/src/sgml/plpgsql.sgml,v 1.93 2006/05/30 12:03:12 momjian Exp $ -->
|
<!-- $PostgreSQL: pgsql/doc/src/sgml/plpgsql.sgml,v 1.94 2006/05/30 13:40:55 momjian Exp $ -->
|
||||||
|
|
||||||
<chapter id="plpgsql">
|
<chapter id="plpgsql">
|
||||||
<title><application>PL/pgSQL</application> - <acronym>SQL</acronym> Procedural Language</title>
|
<title><application>PL/pgSQL</application> - <acronym>SQL</acronym> Procedural Language</title>
|
||||||
@ -879,55 +879,6 @@ SELECT merge_fields(t.*) FROM table1 t WHERE ... ;
|
|||||||
field in it will draw a run-time error.
|
field in it will draw a run-time error.
|
||||||
</para>
|
</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>
|
<para>
|
||||||
Note that <literal>RECORD</> is not a true data type, only a placeholder.
|
Note that <literal>RECORD</> is not a true data type, only a placeholder.
|
||||||
One should also realize that when a <application>PL/pgSQL</application>
|
One should also realize that when a <application>PL/pgSQL</application>
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
*
|
*
|
||||||
*
|
*
|
||||||
* IDENTIFICATION
|
* IDENTIFICATION
|
||||||
* $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_comp.c,v 1.105 2006/05/30 12:03:13 momjian Exp $
|
* $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_comp.c,v 1.106 2006/05/30 13:40:55 momjian Exp $
|
||||||
*
|
*
|
||||||
*-------------------------------------------------------------------------
|
*-------------------------------------------------------------------------
|
||||||
*/
|
*/
|
||||||
@ -884,8 +884,7 @@ plpgsql_parse_dblword(char *word)
|
|||||||
|
|
||||||
new = palloc(sizeof(PLpgSQL_recfield));
|
new = palloc(sizeof(PLpgSQL_recfield));
|
||||||
new->dtype = PLPGSQL_DTYPE_RECFIELD;
|
new->dtype = PLPGSQL_DTYPE_RECFIELD;
|
||||||
new->fieldindex.fieldname = pstrdup(cp[1]);
|
new->fieldname = pstrdup(cp[1]);
|
||||||
new->fieldindex_flag = RECFIELD_USE_FIELDNAME;
|
|
||||||
new->recparentno = ns->itemno;
|
new->recparentno = ns->itemno;
|
||||||
|
|
||||||
plpgsql_adddatum((PLpgSQL_datum *) new);
|
plpgsql_adddatum((PLpgSQL_datum *) new);
|
||||||
@ -991,8 +990,7 @@ plpgsql_parse_tripword(char *word)
|
|||||||
|
|
||||||
new = palloc(sizeof(PLpgSQL_recfield));
|
new = palloc(sizeof(PLpgSQL_recfield));
|
||||||
new->dtype = PLPGSQL_DTYPE_RECFIELD;
|
new->dtype = PLPGSQL_DTYPE_RECFIELD;
|
||||||
new->fieldindex.fieldname = pstrdup(cp[2]);
|
new->fieldname = pstrdup(cp[2]);
|
||||||
new->fieldindex_flag = RECFIELD_USE_FIELDNAME;
|
|
||||||
new->recparentno = ns->itemno;
|
new->recparentno = ns->itemno;
|
||||||
|
|
||||||
plpgsql_adddatum((PLpgSQL_datum *) new);
|
plpgsql_adddatum((PLpgSQL_datum *) new);
|
||||||
@ -1440,132 +1438,6 @@ plpgsql_parse_dblwordrowtype(char *word)
|
|||||||
return T_DTYPE;
|
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
|
* plpgsql_build_variable - build a datum-array entry of a given
|
||||||
* datatype
|
* datatype
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
*
|
*
|
||||||
*
|
*
|
||||||
* IDENTIFICATION
|
* IDENTIFICATION
|
||||||
* $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_exec.c,v 1.168 2006/05/30 12:03:13 momjian Exp $
|
* $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_exec.c,v 1.169 2006/05/30 13:40:55 momjian Exp $
|
||||||
*
|
*
|
||||||
*-------------------------------------------------------------------------
|
*-------------------------------------------------------------------------
|
||||||
*/
|
*/
|
||||||
@ -741,7 +741,7 @@ copy_plpgsql_datum(PLpgSQL_datum *datum)
|
|||||||
case PLPGSQL_DTYPE_RECFIELD:
|
case PLPGSQL_DTYPE_RECFIELD:
|
||||||
case PLPGSQL_DTYPE_ARRAYELEM:
|
case PLPGSQL_DTYPE_ARRAYELEM:
|
||||||
case PLPGSQL_DTYPE_TRIGARG:
|
case PLPGSQL_DTYPE_TRIGARG:
|
||||||
case PLPGSQL_DTYPE_RECFIELDNAMES:
|
|
||||||
/*
|
/*
|
||||||
* These datum records are read-only at runtime, so no need to
|
* These datum records are read-only at runtime, so no need to
|
||||||
* copy them
|
* copy them
|
||||||
@ -851,7 +851,6 @@ exec_stmt_block(PLpgSQL_execstate *estate, PLpgSQL_stmt_block *block)
|
|||||||
|
|
||||||
case PLPGSQL_DTYPE_RECFIELD:
|
case PLPGSQL_DTYPE_RECFIELD:
|
||||||
case PLPGSQL_DTYPE_ARRAYELEM:
|
case PLPGSQL_DTYPE_ARRAYELEM:
|
||||||
case PLPGSQL_DTYPE_RECFIELDNAMES:
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
@ -2180,8 +2179,6 @@ plpgsql_estate_setup(PLpgSQL_execstate *estate,
|
|||||||
static void
|
static void
|
||||||
exec_eval_cleanup(PLpgSQL_execstate *estate)
|
exec_eval_cleanup(PLpgSQL_execstate *estate)
|
||||||
{
|
{
|
||||||
int i;
|
|
||||||
ArrayType *a;
|
|
||||||
/* Clear result of a full SPI_execute */
|
/* Clear result of a full SPI_execute */
|
||||||
if (estate->eval_tuptable != NULL)
|
if (estate->eval_tuptable != NULL)
|
||||||
SPI_freetuptable(estate->eval_tuptable);
|
SPI_freetuptable(estate->eval_tuptable);
|
||||||
@ -2190,14 +2187,6 @@ exec_eval_cleanup(PLpgSQL_execstate *estate)
|
|||||||
/* Clear result of exec_eval_simple_expr (but keep the econtext) */
|
/* Clear result of exec_eval_simple_expr (but keep the econtext) */
|
||||||
if (estate->eval_econtext != NULL)
|
if (estate->eval_econtext != NULL)
|
||||||
ResetExprContext(estate->eval_econtext);
|
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -3167,7 +3156,7 @@ exec_assign_value(PLpgSQL_execstate *estate,
|
|||||||
*/
|
*/
|
||||||
PLpgSQL_recfield *recfield = (PLpgSQL_recfield *) target;
|
PLpgSQL_recfield *recfield = (PLpgSQL_recfield *) target;
|
||||||
PLpgSQL_rec *rec;
|
PLpgSQL_rec *rec;
|
||||||
int fno = 0;
|
int fno;
|
||||||
HeapTuple newtup;
|
HeapTuple newtup;
|
||||||
int natts;
|
int natts;
|
||||||
int i;
|
int i;
|
||||||
@ -3196,35 +3185,12 @@ exec_assign_value(PLpgSQL_execstate *estate,
|
|||||||
* Get the number of the records field to change and the
|
* Get the number of the records field to change and the
|
||||||
* number of attributes in the tuple.
|
* number of attributes in the tuple.
|
||||||
*/
|
*/
|
||||||
if ( recfield->fieldindex_flag == RECFIELD_USE_FIELDNAME ) {
|
fno = SPI_fnumber(rec->tupdesc, recfield->fieldname);
|
||||||
fno = SPI_fnumber(rec->tupdesc, recfield->fieldindex.fieldname);
|
|
||||||
if (fno == SPI_ERROR_NOATTRIBUTE)
|
if (fno == SPI_ERROR_NOATTRIBUTE)
|
||||||
ereport(ERROR,
|
ereport(ERROR,
|
||||||
(errcode(ERRCODE_UNDEFINED_COLUMN),
|
(errcode(ERRCODE_UNDEFINED_COLUMN),
|
||||||
errmsg("record \"%s\" has no field \"%s\"",
|
errmsg("record \"%s\" has no field \"%s\"",
|
||||||
rec->refname, recfield->fieldindex.fieldname)));
|
rec->refname, recfield->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--;
|
fno--;
|
||||||
natts = rec->tupdesc->natts;
|
natts = rec->tupdesc->natts;
|
||||||
|
|
||||||
@ -3544,7 +3510,7 @@ exec_eval_datum(PLpgSQL_execstate *estate,
|
|||||||
{
|
{
|
||||||
PLpgSQL_recfield *recfield = (PLpgSQL_recfield *) datum;
|
PLpgSQL_recfield *recfield = (PLpgSQL_recfield *) datum;
|
||||||
PLpgSQL_rec *rec;
|
PLpgSQL_rec *rec;
|
||||||
int fno = 0;
|
int fno;
|
||||||
|
|
||||||
rec = (PLpgSQL_rec *) (estate->datums[recfield->recparentno]);
|
rec = (PLpgSQL_rec *) (estate->datums[recfield->recparentno]);
|
||||||
if (!HeapTupleIsValid(rec->tup))
|
if (!HeapTupleIsValid(rec->tup))
|
||||||
@ -3553,122 +3519,19 @@ exec_eval_datum(PLpgSQL_execstate *estate,
|
|||||||
errmsg("record \"%s\" is not assigned yet",
|
errmsg("record \"%s\" is not assigned yet",
|
||||||
rec->refname),
|
rec->refname),
|
||||||
errdetail("The tuple structure of a not-yet-assigned record is indeterminate.")));
|
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->fieldname);
|
||||||
fno = SPI_fnumber(rec->tupdesc, recfield->fieldindex.fieldname);
|
|
||||||
if (fno == SPI_ERROR_NOATTRIBUTE)
|
if (fno == SPI_ERROR_NOATTRIBUTE)
|
||||||
ereport(ERROR,
|
ereport(ERROR,
|
||||||
(errcode(ERRCODE_UNDEFINED_COLUMN),
|
(errcode(ERRCODE_UNDEFINED_COLUMN),
|
||||||
errmsg("record \"%s\" has no field \"%s\"",
|
errmsg("record \"%s\" has no field \"%s\"",
|
||||||
rec->refname, recfield->fieldindex.fieldname)));
|
rec->refname, recfield->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);
|
*typeid = SPI_gettypeid(rec->tupdesc, fno);
|
||||||
}
|
*value = SPI_getbinval(rec->tup, rec->tupdesc, fno, isnull);
|
||||||
break;
|
if (expectedtypeid != InvalidOid && expectedtypeid != *typeid)
|
||||||
}
|
|
||||||
|
|
||||||
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,
|
ereport(ERROR,
|
||||||
(errcode(ERRCODE_DATATYPE_MISMATCH),
|
(errcode(ERRCODE_DATATYPE_MISMATCH),
|
||||||
errmsg("type of \"%s\" does not match array type when preparing the plan",
|
errmsg("type of \"%s.%s\" does not match that when preparing the plan",
|
||||||
rec->refname)));
|
rec->refname, recfield->fieldname)));
|
||||||
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;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3769,29 +3632,7 @@ exec_eval_expr(PLpgSQL_execstate *estate,
|
|||||||
*/
|
*/
|
||||||
if (expr->plan == NULL)
|
if (expr->plan == NULL)
|
||||||
exec_prepare_plan(estate, expr);
|
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
|
* If this is a simple expression, bypass SPI and use the executor
|
||||||
* directly
|
* directly
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
*
|
*
|
||||||
*
|
*
|
||||||
* IDENTIFICATION
|
* IDENTIFICATION
|
||||||
* $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_funcs.c,v 1.51 2006/05/30 12:03:13 momjian Exp $
|
* $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_funcs.c,v 1.52 2006/05/30 13:40:55 momjian Exp $
|
||||||
*
|
*
|
||||||
*-------------------------------------------------------------------------
|
*-------------------------------------------------------------------------
|
||||||
*/
|
*/
|
||||||
@ -1044,12 +1044,8 @@ plpgsql_dumptree(PLpgSQL_function *func)
|
|||||||
printf("REC %s\n", ((PLpgSQL_rec *) d)->refname);
|
printf("REC %s\n", ((PLpgSQL_rec *) d)->refname);
|
||||||
break;
|
break;
|
||||||
case PLPGSQL_DTYPE_RECFIELD:
|
case PLPGSQL_DTYPE_RECFIELD:
|
||||||
if ( ((PLpgSQL_recfield *) d)->fieldindex_flag == RECFIELD_USE_FIELDNAME )
|
|
||||||
printf("RECFIELD %-16s of REC %d\n",
|
printf("RECFIELD %-16s of REC %d\n",
|
||||||
((PLpgSQL_recfield *) d)->fieldindex.fieldname,
|
((PLpgSQL_recfield *) d)->fieldname,
|
||||||
((PLpgSQL_recfield *) d)->recparentno);
|
|
||||||
else
|
|
||||||
printf("RECFIELD Variable of REC %d\n",
|
|
||||||
((PLpgSQL_recfield *) d)->recparentno);
|
((PLpgSQL_recfield *) d)->recparentno);
|
||||||
break;
|
break;
|
||||||
case PLPGSQL_DTYPE_ARRAYELEM:
|
case PLPGSQL_DTYPE_ARRAYELEM:
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
*
|
*
|
||||||
*
|
*
|
||||||
* IDENTIFICATION
|
* IDENTIFICATION
|
||||||
* $PostgreSQL: pgsql/src/pl/plpgsql/src/plpgsql.h,v 1.73 2006/05/30 12:03:13 momjian Exp $
|
* $PostgreSQL: pgsql/src/pl/plpgsql/src/plpgsql.h,v 1.74 2006/05/30 13:40:55 momjian Exp $
|
||||||
*
|
*
|
||||||
*-------------------------------------------------------------------------
|
*-------------------------------------------------------------------------
|
||||||
*/
|
*/
|
||||||
@ -52,8 +52,7 @@ enum
|
|||||||
PLPGSQL_DTYPE_RECFIELD,
|
PLPGSQL_DTYPE_RECFIELD,
|
||||||
PLPGSQL_DTYPE_ARRAYELEM,
|
PLPGSQL_DTYPE_ARRAYELEM,
|
||||||
PLPGSQL_DTYPE_EXPR,
|
PLPGSQL_DTYPE_EXPR,
|
||||||
PLPGSQL_DTYPE_TRIGARG,
|
PLPGSQL_DTYPE_TRIGARG
|
||||||
PLPGSQL_DTYPE_RECFIELDNAMES
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/* ----------
|
/* ----------
|
||||||
@ -252,25 +251,10 @@ typedef struct
|
|||||||
{ /* Field in record */
|
{ /* Field in record */
|
||||||
int dtype;
|
int dtype;
|
||||||
int rfno;
|
int rfno;
|
||||||
union {
|
|
||||||
char *fieldname;
|
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 */
|
int recparentno; /* dno of parent record */
|
||||||
} PLpgSQL_recfield;
|
} PLpgSQL_recfield;
|
||||||
|
|
||||||
typedef struct
|
|
||||||
{ /* Field in record */
|
|
||||||
int dtype;
|
|
||||||
int rfno;
|
|
||||||
int recparentno; /* dno of parent record */
|
|
||||||
ArrayType * save_fieldnames;
|
|
||||||
} PLpgSQL_recfieldproperties;
|
|
||||||
|
|
||||||
|
|
||||||
typedef struct
|
typedef struct
|
||||||
{ /* Element of array variable */
|
{ /* Element of array variable */
|
||||||
@ -677,8 +661,6 @@ extern int plpgsql_parse_dblwordtype(char *word);
|
|||||||
extern int plpgsql_parse_tripwordtype(char *word);
|
extern int plpgsql_parse_tripwordtype(char *word);
|
||||||
extern int plpgsql_parse_wordrowtype(char *word);
|
extern int plpgsql_parse_wordrowtype(char *word);
|
||||||
extern int plpgsql_parse_dblwordrowtype(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_parse_datatype(const char *string);
|
||||||
extern PLpgSQL_type *plpgsql_build_datatype(Oid typeOid, int32 typmod);
|
extern PLpgSQL_type *plpgsql_build_datatype(Oid typeOid, int32 typmod);
|
||||||
extern PLpgSQL_variable *plpgsql_build_variable(const char *refname, int lineno,
|
extern PLpgSQL_variable *plpgsql_build_variable(const char *refname, int lineno,
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
*
|
*
|
||||||
*
|
*
|
||||||
* IDENTIFICATION
|
* IDENTIFICATION
|
||||||
* $PostgreSQL: pgsql/src/pl/plpgsql/src/scan.l,v 1.48 2006/05/30 12:03:13 momjian Exp $
|
* $PostgreSQL: pgsql/src/pl/plpgsql/src/scan.l,v 1.49 2006/05/30 13:40:55 momjian Exp $
|
||||||
*
|
*
|
||||||
*-------------------------------------------------------------------------
|
*-------------------------------------------------------------------------
|
||||||
*/
|
*/
|
||||||
@ -222,12 +222,6 @@ dump { return O_DUMP; }
|
|||||||
{param}{space}*\.{space}*{identifier}{space}*%ROWTYPE {
|
{param}{space}*\.{space}*{identifier}{space}*%ROWTYPE {
|
||||||
plpgsql_error_lineno = plpgsql_scanner_lineno();
|
plpgsql_error_lineno = plpgsql_scanner_lineno();
|
||||||
return plpgsql_parse_dblwordrowtype(yytext); }
|
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; }
|
{digit}+ { return T_NUMBER; }
|
||||||
|
|
||||||
|
@ -2725,44 +2725,6 @@ end;
|
|||||||
$$ language plpgsql;
|
$$ language plpgsql;
|
||||||
ERROR: end label "outer_label" specified for unlabelled block
|
ERROR: end label "outer_label" specified for unlabelled block
|
||||||
CONTEXT: compile of PL/pgSQL function "end_label4" near line 5
|
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
|
-- using list of scalars in fori and fore stmts
|
||||||
create function for_vect() returns void as $proc$
|
create function for_vect() returns void as $proc$
|
||||||
<<lbl>>declare a integer; b varchar; c varchar; r record;
|
<<lbl>>declare a integer; b varchar; c varchar; r record;
|
||||||
|
@ -2281,38 +2281,6 @@ begin
|
|||||||
end;
|
end;
|
||||||
$$ language plpgsql;
|
$$ 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
|
-- using list of scalars in fori and fore stmts
|
||||||
create function for_vect() returns void as $proc$
|
create function for_vect() returns void as $proc$
|
||||||
<<lbl>>declare a integer; b varchar; c varchar; r record;
|
<<lbl>>declare a integer; b varchar; c varchar; r record;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user