diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-04 12:15:05 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-04 12:15:05 +0000 |
commit | 46651ce6fe013220ed397add242004d764fc0153 (patch) | |
tree | 6e5299f990f88e60174a1d3ae6e48eedd2688b2b /src/backend/executor/nodeTableFuncscan.c | |
parent | Initial commit. (diff) | |
download | postgresql-14-46651ce6fe013220ed397add242004d764fc0153.tar.xz postgresql-14-46651ce6fe013220ed397add242004d764fc0153.zip |
Adding upstream version 14.5.upstream/14.5upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/backend/executor/nodeTableFuncscan.c')
-rw-r--r-- | src/backend/executor/nodeTableFuncscan.c | 523 |
1 files changed, 523 insertions, 0 deletions
diff --git a/src/backend/executor/nodeTableFuncscan.c b/src/backend/executor/nodeTableFuncscan.c new file mode 100644 index 0000000..4d7eca4 --- /dev/null +++ b/src/backend/executor/nodeTableFuncscan.c @@ -0,0 +1,523 @@ +/*------------------------------------------------------------------------- + * + * nodeTableFuncscan.c + * Support routines for scanning RangeTableFunc (XMLTABLE like functions). + * + * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/executor/nodeTableFuncscan.c + * + *------------------------------------------------------------------------- + */ +/* + * INTERFACE ROUTINES + * ExecTableFuncscan scans a function. + * ExecFunctionNext retrieve next tuple in sequential order. + * ExecInitTableFuncscan creates and initializes a TableFuncscan node. + * ExecEndTableFuncscan releases any storage allocated. + * ExecReScanTableFuncscan rescans the function + */ +#include "postgres.h" + +#include "executor/executor.h" +#include "executor/nodeTableFuncscan.h" +#include "executor/tablefunc.h" +#include "miscadmin.h" +#include "nodes/execnodes.h" +#include "utils/builtins.h" +#include "utils/lsyscache.h" +#include "utils/memutils.h" +#include "utils/xml.h" + +static TupleTableSlot *TableFuncNext(TableFuncScanState *node); +static bool TableFuncRecheck(TableFuncScanState *node, TupleTableSlot *slot); + +static void tfuncFetchRows(TableFuncScanState *tstate, ExprContext *econtext); +static void tfuncInitialize(TableFuncScanState *tstate, ExprContext *econtext, Datum doc); +static void tfuncLoadRows(TableFuncScanState *tstate, ExprContext *econtext); + +/* ---------------------------------------------------------------- + * Scan Support + * ---------------------------------------------------------------- + */ +/* ---------------------------------------------------------------- + * TableFuncNext + * + * This is a workhorse for ExecTableFuncscan + * ---------------------------------------------------------------- + */ +static TupleTableSlot * +TableFuncNext(TableFuncScanState *node) +{ + TupleTableSlot *scanslot; + + scanslot = node->ss.ss_ScanTupleSlot; + + /* + * If first time through, read all tuples from function and put them in a + * tuplestore. Subsequent calls just fetch tuples from tuplestore. + */ + if (node->tupstore == NULL) + tfuncFetchRows(node, node->ss.ps.ps_ExprContext); + + /* + * Get the next tuple from tuplestore. + */ + (void) tuplestore_gettupleslot(node->tupstore, + true, + false, + scanslot); + return scanslot; +} + +/* + * TableFuncRecheck -- access method routine to recheck a tuple in EvalPlanQual + */ +static bool +TableFuncRecheck(TableFuncScanState *node, TupleTableSlot *slot) +{ + /* nothing to check */ + return true; +} + +/* ---------------------------------------------------------------- + * ExecTableFuncscan(node) + * + * Scans the function sequentially and returns the next qualifying + * tuple. + * We call the ExecScan() routine and pass it the appropriate + * access method functions. + * ---------------------------------------------------------------- + */ +static TupleTableSlot * +ExecTableFuncScan(PlanState *pstate) +{ + TableFuncScanState *node = castNode(TableFuncScanState, pstate); + + return ExecScan(&node->ss, + (ExecScanAccessMtd) TableFuncNext, + (ExecScanRecheckMtd) TableFuncRecheck); +} + +/* ---------------------------------------------------------------- + * ExecInitTableFuncscan + * ---------------------------------------------------------------- + */ +TableFuncScanState * +ExecInitTableFuncScan(TableFuncScan *node, EState *estate, int eflags) +{ + TableFuncScanState *scanstate; + TableFunc *tf = node->tablefunc; + TupleDesc tupdesc; + int i; + + /* check for unsupported flags */ + Assert(!(eflags & EXEC_FLAG_MARK)); + + /* + * TableFuncscan should not have any children. + */ + Assert(outerPlan(node) == NULL); + Assert(innerPlan(node) == NULL); + + /* + * create new ScanState for node + */ + scanstate = makeNode(TableFuncScanState); + scanstate->ss.ps.plan = (Plan *) node; + scanstate->ss.ps.state = estate; + scanstate->ss.ps.ExecProcNode = ExecTableFuncScan; + + /* + * Miscellaneous initialization + * + * create expression context for node + */ + ExecAssignExprContext(estate, &scanstate->ss.ps); + + /* + * initialize source tuple type + */ + tupdesc = BuildDescFromLists(tf->colnames, + tf->coltypes, + tf->coltypmods, + tf->colcollations); + /* and the corresponding scan slot */ + ExecInitScanTupleSlot(estate, &scanstate->ss, tupdesc, + &TTSOpsMinimalTuple); + + /* + * Initialize result type and projection. + */ + ExecInitResultTypeTL(&scanstate->ss.ps); + ExecAssignScanProjectionInfo(&scanstate->ss); + + /* + * initialize child expressions + */ + scanstate->ss.ps.qual = + ExecInitQual(node->scan.plan.qual, &scanstate->ss.ps); + + /* Only XMLTABLE is supported currently */ + scanstate->routine = &XmlTableRoutine; + + scanstate->perTableCxt = + AllocSetContextCreate(CurrentMemoryContext, + "TableFunc per value context", + ALLOCSET_DEFAULT_SIZES); + scanstate->opaque = NULL; /* initialized at runtime */ + + scanstate->ns_names = tf->ns_names; + + scanstate->ns_uris = + ExecInitExprList(tf->ns_uris, (PlanState *) scanstate); + scanstate->docexpr = + ExecInitExpr((Expr *) tf->docexpr, (PlanState *) scanstate); + scanstate->rowexpr = + ExecInitExpr((Expr *) tf->rowexpr, (PlanState *) scanstate); + scanstate->colexprs = + ExecInitExprList(tf->colexprs, (PlanState *) scanstate); + scanstate->coldefexprs = + ExecInitExprList(tf->coldefexprs, (PlanState *) scanstate); + + scanstate->notnulls = tf->notnulls; + + /* these are allocated now and initialized later */ + scanstate->in_functions = palloc(sizeof(FmgrInfo) * tupdesc->natts); + scanstate->typioparams = palloc(sizeof(Oid) * tupdesc->natts); + + /* + * Fill in the necessary fmgr infos. + */ + for (i = 0; i < tupdesc->natts; i++) + { + Oid in_funcid; + + getTypeInputInfo(TupleDescAttr(tupdesc, i)->atttypid, + &in_funcid, &scanstate->typioparams[i]); + fmgr_info(in_funcid, &scanstate->in_functions[i]); + } + + return scanstate; +} + +/* ---------------------------------------------------------------- + * ExecEndTableFuncscan + * + * frees any storage allocated through C routines. + * ---------------------------------------------------------------- + */ +void +ExecEndTableFuncScan(TableFuncScanState *node) +{ + /* + * Free the exprcontext + */ + ExecFreeExprContext(&node->ss.ps); + + /* + * clean out the tuple table + */ + if (node->ss.ps.ps_ResultTupleSlot) + ExecClearTuple(node->ss.ps.ps_ResultTupleSlot); + ExecClearTuple(node->ss.ss_ScanTupleSlot); + + /* + * Release tuplestore resources + */ + if (node->tupstore != NULL) + tuplestore_end(node->tupstore); + node->tupstore = NULL; +} + +/* ---------------------------------------------------------------- + * ExecReScanTableFuncscan + * + * Rescans the relation. + * ---------------------------------------------------------------- + */ +void +ExecReScanTableFuncScan(TableFuncScanState *node) +{ + Bitmapset *chgparam = node->ss.ps.chgParam; + + if (node->ss.ps.ps_ResultTupleSlot) + ExecClearTuple(node->ss.ps.ps_ResultTupleSlot); + ExecScanReScan(&node->ss); + + /* + * Recompute when parameters are changed. + */ + if (chgparam) + { + if (node->tupstore != NULL) + { + tuplestore_end(node->tupstore); + node->tupstore = NULL; + } + } + + if (node->tupstore != NULL) + tuplestore_rescan(node->tupstore); +} + +/* ---------------------------------------------------------------- + * tfuncFetchRows + * + * Read rows from a TableFunc producer + * ---------------------------------------------------------------- + */ +static void +tfuncFetchRows(TableFuncScanState *tstate, ExprContext *econtext) +{ + const TableFuncRoutine *routine = tstate->routine; + MemoryContext oldcxt; + Datum value; + bool isnull; + + Assert(tstate->opaque == NULL); + + /* build tuplestore for the result */ + oldcxt = MemoryContextSwitchTo(econtext->ecxt_per_query_memory); + tstate->tupstore = tuplestore_begin_heap(false, false, work_mem); + + /* + * Each call to fetch a new set of rows - of which there may be very many + * if XMLTABLE is being used in a lateral join - will allocate a possibly + * substantial amount of memory, so we cannot use the per-query context + * here. perTableCxt now serves the same function as "argcontext" does in + * FunctionScan - a place to store per-one-call (i.e. one result table) + * lifetime data (as opposed to per-query or per-result-tuple). + */ + MemoryContextSwitchTo(tstate->perTableCxt); + + PG_TRY(); + { + routine->InitOpaque(tstate, + tstate->ss.ss_ScanTupleSlot->tts_tupleDescriptor->natts); + + /* + * If evaluating the document expression returns NULL, the table + * expression is empty and we return immediately. + */ + value = ExecEvalExpr(tstate->docexpr, econtext, &isnull); + + if (!isnull) + { + /* otherwise, pass the document value to the table builder */ + tfuncInitialize(tstate, econtext, value); + + /* initialize ordinality counter */ + tstate->ordinal = 1; + + /* Load all rows into the tuplestore, and we're done */ + tfuncLoadRows(tstate, econtext); + } + } + PG_CATCH(); + { + if (tstate->opaque != NULL) + routine->DestroyOpaque(tstate); + PG_RE_THROW(); + } + PG_END_TRY(); + + /* clean up and return to original memory context */ + + if (tstate->opaque != NULL) + { + routine->DestroyOpaque(tstate); + tstate->opaque = NULL; + } + + MemoryContextSwitchTo(oldcxt); + MemoryContextReset(tstate->perTableCxt); +} + +/* + * Fill in namespace declarations, the row filter, and column filters in a + * table expression builder context. + */ +static void +tfuncInitialize(TableFuncScanState *tstate, ExprContext *econtext, Datum doc) +{ + const TableFuncRoutine *routine = tstate->routine; + TupleDesc tupdesc; + ListCell *lc1, + *lc2; + bool isnull; + int colno; + Datum value; + int ordinalitycol = + ((TableFuncScan *) (tstate->ss.ps.plan))->tablefunc->ordinalitycol; + + /* + * Install the document as a possibly-toasted Datum into the tablefunc + * context. + */ + routine->SetDocument(tstate, doc); + + /* Evaluate namespace specifications */ + forboth(lc1, tstate->ns_uris, lc2, tstate->ns_names) + { + ExprState *expr = (ExprState *) lfirst(lc1); + Value *ns_node = (Value *) lfirst(lc2); + char *ns_uri; + char *ns_name; + + value = ExecEvalExpr((ExprState *) expr, econtext, &isnull); + if (isnull) + ereport(ERROR, + (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), + errmsg("namespace URI must not be null"))); + ns_uri = TextDatumGetCString(value); + + /* DEFAULT is passed down to SetNamespace as NULL */ + ns_name = ns_node ? strVal(ns_node) : NULL; + + routine->SetNamespace(tstate, ns_name, ns_uri); + } + + /* Install the row filter expression into the table builder context */ + value = ExecEvalExpr(tstate->rowexpr, econtext, &isnull); + if (isnull) + ereport(ERROR, + (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), + errmsg("row filter expression must not be null"))); + + routine->SetRowFilter(tstate, TextDatumGetCString(value)); + + /* + * Install the column filter expressions into the table builder context. + * If an expression is given, use that; otherwise the column name itself + * is the column filter. + */ + colno = 0; + tupdesc = tstate->ss.ss_ScanTupleSlot->tts_tupleDescriptor; + foreach(lc1, tstate->colexprs) + { + char *colfilter; + Form_pg_attribute att = TupleDescAttr(tupdesc, colno); + + if (colno != ordinalitycol) + { + ExprState *colexpr = lfirst(lc1); + + if (colexpr != NULL) + { + value = ExecEvalExpr(colexpr, econtext, &isnull); + if (isnull) + ereport(ERROR, + (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), + errmsg("column filter expression must not be null"), + errdetail("Filter for column \"%s\" is null.", + NameStr(att->attname)))); + colfilter = TextDatumGetCString(value); + } + else + colfilter = NameStr(att->attname); + + routine->SetColumnFilter(tstate, colfilter, colno); + } + + colno++; + } +} + +/* + * Load all the rows from the TableFunc table builder into a tuplestore. + */ +static void +tfuncLoadRows(TableFuncScanState *tstate, ExprContext *econtext) +{ + const TableFuncRoutine *routine = tstate->routine; + TupleTableSlot *slot = tstate->ss.ss_ScanTupleSlot; + TupleDesc tupdesc = slot->tts_tupleDescriptor; + Datum *values = slot->tts_values; + bool *nulls = slot->tts_isnull; + int natts = tupdesc->natts; + MemoryContext oldcxt; + int ordinalitycol; + + ordinalitycol = + ((TableFuncScan *) (tstate->ss.ps.plan))->tablefunc->ordinalitycol; + + /* + * We need a short-lived memory context that we can clean up each time + * around the loop, to avoid wasting space. Our default per-tuple context + * is fine for the job, since we won't have used it for anything yet in + * this tuple cycle. + */ + oldcxt = MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory); + + /* + * Keep requesting rows from the table builder until there aren't any. + */ + while (routine->FetchRow(tstate)) + { + ListCell *cell = list_head(tstate->coldefexprs); + int colno; + + CHECK_FOR_INTERRUPTS(); + + ExecClearTuple(tstate->ss.ss_ScanTupleSlot); + + /* + * Obtain the value of each column for this row, installing them into + * the slot; then add the tuple to the tuplestore. + */ + for (colno = 0; colno < natts; colno++) + { + Form_pg_attribute att = TupleDescAttr(tupdesc, colno); + + if (colno == ordinalitycol) + { + /* Fast path for ordinality column */ + values[colno] = Int32GetDatum(tstate->ordinal++); + nulls[colno] = false; + } + else + { + bool isnull; + + values[colno] = routine->GetValue(tstate, + colno, + att->atttypid, + att->atttypmod, + &isnull); + + /* No value? Evaluate and apply the default, if any */ + if (isnull && cell != NULL) + { + ExprState *coldefexpr = (ExprState *) lfirst(cell); + + if (coldefexpr != NULL) + values[colno] = ExecEvalExpr(coldefexpr, econtext, + &isnull); + } + + /* Verify a possible NOT NULL constraint */ + if (isnull && bms_is_member(colno, tstate->notnulls)) + ereport(ERROR, + (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), + errmsg("null is not allowed in column \"%s\"", + NameStr(att->attname)))); + + nulls[colno] = isnull; + } + + /* advance list of default expressions */ + if (cell != NULL) + cell = lnext(tstate->coldefexprs, cell); + } + + tuplestore_putvalues(tstate->tupstore, tupdesc, values, nulls); + + MemoryContextReset(econtext->ecxt_per_tuple_memory); + } + + MemoryContextSwitchTo(oldcxt); +} |