summaryrefslogtreecommitdiffstats
path: root/src/backend/executor/nodeTableFuncscan.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-04 12:15:05 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-04 12:15:05 +0000
commit46651ce6fe013220ed397add242004d764fc0153 (patch)
tree6e5299f990f88e60174a1d3ae6e48eedd2688b2b /src/backend/executor/nodeTableFuncscan.c
parentInitial commit. (diff)
downloadpostgresql-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.c523
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);
+}