【源码日记】了解 PLpgSQL_datum

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;
  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_Datums has valid values only at compile time. More precisely, its values become invalid when do_compile function
completes. see plpgsql_finish_datums.

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;
}

posted on 2024-02-06 12:10  winter-loo  阅读(11)  评论(0编辑  收藏  举报

导航