Postgresql source code (103) expression ExprContext in PLpg/SQL

0 Summary

(You can watch it last)

  1. Two structures contained in PLpgSQL_execstate: EState *simple_eval_estate, ExprContext *eval_econtext
  2. ExprContext is generally enough to be thrown to the SQL engine for execution, but ExprContext will rely on the EState structure to create it, so when PL is executed, the plpgsql_exec_function function needs to pass in the EState structure to facilitate the creation of ExprContext later.
  3. The ExprContext used in PL will be automatically pushed into the simple_econtext_stack stack after creation. Because the exception handling in PL will automatically start sub-transactions, in order to release the resources requested by the expression calculation together with the sub-transactions (to avoid polluting the ExprContext of the top-level transaction), it is necessary to associate the ExprContext with the sub-transactions:
    • So if no exception occurs, then eval_econtext will be released in the subtransaction commit following ReleaseCurrentSubTransaction.
    • If an exception occurs, eval_econtext will be released in the subtransaction rollback following RollbackAndReleaseCurrentSubTransaction.

1 PL runtime information: PLpgSQL_execstate

The running of any statement in PLpg/SQL of PostgreSQL needs to record the status information at runtime. The runtime state in the executor of the SQL layer is recorded using EState, and the state information in the PL is recorded using the PLpgSQL_execstate structure.

/*
 * Runtime execution data
 */
typedef struct PLpgSQL_execstate
{<!-- -->
PLpgSQL_function *func; /* function being executed */
    ...
Datum retval;
bool retisnull;
Oid rettype; // return value processing
    ...
Oid fn_rettype;
bool retistuple;
bool retisset;

bool readonly_func;
bool atomic; // The function is atomic, the process is non-atomic, and non-atomic can execute commit/rollback
                                    // return next of cache
Tuplestorestate *tuple_store;
TupleDesc tuple_store_desc;
...
int ndatums; // number of variable arrays
PLpgSQL_datum **datums; // variable array
    ...
\t
\t
EState *simple_eval_estate; // Why is EState needed here?
ResourceOwner simple_eval_resowner;
    ...
SPITupleTable *eval_tuptable;
uint64 eval_processed;
ExprContext *eval_econtext; // Why is ExprContext needed here?

    ...
} PLpgSQL_execstate;

In the above PLpgSQL_execstate structure, why does EState appear, and what is the function of simple_eval_estate?

Answer: Expression evaluation.

2 PL expression calculation

In PL, there are a large number of grammars that need to call the main parser for calculation, such as:

CREATE or replace function tp14_outter(
  a in integer ,
  b out integer,
  c out integer)
RETURNS int
LANGUAGE plpgsql
AS $$
DECLARE
  rr int;
  b int;
  c int;
BEGIN
  b := 1 + 1;
  c := b / 2;
  rr := b + c + other_func(1,2,3);
  return rr;
END;
$$;

At present, the PL engine will encapsulate the rvalue of the assign statement into a string and save it, and then send it to the SQL engine to calculate the result when it is running.

For example c := b / 2 above:

  1. After PL compilation, the string select b / 2 will be recorded.
  2. When PL is running, it will call the SQL engine, send the string select b / 2 through SPI, and go through the completed grammar and semantic analysis, optimizer, executor (expression calculation module), Finally got the result. (The main parser should not know b, how to calculate it? Answer: the callback hook function gets the value).

Then calling the expression calculation module of the SQL engine must require the runtime structure EState of the SQL engine.

So PLpgSQL_execstate will contain EState *simple_eval_estate;, ExprContext *eval_econtext; structures.

3 PL expression runtime memory structure ExprContext

Two structures contained in PLpgSQL_execstate:

  • EState *simple_eval_estate
  • ExprContext *eval_econtext

