| /*------------------------------------------------------------------------- |
| * |
| * nodeTableFuncscan.c |
| * Support routines for scanning RangeTableFunc (XMLTABLE like functions). |
| * |
| * Portions Copyright (c) 1996-2023, 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); |
| String *ns_node = lfirst_node(String, 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); |
| } |