mirror of
https://github.com/postgres/postgres.git
synced 2025-05-21 15:54:08 +03:00
of tuples when passing data up through multiple plan nodes. A slot can now hold either a normal "physical" HeapTuple, or a "virtual" tuple consisting of Datum/isnull arrays. Upper plan levels can usually just copy the Datum arrays, avoiding heap_formtuple() and possible subsequent nocachegetattr() calls to extract the data again. This work extends Atsushi Ogawa's earlier patch, which provided the key idea of adding Datum arrays to TupleTableSlots. (I believe however that something like this was foreseen way back in Berkeley days --- see the old comment on ExecProject.) A test case involving many levels of join of fairly wide tables (about 80 columns altogether) showed about 3x overall speedup, though simple queries will probably not be helped very much. I have also duplicated some code in heaptuple.c in order to provide versions of heap_formtuple and friends that use "bool" arrays to indicate null attributes, instead of the old convention of "char" arrays containing either 'n' or ' '. This provides a better match to the convention used by ExecEvalExpr. While I have not made a concerted effort to get rid of uses of the old routines, I think they should be deprecated and eventually removed.
400 lines
11 KiB
C
400 lines
11 KiB
C
/*-------------------------------------------------------------------------
|
|
*
|
|
* nodeFunctionscan.c
|
|
* Support routines for scanning RangeFunctions (functions in rangetable).
|
|
*
|
|
* Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group
|
|
* Portions Copyright (c) 1994, Regents of the University of California
|
|
*
|
|
*
|
|
* IDENTIFICATION
|
|
* $PostgreSQL: pgsql/src/backend/executor/nodeFunctionscan.c,v 1.31 2005/03/16 21:38:07 tgl Exp $
|
|
*
|
|
*-------------------------------------------------------------------------
|
|
*/
|
|
/*
|
|
* INTERFACE ROUTINES
|
|
* ExecFunctionScan scans a function.
|
|
* ExecFunctionNext retrieve next tuple in sequential order.
|
|
* ExecInitFunctionScan creates and initializes a functionscan node.
|
|
* ExecEndFunctionScan releases any storage allocated.
|
|
* ExecFunctionReScan rescans the function
|
|
*/
|
|
#include "postgres.h"
|
|
|
|
#include "access/heapam.h"
|
|
#include "catalog/pg_type.h"
|
|
#include "executor/execdebug.h"
|
|
#include "executor/execdefs.h"
|
|
#include "executor/execdesc.h"
|
|
#include "executor/nodeFunctionscan.h"
|
|
#include "parser/parsetree.h"
|
|
#include "parser/parse_expr.h"
|
|
#include "parser/parse_type.h"
|
|
#include "utils/builtins.h"
|
|
#include "utils/lsyscache.h"
|
|
#include "utils/typcache.h"
|
|
|
|
|
|
static TupleTableSlot *FunctionNext(FunctionScanState *node);
|
|
static void tupledesc_match(TupleDesc dst_tupdesc, TupleDesc src_tupdesc);
|
|
|
|
/* ----------------------------------------------------------------
|
|
* Scan Support
|
|
* ----------------------------------------------------------------
|
|
*/
|
|
/* ----------------------------------------------------------------
|
|
* FunctionNext
|
|
*
|
|
* This is a workhorse for ExecFunctionScan
|
|
* ----------------------------------------------------------------
|
|
*/
|
|
static TupleTableSlot *
|
|
FunctionNext(FunctionScanState *node)
|
|
{
|
|
TupleTableSlot *slot;
|
|
EState *estate;
|
|
ScanDirection direction;
|
|
Tuplestorestate *tuplestorestate;
|
|
bool should_free;
|
|
HeapTuple heapTuple;
|
|
|
|
/*
|
|
* get information from the estate and scan state
|
|
*/
|
|
estate = node->ss.ps.state;
|
|
direction = estate->es_direction;
|
|
|
|
tuplestorestate = node->tuplestorestate;
|
|
|
|
/*
|
|
* If first time through, read all tuples from function and put them
|
|
* in a tuplestore. Subsequent calls just fetch tuples from
|
|
* tuplestore.
|
|
*/
|
|
if (tuplestorestate == NULL)
|
|
{
|
|
ExprContext *econtext = node->ss.ps.ps_ExprContext;
|
|
TupleDesc funcTupdesc;
|
|
|
|
node->tuplestorestate = tuplestorestate =
|
|
ExecMakeTableFunctionResult(node->funcexpr,
|
|
econtext,
|
|
node->tupdesc,
|
|
&funcTupdesc);
|
|
|
|
/*
|
|
* If function provided a tupdesc, cross-check it. We only really
|
|
* need to do this for functions returning RECORD, but might as
|
|
* well do it always.
|
|
*/
|
|
if (funcTupdesc)
|
|
tupledesc_match(node->tupdesc, funcTupdesc);
|
|
}
|
|
|
|
/*
|
|
* Get the next tuple from tuplestore. Return NULL if no more tuples.
|
|
*/
|
|
heapTuple = tuplestore_getheaptuple(tuplestorestate,
|
|
ScanDirectionIsForward(direction),
|
|
&should_free);
|
|
slot = node->ss.ss_ScanTupleSlot;
|
|
if (heapTuple)
|
|
return ExecStoreTuple(heapTuple, slot, InvalidBuffer, should_free);
|
|
else
|
|
return ExecClearTuple(slot);
|
|
}
|
|
|
|
/* ----------------------------------------------------------------
|
|
* ExecFunctionScan(node)
|
|
*
|
|
* Scans the function sequentially and returns the next qualifying
|
|
* tuple.
|
|
* It calls the ExecScan() routine and passes it the access method
|
|
* which retrieves tuples sequentially.
|
|
*
|
|
*/
|
|
|
|
TupleTableSlot *
|
|
ExecFunctionScan(FunctionScanState *node)
|
|
{
|
|
/*
|
|
* use FunctionNext as access method
|
|
*/
|
|
return ExecScan(&node->ss, (ExecScanAccessMtd) FunctionNext);
|
|
}
|
|
|
|
/* ----------------------------------------------------------------
|
|
* ExecInitFunctionScan
|
|
* ----------------------------------------------------------------
|
|
*/
|
|
FunctionScanState *
|
|
ExecInitFunctionScan(FunctionScan *node, EState *estate)
|
|
{
|
|
FunctionScanState *scanstate;
|
|
RangeTblEntry *rte;
|
|
Oid funcrettype;
|
|
TypeFuncClass functypclass;
|
|
TupleDesc tupdesc = NULL;
|
|
|
|
/*
|
|
* FunctionScan should not have any children.
|
|
*/
|
|
Assert(outerPlan(node) == NULL);
|
|
Assert(innerPlan(node) == NULL);
|
|
|
|
/*
|
|
* create new ScanState for node
|
|
*/
|
|
scanstate = makeNode(FunctionScanState);
|
|
scanstate->ss.ps.plan = (Plan *) node;
|
|
scanstate->ss.ps.state = estate;
|
|
|
|
/*
|
|
* Miscellaneous initialization
|
|
*
|
|
* create expression context for node
|
|
*/
|
|
ExecAssignExprContext(estate, &scanstate->ss.ps);
|
|
|
|
#define FUNCTIONSCAN_NSLOTS 2
|
|
|
|
/*
|
|
* tuple table initialization
|
|
*/
|
|
ExecInitResultTupleSlot(estate, &scanstate->ss.ps);
|
|
ExecInitScanTupleSlot(estate, &scanstate->ss);
|
|
|
|
/*
|
|
* initialize child expressions
|
|
*/
|
|
scanstate->ss.ps.targetlist = (List *)
|
|
ExecInitExpr((Expr *) node->scan.plan.targetlist,
|
|
(PlanState *) scanstate);
|
|
scanstate->ss.ps.qual = (List *)
|
|
ExecInitExpr((Expr *) node->scan.plan.qual,
|
|
(PlanState *) scanstate);
|
|
|
|
/*
|
|
* get info about function
|
|
*/
|
|
rte = rt_fetch(node->scan.scanrelid, estate->es_range_table);
|
|
Assert(rte->rtekind == RTE_FUNCTION);
|
|
funcrettype = exprType(rte->funcexpr);
|
|
|
|
/*
|
|
* Now determine if the function returns a simple or composite type,
|
|
* and build an appropriate tupdesc.
|
|
*/
|
|
functypclass = get_type_func_class(funcrettype);
|
|
|
|
if (functypclass == TYPEFUNC_COMPOSITE)
|
|
{
|
|
/* Composite data type, e.g. a table's row type */
|
|
tupdesc = CreateTupleDescCopy(lookup_rowtype_tupdesc(funcrettype, -1));
|
|
}
|
|
else if (functypclass == TYPEFUNC_SCALAR)
|
|
{
|
|
/* Base data type, i.e. scalar */
|
|
char *attname = strVal(linitial(rte->eref->colnames));
|
|
|
|
tupdesc = CreateTemplateTupleDesc(1, false);
|
|
TupleDescInitEntry(tupdesc,
|
|
(AttrNumber) 1,
|
|
attname,
|
|
funcrettype,
|
|
-1,
|
|
0);
|
|
}
|
|
else if (functypclass == TYPEFUNC_RECORD)
|
|
{
|
|
tupdesc = BuildDescForRelation(rte->coldeflist);
|
|
}
|
|
else
|
|
{
|
|
/* crummy error message, but parser should have caught this */
|
|
elog(ERROR, "function in FROM has unsupported return type");
|
|
}
|
|
|
|
/*
|
|
* For RECORD results, make sure a typmod has been assigned. (The
|
|
* function should do this for itself, but let's cover things in case
|
|
* it doesn't.)
|
|
*/
|
|
if (tupdesc->tdtypeid == RECORDOID && tupdesc->tdtypmod < 0)
|
|
assign_record_type_typmod(tupdesc);
|
|
|
|
scanstate->tupdesc = tupdesc;
|
|
ExecSetSlotDescriptor(scanstate->ss.ss_ScanTupleSlot,
|
|
tupdesc, false);
|
|
|
|
/*
|
|
* Other node-specific setup
|
|
*/
|
|
scanstate->tuplestorestate = NULL;
|
|
scanstate->funcexpr = ExecInitExpr((Expr *) rte->funcexpr,
|
|
(PlanState *) scanstate);
|
|
|
|
scanstate->ss.ps.ps_TupFromTlist = false;
|
|
|
|
/*
|
|
* Initialize result tuple type and projection info.
|
|
*/
|
|
ExecAssignResultTypeFromTL(&scanstate->ss.ps);
|
|
ExecAssignProjectionInfo(&scanstate->ss.ps);
|
|
|
|
return scanstate;
|
|
}
|
|
|
|
int
|
|
ExecCountSlotsFunctionScan(FunctionScan *node)
|
|
{
|
|
return ExecCountSlotsNode(outerPlan(node)) +
|
|
ExecCountSlotsNode(innerPlan(node)) +
|
|
FUNCTIONSCAN_NSLOTS;
|
|
}
|
|
|
|
/* ----------------------------------------------------------------
|
|
* ExecEndFunctionScan
|
|
*
|
|
* frees any storage allocated through C routines.
|
|
* ----------------------------------------------------------------
|
|
*/
|
|
void
|
|
ExecEndFunctionScan(FunctionScanState *node)
|
|
{
|
|
/*
|
|
* Free the exprcontext
|
|
*/
|
|
ExecFreeExprContext(&node->ss.ps);
|
|
|
|
/*
|
|
* clean out the tuple table
|
|
*/
|
|
ExecClearTuple(node->ss.ps.ps_ResultTupleSlot);
|
|
ExecClearTuple(node->ss.ss_ScanTupleSlot);
|
|
|
|
/*
|
|
* Release tuplestore resources
|
|
*/
|
|
if (node->tuplestorestate != NULL)
|
|
tuplestore_end(node->tuplestorestate);
|
|
node->tuplestorestate = NULL;
|
|
}
|
|
|
|
/* ----------------------------------------------------------------
|
|
* ExecFunctionMarkPos
|
|
*
|
|
* Calls tuplestore to save the current position in the stored file.
|
|
* ----------------------------------------------------------------
|
|
*/
|
|
void
|
|
ExecFunctionMarkPos(FunctionScanState *node)
|
|
{
|
|
/*
|
|
* if we haven't materialized yet, just return.
|
|
*/
|
|
if (!node->tuplestorestate)
|
|
return;
|
|
|
|
tuplestore_markpos(node->tuplestorestate);
|
|
}
|
|
|
|
/* ----------------------------------------------------------------
|
|
* ExecFunctionRestrPos
|
|
*
|
|
* Calls tuplestore to restore the last saved file position.
|
|
* ----------------------------------------------------------------
|
|
*/
|
|
void
|
|
ExecFunctionRestrPos(FunctionScanState *node)
|
|
{
|
|
/*
|
|
* if we haven't materialized yet, just return.
|
|
*/
|
|
if (!node->tuplestorestate)
|
|
return;
|
|
|
|
tuplestore_restorepos(node->tuplestorestate);
|
|
}
|
|
|
|
/* ----------------------------------------------------------------
|
|
* ExecFunctionReScan
|
|
*
|
|
* Rescans the relation.
|
|
* ----------------------------------------------------------------
|
|
*/
|
|
void
|
|
ExecFunctionReScan(FunctionScanState *node, ExprContext *exprCtxt)
|
|
{
|
|
ExecClearTuple(node->ss.ps.ps_ResultTupleSlot);
|
|
|
|
/*
|
|
* If we haven't materialized yet, just return.
|
|
*/
|
|
if (!node->tuplestorestate)
|
|
return;
|
|
|
|
/*
|
|
* Here we have a choice whether to drop the tuplestore (and recompute
|
|
* the function outputs) or just rescan it. This should depend on
|
|
* whether the function expression contains parameters and/or is
|
|
* marked volatile. FIXME soon.
|
|
*/
|
|
if (node->ss.ps.chgParam != NULL)
|
|
{
|
|
tuplestore_end(node->tuplestorestate);
|
|
node->tuplestorestate = NULL;
|
|
}
|
|
else
|
|
tuplestore_rescan(node->tuplestorestate);
|
|
}
|
|
|
|
/*
|
|
* Check that function result tuple type (src_tupdesc) matches or can
|
|
* be considered to match what the query expects (dst_tupdesc). If
|
|
* they don't match, ereport.
|
|
*
|
|
* We really only care about number of attributes and data type.
|
|
* Also, we can ignore type mismatch on columns that are dropped in the
|
|
* destination type, so long as the physical storage matches. This is
|
|
* helpful in some cases involving out-of-date cached plans.
|
|
*/
|
|
static void
|
|
tupledesc_match(TupleDesc dst_tupdesc, TupleDesc src_tupdesc)
|
|
{
|
|
int i;
|
|
|
|
if (dst_tupdesc->natts != src_tupdesc->natts)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_DATATYPE_MISMATCH),
|
|
errmsg("function return row and query-specified return row do not match"),
|
|
errdetail("Returned row contains %d attributes, but query expects %d.",
|
|
src_tupdesc->natts, dst_tupdesc->natts)));
|
|
|
|
for (i = 0; i < dst_tupdesc->natts; i++)
|
|
{
|
|
Form_pg_attribute dattr = dst_tupdesc->attrs[i];
|
|
Form_pg_attribute sattr = src_tupdesc->attrs[i];
|
|
|
|
if (dattr->atttypid == sattr->atttypid)
|
|
continue; /* no worries */
|
|
if (!dattr->attisdropped)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_DATATYPE_MISMATCH),
|
|
errmsg("function return row and query-specified return row do not match"),
|
|
errdetail("Returned type %s at ordinal position %d, but query expects %s.",
|
|
format_type_be(sattr->atttypid),
|
|
i + 1,
|
|
format_type_be(dattr->atttypid))));
|
|
|
|
if (dattr->attlen != sattr->attlen ||
|
|
dattr->attalign != sattr->attalign)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_DATATYPE_MISMATCH),
|
|
errmsg("function return row and query-specified return row do not match"),
|
|
errdetail("Physical storage mismatch on dropped attribute at ordinal position %d.",
|
|
i + 1)));
|
|
}
|
|
}
|