How PLpgSQL Handles Record Variables and Field Access in Compiler Internals

When a PL/pgSQL function declares a RECORD variable and accesses its fields, the compiler constructs a structured internal representation to manage dynamic field referneces at runtime. Consider this function:

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;

The declaration a RECORD; triggers the creation of a PLpgSQL_rec structure during parsing. The parser invokes plpgsql_build_variable, which detects the RECORD type and delegates to plpgsql_build_record. This function allocates memory for the record variable, assigns its name (a), line number, and associated type OID, then registers it into the global plpgsql_Datums array via plpgsql_adddatum. The variable’s index (dno) is assinged sequentially, starting from 0.

Simultaneously, the variable is registered in the namespace chain via plpgsql_ns_additem, linking the identifier a to its datum index. This enables later lookups during expression parsing.

When encountering field access like a.id, the parser invokes plpgsql_parse_dblword, which splits the double-word token into word1 = "a" and word2 = "id". It then searches the namespace for a using plpgsql_ns_lookup. Upon finding the matching PLpgSQL_rec entry, it constructs a PLpgSQL_recfield structure to represent the field reference.

The plpgsql_build_recfield function creates a new datum of type PLPGSQL_DTYPE_RECFIELD, storing the field name (id) and a reference to its parent record via recparentno (the dno of a). This new field datum is added to plpgsql_Datums, and its index is linked into the parent record’s firstfield chain, forming a singly-linked list of all referenced fields for that record.

Each field access—whether a.id or a.name—results in a separate PLpgSQL_recfield node. These nodes are not resolved to actual column types during compilation; instead, they are deferred until execution time, when the record’s runtime tuple descriptor becomes available. The parser wraps each field reference in a PLpgSQL_expr structure, storing the raw text (a.id) and the current namespace context for later evaluation.

Throughout this process, all datums—variables, records, and record fields—are stored in the global plpgsql_Datums array, indexed by their dno. This array is only valid during the compilation phase and is discarded after do_compile completes. The namespace chain (ns_top) provides symbol resolution during parsing, ensuring identifiers like a correctly map to their corresponding record structures.

Tags: plpgsql PostgreSQL record-variable compiler-internal plpgsql_datum

Posted on Thu, 29 Oct 2026 16:22:47 +0000 by cgrenda