mirror of https://github.com/postgres/postgres
This replaces the old, recursive tree-walk based evaluation, with non-recursive, opcode dispatch based, expression evaluation. Projection is now implemented as part of expression evaluation. This both leads to significant performance improvements, and makes future just-in-time compilation of expressions easier. The speed gains primarily come from: - non-recursive implementation reduces stack usage / overhead - simple sub-expressions are implemented with a single jump, without function calls - sharing some state between different sub-expressions - reduced amount of indirect/hard to predict memory accesses by laying out operation metadata sequentially; including the avoidance of nearly all of the previously used linked lists - more code has been moved to expression initialization, avoiding constant re-checks at evaluation time Future just-in-time compilation (JIT) has become easier, as demonstrated by released patches intended to be merged in a later release, for primarily two reasons: Firstly, due to a stricter split between expression initialization and evaluation, less code has to be handled by the JIT. Secondly, due to the non-recursive nature of the generated "instructions", less performance-critical code-paths can easily be shared between interpreted and compiled evaluation. The new framework allows for significant future optimizations. E.g.: - basic infrastructure for to later reduce the per executor-startup overhead of expression evaluation, by caching state in prepared statements. That'd be helpful in OLTPish scenarios where initialization overhead is measurable. - optimizing the generated "code". A number of proposals for potential work has already been made. - optimizing the interpreter. Similarly a number of proposals have been made here too. The move of logic into the expression initialization step leads to some backward-incompatible changes: - Function permission checks are now done during expression initialization, whereas previously they were done during execution. In edge cases this can lead to errors being raised that previously wouldn't have been, e.g. a NULL array being coerced to a different array type previously didn't perform checks. - The set of domain constraints to be checked, is now evaluated once during expression initialization, previously it was re-built every time a domain check was evaluated. For normal queries this doesn't change much, but e.g. for plpgsql functions, which caches ExprStates, the old set could stick around longer. The behavior around might still change. Author: Andres Freund, with significant changes by Tom Lane, changes by Heikki Linnakangas Reviewed-By: Tom Lane, Heikki Linnakangas Discussion: https://postgr.es/m/20161206034955.bh33paeralxbtluv@alap3.anarazel.depull/3/merge
parent
7d3957e53e
commit
b8d7f053c5
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,924 @@ |
||||
/*-------------------------------------------------------------------------
|
||||
* |
||||
* execSRF.c |
||||
* Routines implementing the API for set-returning functions |
||||
* |
||||
* This file serves nodeFunctionscan.c and nodeProjectSet.c, providing |
||||
* common code for calling set-returning functions according to the |
||||
* ReturnSetInfo API. |
||||
* |
||||
* Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group |
||||
* Portions Copyright (c) 1994, Regents of the University of California |
||||
* |
||||
* |
||||
* IDENTIFICATION |
||||
* src/backend/executor/execSRF.c |
||||
* |
||||
*------------------------------------------------------------------------- |
||||
*/ |
||||
#include "postgres.h" |
||||
|
||||
#include "access/htup_details.h" |
||||
#include "catalog/objectaccess.h" |
||||
#include "executor/execdebug.h" |
||||
#include "funcapi.h" |
||||
#include "miscadmin.h" |
||||
#include "nodes/nodeFuncs.h" |
||||
#include "parser/parse_coerce.h" |
||||
#include "pgstat.h" |
||||
#include "utils/acl.h" |
||||
#include "utils/builtins.h" |
||||
#include "utils/lsyscache.h" |
||||
#include "utils/memutils.h" |
||||
#include "utils/typcache.h" |
||||
|
||||
|
||||
/* static function decls */ |
||||
static void init_sexpr(Oid foid, Oid input_collation, SetExprState *sexpr, |
||||
MemoryContext sexprCxt, bool allowSRF, bool needDescForSRF); |
||||
static void ShutdownSetExpr(Datum arg); |
||||
static void ExecEvalFuncArgs(FunctionCallInfo fcinfo, |
||||
List *argList, ExprContext *econtext); |
||||
static void ExecPrepareTuplestoreResult(SetExprState *sexpr, |
||||
ExprContext *econtext, |
||||
Tuplestorestate *resultStore, |
||||
TupleDesc resultDesc); |
||||
static void tupledesc_match(TupleDesc dst_tupdesc, TupleDesc src_tupdesc); |
||||
|
||||
|
||||
/*
|
||||
* Prepare function call in FROM (ROWS FROM) for execution. |
||||
* |
||||
* This is used by nodeFunctionscan.c. |
||||
*/ |
||||
SetExprState * |
||||
ExecInitTableFunctionResult(Expr *expr, |
||||
ExprContext *econtext, PlanState *parent) |
||||
{ |
||||
SetExprState *state = makeNode(SetExprState); |
||||
|
||||
state->funcReturnsSet = false; |
||||
state->expr = expr; |
||||
state->func.fn_oid = InvalidOid; |
||||
|
||||
/*
|
||||
* Normally the passed expression tree will be a FuncExpr, since the |
||||
* grammar only allows a function call at the top level of a table |
||||
* function reference. However, if the function doesn't return set then |
||||
* the planner might have replaced the function call via constant-folding |
||||
* or inlining. So if we see any other kind of expression node, execute |
||||
* it via the general ExecEvalExpr() code. That code path will not |
||||
* support set-returning functions buried in the expression, though. |
||||
*/ |
||||
if (IsA(expr, FuncExpr)) |
||||
{ |
||||
FuncExpr *func = (FuncExpr *) expr; |
||||
|
||||
state->funcReturnsSet = func->funcretset; |
||||
state->args = ExecInitExprList(func->args, parent); |
||||
|
||||
init_sexpr(func->funcid, func->inputcollid, state, |
||||
econtext->ecxt_per_query_memory, func->funcretset, false); |
||||
} |
||||
else |
||||
{ |
||||
state->elidedFuncState = ExecInitExpr(expr, parent); |
||||
} |
||||
|
||||
return state; |
||||
} |
||||
|
||||
/*
|
||||
* ExecMakeTableFunctionResult |
||||
* |
||||
* Evaluate a table function, producing a materialized result in a Tuplestore |
||||
* object. |
||||
* |
||||
* This is used by nodeFunctionscan.c. |
||||
*/ |
||||
Tuplestorestate * |
||||
ExecMakeTableFunctionResult(SetExprState *setexpr, |
||||
ExprContext *econtext, |
||||
MemoryContext argContext, |
||||
TupleDesc expectedDesc, |
||||
bool randomAccess) |
||||
{ |
||||
Tuplestorestate *tupstore = NULL; |
||||
TupleDesc tupdesc = NULL; |
||||
Oid funcrettype; |
||||
bool returnsTuple; |
||||
bool returnsSet = false; |
||||
FunctionCallInfoData fcinfo; |
||||
PgStat_FunctionCallUsage fcusage; |
||||
ReturnSetInfo rsinfo; |
||||
HeapTupleData tmptup; |
||||
MemoryContext callerContext; |
||||
MemoryContext oldcontext; |
||||
bool first_time = true; |
||||
|
||||
callerContext = CurrentMemoryContext; |
||||
|
||||
funcrettype = exprType((Node *) setexpr->expr); |
||||
|
||||
returnsTuple = type_is_rowtype(funcrettype); |
||||
|
||||
/*
|
||||
* Prepare a resultinfo node for communication. We always do this even if |
||||
* not expecting a set result, so that we can pass expectedDesc. In the |
||||
* generic-expression case, the expression doesn't actually get to see the |
||||
* resultinfo, but set it up anyway because we use some of the fields as |
||||
* our own state variables. |
||||
*/ |
||||
rsinfo.type = T_ReturnSetInfo; |
||||
rsinfo.econtext = econtext; |
||||
rsinfo.expectedDesc = expectedDesc; |
||||
rsinfo.allowedModes = (int) (SFRM_ValuePerCall | SFRM_Materialize | SFRM_Materialize_Preferred); |
||||
if (randomAccess) |
||||
rsinfo.allowedModes |= (int) SFRM_Materialize_Random; |
||||
rsinfo.returnMode = SFRM_ValuePerCall; |
||||
/* isDone is filled below */ |
||||
rsinfo.setResult = NULL; |
||||
rsinfo.setDesc = NULL; |
||||
|
||||
/*
|
||||
* Normally the passed expression tree will be a SetExprState, since the |
||||
* grammar only allows a function call at the top level of a table |
||||
* function reference. However, if the function doesn't return set then |
||||
* the planner might have replaced the function call via constant-folding |
||||
* or inlining. So if we see any other kind of expression node, execute |
||||
* it via the general ExecEvalExpr() code; the only difference is that we |
||||
* don't get a chance to pass a special ReturnSetInfo to any functions |
||||
* buried in the expression. |
||||
*/ |
||||
if (!setexpr->elidedFuncState) |
||||
{ |
||||
/*
|
||||
* This path is similar to ExecMakeFunctionResultSet. |
||||
*/ |
||||
returnsSet = setexpr->funcReturnsSet; |
||||
InitFunctionCallInfoData(fcinfo, &(setexpr->func), |
||||
list_length(setexpr->args), |
||||
setexpr->fcinfo_data.fncollation, |
||||
NULL, (Node *) &rsinfo); |
||||
|
||||
/*
|
||||
* Evaluate the function's argument list. |
||||
* |
||||
* We can't do this in the per-tuple context: the argument values |
||||
* would disappear when we reset that context in the inner loop. And |
||||
* the caller's CurrentMemoryContext is typically a query-lifespan |
||||
* context, so we don't want to leak memory there. We require the |
||||
* caller to pass a separate memory context that can be used for this, |
||||
* and can be reset each time through to avoid bloat. |
||||
*/ |
||||
MemoryContextReset(argContext); |
||||
oldcontext = MemoryContextSwitchTo(argContext); |
||||
ExecEvalFuncArgs(&fcinfo, setexpr->args, econtext); |
||||
MemoryContextSwitchTo(oldcontext); |
||||
|
||||
/*
|
||||
* If function is strict, and there are any NULL arguments, skip |
||||
* calling the function and act like it returned NULL (or an empty |
||||
* set, in the returns-set case). |
||||
*/ |
||||
if (setexpr->func.fn_strict) |
||||
{ |
||||
int i; |
||||
|
||||
for (i = 0; i < fcinfo.nargs; i++) |
||||
{ |
||||
if (fcinfo.argnull[i]) |
||||
goto no_function_result; |
||||
} |
||||
} |
||||
} |
||||
else |
||||
{ |
||||
/* Treat setexpr as a generic expression */ |
||||
InitFunctionCallInfoData(fcinfo, NULL, 0, InvalidOid, NULL, NULL); |
||||
} |
||||
|
||||
/*
|
||||
* Switch to short-lived context for calling the function or expression. |
||||
*/ |
||||
MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory); |
||||
|
||||
/*
|
||||
* Loop to handle the ValuePerCall protocol (which is also the same |
||||
* behavior needed in the generic ExecEvalExpr path). |
||||
*/ |
||||
for (;;) |
||||
{ |
||||
Datum result; |
||||
|
||||
CHECK_FOR_INTERRUPTS(); |
||||
|
||||
/*
|
||||
* reset per-tuple memory context before each call of the function or |
||||
* expression. This cleans up any local memory the function may leak |
||||
* when called. |
||||
*/ |
||||
ResetExprContext(econtext); |
||||
|
||||
/* Call the function or expression one time */ |
||||
if (!setexpr->elidedFuncState) |
||||
{ |
||||
pgstat_init_function_usage(&fcinfo, &fcusage); |
||||
|
||||
fcinfo.isnull = false; |
||||
rsinfo.isDone = ExprSingleResult; |
||||
result = FunctionCallInvoke(&fcinfo); |
||||
|
||||
pgstat_end_function_usage(&fcusage, |
||||
rsinfo.isDone != ExprMultipleResult); |
||||
} |
||||
else |
||||
{ |
||||
result = |
||||
ExecEvalExpr(setexpr->elidedFuncState, econtext, &fcinfo.isnull); |
||||
rsinfo.isDone = ExprSingleResult; |
||||
} |
||||
|
||||
/* Which protocol does function want to use? */ |
||||
if (rsinfo.returnMode == SFRM_ValuePerCall) |
||||
{ |
||||
/*
|
||||
* Check for end of result set. |
||||
*/ |
||||
if (rsinfo.isDone == ExprEndResult) |
||||
break; |
||||
|
||||
/*
|
||||
* If first time through, build tuplestore for result. For a |
||||
* scalar function result type, also make a suitable tupdesc. |
||||
*/ |
||||
if (first_time) |
||||
{ |
||||
oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_query_memory); |
||||
tupstore = tuplestore_begin_heap(randomAccess, false, work_mem); |
||||
rsinfo.setResult = tupstore; |
||||
if (!returnsTuple) |
||||
{ |
||||
tupdesc = CreateTemplateTupleDesc(1, false); |
||||
TupleDescInitEntry(tupdesc, |
||||
(AttrNumber) 1, |
||||
"column", |
||||
funcrettype, |
||||
-1, |
||||
0); |
||||
rsinfo.setDesc = tupdesc; |
||||
} |
||||
MemoryContextSwitchTo(oldcontext); |
||||
} |
||||
|
||||
/*
|
||||
* Store current resultset item. |
||||
*/ |
||||
if (returnsTuple) |
||||
{ |
||||
if (!fcinfo.isnull) |
||||
{ |
||||
HeapTupleHeader td = DatumGetHeapTupleHeader(result); |
||||
|
||||
if (tupdesc == NULL) |
||||
{ |
||||
/*
|
||||
* This is the first non-NULL result from the |
||||
* function. Use the type info embedded in the |
||||
* rowtype Datum to look up the needed tupdesc. Make |
||||
* a copy for the query. |
||||
*/ |
||||
oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_query_memory); |
||||
tupdesc = lookup_rowtype_tupdesc_copy(HeapTupleHeaderGetTypeId(td), |
||||
HeapTupleHeaderGetTypMod(td)); |
||||
rsinfo.setDesc = tupdesc; |
||||
MemoryContextSwitchTo(oldcontext); |
||||
} |
||||
else |
||||
{ |
||||
/*
|
||||
* Verify all later returned rows have same subtype; |
||||
* necessary in case the type is RECORD. |
||||
*/ |
||||
if (HeapTupleHeaderGetTypeId(td) != tupdesc->tdtypeid || |
||||
HeapTupleHeaderGetTypMod(td) != tupdesc->tdtypmod) |
||||
ereport(ERROR, |
||||
(errcode(ERRCODE_DATATYPE_MISMATCH), |
||||
errmsg("rows returned by function are not all of the same row type"))); |
||||
} |
||||
|
||||
/*
|
||||
* tuplestore_puttuple needs a HeapTuple not a bare |
||||
* HeapTupleHeader, but it doesn't need all the fields. |
||||
*/ |
||||
tmptup.t_len = HeapTupleHeaderGetDatumLength(td); |
||||
tmptup.t_data = td; |
||||
|
||||
tuplestore_puttuple(tupstore, &tmptup); |
||||
} |
||||
else |
||||
{ |
||||
/*
|
||||
* NULL result from a tuple-returning function; expand it |
||||
* to a row of all nulls. We rely on the expectedDesc to |
||||
* form such rows. (Note: this would be problematic if |
||||
* tuplestore_putvalues saved the tdtypeid/tdtypmod from |
||||
* the provided descriptor, since that might not match |
||||
* what we get from the function itself. But it doesn't.) |
||||
*/ |
||||
int natts = expectedDesc->natts; |
||||
bool *nullflags; |
||||
|
||||
nullflags = (bool *) palloc(natts * sizeof(bool)); |
||||
memset(nullflags, true, natts * sizeof(bool)); |
||||
tuplestore_putvalues(tupstore, expectedDesc, NULL, nullflags); |
||||
} |
||||
} |
||||
else |
||||
{ |
||||
/* Scalar-type case: just store the function result */ |
||||
tuplestore_putvalues(tupstore, tupdesc, &result, &fcinfo.isnull); |
||||
} |
||||
|
||||
/*
|
||||
* Are we done? |
||||
*/ |
||||
if (rsinfo.isDone != ExprMultipleResult) |
||||
break; |
||||
} |
||||
else if (rsinfo.returnMode == SFRM_Materialize) |
||||
{ |
||||
/* check we're on the same page as the function author */ |
||||
if (!first_time || rsinfo.isDone != ExprSingleResult) |
||||
ereport(ERROR, |
||||
(errcode(ERRCODE_E_R_I_E_SRF_PROTOCOL_VIOLATED), |
||||
errmsg("table-function protocol for materialize mode was not followed"))); |
||||
/* Done evaluating the set result */ |
||||
break; |
||||
} |
||||
else |
||||
ereport(ERROR, |
||||
(errcode(ERRCODE_E_R_I_E_SRF_PROTOCOL_VIOLATED), |
||||
errmsg("unrecognized table-function returnMode: %d", |
||||
(int) rsinfo.returnMode))); |
||||
|
||||
first_time = false; |
||||
} |
||||
|
||||
no_function_result: |
||||
|
||||
/*
|
||||
* If we got nothing from the function (ie, an empty-set or NULL result), |
||||
* we have to create the tuplestore to return, and if it's a |
||||
* non-set-returning function then insert a single all-nulls row. As |
||||
* above, we depend on the expectedDesc to manufacture the dummy row. |
||||
*/ |
||||
if (rsinfo.setResult == NULL) |
||||
{ |
||||
MemoryContextSwitchTo(econtext->ecxt_per_query_memory); |
||||
tupstore = tuplestore_begin_heap(randomAccess, false, work_mem); |
||||
rsinfo.setResult = tupstore; |
||||
if (!returnsSet) |
||||
{ |
||||
int natts = expectedDesc->natts; |
||||
bool *nullflags; |
||||
|
||||
MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory); |
||||
nullflags = (bool *) palloc(natts * sizeof(bool)); |
||||
memset(nullflags, true, natts * sizeof(bool)); |
||||
tuplestore_putvalues(tupstore, expectedDesc, NULL, nullflags); |
||||
} |
||||
} |
||||
|
||||
/*
|
||||
* 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 (rsinfo.setDesc) |
||||
{ |
||||
tupledesc_match(expectedDesc, rsinfo.setDesc); |
||||
|
||||
/*
|
||||
* If it is a dynamically-allocated TupleDesc, free it: it is |
||||
* typically allocated in a per-query context, so we must avoid |
||||
* leaking it across multiple usages. |
||||
*/ |
||||
if (rsinfo.setDesc->tdrefcount == -1) |
||||
FreeTupleDesc(rsinfo.setDesc); |
||||
} |
||||
|
||||
MemoryContextSwitchTo(callerContext); |
||||
|
||||
/* All done, pass back the tuplestore */ |
||||
return rsinfo.setResult; |
||||
} |
||||
|
||||
|
||||
/*
|
||||
* Prepare targetlist SRF function call for execution. |
||||
* |
||||
* This is used by nodeProjectSet.c. |
||||
*/ |
||||
SetExprState * |
||||
ExecInitFunctionResultSet(Expr *expr, |
||||
ExprContext *econtext, PlanState *parent) |
||||
{ |
||||
SetExprState *state = makeNode(SetExprState); |
||||
|
||||
state->funcReturnsSet = true; |
||||
state->expr = expr; |
||||
state->func.fn_oid = InvalidOid; |
||||
|
||||
/*
|
||||
* Initialize metadata. The expression node could be either a FuncExpr or |
||||
* an OpExpr. |
||||
*/ |
||||
if (IsA(expr, FuncExpr)) |
||||
{ |
||||
FuncExpr *func = (FuncExpr *) expr; |
||||
|
||||
state->args = ExecInitExprList(func->args, parent); |
||||
init_sexpr(func->funcid, func->inputcollid, state, |
||||
econtext->ecxt_per_query_memory, true, true); |
||||
} |
||||
else if (IsA(expr, OpExpr)) |
||||
{ |
||||
OpExpr *op = (OpExpr *) expr; |
||||
|
||||
state->args = ExecInitExprList(op->args, parent); |
||||
init_sexpr(op->opfuncid, op->inputcollid, state, |
||||
econtext->ecxt_per_query_memory, true, true); |
||||
} |
||||
else |
||||
elog(ERROR, "unrecognized node type: %d", |
||||
(int) nodeTag(expr)); |
||||
|
||||
/* shouldn't get here unless the selected function returns set */ |
||||
Assert(state->func.fn_retset); |
||||
|
||||
return state; |
||||
} |
||||
|
||||
/*
|
||||
* ExecMakeFunctionResultSet |
||||
* |
||||
* Evaluate the arguments to a set-returning function and then call the |
||||
* function itself. The argument expressions may not contain set-returning |
||||
* functions (the planner is supposed to have separated evaluation for those). |
||||
* |
||||
* This is used by nodeProjectSet.c. |
||||
*/ |
||||
Datum |
||||
ExecMakeFunctionResultSet(SetExprState *fcache, |
||||
ExprContext *econtext, |
||||
bool *isNull, |
||||
ExprDoneCond *isDone) |
||||
{ |
||||
List *arguments; |
||||
Datum result; |
||||
FunctionCallInfo fcinfo; |
||||
PgStat_FunctionCallUsage fcusage; |
||||
ReturnSetInfo rsinfo; |
||||
bool callit; |
||||
int i; |
||||
|
||||
restart: |
||||
|
||||
/* Guard against stack overflow due to overly complex expressions */ |
||||
check_stack_depth(); |
||||
|
||||
/*
|
||||
* If a previous call of the function returned a set result in the form of |
||||
* a tuplestore, continue reading rows from the tuplestore until it's |
||||
* empty. |
||||
*/ |
||||
if (fcache->funcResultStore) |
||||
{ |
||||
if (tuplestore_gettupleslot(fcache->funcResultStore, true, false, |
||||
fcache->funcResultSlot)) |
||||
{ |
||||
*isDone = ExprMultipleResult; |
||||
if (fcache->funcReturnsTuple) |
||||
{ |
||||
/* We must return the whole tuple as a Datum. */ |
||||
*isNull = false; |
||||
return ExecFetchSlotTupleDatum(fcache->funcResultSlot); |
||||
} |
||||
else |
||||
{ |
||||
/* Extract the first column and return it as a scalar. */ |
||||
return slot_getattr(fcache->funcResultSlot, 1, isNull); |
||||
} |
||||
} |
||||
/* Exhausted the tuplestore, so clean up */ |
||||
tuplestore_end(fcache->funcResultStore); |
||||
fcache->funcResultStore = NULL; |
||||
*isDone = ExprEndResult; |
||||
*isNull = true; |
||||
return (Datum) 0; |
||||
} |
||||
|
||||
/*
|
||||
* arguments is a list of expressions to evaluate before passing to the |
||||
* function manager. We skip the evaluation if it was already done in the |
||||
* previous call (ie, we are continuing the evaluation of a set-valued |
||||
* function). Otherwise, collect the current argument values into fcinfo. |
||||
*/ |
||||
fcinfo = &fcache->fcinfo_data; |
||||
arguments = fcache->args; |
||||
if (!fcache->setArgsValid) |
||||
ExecEvalFuncArgs(fcinfo, arguments, econtext); |
||||
else |
||||
{ |
||||
/* Reset flag (we may set it again below) */ |
||||
fcache->setArgsValid = false; |
||||
} |
||||
|
||||
/*
|
||||
* Now call the function, passing the evaluated parameter values. |
||||
*/ |
||||
|
||||
/* Prepare a resultinfo node for communication. */ |
||||
fcinfo->resultinfo = (Node *) &rsinfo; |
||||
rsinfo.type = T_ReturnSetInfo; |
||||
rsinfo.econtext = econtext; |
||||
rsinfo.expectedDesc = fcache->funcResultDesc; |
||||
rsinfo.allowedModes = (int) (SFRM_ValuePerCall | SFRM_Materialize); |
||||
/* note we do not set SFRM_Materialize_Random or _Preferred */ |
||||
rsinfo.returnMode = SFRM_ValuePerCall; |
||||
/* isDone is filled below */ |
||||
rsinfo.setResult = NULL; |
||||
rsinfo.setDesc = NULL; |
||||
|
||||
/*
|
||||
* If function is strict, and there are any NULL arguments, skip calling |
||||
* the function. |
||||
*/ |
||||
callit = true; |
||||
if (fcache->func.fn_strict) |
||||
{ |
||||
for (i = 0; i < fcinfo->nargs; i++) |
||||
{ |
||||
if (fcinfo->argnull[i]) |
||||
{ |
||||
callit = false; |
||||
break; |
||||
} |
||||
} |
||||
} |
||||
|
||||
if (callit) |
||||
{ |
||||
pgstat_init_function_usage(fcinfo, &fcusage); |
||||
|
||||
fcinfo->isnull = false; |
||||
rsinfo.isDone = ExprSingleResult; |
||||
result = FunctionCallInvoke(fcinfo); |
||||
*isNull = fcinfo->isnull; |
||||
*isDone = rsinfo.isDone; |
||||
|
||||
pgstat_end_function_usage(&fcusage, |
||||
rsinfo.isDone != ExprMultipleResult); |
||||
} |
||||
else |
||||
{ |
||||
/* for a strict SRF, result for NULL is an empty set */ |
||||
result = (Datum) 0; |
||||
*isNull = true; |
||||
*isDone = ExprEndResult; |
||||
} |
||||
|
||||
/* Which protocol does function want to use? */ |
||||
if (rsinfo.returnMode == SFRM_ValuePerCall) |
||||
{ |
||||
if (*isDone != ExprEndResult) |
||||
{ |
||||
/*
|
||||
* Save the current argument values to re-use on the next call. |
||||
*/ |
||||
if (*isDone == ExprMultipleResult) |
||||
{ |
||||
fcache->setArgsValid = true; |
||||
/* Register cleanup callback if we didn't already */ |
||||
if (!fcache->shutdown_reg) |
||||
{ |
||||
RegisterExprContextCallback(econtext, |
||||
ShutdownSetExpr, |
||||
PointerGetDatum(fcache)); |
||||
fcache->shutdown_reg = true; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
else if (rsinfo.returnMode == SFRM_Materialize) |
||||
{ |
||||
/* check we're on the same page as the function author */ |
||||
if (rsinfo.isDone != ExprSingleResult) |
||||
ereport(ERROR, |
||||
(errcode(ERRCODE_E_R_I_E_SRF_PROTOCOL_VIOLATED), |
||||
errmsg("table-function protocol for materialize mode was not followed"))); |
||||
if (rsinfo.setResult != NULL) |
||||
{ |
||||
/* prepare to return values from the tuplestore */ |
||||
ExecPrepareTuplestoreResult(fcache, econtext, |
||||
rsinfo.setResult, |
||||
rsinfo.setDesc); |
||||
/* loop back to top to start returning from tuplestore */ |
||||
goto restart; |
||||
} |
||||
/* if setResult was left null, treat it as empty set */ |
||||
*isDone = ExprEndResult; |
||||
*isNull = true; |
||||
result = (Datum) 0; |
||||
} |
||||
else |
||||
ereport(ERROR, |
||||
(errcode(ERRCODE_E_R_I_E_SRF_PROTOCOL_VIOLATED), |
||||
errmsg("unrecognized table-function returnMode: %d", |
||||
(int) rsinfo.returnMode))); |
||||
|
||||
return result; |
||||
} |
||||
|
||||
|
||||
/*
|
||||
* init_sexpr - initialize a SetExprState node during first use |
||||
*/ |
||||
static void |
||||
init_sexpr(Oid foid, Oid input_collation, SetExprState *sexpr, |
||||
MemoryContext sexprCxt, bool allowSRF, bool needDescForSRF) |
||||
{ |
||||
AclResult aclresult; |
||||
|
||||
/* Check permission to call function */ |
||||
aclresult = pg_proc_aclcheck(foid, GetUserId(), ACL_EXECUTE); |
||||
if (aclresult != ACLCHECK_OK) |
||||
aclcheck_error(aclresult, ACL_KIND_PROC, get_func_name(foid)); |
||||
InvokeFunctionExecuteHook(foid); |
||||
|
||||
/*
|
||||
* Safety check on nargs. Under normal circumstances this should never |
||||
* fail, as parser should check sooner. But possibly it might fail if |
||||
* server has been compiled with FUNC_MAX_ARGS smaller than some functions |
||||
* declared in pg_proc? |
||||
*/ |
||||
if (list_length(sexpr->args) > FUNC_MAX_ARGS) |
||||
ereport(ERROR, |
||||
(errcode(ERRCODE_TOO_MANY_ARGUMENTS), |
||||
errmsg_plural("cannot pass more than %d argument to a function", |
||||
"cannot pass more than %d arguments to a function", |
||||
FUNC_MAX_ARGS, |
||||
FUNC_MAX_ARGS))); |
||||
|
||||
/* Set up the primary fmgr lookup information */ |
||||
fmgr_info_cxt(foid, &(sexpr->func), sexprCxt); |
||||
fmgr_info_set_expr((Node *) sexpr->expr, &(sexpr->func)); |
||||
|
||||
/* Initialize the function call parameter struct as well */ |
||||
InitFunctionCallInfoData(sexpr->fcinfo_data, &(sexpr->func), |
||||
list_length(sexpr->args), |
||||
input_collation, NULL, NULL); |
||||
|
||||
/* If function returns set, check if that's allowed by caller */ |
||||
if (sexpr->func.fn_retset && !allowSRF) |
||||
ereport(ERROR, |
||||
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
||||
errmsg("set-valued function called in context that cannot accept a set"))); |
||||
|
||||
/* Otherwise, caller should have marked the sexpr correctly */ |
||||
Assert(sexpr->func.fn_retset == sexpr->funcReturnsSet); |
||||
|
||||
/* If function returns set, prepare expected tuple descriptor */ |
||||
if (sexpr->func.fn_retset && needDescForSRF) |
||||
{ |
||||
TypeFuncClass functypclass; |
||||
Oid funcrettype; |
||||
TupleDesc tupdesc; |
||||
MemoryContext oldcontext; |
||||
|
||||
functypclass = get_expr_result_type(sexpr->func.fn_expr, |
||||
&funcrettype, |
||||
&tupdesc); |
||||
|
||||
/* Must save tupdesc in sexpr's context */ |
||||
oldcontext = MemoryContextSwitchTo(sexprCxt); |
||||
|
||||
if (functypclass == TYPEFUNC_COMPOSITE) |
||||
{ |
||||
/* Composite data type, e.g. a table's row type */ |
||||
Assert(tupdesc); |
||||
/* Must copy it out of typcache for safety */ |
||||
sexpr->funcResultDesc = CreateTupleDescCopy(tupdesc); |
||||
sexpr->funcReturnsTuple = true; |
||||
} |
||||
else if (functypclass == TYPEFUNC_SCALAR) |
||||
{ |
||||
/* Base data type, i.e. scalar */ |
||||
tupdesc = CreateTemplateTupleDesc(1, false); |
||||
TupleDescInitEntry(tupdesc, |
||||
(AttrNumber) 1, |
||||
NULL, |
||||
funcrettype, |
||||
-1, |
||||
0); |
||||
sexpr->funcResultDesc = tupdesc; |
||||
sexpr->funcReturnsTuple = false; |
||||
} |
||||
else if (functypclass == TYPEFUNC_RECORD) |
||||
{ |
||||
/* This will work if function doesn't need an expectedDesc */ |
||||
sexpr->funcResultDesc = NULL; |
||||
sexpr->funcReturnsTuple = true; |
||||
} |
||||
else |
||||
{ |
||||
/* Else, we will fail if function needs an expectedDesc */ |
||||
sexpr->funcResultDesc = NULL; |
||||
} |
||||
|
||||
MemoryContextSwitchTo(oldcontext); |
||||
} |
||||
else |
||||
sexpr->funcResultDesc = NULL; |
||||
|
||||
/* Initialize additional state */ |
||||
sexpr->funcResultStore = NULL; |
||||
sexpr->funcResultSlot = NULL; |
||||
sexpr->shutdown_reg = false; |
||||
} |
||||
|
||||
/*
|
||||
* callback function in case a SetExprState needs to be shut down before it |
||||
* has been run to completion |
||||
*/ |
||||
static void |
||||
ShutdownSetExpr(Datum arg) |
||||
{ |
||||
SetExprState *sexpr = castNode(SetExprState, DatumGetPointer(arg)); |
||||
|
||||
/* If we have a slot, make sure it's let go of any tuplestore pointer */ |
||||
if (sexpr->funcResultSlot) |
||||
ExecClearTuple(sexpr->funcResultSlot); |
||||
|
||||
/* Release any open tuplestore */ |
||||
if (sexpr->funcResultStore) |
||||
tuplestore_end(sexpr->funcResultStore); |
||||
sexpr->funcResultStore = NULL; |
||||
|
||||
/* Clear any active set-argument state */ |
||||
sexpr->setArgsValid = false; |
||||
|
||||
/* execUtils will deregister the callback... */ |
||||
sexpr->shutdown_reg = false; |
||||
} |
||||
|
||||
/*
|
||||
* Evaluate arguments for a function. |
||||
*/ |
||||
static void |
||||
ExecEvalFuncArgs(FunctionCallInfo fcinfo, |
||||
List *argList, |
||||
ExprContext *econtext) |
||||
{ |
||||
int i; |
||||
ListCell *arg; |
||||
|
||||
i = 0; |
||||
foreach(arg, argList) |
||||
{ |
||||
ExprState *argstate = (ExprState *) lfirst(arg); |
||||
|
||||
fcinfo->arg[i] = ExecEvalExpr(argstate, |
||||
econtext, |
||||
&fcinfo->argnull[i]); |
||||
i++; |
||||
} |
||||
|
||||
Assert(i == fcinfo->nargs); |
||||
} |
||||
|
||||
/*
|
||||
* ExecPrepareTuplestoreResult |
||||
* |
||||
* Subroutine for ExecMakeFunctionResultSet: prepare to extract rows from a |
||||
* tuplestore function result. We must set up a funcResultSlot (unless |
||||
* already done in a previous call cycle) and verify that the function |
||||
* returned the expected tuple descriptor. |
||||
*/ |
||||
static void |
||||
ExecPrepareTuplestoreResult(SetExprState *sexpr, |
||||
ExprContext *econtext, |
||||
Tuplestorestate *resultStore, |
||||
TupleDesc resultDesc) |
||||
{ |
||||
sexpr->funcResultStore = resultStore; |
||||
|
||||
if (sexpr->funcResultSlot == NULL) |
||||
{ |
||||
/* Create a slot so we can read data out of the tuplestore */ |
||||
TupleDesc slotDesc; |
||||
MemoryContext oldcontext; |
||||
|
||||
oldcontext = MemoryContextSwitchTo(sexpr->func.fn_mcxt); |
||||
|
||||
/*
|
||||
* If we were not able to determine the result rowtype from context, |
||||
* and the function didn't return a tupdesc, we have to fail. |
||||
*/ |
||||
if (sexpr->funcResultDesc) |
||||
slotDesc = sexpr->funcResultDesc; |
||||
else if (resultDesc) |
||||
{ |
||||
/* don't assume resultDesc is long-lived */ |
||||
slotDesc = CreateTupleDescCopy(resultDesc); |
||||
} |
||||
else |
||||
{ |
||||
ereport(ERROR, |
||||
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
||||
errmsg("function returning setof record called in " |
||||
"context that cannot accept type record"))); |
||||
slotDesc = NULL; /* keep compiler quiet */ |
||||
} |
||||
|
||||
sexpr->funcResultSlot = MakeSingleTupleTableSlot(slotDesc); |
||||
MemoryContextSwitchTo(oldcontext); |
||||
} |
||||
|
||||
/*
|
||||
* 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 (resultDesc) |
||||
{ |
||||
if (sexpr->funcResultDesc) |
||||
tupledesc_match(sexpr->funcResultDesc, resultDesc); |
||||
|
||||
/*
|
||||
* If it is a dynamically-allocated TupleDesc, free it: it is |
||||
* typically allocated in a per-query context, so we must avoid |
||||
* leaking it across multiple usages. |
||||
*/ |
||||
if (resultDesc->tdrefcount == -1) |
||||
FreeTupleDesc(resultDesc); |
||||
} |
||||
|
||||
/* Register cleanup callback if we didn't already */ |
||||
if (!sexpr->shutdown_reg) |
||||
{ |
||||
RegisterExprContextCallback(econtext, |
||||
ShutdownSetExpr, |
||||
PointerGetDatum(sexpr)); |
||||
sexpr->shutdown_reg = true; |
||||
} |
||||
} |
||||
|
||||
/*
|
||||
* 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_plural("Returned row contains %d attribute, but query expects %d.", |
||||
"Returned row contains %d attributes, but query expects %d.", |
||||
src_tupdesc->natts, |
||||
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 (IsBinaryCoercible(sattr->atttypid, dattr->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))); |
||||
} |
||||
} |
@ -0,0 +1,642 @@ |
||||
/*-------------------------------------------------------------------------
|
||||
* |
||||
* execExpr.h |
||||
* Low level infrastructure related to expression evaluation |
||||
* |
||||
* |
||||
* Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group |
||||
* Portions Copyright (c) 1994, Regents of the University of California |
||||
* |
||||
* src/include/executor/execExpr.h |
||||
* |
||||
*------------------------------------------------------------------------- |
||||
*/ |
||||
#ifndef EXEC_EXPR_H |
||||
#define EXEC_EXPR_H |
||||
|
||||
#include "nodes/execnodes.h" |
||||
|
||||
/* forward reference to avoid circularity */ |
||||
struct ArrayRefState; |
||||
|
||||
/* Bits in ExprState->flags (see also execnodes.h for public flag bits): */ |
||||
/* expression's interpreter has been initialized */ |
||||
#define EEO_FLAG_INTERPRETER_INITIALIZED (1 << 1) |
||||
/* jump-threading is in use */ |
||||
#define EEO_FLAG_DIRECT_THREADED (1 << 2) |
||||
|
||||
/*
|
||||
* Discriminator for ExprEvalSteps. |
||||
* |
||||
* Identifies the operation to be executed and which member in the |
||||
* ExprEvalStep->d union is valid. |
||||
* |
||||
* The order of entries needs to be kept in sync with the dispatch_table[] |
||||
* array in execExprInterp.c:ExecInterpExpr(). |
||||
*/ |
||||
typedef enum ExprEvalOp |
||||
{ |
||||
/* entire expression has been evaluated completely, return */ |
||||
EEOP_DONE, |
||||
|
||||
/* apply slot_getsomeattrs on corresponding tuple slot */ |
||||
EEOP_INNER_FETCHSOME, |
||||
EEOP_OUTER_FETCHSOME, |
||||
EEOP_SCAN_FETCHSOME, |
||||
|
||||
/* compute non-system Var value */ |
||||
/* "FIRST" variants are used only the first time through */ |
||||
EEOP_INNER_VAR_FIRST, |
||||
EEOP_INNER_VAR, |
||||
EEOP_OUTER_VAR_FIRST, |
||||
EEOP_OUTER_VAR, |
||||
EEOP_SCAN_VAR_FIRST, |
||||
EEOP_SCAN_VAR, |
||||
|
||||
/* compute system Var value */ |
||||
EEOP_INNER_SYSVAR, |
||||
EEOP_OUTER_SYSVAR, |
||||
EEOP_SCAN_SYSVAR, |
||||
|
||||
/* compute wholerow Var */ |
||||
EEOP_WHOLEROW, |
||||
|
||||
/* compute non-system Var value, assign it into ExprState's resultslot */ |
||||
/* (these are not used if _FIRST checks would be needed) */ |
||||
EEOP_ASSIGN_INNER_VAR, |
||||
EEOP_ASSIGN_OUTER_VAR, |
||||
EEOP_ASSIGN_SCAN_VAR, |
||||
|
||||
/* assign ExprState's resvalue/resnull to a column of its resultslot */ |
||||
EEOP_ASSIGN_TMP, |
||||
/* ditto, applying MakeExpandedObjectReadOnly() */ |
||||
EEOP_ASSIGN_TMP_MAKE_RO, |
||||
|
||||
/* evaluate Const value */ |
||||
EEOP_CONST, |
||||
|
||||
/*
|
||||
* Evaluate function call (including OpExprs etc). For speed, we |
||||
* distinguish in the opcode whether the function is strict and/or |
||||
* requires usage stats tracking. |
||||
*/ |
||||
EEOP_FUNCEXPR, |
||||
EEOP_FUNCEXPR_STRICT, |
||||
EEOP_FUNCEXPR_FUSAGE, |
||||
EEOP_FUNCEXPR_STRICT_FUSAGE, |
||||
|
||||
/*
|
||||
* Evaluate boolean AND expression, one step per subexpression. FIRST/LAST |
||||
* subexpressions are special-cased for performance. Since AND always has |
||||
* at least two subexpressions, FIRST and LAST never apply to the same |
||||
* subexpression. |
||||
*/ |
||||
EEOP_BOOL_AND_STEP_FIRST, |
||||
EEOP_BOOL_AND_STEP, |
||||
EEOP_BOOL_AND_STEP_LAST, |
||||
|
||||
/* similarly for boolean OR expression */ |
||||
EEOP_BOOL_OR_STEP_FIRST, |
||||
EEOP_BOOL_OR_STEP, |
||||
EEOP_BOOL_OR_STEP_LAST, |
||||
|
||||
/* evaluate boolean NOT expression */ |
||||
EEOP_BOOL_NOT_STEP, |
||||
|
||||
/* simplified version of BOOL_AND_STEP for use by ExecQual() */ |
||||
EEOP_QUAL, |
||||
|
||||
/* unconditional jump to another step */ |
||||
EEOP_JUMP, |
||||
|
||||
/* conditional jumps based on current result value */ |
||||
EEOP_JUMP_IF_NULL, |
||||
EEOP_JUMP_IF_NOT_NULL, |
||||
EEOP_JUMP_IF_NOT_TRUE, |
||||
|
||||
/* perform NULL tests for scalar values */ |
||||
EEOP_NULLTEST_ISNULL, |
||||
EEOP_NULLTEST_ISNOTNULL, |
||||
|
||||
/* perform NULL tests for row values */ |
||||
EEOP_NULLTEST_ROWISNULL, |
||||
EEOP_NULLTEST_ROWISNOTNULL, |
||||
|
||||
/* evaluate a BooleanTest expression */ |
||||
EEOP_BOOLTEST_IS_TRUE, |
||||
EEOP_BOOLTEST_IS_NOT_TRUE, |
||||
EEOP_BOOLTEST_IS_FALSE, |
||||
EEOP_BOOLTEST_IS_NOT_FALSE, |
||||
|
||||
/* evaluate PARAM_EXEC/EXTERN parameters */ |
||||
EEOP_PARAM_EXEC, |
||||
EEOP_PARAM_EXTERN, |
||||
|
||||
/* return CaseTestExpr value */ |
||||
EEOP_CASE_TESTVAL, |
||||
|
||||
/* apply MakeExpandedObjectReadOnly() to target value */ |
||||
EEOP_MAKE_READONLY, |
||||
|
||||
/* evaluate assorted special-purpose expression types */ |
||||
EEOP_IOCOERCE, |
||||
EEOP_DISTINCT, |
||||
EEOP_NULLIF, |
||||
EEOP_SQLVALUEFUNCTION, |
||||
EEOP_CURRENTOFEXPR, |
||||
EEOP_ARRAYEXPR, |
||||
EEOP_ARRAYCOERCE, |
||||
EEOP_ROW, |
||||
|
||||
/*
|
||||
* Compare two individual elements of each of two compared ROW() |
||||
* expressions. Skip to ROWCOMPARE_FINAL if elements are not equal. |
||||
*/ |
||||
EEOP_ROWCOMPARE_STEP, |
||||
|
||||
/* evaluate boolean value based on previous ROWCOMPARE_STEP operations */ |
||||
EEOP_ROWCOMPARE_FINAL, |
||||
|
||||
/* evaluate GREATEST() or LEAST() */ |
||||
EEOP_MINMAX, |
||||
|
||||
/* evaluate FieldSelect expression */ |
||||
EEOP_FIELDSELECT, |
||||
|
||||
/*
|
||||
* Deform tuple before evaluating new values for individual fields in a |
||||
* FieldStore expression. |
||||
*/ |
||||
EEOP_FIELDSTORE_DEFORM, |
||||
|
||||
/*
|
||||
* Form the new tuple for a FieldStore expression. Individual fields will |
||||
* have been evaluated into columns of the tuple deformed by the preceding |
||||
* DEFORM step. |
||||
*/ |
||||
EEOP_FIELDSTORE_FORM, |
||||
|
||||
/* Process an array subscript; short-circuit expression to NULL if NULL */ |
||||
EEOP_ARRAYREF_SUBSCRIPT, |
||||
|
||||
/*
|
||||
* Compute old array element/slice when an ArrayRef assignment expression |
||||
* contains ArrayRef/FieldStore subexpressions. Value is accessed using |
||||
* the CaseTest mechanism. |
||||
*/ |
||||
EEOP_ARRAYREF_OLD, |
||||
|
||||
/* compute new value for ArrayRef assignment expression */ |
||||
EEOP_ARRAYREF_ASSIGN, |
||||
|
||||
/* compute element/slice for ArrayRef fetch expression */ |
||||
EEOP_ARRAYREF_FETCH, |
||||
|
||||
/* evaluate value for CoerceToDomainValue */ |
||||
EEOP_DOMAIN_TESTVAL, |
||||
|
||||
/* evaluate a domain's NOT NULL constraint */ |
||||
EEOP_DOMAIN_NOTNULL, |
||||
|
||||
/* evaluate a single domain CHECK constraint */ |
||||
EEOP_DOMAIN_CHECK, |
||||
|
||||
/* evaluate assorted special-purpose expression types */ |
||||
EEOP_CONVERT_ROWTYPE, |
||||
EEOP_SCALARARRAYOP, |
||||
EEOP_XMLEXPR, |
||||
EEOP_AGGREF, |
||||
EEOP_GROUPING_FUNC, |
||||
EEOP_WINDOW_FUNC, |
||||
EEOP_SUBPLAN, |
||||
EEOP_ALTERNATIVE_SUBPLAN, |
||||
|
||||
/* non-existent operation, used e.g. to check array lengths */ |
||||
EEOP_LAST |
||||
} ExprEvalOp; |
||||
|
||||
|
||||
typedef struct ExprEvalStep |
||||
{ |
||||
/*
|
||||
* Instruction to be executed. During instruction preparation this is an |
||||
* enum ExprEvalOp, but later it can be changed to some other type, e.g. a |
||||
* pointer for computed goto (that's why it's an intptr_t). |
||||
*/ |
||||
intptr_t opcode; |
||||
|
||||
/* where to store the result of this step */ |
||||
Datum *resvalue; |
||||
bool *resnull; |
||||
|
||||
/*
|
||||
* Inline data for the operation. Inline data is faster to access, but |
||||
* also bloats the size of all instructions. The union should be kept to |
||||
* no more than 40 bytes on 64-bit systems (so that the entire struct is |
||||
* no more than 64 bytes, a single cacheline on common systems). |
||||
*/ |
||||
union |
||||
{ |
||||
/* for EEOP_INNER/OUTER/SCAN_FETCHSOME */ |
||||
struct |
||||
{ |
||||
/* attribute number up to which to fetch (inclusive) */ |
||||
int last_var; |
||||
} fetch; |
||||
|
||||
/* for EEOP_INNER/OUTER/SCAN_[SYS]VAR[_FIRST] */ |
||||
struct |
||||
{ |
||||
/* attnum is attr number - 1 for regular VAR ... */ |
||||
/* but it's just the normal (negative) attr number for SYSVAR */ |
||||
int attnum; |
||||
Oid vartype; /* type OID of variable */ |
||||
} var; |
||||
|
||||
/* for EEOP_WHOLEROW */ |
||||
struct |
||||
{ |
||||
Var *var; /* original Var node in plan tree */ |
||||
bool first; /* first time through, need to initialize? */ |
||||
bool slow; /* need runtime check for nulls? */ |
||||
TupleDesc tupdesc; /* descriptor for resulting tuples */ |
||||
JunkFilter *junkFilter; /* JunkFilter to remove resjunk cols */ |
||||
} wholerow; |
||||
|
||||
/* for EEOP_ASSIGN_*_VAR */ |
||||
struct |
||||
{ |
||||
/* target index in ExprState->resultslot->tts_values/nulls */ |
||||
int resultnum; |
||||
/* source attribute number - 1 */ |
||||
int attnum; |
||||
} assign_var; |
||||
|
||||
/* for EEOP_ASSIGN_TMP[_MAKE_RO] */ |
||||
struct |
||||
{ |
||||
/* target index in ExprState->resultslot->tts_values/nulls */ |
||||
int resultnum; |
||||
} assign_tmp; |
||||
|
||||
/* for EEOP_CONST */ |
||||
struct |
||||
{ |
||||
/* constant's value */ |
||||
Datum value; |
||||
bool isnull; |
||||
} constval; |
||||
|
||||
/* for EEOP_FUNCEXPR_* / NULLIF / DISTINCT */ |
||||
struct |
||||
{ |
||||
FmgrInfo *finfo; /* function's lookup data */ |
||||
FunctionCallInfo fcinfo_data; /* arguments etc */ |
||||
/* faster to access without additional indirection: */ |
||||
PGFunction fn_addr; /* actual call address */ |
||||
int nargs; /* number of arguments */ |
||||
} func; |
||||
|
||||
/* for EEOP_BOOL_*_STEP */ |
||||
struct |
||||
{ |
||||
bool *anynull; /* track if any input was NULL */ |
||||
int jumpdone; /* jump here if result determined */ |
||||
} boolexpr; |
||||
|
||||
/* for EEOP_QUAL */ |
||||
struct |
||||
{ |
||||
int jumpdone; /* jump here on false or null */ |
||||
} qualexpr; |
||||
|
||||
/* for EEOP_JUMP[_CONDITION] */ |
||||
struct |
||||
{ |
||||
int jumpdone; /* target instruction's index */ |
||||
} jump; |
||||
|
||||
/* for EEOP_NULLTEST_ROWIS[NOT]NULL */ |
||||
struct |
||||
{ |
||||
/* cached tupdesc pointer - filled at runtime */ |
||||
TupleDesc argdesc; |
||||
} nulltest_row; |
||||
|
||||
/* for EEOP_PARAM_EXEC/EXTERN */ |
||||
struct |
||||
{ |
||||
int paramid; /* numeric ID for parameter */ |
||||
Oid paramtype; /* OID of parameter's datatype */ |
||||
} param; |
||||
|
||||
/* for EEOP_CASE_TESTVAL/DOMAIN_TESTVAL */ |
||||
struct |
||||
{ |
||||
Datum *value; /* value to return */ |
||||
bool *isnull; |
||||
} casetest; |
||||
|
||||
/* for EEOP_MAKE_READONLY */ |
||||
struct |
||||
{ |
||||
Datum *value; /* value to coerce to read-only */ |
||||
bool *isnull; |
||||
} make_readonly; |
||||
|
||||
/* for EEOP_IOCOERCE */ |
||||
struct |
||||
{ |
||||
/* lookup and call info for source type's output function */ |
||||
FmgrInfo *finfo_out; |
||||
FunctionCallInfo fcinfo_data_out; |
||||
/* lookup and call info for result type's input function */ |
||||
FmgrInfo *finfo_in; |
||||
FunctionCallInfo fcinfo_data_in; |
||||
} iocoerce; |
||||
|
||||
/* for EEOP_SQLVALUEFUNCTION */ |
||||
struct |
||||
{ |
||||
SQLValueFunction *svf; |
||||
} sqlvaluefunction; |
||||
|
||||
/* for EEOP_ARRAYEXPR */ |
||||
struct |
||||
{ |
||||
Datum *elemvalues; /* element values get stored here */ |
||||
bool *elemnulls; |
||||
int nelems; /* length of the above arrays */ |
||||
Oid elemtype; /* array element type */ |
||||
int16 elemlength; /* typlen of the array element type */ |
||||
bool elembyval; /* is the element type pass-by-value? */ |
||||
char elemalign; /* typalign of the element type */ |
||||
bool multidims; /* is array expression multi-D? */ |
||||
} arrayexpr; |
||||
|
||||
/* for EEOP_ARRAYCOERCE */ |
||||
struct |
||||
{ |
||||
ArrayCoerceExpr *coerceexpr; |
||||
Oid resultelemtype; /* element type of result array */ |
||||
FmgrInfo *elemfunc; /* lookup info for element coercion
|
||||
* function */ |
||||
struct ArrayMapState *amstate; /* workspace for array_map */ |
||||
} arraycoerce; |
||||
|
||||
/* for EEOP_ROW */ |
||||
struct |
||||
{ |
||||
TupleDesc tupdesc; /* descriptor for result tuples */ |
||||
/* workspace for the values constituting the row: */ |
||||
Datum *elemvalues; |
||||
bool *elemnulls; |
||||
} row; |
||||
|
||||
/* for EEOP_ROWCOMPARE_STEP */ |
||||
struct |
||||
{ |
||||
/* lookup and call data for column comparison function */ |
||||
FmgrInfo *finfo; |
||||
FunctionCallInfo fcinfo_data; |
||||
PGFunction fn_addr; |
||||
/* target for comparison resulting in NULL */ |
||||
int jumpnull; |
||||
/* target for comparison yielding inequality */ |
||||
int jumpdone; |
||||
} rowcompare_step; |
||||
|
||||
/* for EEOP_ROWCOMPARE_FINAL */ |
||||
struct |
||||
{ |
||||
RowCompareType rctype; |
||||
} rowcompare_final; |
||||
|
||||
/* for EEOP_MINMAX */ |
||||
struct |
||||
{ |
||||
/* workspace for argument values */ |
||||
Datum *values; |
||||
bool *nulls; |
||||
int nelems; |
||||
/* is it GREATEST or LEAST? */ |
||||
MinMaxOp op; |
||||
/* lookup and call data for comparison function */ |
||||
FmgrInfo *finfo; |
||||
FunctionCallInfo fcinfo_data; |
||||
} minmax; |
||||
|
||||
/* for EEOP_FIELDSELECT */ |
||||
struct |
||||
{ |
||||
AttrNumber fieldnum; /* field number to extract */ |
||||
Oid resulttype; /* field's type */ |
||||
/* cached tupdesc pointer - filled at runtime */ |
||||
TupleDesc argdesc; |
||||
} fieldselect; |
||||
|
||||
/* for EEOP_FIELDSTORE_DEFORM / FIELDSTORE_FORM */ |
||||
struct |
||||
{ |
||||
/* original expression node */ |
||||
FieldStore *fstore; |
||||
|
||||
/* cached tupdesc pointer - filled at runtime */ |
||||
/* note that a DEFORM and FORM pair share the same tupdesc */ |
||||
TupleDesc *argdesc; |
||||
|
||||
/* workspace for column values */ |
||||
Datum *values; |
||||
bool *nulls; |
||||
int ncolumns; |
||||
} fieldstore; |
||||
|
||||
/* for EEOP_ARRAYREF_SUBSCRIPT */ |
||||
struct |
||||
{ |
||||
/* too big to have inline */ |
||||
struct ArrayRefState *state; |
||||
int off; /* 0-based index of this subscript */ |
||||
bool isupper; /* is it upper or lower subscript? */ |
||||
int jumpdone; /* jump here on null */ |
||||
} arrayref_subscript; |
||||
|
||||
/* for EEOP_ARRAYREF_OLD / ASSIGN / FETCH */ |
||||
struct |
||||
{ |
||||
/* too big to have inline */ |
||||
struct ArrayRefState *state; |
||||
} arrayref; |
||||
|
||||
/* for EEOP_DOMAIN_NOTNULL / DOMAIN_CHECK */ |
||||
struct |
||||
{ |
||||
/* name of constraint */ |
||||
char *constraintname; |
||||
/* where the result of a CHECK constraint will be stored */ |
||||
Datum *checkvalue; |
||||
bool *checknull; |
||||
/* OID of domain type */ |
||||
Oid resulttype; |
||||
} domaincheck; |
||||
|
||||
/* for EEOP_CONVERT_ROWTYPE */ |
||||
struct |
||||
{ |
||||
ConvertRowtypeExpr *convert; /* original expression */ |
||||
/* these three fields are filled at runtime: */ |
||||
TupleDesc indesc; /* tupdesc for input type */ |
||||
TupleDesc outdesc; /* tupdesc for output type */ |
||||
TupleConversionMap *map; /* column mapping */ |
||||
bool initialized; /* initialized for current types? */ |
||||
} convert_rowtype; |
||||
|
||||
/* for EEOP_SCALARARRAYOP */ |
||||
struct |
||||
{ |
||||
/* element_type/typlen/typbyval/typalign are filled at runtime */ |
||||
Oid element_type; /* InvalidOid if not yet filled */ |
||||
bool useOr; /* use OR or AND semantics? */ |
||||
int16 typlen; /* array element type storage info */ |
||||
bool typbyval; |
||||
char typalign; |
||||
FmgrInfo *finfo; /* function's lookup data */ |
||||
FunctionCallInfo fcinfo_data; /* arguments etc */ |
||||
/* faster to access without additional indirection: */ |
||||
PGFunction fn_addr; /* actual call address */ |
||||
} scalararrayop; |
||||
|
||||
/* for EEOP_XMLEXPR */ |
||||
struct |
||||
{ |
||||
XmlExpr *xexpr; /* original expression node */ |
||||
/* workspace for evaluating named args, if any */ |
||||
Datum *named_argvalue; |
||||
bool *named_argnull; |
||||
/* workspace for evaluating unnamed args, if any */ |
||||
Datum *argvalue; |
||||
bool *argnull; |
||||
} xmlexpr; |
||||
|
||||
/* for EEOP_AGGREF */ |
||||
struct |
||||
{ |
||||
/* out-of-line state, modified by nodeAgg.c */ |
||||
AggrefExprState *astate; |
||||
} aggref; |
||||
|
||||
/* for EEOP_GROUPING_FUNC */ |
||||
struct |
||||
{ |
||||
AggState *parent; /* parent Agg */ |
||||
List *clauses; /* integer list of column numbers */ |
||||
} grouping_func; |
||||
|
||||
/* for EEOP_WINDOW_FUNC */ |
||||
struct |
||||
{ |
||||
/* out-of-line state, modified by nodeWindowFunc.c */ |
||||
WindowFuncExprState *wfstate; |
||||
} window_func; |
||||
|
||||
/* for EEOP_SUBPLAN */ |
||||
struct |
||||
{ |
||||
/* out-of-line state, created by nodeSubplan.c */ |
||||
SubPlanState *sstate; |
||||
} subplan; |
||||
|
||||
/* for EEOP_ALTERNATIVE_SUBPLAN */ |
||||
struct |
||||
{ |
||||
/* out-of-line state, created by nodeSubplan.c */ |
||||
AlternativeSubPlanState *asstate; |
||||
} alternative_subplan; |
||||
} d; |
||||
} ExprEvalStep; |
||||
|
||||
|
||||
/* Non-inline data for array operations */ |
||||
typedef struct ArrayRefState |
||||
{ |
||||
bool isassignment; /* is it assignment, or just fetch? */ |
||||
|
||||
Oid refelemtype; /* OID of the array element type */ |
||||
int16 refattrlength; /* typlen of array type */ |
||||
int16 refelemlength; /* typlen of the array element type */ |
||||
bool refelembyval; /* is the element type pass-by-value? */ |
||||
char refelemalign; /* typalign of the element type */ |
||||
|
||||
/* numupper and upperprovided[] are filled at compile time */ |
||||
/* at runtime, extracted subscript datums get stored in upperindex[] */ |
||||
int numupper; |
||||
bool upperprovided[MAXDIM]; |
||||
int upperindex[MAXDIM]; |
||||
|
||||
/* similarly for lower indexes, if any */ |
||||
int numlower; |
||||
bool lowerprovided[MAXDIM]; |
||||
int lowerindex[MAXDIM]; |
||||
|
||||
/* subscript expressions get evaluated into here */ |
||||
Datum subscriptvalue; |
||||
bool subscriptnull; |
||||
|
||||
/* for assignment, new value to assign is evaluated into here */ |
||||
Datum replacevalue; |
||||
bool replacenull; |
||||
|
||||
/* if we have a nested assignment, ARRAYREF_OLD puts old value here */ |
||||
Datum prevvalue; |
||||
bool prevnull; |
||||
} ArrayRefState; |
||||
|
||||
|
||||
extern void ExecReadyInterpretedExpr(ExprState *state); |
||||
|
||||
extern ExprEvalOp ExecEvalStepOp(ExprState *state, ExprEvalStep *op); |
||||
|
||||
/*
|
||||
* Non fast-path execution functions. These are externs instead of statics in |
||||
* execExprInterp.c, because that allows them to be used by other methods of |
||||
* expression evaluation, reducing code duplication. |
||||
*/ |
||||
extern void ExecEvalParamExec(ExprState *state, ExprEvalStep *op, |
||||
ExprContext *econtext); |
||||
extern void ExecEvalParamExtern(ExprState *state, ExprEvalStep *op, |
||||
ExprContext *econtext); |
||||
extern void ExecEvalSQLValueFunction(ExprState *state, ExprEvalStep *op); |
||||
extern void ExecEvalCurrentOfExpr(ExprState *state, ExprEvalStep *op); |
||||
extern void ExecEvalRowNull(ExprState *state, ExprEvalStep *op, |
||||
ExprContext *econtext); |
||||
extern void ExecEvalRowNotNull(ExprState *state, ExprEvalStep *op, |
||||
ExprContext *econtext); |
||||
extern void ExecEvalArrayExpr(ExprState *state, ExprEvalStep *op); |
||||
extern void ExecEvalArrayCoerce(ExprState *state, ExprEvalStep *op); |
||||
extern void ExecEvalRow(ExprState *state, ExprEvalStep *op); |
||||
extern void ExecEvalMinMax(ExprState *state, ExprEvalStep *op); |
||||
extern void ExecEvalFieldSelect(ExprState *state, ExprEvalStep *op, |
||||
ExprContext *econtext); |
||||
extern void ExecEvalFieldStoreDeForm(ExprState *state, ExprEvalStep *op, |
||||
ExprContext *econtext); |
||||
extern void ExecEvalFieldStoreForm(ExprState *state, ExprEvalStep *op, |
||||
ExprContext *econtext); |
||||
extern bool ExecEvalArrayRefSubscript(ExprState *state, ExprEvalStep *op); |
||||
extern void ExecEvalArrayRefFetch(ExprState *state, ExprEvalStep *op); |
||||
extern void ExecEvalArrayRefOld(ExprState *state, ExprEvalStep *op); |
||||
extern void ExecEvalArrayRefAssign(ExprState *state, ExprEvalStep *op); |
||||
extern void ExecEvalConvertRowtype(ExprState *state, ExprEvalStep *op, |
||||
ExprContext *econtext); |
||||
extern void ExecEvalScalarArrayOp(ExprState *state, ExprEvalStep *op); |
||||
extern void ExecEvalConstraintNotNull(ExprState *state, ExprEvalStep *op); |
||||
extern void ExecEvalConstraintCheck(ExprState *state, ExprEvalStep *op); |
||||
extern void ExecEvalXmlExpr(ExprState *state, ExprEvalStep *op); |
||||
extern void ExecEvalGroupingFunc(ExprState *state, ExprEvalStep *op); |
||||
extern void ExecEvalSubPlan(ExprState *state, ExprEvalStep *op, |
||||
ExprContext *econtext); |
||||
extern void ExecEvalAlternativeSubPlan(ExprState *state, ExprEvalStep *op, |
||||
ExprContext *econtext); |
||||
extern void ExecEvalWholeRowVar(ExprState *state, ExprEvalStep *op, |
||||
ExprContext *econtext); |
||||
|
||||
#endif /* EXEC_EXPR_H */ |
Loading…
Reference in new issue