该函数用在初始化索引搜索键的处理,也就是处理where条件的部分
void
ExecIndexBuildScanKeys(PlanState *planstate, Relation index,
List *quals, bool isorderby,
ScanKey *scanKeys, int *numScanKeys,
IndexRuntimeKeyInfo **runtimeKeys, int *numRuntimeKeys,
IndexArrayKeyInfo **arrayKeys, int *numArrayKeys)
{
ListCell *qual_cell;
ScanKey scan_keys;
IndexRuntimeKeyInfo *runtime_keys;
IndexArrayKeyInfo *array_keys;
int n_scan_keys;
int n_runtime_keys;
int max_runtime_keys;
int n_array_keys;
int j;
/* Allocate array for ScanKey structs: one per qual */
n_scan_keys = list_length(quals);
scan_keys = (ScanKey) palloc(n_scan_keys * sizeof(ScanKeyData));
/*
* runtime_keys array is dynamically resized as needed. We handle it this
* way so that the same runtime keys array can be shared between
* indexquals and indexorderbys, which will be processed in separate calls
* of this function. Caller must be sure to pass in NULL/0 for first
* call.
*/
runtime_keys = *runtimeKeys;
n_runtime_keys = max_runtime_keys = *numRuntimeKeys;
/* Allocate array_keys as large as it could possibly need to be */
array_keys = (IndexArrayKeyInfo *)
palloc0(n_scan_keys * sizeof(IndexArrayKeyInfo));
n_array_keys = 0;
/*
* for each opclause in the given qual, convert the opclause into a single
* scan key
*/
j = 0;
foreach(qual_cell, quals)
{
Expr *clause = (Expr *) lfirst(qual_cell);
ScanKey this_scan_key = &scan_keys[j++];
Oid opno; /* operator's OID */
RegProcedure opfuncid; /* operator proc id used in scan */
Oid opfamily; /* opfamily of index column */
int op_strategy; /* operator's strategy number */
Oid op_lefttype; /* operator's declared input types */
Oid op_righttype;
Expr *leftop; /* expr on lhs of operator */
Expr *rightop; /* expr on rhs ... */
AttrNumber varattno; /* att number used in scan */
int indnkeyatts;
indnkeyatts = IndexRelationGetNumberOfKeyAttributes(index);
if (IsA(clause, OpExpr))
{
//where objectid=1会进这个分支
/* indexkey op const or indexkey op expression */
int flags = 0;
Datum scanvalue;
//operator oid
/*
p *clause
$5 = {type = T_OpExpr}
(gdb) p *(OpExpr*)clause
$6 = {xpr = {type = T_OpExpr}, opno = 96, opfuncid = 65, opresulttype = 16, opretset = false, opcollid = 0, inputcollid = 0, args = 0x25f5ad8, location = 60}
*/
/*
p opno
$11 = 96
t select 96::regoper;
regoper
--------------
pg_catalog.=
*/
opno = ((OpExpr *) clause)->opno;
/*
p opfuncid
$14 = 65
select 65::regprocedure;
regprocedure
-------------------------
int4eq(integer,integer)
*/
opfuncid = ((OpExpr *) clause)->opfuncid;
/*
* leftop should be the index key Var, possibly relabeled
*/
/*
p *(Var*)leftop
$19 = {xpr = {type = T_Var}, varno = -3, varattno = 1, vartype = 23, vartypmod = -1, varcollid = 0, varnullingrels = 0x0, varlevelsup = 0, varnosyn = 1, varattnosyn = 1,
location = 52}
*/
leftop = (Expr *) get_leftop(clause);
if (leftop && IsA(leftop, RelabelType))
leftop = ((RelabelType *) leftop)->arg;
Assert(leftop != NULL);
if (!(IsA(leftop, Var) &&
((Var *) leftop)->varno == INDEX_VAR))
elog(ERROR, "indexqual doesn't have key on left side");
varattno = ((Var *) leftop)->varattno;
if (varattno < 1 || varattno > indnkeyatts)
elog(ERROR, "bogus index qualification");
/*
* We have to look up the operator's strategy number. This
* provides a cross-check that the operator does match the index.
*/
/*opfamily=1976
*/
opfamily = index->rd_opfamily[varattno - 1];
/*
select amoplefttype::regtype,amoprighttype::regtype,amopstrategy from pg_amop where amopfamily=1976 and amoppurpose='s' and amopopr=96;
amoplefttype | amoprighttype | amopstrategy
--------------+---------------+--------------
integer | integer | 3
*/
get_op_opfamily_properties(opno, opfamily, isorderby,
&op_strategy,
&op_lefttype,
&op_righttype);
if (isorderby)
flags |= SK_ORDER_BY;
/*
* rightop is the constant or variable comparison value
*/
rightop = (Expr *) get_rightop(clause);
if (rightop && IsA(rightop, RelabelType))
rightop = ((RelabelType *) rightop)->arg;
Assert(rightop != NULL);
//常量
if (IsA(rightop, Const))
{
/* OK, simple constant comparison value */
//scanvalue=100
scanvalue = ((Const *) rightop)->constvalue;
if (((Const *) rightop)->constisnull)
flags |= SK_ISNULL;
}
else
{
/* Need to treat this one as a runtime key */
if (n_runtime_keys >= max_runtime_keys)
{
if (max_runtime_keys == 0)
{
max_runtime_keys = 8;
runtime_keys = (IndexRuntimeKeyInfo *)
palloc(max_runtime_keys * sizeof(IndexRuntimeKeyInfo));
}
else
{
max_runtime_keys *= 2;
runtime_keys = (IndexRuntimeKeyInfo *)
repalloc(runtime_keys, max_runtime_keys * sizeof(IndexRuntimeKeyInfo));
}
}
runtime_keys[n_runtime_keys].scan_key = this_scan_key;
runtime_keys[n_runtime_keys].key_expr =
ExecInitExpr(rightop, planstate);
runtime_keys[n_runtime_keys].key_toastable =
TypeIsToastable(op_righttype);
n_runtime_keys++;
scanvalue = (Datum) 0;
}
/*
* initialize the scan key's fields appropriately
*/
ScanKeyEntryInitialize(this_scan_key,
flags,
varattno, /* attribute number to scan */
op_strategy, /* op's strategy */
op_righttype, /* strategy subtype */
((OpExpr *) clause)->inputcollid, /* collation */
opfuncid, /* reg proc to use */
scanvalue); /* constant */
}
else if (IsA(clause, RowCompareExpr))
{
/* (indexkey, indexkey, ...) op (expression, expression, ...) */
RowCompareExpr *rc = (RowCompareExpr *) clause;
ScanKey first_sub_key;
int n_sub_key;
ListCell *largs_cell;
ListCell *rargs_cell;
ListCell *opnos_cell;
ListCell *collids_cell;
Assert(!isorderby);
first_sub_key = (ScanKey)
palloc(list_length(rc->opnos) * sizeof(ScanKeyData));
n_sub_key = 0;
/* Scan RowCompare columns and generate subsidiary ScanKey items */
forfour(largs_cell, rc->largs, rargs_cell, rc->rargs,
opnos_cell, rc->opnos, collids_cell, rc->inputcollids)
{
ScanKey this_sub_key = &first_sub_key[n_sub_key];
int flags = SK_ROW_MEMBER;
Datum scanvalue;
Oid inputcollation;
leftop = (Expr *) lfirst(largs_cell);
rightop = (Expr *) lfirst(rargs_cell);
opno = lfirst_oid(opnos_cell);
inputcollation = lfirst_oid(collids_cell);
/*
* leftop should be the index key Var, possibly relabeled
*/
if (leftop && IsA(leftop, RelabelType))
leftop = ((RelabelType *) leftop)->arg;
Assert(leftop != NULL);
if (!(IsA(leftop, Var) &&
((Var *) leftop)->varno == INDEX_VAR))
elog(ERROR, "indexqual doesn't have key on left side");
varattno = ((Var *) leftop)->varattno;
/*
* We have to look up the operator's associated btree support
* function
*/
if (index->rd_rel->relam != BTREE_AM_OID ||
varattno < 1 || varattno > indnkeyatts)
elog(ERROR, "bogus RowCompare index qualification");
opfamily = index->rd_opfamily[varattno - 1];
get_op_opfamily_properties(opno, opfamily, isorderby,
&op_strategy,
&op_lefttype,
&op_righttype);
if (op_strategy != rc->rctype)
elog(ERROR, "RowCompare index qualification contains wrong operator");
opfuncid = get_opfamily_proc(opfamily,
op_lefttype,
op_righttype,
BTORDER_PROC);
if (!RegProcedureIsValid(opfuncid))
elog(ERROR, "missing support function %d(%u,%u) in opfamily %u",
BTORDER_PROC, op_lefttype, op_righttype, opfamily);
/*
* rightop is the constant or variable comparison value
*/
if (rightop && IsA(rightop, RelabelType))
rightop = ((RelabelType *) rightop)->arg;
Assert(rightop != NULL);
if (IsA(rightop, Const))
{
/* OK, simple constant comparison value */
scanvalue = ((Const *) rightop)->constvalue;
if (((Const *) rightop)->constisnull)
flags |= SK_ISNULL;
}
else
{
/* Need to treat this one as a runtime key */
if (n_runtime_keys >= max_runtime_keys)
{
if (max_runtime_keys == 0)
{
max_runtime_keys = 8;
runtime_keys = (IndexRuntimeKeyInfo *)
palloc(max_runtime_keys * sizeof(IndexRuntimeKeyInfo));
}
else
{
max_runtime_keys *= 2;
runtime_keys = (IndexRuntimeKeyInfo *)
repalloc(runtime_keys, max_runtime_keys * sizeof(IndexRuntimeKeyInfo));
}
}
runtime_keys[n_runtime_keys].scan_key = this_sub_key;
runtime_keys[n_runtime_keys].key_expr =
ExecInitExpr(rightop, planstate);
runtime_keys[n_runtime_keys].key_toastable =
TypeIsToastable(op_righttype);
n_runtime_keys++;
scanvalue = (Datum) 0;
}
/*
* initialize the subsidiary scan key's fields appropriately
*/
ScanKeyEntryInitialize(this_sub_key,
flags,
varattno, /* attribute number */
op_strategy, /* op's strategy */
op_righttype, /* strategy subtype */
inputcollation, /* collation */
opfuncid, /* reg proc to use */
scanvalue); /* constant */
n_sub_key++;
}
/* Mark the last subsidiary scankey correctly */
first_sub_key[n_sub_key - 1].sk_flags |= SK_ROW_END;
/*
* We don't use ScanKeyEntryInitialize for the header because it
* isn't going to contain a valid sk_func pointer.
*/
MemSet(this_scan_key, 0, sizeof(ScanKeyData));
this_scan_key->sk_flags = SK_ROW_HEADER;
this_scan_key->sk_attno = first_sub_key->sk_attno;
this_scan_key->sk_strategy = rc->rctype;
/* sk_subtype, sk_collation, sk_func not used in a header */
this_scan_key->sk_argument = PointerGetDatum(first_sub_key);
}
else if (IsA(clause, ScalarArrayOpExpr))
{
/* indexkey op ANY (array-expression) */
/*
explain select length(st_asewkt(shape)) from poly100w where globalid in ('{04C70C50-5FBD-4EC9-AFC7-8A405CD0548B}','{8D5FB77F-2EB0-4806-B584-84ACD9166EDC}','{DD792D3C-372C-45E6-B28E-264E5F79D8EB}');
QUERY PLAN
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Index Scan using uuid_13 on poly100w (cost=0.43..27.22 rows=3 width=4)
Index Cond: ((globalid)::text = ANY ('{"{04C70C50-5FBD-4EC9-AFC7-8A405CD0548B}","{8D5FB77F-2EB0-4806-B584-84ACD9166EDC}","{DD792D3C-372C-45E6-B28E-264E5F79D8EB}"}'::text[]))
优化器把in 操作子优化成 ANY test[]操作符了,在PG中用ScalarArrayOpExpr表示
*/
/*
saop={xpr = {type = T_ScalarArrayOpExpr}, opno = 98, opfuncid = 67, hashfuncid = 0, negfuncid = 0, useOr = true, inputcollid = 100, args = 0x25e5850, location = 61}
*/
ScalarArrayOpExpr *saop = (ScalarArrayOpExpr *) clause;
int flags = 0;
Datum scanvalue;
Assert(!isorderby);
Assert(saop->useOr);
/*98::regoper=pg_catalog.=*/
opno = saop->opno;
//67::regprocedure=texteq(text,text)
opfuncid = saop->opfuncid;
/*
* leftop should be the index key Var, possibly relabeled
*/
/*leftop={xpr = {type = T_Var}, varno = -3, varattno = 1, vartype = 1043, vartypmod = 42, varcollid = 100, varnullingrels = 0x0, varlevelsup = 0, varnosyn = 1, varattnosyn = 2,
location = 52}*/
leftop = (Expr *) linitial(saop->args);
if (leftop && IsA(leftop, RelabelType))
leftop = ((RelabelType *) leftop)->arg;
Assert(leftop != NULL);
if (!(IsA(leftop, Var) &&
((Var *) leftop)->varno == INDEX_VAR))
elog(ERROR, "indexqual doesn't have key on left side");
varattno = ((Var *) leftop)->varattno;
if (varattno < 1 || varattno > indnkeyatts)
elog(ERROR, "bogus index qualification");
/*
* We have to look up the operator's strategy number. This
* provides a cross-check that the operator does match the index.
*/
//opfamily=1994
opfamily = index->rd_opfamily[varattno - 1];
//op_strategy=3 lefttype=25 righttype=25
get_op_opfamily_properties(opno, opfamily, isorderby,
&op_strategy,
&op_lefttype,
&op_righttype);
/*
* rightop is the constant or variable array value
*/
/*
rightop={xpr = {type = T_Const}, consttype = 1009, consttypmod = -1, constcollid = 100, constlen = -1, constvalue = 38965072, constisnull = false, constbyval = false,
location = -1}
*/
rightop = (Expr *) lsecond(saop->args);
if (rightop && IsA(rightop, RelabelType))
rightop = ((RelabelType *) rightop)->arg;
Assert(rightop != NULL);
//查看索引是否支持多值查询
if (index->rd_indam->amsearcharray)
{
/* Index AM will handle this like a simple operator */
/*后续执行过程要用到这个falgs,以确定rightop-->constvalue为text[]数组类型,并在_bt_first-->_bt_preprocess_keys会被展开
会
*/
flags |= SK_SEARCHARRAY;
if (IsA(rightop, Const))
{
/* OK, simple constant comparison value */
scanvalue = ((Const *) rightop)->constvalue;
if (((Const *) rightop)->constisnull)
flags |= SK_ISNULL;
}
else
{
/* Need to treat this one as a runtime key */
if (n_runtime_keys >= max_runtime_keys)
{
if (max_runtime_keys == 0)
{
max_runtime_keys = 8;
runtime_keys = (IndexRuntimeKeyInfo *)
palloc(max_runtime_keys * sizeof(IndexRuntimeKeyInfo));
}
else
{
max_runtime_keys *= 2;
runtime_keys = (IndexRuntimeKeyInfo *)
repalloc(runtime_keys, max_runtime_keys * sizeof(IndexRuntimeKeyInfo));
}
}
runtime_keys[n_runtime_keys].scan_key = this_scan_key;
runtime_keys[n_runtime_keys].key_expr =
ExecInitExpr(rightop, planstate);
/*
* Careful here: the runtime expression is not of
* op_righttype, but rather is an array of same; so
* TypeIsToastable() isn't helpful. However, we can
* assume that all array types are toastable.
*/
runtime_keys[n_runtime_keys].key_toastable = true;
n_runtime_keys++;
scanvalue = (Datum) 0;
}
}
else
{
/* Executor has to expand the array value */
array_keys[n_array_keys].scan_key = this_scan_key;
array_keys[n_array_keys].array_expr =
ExecInitExpr(rightop, planstate);
/* the remaining fields were zeroed by palloc0 */
n_array_keys++;
scanvalue = (Datum) 0;
}
/*
* initialize the scan key's fields appropriately
*/
/*具体看到代码2*/
ScanKeyEntryInitialize(this_scan_key,
flags,
varattno, /* attribute number to scan */
op_strategy, /* op's strategy */
op_righttype, /* strategy subtype */
saop->inputcollid, /* collation */
opfuncid, /* reg proc to use */
scanvalue); /* constant */
}
else if (IsA(clause, NullTest))
{
/* indexkey IS NULL or indexkey IS NOT NULL */
NullTest *ntest = (NullTest *) clause;
int flags;
Assert(!isorderby);
/*
* argument should be the index key Var, possibly relabeled
*/
leftop = ntest->arg;
if (leftop && IsA(leftop, RelabelType))
leftop = ((RelabelType *) leftop)->arg;
Assert(leftop != NULL);
if (!(IsA(leftop, Var) &&
((Var *) leftop)->varno == INDEX_VAR))
elog(ERROR, "NullTest indexqual has wrong key");
varattno = ((Var *) leftop)->varattno;
/*
* initialize the scan key's fields appropriately
*/
switch (ntest->nulltesttype)
{
case IS_NULL:
flags = SK_ISNULL | SK_SEARCHNULL;
break;
case IS_NOT_NULL:
flags = SK_ISNULL | SK_SEARCHNOTNULL;
break;
default:
elog(ERROR, "unrecognized nulltesttype: %d",
(int) ntest->nulltesttype);
flags = 0; /* keep compiler quiet */
break;
}
ScanKeyEntryInitialize(this_scan_key,
flags,
varattno, /* attribute number to scan */
InvalidStrategy, /* no strategy */
InvalidOid, /* no strategy subtype */
InvalidOid, /* no collation */
InvalidOid, /* no reg proc for this */
(Datum) 0); /* constant */
}
else
elog(ERROR, "unsupported indexqual type: %d",
(int) nodeTag(clause));
}
Assert(n_runtime_keys <= max_runtime_keys);
/* Get rid of any unused arrays */
if (n_array_keys == 0)
{
pfree(array_keys);
array_keys = NULL;
}
/*
* Return info to our caller.
*/
*scanKeys = scan_keys;
*numScanKeys = n_scan_keys;
*runtimeKeys = runtime_keys;
*numRuntimeKeys = n_runtime_keys;
if (arrayKeys)
{
*arrayKeys = array_keys;
*numArrayKeys = n_array_keys;
}
else if (n_array_keys != 0)
elog(ERROR, "ScalarArrayOpExpr index qual found where not allowed");
}
代码2
/*
* ScanKeyEntryInitialize
* Initializes a scan key entry given all the field values.
* The target procedure is specified by OID (but can be invalid
* if SK_SEARCHNULL or SK_SEARCHNOTNULL is set).
*
* Note: CurrentMemoryContext at call should be as long-lived as the ScanKey
* itself, because that's what will be used for any subsidiary info attached
* to the ScanKey's FmgrInfo record.
*/
void
ScanKeyEntryInitialize(ScanKey entry,
int flags,
AttrNumber attributeNumber,
StrategyNumber strategy,
Oid subtype,
Oid collation,
RegProcedure procedure,
Datum argument)
{
entry->sk_flags = flags;
entry->sk_attno = attributeNumber;
entry->sk_strategy = strategy;
//过滤字段类型
entry->sk_subtype = subtype;
entry->sk_collation = collation;
//Const参数,可以是数组
entry->sk_argument = argument;
if (RegProcedureIsValid(procedure))
{
//初始化函数
//根据函数的oid,填充函数结构体,具体看代码3
fmgr_info(procedure, &entry->sk_func);
}
else
{
Assert(flags & (SK_SEARCHNULL | SK_SEARCHNOTNULL));
MemSet(&entry->sk_func, 0, sizeof(entry->sk_func));
}
}
代码3
/*
* This routine fills a FmgrInfo struct, given the OID
* of the function to be called.
*
* The caller's CurrentMemoryContext is used as the fn_mcxt of the info
* struct; this means that any subsidiary data attached to the info struct
* (either by fmgr_info itself, or later on by a function call handler)
* will be allocated in that context. The caller must ensure that this
* context is at least as long-lived as the info struct itself. This is
* not a problem in typical cases where the info struct is on the stack or
* in freshly-palloc'd space. However, if one intends to store an info
* struct in a long-lived table, it's better to use fmgr_info_cxt.
*/
void
fmgr_info(Oid functionId, FmgrInfo *finfo)
{
fmgr_info_cxt_security(functionId, finfo, CurrentMemoryContext, false);
}
/*
* This one does the actual work. ignore_security is ordinarily false
* but is set to true when we need to avoid recursion.
*/
static void
fmgr_info_cxt_security(Oid functionId, FmgrInfo *finfo, MemoryContext mcxt,
bool ignore_security)
{
const FmgrBuiltin *fbp;
HeapTuple procedureTuple;
Form_pg_proc procedureStruct;
Datum prosrcdatum;
char *prosrc;
/*
* fn_oid *must* be filled in last. Some code assumes that if fn_oid is
* valid, the whole struct is valid. Some FmgrInfo struct's do survive
* elogs.
*/
finfo->fn_oid = InvalidOid;
finfo->fn_extra = NULL;
finfo->fn_mcxt = mcxt;
finfo->fn_expr = NULL; /* caller may set this later */
//是否内置函数
if ((fbp = fmgr_isbuiltin(functionId)) != NULL)
{
/*
* Fast path for builtin functions: don't bother consulting pg_proc
*/
finfo->fn_nargs = fbp->nargs;
finfo->fn_strict = fbp->strict;
finfo->fn_retset = fbp->retset;
finfo->fn_stats = TRACK_FUNC_ALL; /* ie, never track */
finfo->fn_addr = fbp->func;
finfo->fn_oid = functionId;
return;
}
/* Otherwise we need the pg_proc entry */
/*不是内置,从pg_proc中查询*/
procedureTuple = SearchSysCache1(PROCOID, ObjectIdGetDatum(functionId));
if (!HeapTupleIsValid(procedureTuple))
elog(ERROR, "cache lookup failed for function %u", functionId);
procedureStruct = (Form_pg_proc) GETSTRUCT(procedureTuple);
finfo->fn_nargs = procedureStruct->pronargs;
finfo->fn_strict = procedureStruct->proisstrict;
finfo->fn_retset = procedureStruct->proretset;
/*
* If it has prosecdef set, non-null proconfig, or if a plugin wants to
* hook function entry/exit, use fmgr_security_definer call handler ---
* unless we are being called again by fmgr_security_definer or
* fmgr_info_other_lang.
*
* When using fmgr_security_definer, function stats tracking is always
* disabled at the outer level, and instead we set the flag properly in
* fmgr_security_definer's private flinfo and implement the tracking
* inside fmgr_security_definer. This loses the ability to charge the
* overhead of fmgr_security_definer to the function, but gains the
* ability to set the track_functions GUC as a local GUC parameter of an
* interesting function and have the right things happen.
*/
if (!ignore_security &&
(procedureStruct->prosecdef ||
!heap_attisnull(procedureTuple, Anum_pg_proc_proconfig, NULL) ||
FmgrHookIsNeeded(functionId)))
{
finfo->fn_addr = fmgr_security_definer;
finfo->fn_stats = TRACK_FUNC_ALL; /* ie, never track */
finfo->fn_oid = functionId;
ReleaseSysCache(procedureTuple);
return;
}
switch (procedureStruct->prolang)
{
case INTERNALlanguageId:
/*
* For an ordinary builtin function, we should never get here
* because the fmgr_isbuiltin() search above will have succeeded.
* However, if the user has done a CREATE FUNCTION to create an
* alias for a builtin function, we can end up here. In that case
* we have to look up the function by name. The name of the
* internal function is stored in prosrc (it doesn't have to be
* the same as the name of the alias!)
*/
prosrcdatum = SysCacheGetAttrNotNull(PROCOID, procedureTuple,
Anum_pg_proc_prosrc);
prosrc = TextDatumGetCString(prosrcdatum);
fbp = fmgr_lookupByName(prosrc);
if (fbp == NULL)
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_FUNCTION),
errmsg("internal function \"%s\" is not in internal lookup table",
prosrc)));
pfree(prosrc);
/* Should we check that nargs, strict, retset match the table? */
finfo->fn_addr = fbp->func;
/* note this policy is also assumed in fast path above */
finfo->fn_stats = TRACK_FUNC_ALL; /* ie, never track */
break;
/*自定义c函数*/
case ClanguageId:
fmgr_info_C_lang(functionId, finfo, procedureTuple);
finfo->fn_stats = TRACK_FUNC_PL; /* ie, track if ALL */
break;
case SQLlanguageId:
finfo->fn_addr = fmgr_sql;
finfo->fn_stats = TRACK_FUNC_PL; /* ie, track if ALL */
break;
/*其它语言*/
default:
fmgr_info_other_lang(functionId, finfo, procedureTuple);
finfo->fn_stats = TRACK_FUNC_OFF; /* ie, track if not OFF */
break;
}
finfo->fn_oid = functionId;
ReleaseSysCache(procedureTuple);
}