In fact, ExprContext is generally enough to be thrown to the SQL engine for execution, but ExprContext will rely on the EState structure to create it, so when PL is executed, the plpgsql_exec_function function needs to pass in the EState structure to facilitate the creation of ExprContext later.

  • The function in PL will use the shared EState structure to create ExprContext: shared_simple_eval_estate
  • Anonymous blocks in PL will use private EState structure for creating ExprContext


The ExprContext used in PL, after creation, will be automatically pushed into the simple_econtext_stack stack, why?

Because the exception handling in PL will automatically start the sub-transaction, in order to release the resources applied by the expression calculation together with the sub-transaction, it is necessary to associate the ExprContext with the sub-transaction:

Once the sub-transaction is released, in the callback function plpgsql_subxact_cb, all ExprContexts related to the sub-transaction in the simple_econtext_stack stack will be released.

void
plpgsql_subxact_cb(SubXactEvent event, SubTransactionId mySubid,
SubTransactionId parentSubid, void *arg)
{<!-- -->
if (event == SUBXACT_EVENT_COMMIT_SUB || event == SUBXACT_EVENT_ABORT_SUB)
{<!-- -->
while (simple_econtext_stack != NULL & amp; & amp;
simple_econtext_stack->xact_subxid == mySubid)
{<!-- -->
SimpleEcontextStackEntry *next;

FreeExprContext(simple_econtext_stack->stack_econtext,
(event == SUBXACT_EVENT_COMMIT_SUB));
next = simple_econtext_stack->next;
pfree(simple_econtext_stack);
simple_econtext_stack = next;
}
}
}

So if no exception occurs, then eval_econtext will be released in the subtransaction commit following ReleaseCurrentSubTransaction.

if (block->exceptions)
    ExprContext *old_eval_econtext = estate->eval_econtext;
    BeginInternalSubTransaction(NULL);
    PG_TRY();
    {<!-- -->
        plpgsql_create_econtext(estate);
        rc = exec_stmts(estate, block->body);
        ReleaseCurrentSubTransaction(); <<<<<<-------
        estate->eval_econtext = old_eval_econtext;
    }

If an exception occurs, eval_econtext will be released in the subtransaction rollback following RollbackAndReleaseCurrentSubTransaction.

if (block->exceptions)
    ExprContext *old_eval_econtext = estate->eval_econtext;
    BeginInternalSubTransaction(NULL);
    PG_TRY();
    {<!-- -->
        plpgsql_create_econtext(estate);
        rc = exec_stmts(estate, block->body);
        ReleaseCurrentSubTransaction(); <<<<<<-------
        estate->eval_econtext = old_eval_econtext;
    }
    PG_CATCH();
    {
        RollbackAndReleaseCurrentSubTransaction();
        estate->eval_econtext = old_eval_econtext;
    }

4 related global variables and functions

typedef struct SimpleEcontextStackEntry
{<!-- -->
ExprContext *stack_econtext; /* a stacked econtext */
SubTransactionId xact_subxid; /* ID for current subxact */
struct SimpleEcontextStackEntry *next; /* next stack entry up */
} SimpleEcontextStackEntry;

static EState *shared_simple_eval_estate = NULL;
static SimpleEcontextStackEntry *simple_econtext_stack = NULL;

Global variables:

  • simple_econtext_stack: ExprContext stack, each element corresponds to a sub-transaction, which needs to be actively applied for after the sub-transaction is created, and will be released after the sub-transaction is cleaned up after the sub-transaction is released.
  • shared_simple_eval_estate: The EState structure that ExprContext depends on. The runtime structure of the SQL engine is very important. It is mainly used to create ExprContext in PL.

function:

  • plpgsql_create_econtext
    • For creating the ExprContext structure, EState (shared_simple_eval_estate) will be actively used.
    • CreateExprContext is created internally using CreateExprContext.
  • plpgsql_estate_setup
    • Runtime structure PLpgSQL_execstate for creating PL
    • Will actively use EState (shared_simple_eval_estate).
    • Will call plpgsql_create_econtext to create ExprContext.