based on postgres commit b96115acb8a0e08a46877c2b8ef2a7b5560b371b
The SQL
CREATE OR REPLACE FUNCTION demo_fors()
RETURNS VOID AS
$$
DECLARE
a RECORD;
BEGIN
FOR a IN SELECT * FROM some_table LOOP
RAISE NOTICE 'id: %, name: %', a.id, a.name;
END LOOP;
END;
$$
LANGUAGE plpgsql;
dissect declare
a RECORD;
decl_statement : decl_varname decl_const decl_datatype decl_collate decl_notnull decl_defval
{
PLpgSQL_variable *var;
/* $1.name is `a` */
var = plpgsql_build_variable($1.name, $1.lineno, $3, true);
var->isconst = $2;
var->notnull = $5;
var->default_val = $6;
}
plpgsql_build_variable
PLpgSQL_variable *
plpgsql_build_variable(const char *refname, int lineno, PLpgSQL_type *dtype, bool add2namespace)
{
PLpgSQL_variable *result;
switch (dtype->ttype) {
case PLPGSQL_TTYPE_REC:
{
/* Composite type -- build a record variable */
PLpgSQL_rec *rec;
/* refname is `a` */
rec = plpgsql_build_record(refname, lineno, dtype, dtype->typoid, add2namespace);
result = (PLpgSQL_variable *) rec;
break;
}
}
return result;
}
plpgsql_build_record
/*
* Build empty named record variable, and optionally add it to namespace
*/
PLpgSQL_rec *
plpgsql_build_record(const char *refname, int lineno,
PLpgSQL_type *dtype, Oid rectypeid,
bool add2namespace)
{
PLpgSQL_rec *rec;
rec = palloc0(sizeof(PLpgSQL_rec));
rec->dtype = PLPGSQL_DTYPE_REC;
rec->refname = pstrdup(refname);
rec->lineno = lineno;
/* other fields are left as 0, might be changed by caller */
rec->datatype = dtype;
rec->rectypeid = rectypeid;
rec->firstfield = -1;
/**** !!! I will talk about `erh` field in another article !!! ****/
rec->erh = NULL;
plpgsql_adddatum((PLpgSQL_datum *) rec);
if (add2namespace)
plpgsql_ns_additem(PLPGSQL_NSTYPE_REC, rec->dno, rec->refname);
return rec;
}
plpgsql_adddatum
/* ----------
* plpgsql_adddatum Add a variable, record or row
* to the compiler's datum list.
* ----------
*/
void
plpgsql_adddatum(PLpgSQL_datum *newdatum)
{
/* `plpgsql_nDatums` is a global variable that tracks the number of PLpgSQL_datum we know so far */
newdatum->dno = plpgsql_nDatums;
/* `plpgsql_Datums` is a global variable that chains all PLpgSQL_datum in an array */
plpgsql_Datums[plpgsql_nDatums++] = newdatum;
}
plpgsql_ns_additem
/* ----------
* plpgsql_ns_additem Add an item to the current namespace chain
* ----------
*/
void
plpgsql_ns_additem(PLpgSQL_nsitem_type itemtype, int itemno, const char *name)
{
PLpgSQL_nsitem *nse;
Assert(name != NULL);
/* first item added must be a label */
Assert(ns_top != NULL || itemtype == PLPGSQL_NSTYPE_LABEL);
nse = palloc(offsetof(PLpgSQL_nsitem, name) + strlen(name) + 1);
nse->itemtype = itemtype;
/* `itemno` is an index into the global `plpgsql_Datums` array */
nse->itemno = itemno;
/* `name` is `a` here */
strcpy(nse->name, name);
/* `ns_top` is another global variable which forms a namespace chain */
nse->prev = ns_top;
/* `ns_top` always points to newly created namespace item */
ns_top = nse;
}
dissect raise
RAISE NOTICE 'id: %, name: %', a.id, a.name;
pl_gram.y
stmt_raise: K_RAISE
{
PLpgSQL_stmt_raise *new;
/* initialize PLpgSQL_stmt_raise, ignored here */
/* read `NOTICE` token, ignored here */
/* here we only focus parsing `a.id`, `a.name` */
while (tok == ',')
{
PLpgSQL_expr *expr;
expr = read_sql_construct(',', ';', K_USING,
", or ; or USING",
RAW_PARSE_PLPGSQL_EXPR,
true, true, true,
NULL, &tok);
new->params = lappend(new->params, expr);
}
}
read_sql_construct
static PLpgSQL_expr *
read_sql_construct(...)
{
/* switch to `IDENTIFIER_LOOKUP_EXPR` mode */
for(;;)
{
/* read out `a.id`, `a.name` and put it into `StringInfoData ds` */
tok = yylex() {
plpgsql_yylex() {
/* use `internal_yylex()` to find out `a.id` or `a.name` */
plpgsql_parse_dblword(...);
}
};
}
/* construct PLpgSQL_expr */
expr = palloc0(sizeof(PLpgSQL_expr));
expr->query = pstrdup(ds.data);
expr->parseMode = parsemode;
expr->plan = NULL;
expr->paramnos = NULL;
expr->target_param = -1;
expr->ns = plpgsql_ns_top();
if (valid_sql)
check_sql_expr(expr->query, expr->parseMode, startlocation);
}
plpgsql_parse_dblworld
/*
* `word1` is `a`, `word2` is `id` or `name`
* `wdatum` value will be moved to YYSTYPE.wdatum, in other words, it is fed into bison parser
*/
bool plpgsql_parse_dblword(char *word1, char *word2,
PLwdatum *wdatum, PLcword *cword)
{
List *idents = list_make2(makeString(word1), makeString(word2));
/* we can find `a` in the global namespace variable `ns_top` */
ns = plpgsql_ns_lookup(plpgsql_ns_top(), false, word1, word2, NULL, &nnames);
/* and `a` is of type PLPGSQL_NSTYPE_REC */
/*
* First word is a record name, so second word could
* be a field in this record. We build a RECFIELD
* datum whether it is or not --- any error will be
* detected later.
*/
PLpgSQL_rec *rec = (PLpgSQL_rec *) (plpgsql_Datums[ns->itemno]);
PLpgSQL_recfield *new = plpgsql_build_recfield(rec, word2);
wdatum->datum = (PLpgSQL_datum *) new;
}
plpgsql_build_recfield
/* fldname is `id` or `name` */
PLpgSQL_recfield *
plpgsql_build_recfield(PLpgSQL_rec *rec, const char *fldname)
{
/* search for an existing datum referencing this field, code ignored here */
/* nope, so make a new one */
PLpgSQL_recfield *recfield = palloc0(sizeof(PLpgSQL_recfield));
recfield->dtype = PLPGSQL_DTYPE_RECFIELD;
recfield->fieldname = pstrdup(fldname);
recfield->recparentno = rec->dno;
recfield->rectupledescid = INVALID_TUPLEDESC_IDENTIFIER;
plpgsql_adddatum((PLpgSQL_datum *) recfield);
/* now we can link it into the parent's chain */
recfield->nextfield = rec->firstfield;
rec->firstfield = recfield->dno;
return recfield;
}
标签:name,recfield,expr,datum,源码,PLpgSQL,rec,plpgsql
From: https://www.cnblogs.com/lddcool/p/18009337