diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c index 9e213edeae..9e5002e37f 100644 --- a/src/backend/catalog/namespace.c +++ b/src/backend/catalog/namespace.c @@ -13,7 +13,7 @@ * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/catalog/namespace.c,v 1.18 2002/05/05 00:03:28 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/catalog/namespace.c,v 1.19 2002/05/12 20:10:01 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -492,7 +492,8 @@ FuncnameGetCandidates(List *names, int nargs) elog(ERROR, "Cross-database references are not implemented"); break; default: - elog(ERROR, "Improper qualified name (too many dotted names)"); + elog(ERROR, "Improper qualified name (too many dotted names): %s", + NameListToString(names)); break; } @@ -746,7 +747,8 @@ OpernameGetCandidates(List *names, char oprkind) elog(ERROR, "Cross-database references are not implemented"); break; default: - elog(ERROR, "Improper qualified name (too many dotted names)"); + elog(ERROR, "Improper qualified name (too many dotted names): %s", + NameListToString(names)); break; } @@ -1199,7 +1201,8 @@ QualifiedNameGetCreationNamespace(List *names, char **objname_p) elog(ERROR, "Cross-database references are not implemented"); break; default: - elog(ERROR, "Improper qualified name (too many dotted names)"); + elog(ERROR, "Improper qualified name (too many dotted names): %s", + NameListToString(names)); break; } diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c index 03f494d63f..cb2b2d0f23 100644 --- a/src/backend/commands/explain.c +++ b/src/backend/commands/explain.c @@ -5,7 +5,7 @@ * Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group * Portions Copyright (c) 1994-5, Regents of the University of California * - * $Header: /cvsroot/pgsql/src/backend/commands/explain.c,v 1.76 2002/05/03 15:56:45 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/commands/explain.c,v 1.77 2002/05/12 20:10:02 tgl Exp $ * */ @@ -262,6 +262,9 @@ explain_outNode(StringInfo str, Plan *plan, Plan *outer_plan, case T_SubqueryScan: pname = "Subquery Scan"; break; + case T_FunctionScan: + pname = "Function Scan"; + break; case T_Material: pname = "Materialize"; break; @@ -336,7 +339,7 @@ explain_outNode(StringInfo str, Plan *plan, Plan *outer_plan, char *relname; /* Assume it's on a real relation */ - Assert(rte->relid); + Assert(rte->rtekind == RTE_RELATION); /* We only show the rel name, not schema name */ relname = get_rel_name(rte->relid); @@ -358,6 +361,33 @@ explain_outNode(StringInfo str, Plan *plan, Plan *outer_plan, quote_identifier(rte->eref->aliasname)); } break; + case T_FunctionScan: + if (((Scan *) plan)->scanrelid > 0) + { + RangeTblEntry *rte = rt_fetch(((Scan *) plan)->scanrelid, + es->rtable); + Expr *expr; + Func *funcnode; + Oid funcid; + char *proname; + + /* Assert it's on a RangeFunction */ + Assert(rte->rtekind == RTE_FUNCTION); + + expr = (Expr *) rte->funcexpr; + funcnode = (Func *) expr->oper; + funcid = funcnode->funcid; + + /* We only show the func name, not schema name */ + proname = get_func_name(funcid); + + appendStringInfo(str, " on %s", + quote_identifier(proname)); + if (strcmp(rte->eref->aliasname, proname) != 0) + appendStringInfo(str, " %s", + quote_identifier(rte->eref->aliasname)); + } + break; default: break; } @@ -397,6 +427,7 @@ explain_outNode(StringInfo str, Plan *plan, Plan *outer_plan, break; case T_SeqScan: case T_TidScan: + case T_FunctionScan: show_scan_qual(plan->qual, false, "Filter", ((Scan *) plan)->scanrelid, @@ -545,7 +576,7 @@ explain_outNode(StringInfo str, Plan *plan, Plan *outer_plan, es->rtable); List *saved_rtable = es->rtable; - Assert(rte->subquery != NULL); + Assert(rte->rtekind == RTE_SUBQUERY); es->rtable = rte->subquery->rtable; for (i = 0; i < indent; i++) @@ -623,11 +654,7 @@ show_scan_qual(List *qual, bool is_or_qual, const char *qlabel, /* Generate deparse context */ Assert(scanrelid > 0 && scanrelid <= length(es->rtable)); rte = rt_fetch(scanrelid, es->rtable); - - /* Assume it's on a real relation */ - Assert(rte->relid); - scancontext = deparse_context_for_relation(rte->eref->aliasname, - rte->relid); + scancontext = deparse_context_for_rte(rte); /* * If we have an outer plan that is referenced by the qual, add it to diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c index df6f81cd1d..e2ae7f0631 100644 --- a/src/backend/commands/indexcmds.c +++ b/src/backend/commands/indexcmds.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/commands/indexcmds.c,v 1.72 2002/04/27 03:45:01 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/commands/indexcmds.c,v 1.73 2002/05/12 20:10:02 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -452,7 +452,8 @@ GetAttrOpClass(IndexElem *attribute, Oid attrType, elog(ERROR, "Cross-database references are not implemented"); break; default: - elog(ERROR, "Improper opclass name (too many dotted names)"); + elog(ERROR, "Improper opclass name (too many dotted names): %s", + NameListToString(attribute->opclass)); break; } diff --git a/src/backend/executor/Makefile b/src/backend/executor/Makefile index 363ea342e9..0a66e1be03 100644 --- a/src/backend/executor/Makefile +++ b/src/backend/executor/Makefile @@ -4,7 +4,7 @@ # Makefile for executor # # IDENTIFICATION -# $Header: /cvsroot/pgsql/src/backend/executor/Makefile,v 1.17 2001/09/18 01:59:06 tgl Exp $ +# $Header: /cvsroot/pgsql/src/backend/executor/Makefile,v 1.18 2002/05/12 20:10:02 tgl Exp $ # #------------------------------------------------------------------------- @@ -16,9 +16,9 @@ OBJS = execAmi.o execFlatten.o execJunk.o execMain.o \ execProcnode.o execQual.o execScan.o execTuples.o \ execUtils.o functions.o instrument.o nodeAppend.o nodeAgg.o nodeHash.o \ nodeHashjoin.o nodeIndexscan.o nodeMaterial.o nodeMergejoin.o \ - nodeNestloop.o nodeResult.o nodeSeqscan.o nodeSetOp.o nodeSort.o \ - nodeUnique.o nodeLimit.o nodeGroup.o nodeSubplan.o \ - nodeSubqueryscan.o nodeTidscan.o spi.o + nodeNestloop.o nodeFunctionscan.o nodeResult.o nodeSeqscan.o \ + nodeSetOp.o nodeSort.o nodeUnique.o nodeLimit.o nodeGroup.o \ + nodeSubplan.o nodeSubqueryscan.o nodeTidscan.o spi.o all: SUBSYS.o diff --git a/src/backend/executor/execAmi.c b/src/backend/executor/execAmi.c index 119c89b1c2..d2fe9da9ad 100644 --- a/src/backend/executor/execAmi.c +++ b/src/backend/executor/execAmi.c @@ -6,7 +6,7 @@ * Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $Id: execAmi.c,v 1.62 2002/03/02 21:39:24 momjian Exp $ + * $Id: execAmi.c,v 1.63 2002/05/12 20:10:02 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -35,6 +35,7 @@ #include "executor/nodeSort.h" #include "executor/nodeSubplan.h" #include "executor/nodeSubqueryscan.h" +#include "executor/nodeFunctionscan.h" #include "executor/nodeUnique.h" @@ -100,6 +101,10 @@ ExecReScan(Plan *node, ExprContext *exprCtxt, Plan *parent) ExecSubqueryReScan((SubqueryScan *) node, exprCtxt, parent); break; + case T_FunctionScan: + ExecFunctionReScan((FunctionScan *) node, exprCtxt, parent); + break; + case T_Material: ExecMaterialReScan((Material *) node, exprCtxt, parent); break; @@ -187,6 +192,10 @@ ExecMarkPos(Plan *node) ExecIndexMarkPos((IndexScan *) node); break; + case T_FunctionScan: + ExecFunctionMarkPos((FunctionScan *) node); + break; + case T_Material: ExecMaterialMarkPos((Material *) node); break; @@ -229,6 +238,10 @@ ExecRestrPos(Plan *node) ExecIndexRestrPos((IndexScan *) node); break; + case T_FunctionScan: + ExecFunctionRestrPos((FunctionScan *) node); + break; + case T_Material: ExecMaterialRestrPos((Material *) node); break; diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c index 68fcb325a6..a2c43bc035 100644 --- a/src/backend/executor/execMain.c +++ b/src/backend/executor/execMain.c @@ -27,7 +27,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/executor/execMain.c,v 1.160 2002/04/27 21:24:34 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/executor/execMain.c,v 1.161 2002/05/12 20:10:02 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -311,7 +311,7 @@ ExecCheckPlanPerms(Plan *plan, List *rangeTable, CmdType operation) /* Recursively check the subquery */ rte = rt_fetch(scan->scan.scanrelid, rangeTable); - Assert(rte->subquery != NULL); + Assert(rte->rtekind == RTE_SUBQUERY); ExecCheckQueryPerms(operation, rte->subquery, scan->subplan); break; } @@ -362,10 +362,12 @@ ExecCheckRTEPerms(RangeTblEntry *rte, CmdType operation) Oid userid; AclResult aclcheck_result; - /* - * If it's a subquery RTE, ignore it --- it will be checked when - * ExecCheckPlanPerms finds the SubqueryScan node for it. - */ + /* + * Only plain-relation RTEs need to be checked here. Subquery RTEs + * will be checked when ExecCheckPlanPerms finds the SubqueryScan node, + * and function RTEs are checked by init_fcache when the function is + * prepared for execution. Join and special RTEs need no checks. + */ if (rte->rtekind != RTE_RELATION) return; diff --git a/src/backend/executor/execProcnode.c b/src/backend/executor/execProcnode.c index 5cbd2ea562..c0f005f1e6 100644 --- a/src/backend/executor/execProcnode.c +++ b/src/backend/executor/execProcnode.c @@ -12,7 +12,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/executor/execProcnode.c,v 1.28 2001/10/25 05:49:27 momjian Exp $ + * $Header: /cvsroot/pgsql/src/backend/executor/execProcnode.c,v 1.29 2002/05/12 20:10:02 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -96,6 +96,7 @@ #include "executor/nodeSort.h" #include "executor/nodeSubplan.h" #include "executor/nodeSubqueryscan.h" +#include "executor/nodeFunctionscan.h" #include "executor/nodeUnique.h" #include "miscadmin.h" #include "tcop/tcopprot.h" @@ -168,6 +169,11 @@ ExecInitNode(Plan *node, EState *estate, Plan *parent) parent); break; + case T_FunctionScan: + result = ExecInitFunctionScan((FunctionScan *) node, estate, + parent); + break; + /* * join nodes */ @@ -297,6 +303,10 @@ ExecProcNode(Plan *node, Plan *parent) result = ExecSubqueryScan((SubqueryScan *) node); break; + case T_FunctionScan: + result = ExecFunctionScan((FunctionScan *) node); + break; + /* * join nodes */ @@ -392,6 +402,9 @@ ExecCountSlotsNode(Plan *node) case T_SubqueryScan: return ExecCountSlotsSubqueryScan((SubqueryScan *) node); + case T_FunctionScan: + return ExecCountSlotsFunctionScan((FunctionScan *) node); + /* * join nodes */ @@ -503,6 +516,10 @@ ExecEndNode(Plan *node, Plan *parent) ExecEndSubqueryScan((SubqueryScan *) node); break; + case T_FunctionScan: + ExecEndFunctionScan((FunctionScan *) node); + break; + /* * join nodes */ @@ -640,6 +657,14 @@ ExecGetTupType(Plan *node) } break; + case T_FunctionScan: + { + CommonScanState *scanstate = ((FunctionScan *) node)->scan.scanstate; + + slot = scanstate->cstate.cs_ResultTupleSlot; + } + break; + case T_Material: { MaterialState *matstate = ((Material *) node)->matstate; diff --git a/src/backend/executor/execQual.c b/src/backend/executor/execQual.c index 5cc1bbaa71..bebb664f29 100644 --- a/src/backend/executor/execQual.c +++ b/src/backend/executor/execQual.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/executor/execQual.c,v 1.91 2002/04/27 03:45:03 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/executor/execQual.c,v 1.92 2002/05/12 20:10:02 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -700,6 +700,7 @@ ExecMakeFunctionResult(FunctionCachePtr fcache, { fcinfo.resultinfo = (Node *) &rsinfo; rsinfo.type = T_ReturnSetInfo; + rsinfo.econtext = econtext; } /* diff --git a/src/backend/executor/execUtils.c b/src/backend/executor/execUtils.c index 9b03401e44..a6b5048326 100644 --- a/src/backend/executor/execUtils.c +++ b/src/backend/executor/execUtils.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/executor/execUtils.c,v 1.80 2002/04/12 20:38:26 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/executor/execUtils.c,v 1.81 2002/05/12 20:10:02 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -20,6 +20,9 @@ * ExecCloseIndices | referenced by InitPlan, EndPlan, * ExecInsertIndexTuples / ExecAppend, ExecReplace * + * RegisterExprContextCallback Register function shutdown callback + * UnregisterExprContextCallback Deregister function shutdown callback + * * NOTES * This file has traditionally been the place to stick misc. * executor support stuff that doesn't really go anyplace else. @@ -58,6 +61,9 @@ extern int NIndexTupleProcessed; /* have to be defined in the * access method level so that the * cinterface.a will link ok. */ + +static void ShutdownExprContext(ExprContext *econtext); + /* ---------------------------------------------------------------- * statistic functions * ---------------------------------------------------------------- @@ -120,8 +126,6 @@ DisplayTupleCount(FILE *statfp) /* ---------------------------------------------------------------- * miscellaneous node-init support functions - * - * ExecAssignExprContext - assigns the node's expression context * ---------------------------------------------------------------- */ @@ -160,6 +164,7 @@ ExecAssignExprContext(EState *estate, CommonState *commonstate) econtext->ecxt_param_list_info = estate->es_param_list_info; econtext->ecxt_aggvalues = NULL; econtext->ecxt_aggnulls = NULL; + econtext->ecxt_callbacks = NULL; commonstate->cs_ExprContext = econtext; } @@ -204,6 +209,7 @@ MakeExprContext(TupleTableSlot *slot, econtext->ecxt_param_list_info = NULL; econtext->ecxt_aggvalues = NULL; econtext->ecxt_aggnulls = NULL; + econtext->ecxt_callbacks = NULL; return econtext; } @@ -216,6 +222,9 @@ MakeExprContext(TupleTableSlot *slot, void FreeExprContext(ExprContext *econtext) { + /* Call any registered callbacks */ + ShutdownExprContext(econtext); + /* And clean up the memory used */ MemoryContextDelete(econtext->ecxt_per_tuple_memory); pfree(econtext); } @@ -369,6 +378,11 @@ ExecFreeExprContext(CommonState *commonstate) if (econtext == NULL) return; + /* + * clean up any registered callbacks + */ + ShutdownExprContext(econtext); + /* * clean up memory used. */ @@ -689,3 +703,85 @@ SetChangedParamList(Plan *node, List *newchg) node->chgParam = lappendi(node->chgParam, paramId); } } + +/* + * Register a shutdown callback in an ExprContext. + * + * Shutdown callbacks will be called (in reverse order of registration) + * when the ExprContext is deleted or rescanned. This provides a hook + * for functions called in the context to do any cleanup needed --- it's + * particularly useful for functions returning sets. Note that the + * callback will *not* be called in the event that execution is aborted + * by an error. + */ +void +RegisterExprContextCallback(ExprContext *econtext, + ExprContextCallbackFunction function, + Datum arg) +{ + ExprContext_CB *ecxt_callback; + + /* Save the info in appropriate memory context */ + ecxt_callback = (ExprContext_CB *) + MemoryContextAlloc(econtext->ecxt_per_query_memory, + sizeof(ExprContext_CB)); + + ecxt_callback->function = function; + ecxt_callback->arg = arg; + + /* link to front of list for appropriate execution order */ + ecxt_callback->next = econtext->ecxt_callbacks; + econtext->ecxt_callbacks = ecxt_callback; +} + +/* + * Deregister a shutdown callback in an ExprContext. + * + * Any list entries matching the function and arg will be removed. + * This can be used if it's no longer necessary to call the callback. + */ +void +UnregisterExprContextCallback(ExprContext *econtext, + ExprContextCallbackFunction function, + Datum arg) +{ + ExprContext_CB **prev_callback; + ExprContext_CB *ecxt_callback; + + prev_callback = &econtext->ecxt_callbacks; + + while ((ecxt_callback = *prev_callback) != NULL) + { + if (ecxt_callback->function == function && ecxt_callback->arg == arg) + { + *prev_callback = ecxt_callback->next; + pfree(ecxt_callback); + } + else + { + prev_callback = &ecxt_callback->next; + } + } +} + +/* + * Call all the shutdown callbacks registered in an ExprContext. + * + * The callback list is emptied (important in case this is only a rescan + * reset, and not deletion of the ExprContext). + */ +static void +ShutdownExprContext(ExprContext *econtext) +{ + ExprContext_CB *ecxt_callback; + + /* + * Call each callback function in reverse registration order. + */ + while ((ecxt_callback = econtext->ecxt_callbacks) != NULL) + { + econtext->ecxt_callbacks = ecxt_callback->next; + (*ecxt_callback->function) (ecxt_callback->arg); + pfree(ecxt_callback); + } +} diff --git a/src/backend/executor/functions.c b/src/backend/executor/functions.c index 885d93a2af..938f7e17f9 100644 --- a/src/backend/executor/functions.c +++ b/src/backend/executor/functions.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/executor/functions.c,v 1.49 2002/02/27 19:34:51 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/executor/functions.c,v 1.50 2002/05/12 20:10:02 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -28,7 +28,7 @@ /* - * We have an execution_state record for each query in the function. + * We have an execution_state record for each query in a function. */ typedef enum { @@ -56,6 +56,7 @@ typedef struct int typlen; /* length of the return type */ bool typbyval; /* true if return type is pass by value */ bool returnsTuple; /* true if return type is a tuple */ + bool shutdown_reg; /* true if registered shutdown callback */ TupleTableSlot *funcSlot; /* if one result we need to copy it before * we end execution of the function and @@ -79,6 +80,7 @@ static void postquel_sub_params(execution_state *es, FunctionCallInfo fcinfo); static Datum postquel_execute(execution_state *es, FunctionCallInfo fcinfo, SQLFunctionCachePtr fcache); +static void ShutdownSQLFunction(Datum arg); static execution_state * @@ -546,6 +548,15 @@ fmgr_sql(PG_FUNCTION_ARGS) elog(ERROR, "Set-valued function called in context that cannot accept a set"); fcinfo->isnull = true; result = (Datum) 0; + + /* Deregister shutdown callback, if we made one */ + if (fcache->shutdown_reg) + { + UnregisterExprContextCallback(rsi->econtext, + ShutdownSQLFunction, + PointerGetDatum(fcache)); + fcache->shutdown_reg = false; + } } MemoryContextSwitchTo(oldcontext); @@ -570,9 +581,45 @@ fmgr_sql(PG_FUNCTION_ARGS) rsi->isDone = ExprMultipleResult; else elog(ERROR, "Set-valued function called in context that cannot accept a set"); + + /* + * Ensure we will get shut down cleanly if the exprcontext is + * not run to completion. + */ + if (!fcache->shutdown_reg) + { + RegisterExprContextCallback(rsi->econtext, + ShutdownSQLFunction, + PointerGetDatum(fcache)); + fcache->shutdown_reg = true; + } } MemoryContextSwitchTo(oldcontext); return result; } + +/* + * callback function in case a function-returning-set needs to be shut down + * before it has been run to completion + */ +static void +ShutdownSQLFunction(Datum arg) +{ + SQLFunctionCachePtr fcache = (SQLFunctionCachePtr) DatumGetPointer(arg); + execution_state *es = fcache->func_state; + + while (es != NULL) + { + /* Shut down anything still running */ + if (es->status == F_EXEC_RUN) + postquel_end(es); + /* Reset states to START in case we're called again */ + es->status = F_EXEC_START; + es = es->next; + } + + /* execUtils will deregister the callback... */ + fcache->shutdown_reg = false; +} diff --git a/src/backend/executor/nodeFunctionscan.c b/src/backend/executor/nodeFunctionscan.c new file mode 100644 index 0000000000..7b6d466a81 --- /dev/null +++ b/src/backend/executor/nodeFunctionscan.c @@ -0,0 +1,469 @@ +/*------------------------------------------------------------------------- + * + * nodeFunctionscan.c + * Support routines for scanning RangeFunctions (functions in rangetable). + * + * Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * $Header: /cvsroot/pgsql/src/backend/executor/nodeFunctionscan.c,v 1.1 2002/05/12 20:10:02 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 "miscadmin.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 "storage/lmgr.h" +#include "tcop/pquery.h" +#include "utils/lsyscache.h" +#include "utils/syscache.h" +#include "utils/tuplestore.h" + +static TupleTableSlot *FunctionNext(FunctionScan *node); +static TupleTableSlot *function_getonetuple(TupleTableSlot *slot, + Node *expr, + ExprContext *econtext, + TupleDesc tupdesc, + bool returnsTuple, + bool *isNull, + ExprDoneCond *isDone); +static FunctionMode get_functionmode(Node *expr); + +/* ---------------------------------------------------------------- + * Scan Support + * ---------------------------------------------------------------- + */ +/* ---------------------------------------------------------------- + * FunctionNext + * + * This is a workhorse for ExecFunctionScan + * ---------------------------------------------------------------- + */ +static TupleTableSlot * +FunctionNext(FunctionScan *node) +{ + TupleTableSlot *slot; + Node *expr; + ExprContext *econtext; + TupleDesc tupdesc; + EState *estate; + ScanDirection direction; + Tuplestorestate *tuplestorestate; + FunctionScanState *scanstate; + bool should_free; + HeapTuple heapTuple; + + /* + * get information from the estate and scan state + */ + scanstate = (FunctionScanState *) node->scan.scanstate; + estate = node->scan.plan.state; + direction = estate->es_direction; + econtext = scanstate->csstate.cstate.cs_ExprContext; + + tuplestorestate = scanstate->tuplestorestate; + tupdesc = scanstate->tupdesc; + expr = scanstate->funcexpr; + + /* + * If first time through, read all tuples from function and pass them to + * tuplestore.c. Subsequent calls just fetch tuples from tuplestore. + */ + if (tuplestorestate == NULL) + { + /* + * Initialize tuplestore module. + */ + tuplestorestate = tuplestore_begin_heap(true, /* randomAccess */ + SortMem); + + scanstate->tuplestorestate = (void *) tuplestorestate; + + /* + * Compute all the function tuples and pass to tuplestore. + */ + for (;;) + { + bool isNull; + ExprDoneCond isDone; + + isNull = false; + isDone = ExprSingleResult; + slot = function_getonetuple(scanstate->csstate.css_ScanTupleSlot, + expr, econtext, tupdesc, + scanstate->returnsTuple, + &isNull, &isDone); + if (TupIsNull(slot)) + break; + + tuplestore_puttuple(tuplestorestate, (void *) slot->val); + ExecClearTuple(slot); + + if (isDone != ExprMultipleResult) + break; + } + + /* + * Complete the store. + */ + tuplestore_donestoring(tuplestorestate); + } + + /* + * Get the next tuple from tuplestore. Return NULL if no more tuples. + */ + slot = scanstate->csstate.css_ScanTupleSlot; + heapTuple = tuplestore_getheaptuple(tuplestorestate, + ScanDirectionIsForward(direction), + &should_free); + + return ExecStoreTuple(heapTuple, slot, InvalidBuffer, should_free); +} + +/* ---------------------------------------------------------------- + * ExecFunctionScan(node) + * + * Scans the Function sequentially and returns the next qualifying + * tuple. + * It calls the ExecScan() routine and passes it the access method + * which retrieve tuples sequentially. + * + */ + +TupleTableSlot * +ExecFunctionScan(FunctionScan *node) +{ + /* + * use FunctionNext as access method + */ + return ExecScan(&node->scan, (ExecScanAccessMtd) FunctionNext); +} + +/* ---------------------------------------------------------------- + * ExecInitFunctionScan + * ---------------------------------------------------------------- + */ +bool +ExecInitFunctionScan(FunctionScan *node, EState *estate, Plan *parent) +{ + FunctionScanState *scanstate; + RangeTblEntry *rte; + Oid funcrettype; + Oid funcrelid; + TupleDesc tupdesc; + + /* + * FunctionScan should not have any children. + */ + Assert(outerPlan((Plan *) node) == NULL); + Assert(innerPlan((Plan *) node) == NULL); + + /* + * assign the node's execution state + */ + node->scan.plan.state = estate; + + /* + * create new ScanState for node + */ + scanstate = makeNode(FunctionScanState); + node->scan.scanstate = &scanstate->csstate; + + /* + * Miscellaneous initialization + * + * create expression context for node + */ + ExecAssignExprContext(estate, &scanstate->csstate.cstate); + +#define FUNCTIONSCAN_NSLOTS 2 + + /* + * tuple table initialization + */ + ExecInitResultTupleSlot(estate, &scanstate->csstate.cstate); + ExecInitScanTupleSlot(estate, &scanstate->csstate); + + /* + * get info about function + */ + rte = rt_fetch(node->scan.scanrelid, estate->es_range_table); + Assert(rte->rtekind == RTE_FUNCTION); + funcrettype = exprType(rte->funcexpr); + funcrelid = typeidTypeRelid(funcrettype); + + /* + * Build a suitable tupledesc representing the output rows + */ + if (OidIsValid(funcrelid)) + { + /* + * Composite data type, i.e. a table's row type + * Same as ordinary relation RTE + */ + Relation rel; + + rel = relation_open(funcrelid, AccessShareLock); + tupdesc = CreateTupleDescCopy(RelationGetDescr(rel)); + relation_close(rel, AccessShareLock); + scanstate->returnsTuple = true; + } + else + { + /* + * Must be a base data type, i.e. scalar + */ + char *attname = strVal(lfirst(rte->eref->colnames)); + + tupdesc = CreateTemplateTupleDesc(1); + TupleDescInitEntry(tupdesc, + (AttrNumber) 1, + attname, + funcrettype, + -1, + 0, + false); + scanstate->returnsTuple = false; + } + scanstate->tupdesc = tupdesc; + ExecSetSlotDescriptor(scanstate->csstate.css_ScanTupleSlot, + tupdesc, false); + + /* + * Other node-specific setup + */ + scanstate->tuplestorestate = NULL; + scanstate->funcexpr = rte->funcexpr; + + scanstate->functionmode = get_functionmode(rte->funcexpr); + + scanstate->csstate.cstate.cs_TupFromTlist = false; + + /* + * initialize tuple type + */ + ExecAssignResultTypeFromTL((Plan *) node, &scanstate->csstate.cstate); + ExecAssignProjectionInfo((Plan *) node, &scanstate->csstate.cstate); + + return TRUE; +} + +int +ExecCountSlotsFunctionScan(FunctionScan *node) +{ + return ExecCountSlotsNode(outerPlan(node)) + + ExecCountSlotsNode(innerPlan(node)) + + FUNCTIONSCAN_NSLOTS; +} + +/* ---------------------------------------------------------------- + * ExecEndFunctionScan + * + * frees any storage allocated through C routines. + * ---------------------------------------------------------------- + */ +void +ExecEndFunctionScan(FunctionScan *node) +{ + FunctionScanState *scanstate; + EState *estate; + + /* + * get information from node + */ + scanstate = (FunctionScanState *) node->scan.scanstate; + estate = node->scan.plan.state; + + /* + * Free the projection info and the scan attribute info + * + * Note: we don't ExecFreeResultType(scanstate) because the rule manager + * depends on the tupType returned by ExecMain(). So for now, this is + * freed at end-transaction time. -cim 6/2/91 + */ + ExecFreeProjectionInfo(&scanstate->csstate.cstate); + ExecFreeExprContext(&scanstate->csstate.cstate); + + /* + * clean out the tuple table + */ + ExecClearTuple(scanstate->csstate.cstate.cs_ResultTupleSlot); + ExecClearTuple(scanstate->csstate.css_ScanTupleSlot); + + /* + * Release tuplestore resources + */ + if (scanstate->tuplestorestate != NULL) + tuplestore_end((Tuplestorestate *) scanstate->tuplestorestate); + scanstate->tuplestorestate = NULL; +} + +/* ---------------------------------------------------------------- + * ExecFunctionMarkPos + * + * Calls tuplestore to save the current position in the stored file. + * ---------------------------------------------------------------- + */ +void +ExecFunctionMarkPos(FunctionScan *node) +{ + FunctionScanState *scanstate; + + scanstate = (FunctionScanState *) node->scan.scanstate; + + /* + * if we haven't materialized yet, just return. + */ + if (!scanstate->tuplestorestate) + return; + + tuplestore_markpos((Tuplestorestate *) scanstate->tuplestorestate); +} + +/* ---------------------------------------------------------------- + * ExecFunctionRestrPos + * + * Calls tuplestore to restore the last saved file position. + * ---------------------------------------------------------------- + */ +void +ExecFunctionRestrPos(FunctionScan *node) +{ + FunctionScanState *scanstate; + + scanstate = (FunctionScanState *) node->scan.scanstate; + + /* + * if we haven't materialized yet, just return. + */ + if (!scanstate->tuplestorestate) + return; + + tuplestore_restorepos((Tuplestorestate *) scanstate->tuplestorestate); +} + +/* ---------------------------------------------------------------- + * ExecFunctionReScan + * + * Rescans the relation. + * ---------------------------------------------------------------- + */ +void +ExecFunctionReScan(FunctionScan *node, ExprContext *exprCtxt, Plan *parent) +{ + FunctionScanState *scanstate; + + /* + * get information from node + */ + scanstate = (FunctionScanState *) node->scan.scanstate; + + ExecClearTuple(scanstate->csstate.cstate.cs_ResultTupleSlot); + + /* + * If we haven't materialized yet, just return. + */ + if (!scanstate->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->scan.plan.chgParam != NULL) + { + tuplestore_end((Tuplestorestate *) scanstate->tuplestorestate); + scanstate->tuplestorestate = NULL; + } + else + tuplestore_rescan((Tuplestorestate *) scanstate->tuplestorestate); +} + +/* + * Run the underlying function to get the next tuple + */ +static TupleTableSlot * +function_getonetuple(TupleTableSlot *slot, + Node *expr, + ExprContext *econtext, + TupleDesc tupdesc, + bool returnsTuple, + bool *isNull, + ExprDoneCond *isDone) +{ + HeapTuple tuple; + Datum retDatum; + char nullflag; + + /* + * get the next Datum from the function + */ + retDatum = ExecEvalExprSwitchContext(expr, econtext, isNull, isDone); + + /* + * check to see if we're really done + */ + if (*isDone == ExprEndResult) + slot = NULL; + else + { + if (returnsTuple) + { + /* + * Composite data type, i.e. a table's row type + * function returns pointer to tts?? + */ + slot = (TupleTableSlot *) retDatum; + } + else + { + /* + * Must be a base data type, i.e. scalar + * turn it into a tuple + */ + nullflag = *isNull ? 'n' : ' '; + tuple = heap_formtuple(tupdesc, &retDatum, &nullflag); + + /* + * save the tuple in the scan tuple slot and return the slot. + */ + slot = ExecStoreTuple(tuple, /* tuple to store */ + slot, /* slot to store in */ + InvalidBuffer, /* buffer associated with + * this tuple */ + true); /* pfree this pointer */ + } + } + + return slot; +} + +static FunctionMode +get_functionmode(Node *expr) +{ + /* + * for the moment, hardwire this + */ + return PM_REPEATEDCALL; +} diff --git a/src/backend/executor/nodeSubqueryscan.c b/src/backend/executor/nodeSubqueryscan.c index edd4640ba4..12f659847c 100644 --- a/src/backend/executor/nodeSubqueryscan.c +++ b/src/backend/executor/nodeSubqueryscan.c @@ -12,7 +12,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/executor/nodeSubqueryscan.c,v 1.11 2001/10/25 05:49:29 momjian Exp $ + * $Header: /cvsroot/pgsql/src/backend/executor/nodeSubqueryscan.c,v 1.12 2002/05/12 20:10:02 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -146,7 +146,7 @@ ExecInitSubqueryScan(SubqueryScan *node, EState *estate, Plan *parent) * This should agree with ExecInitSubPlan */ rte = rt_fetch(node->scan.scanrelid, estate->es_range_table); - Assert(rte->subquery != NULL); + Assert(rte->rtekind == RTE_SUBQUERY); sp_estate = CreateExecutorState(); subquerystate->sss_SubEState = sp_estate; diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index c461f147ef..cfcf00a4d4 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -15,7 +15,7 @@ * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/nodes/copyfuncs.c,v 1.182 2002/04/28 19:54:28 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/nodes/copyfuncs.c,v 1.183 2002/05/12 20:10:02 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -310,6 +310,23 @@ _copySubqueryScan(SubqueryScan *from) return newnode; } +/* ---------------- + * _copyFunctionScan + * ---------------- + */ +static FunctionScan * +_copyFunctionScan(FunctionScan *from) +{ + FunctionScan *newnode = makeNode(FunctionScan); + + /* + * copy node superclass fields + */ + CopyPlanFields((Plan *) from, (Plan *) newnode); + CopyScanFields((Scan *) from, (Scan *) newnode); + + return newnode; +} /* ---------------- * CopyJoinFields @@ -1083,7 +1100,7 @@ _copyRelOptInfo(RelOptInfo *from) Node_Copy(from, newnode, cheapest_total_path); newnode->pruneable = from->pruneable; - newnode->issubquery = from->issubquery; + newnode->rtekind = from->rtekind; Node_Copy(from, newnode, indexlist); newnode->pages = from->pages; newnode->tuples = from->tuples; @@ -1473,6 +1490,7 @@ _copyRangeTblEntry(RangeTblEntry *from) newnode->rtekind = from->rtekind; newnode->relid = from->relid; Node_Copy(from, newnode, subquery); + Node_Copy(from, newnode, funcexpr); newnode->jointype = from->jointype; Node_Copy(from, newnode, joinaliasvars); Node_Copy(from, newnode, alias); @@ -1690,6 +1708,17 @@ _copyRangeSubselect(RangeSubselect *from) return newnode; } +static RangeFunction * +_copyRangeFunction(RangeFunction *from) +{ + RangeFunction *newnode = makeNode(RangeFunction); + + Node_Copy(from, newnode, funccallnode); + Node_Copy(from, newnode, alias); + + return newnode; +} + static TypeCast * _copyTypeCast(TypeCast *from) { @@ -2621,6 +2650,9 @@ copyObject(void *from) case T_SubqueryScan: retval = _copySubqueryScan(from); break; + case T_FunctionScan: + retval = _copyFunctionScan(from); + break; case T_Join: retval = _copyJoin(from); break; @@ -3001,6 +3033,9 @@ copyObject(void *from) case T_RangeSubselect: retval = _copyRangeSubselect(from); break; + case T_RangeFunction: + retval = _copyRangeFunction(from); + break; case T_TypeName: retval = _copyTypeName(from); break; diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c index 31ee46e060..185a16beb5 100644 --- a/src/backend/nodes/equalfuncs.c +++ b/src/backend/nodes/equalfuncs.c @@ -20,7 +20,7 @@ * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/nodes/equalfuncs.c,v 1.130 2002/04/28 19:54:28 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/nodes/equalfuncs.c,v 1.131 2002/05/12 20:10:03 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -1573,6 +1573,17 @@ _equalRangeSubselect(RangeSubselect *a, RangeSubselect *b) return true; } +static bool +_equalRangeFunction(RangeFunction *a, RangeFunction *b) +{ + if (!equal(a->funccallnode, b->funccallnode)) + return false; + if (!equal(a->alias, b->alias)) + return false; + + return true; +} + static bool _equalTypeName(TypeName *a, TypeName *b) { @@ -1678,6 +1689,8 @@ _equalRangeTblEntry(RangeTblEntry *a, RangeTblEntry *b) return false; if (!equal(a->subquery, b->subquery)) return false; + if (!equal(a->funcexpr, b->funcexpr)) + return false; if (a->jointype != b->jointype) return false; if (!equal(a->joinaliasvars, b->joinaliasvars)) @@ -2166,6 +2179,9 @@ equal(void *a, void *b) case T_RangeSubselect: retval = _equalRangeSubselect(a, b); break; + case T_RangeFunction: + retval = _equalRangeFunction(a, b); + break; case T_TypeName: retval = _equalTypeName(a, b); break; diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index 34264db11e..698beb5d99 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -5,7 +5,7 @@ * Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $Header: /cvsroot/pgsql/src/backend/nodes/outfuncs.c,v 1.157 2002/04/28 19:54:28 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/nodes/outfuncs.c,v 1.158 2002/05/12 20:10:03 tgl Exp $ * * NOTES * Every (plan) node in POSTGRES has an associated "out" routine which @@ -551,6 +551,18 @@ _outSubqueryScan(StringInfo str, SubqueryScan *node) _outNode(str, node->subplan); } +/* + * FunctionScan is a subclass of Scan + */ +static void +_outFunctionScan(StringInfo str, FunctionScan *node) +{ + appendStringInfo(str, " FUNCTIONSCAN "); + _outPlanInfo(str, (Plan *) node); + + appendStringInfo(str, " :scanrelid %u ", node->scan.scanrelid); +} + /* * Material is a subclass of Plan */ @@ -980,6 +992,10 @@ _outRangeTblEntry(StringInfo str, RangeTblEntry *node) appendStringInfo(str, ":subquery "); _outNode(str, node->subquery); break; + case RTE_FUNCTION: + appendStringInfo(str, ":funcexpr "); + _outNode(str, node->funcexpr); + break; case RTE_JOIN: appendStringInfo(str, ":jointype %d :joinaliasvars ", (int) node->jointype); @@ -1598,6 +1614,9 @@ _outNode(StringInfo str, void *obj) case T_SubqueryScan: _outSubqueryScan(str, obj); break; + case T_FunctionScan: + _outFunctionScan(str, obj); + break; case T_Material: _outMaterial(str, obj); break; diff --git a/src/backend/nodes/print.c b/src/backend/nodes/print.c index a1fd2b9387..49bf55e351 100644 --- a/src/backend/nodes/print.c +++ b/src/backend/nodes/print.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/nodes/print.c,v 1.54 2002/03/24 04:31:07 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/nodes/print.c,v 1.55 2002/05/12 20:10:03 tgl Exp $ * * HISTORY * AUTHOR DATE MAJOR EVENT @@ -254,12 +254,33 @@ print_rt(List *rtable) { RangeTblEntry *rte = lfirst(l); - if (rte->rtekind == RTE_RELATION) - printf("%d\t%s\t%u", - i, rte->eref->aliasname, rte->relid); - else - printf("%d\t%s\t[subquery]", - i, rte->eref->aliasname); + switch (rte->rtekind) + { + case RTE_RELATION: + printf("%d\t%s\t%u", + i, rte->eref->aliasname, rte->relid); + break; + case RTE_SUBQUERY: + printf("%d\t%s\t[subquery]", + i, rte->eref->aliasname); + break; + case RTE_FUNCTION: + printf("%d\t%s\t[rangefunction]", + i, rte->eref->aliasname); + break; + case RTE_JOIN: + printf("%d\t%s\t[join]", + i, rte->eref->aliasname); + break; + case RTE_SPECIAL: + printf("%d\t%s\t[special]", + i, rte->eref->aliasname); + break; + default: + printf("%d\t%s\t[unknown rtekind]", + i, rte->eref->aliasname); + } + printf("\t%s\t%s\n", (rte->inh ? "inh" : ""), (rte->inFromCl ? "inFromCl" : "")); @@ -459,6 +480,8 @@ plannode_type(Plan *p) return "TIDSCAN"; case T_SubqueryScan: return "SUBQUERYSCAN"; + case T_FunctionScan: + return "FUNCTIONSCAN"; case T_Join: return "JOIN"; case T_NestLoop: @@ -489,12 +512,8 @@ plannode_type(Plan *p) } /* - prints the ascii description of the plan nodes - does this recursively by doing a depth-first traversal of the - plan tree. for SeqScan and IndexScan, the name of the table is also - printed out - -*/ + * Recursively prints a simple text description of the plan tree + */ void print_plan_recursive(Plan *p, Query *parsetree, int indentLevel, char *label) { @@ -523,6 +542,13 @@ print_plan_recursive(Plan *p, Query *parsetree, int indentLevel, char *label) rte = rt_fetch(((IndexScan *) p)->scan.scanrelid, parsetree->rtable); StrNCpy(extraInfo, rte->eref->aliasname, NAMEDATALEN); } + else if (IsA(p, FunctionScan)) + { + RangeTblEntry *rte; + + rte = rt_fetch(((FunctionScan *) p)->scan.scanrelid, parsetree->rtable); + StrNCpy(extraInfo, rte->eref->aliasname, NAMEDATALEN); + } else extraInfo[0] = '\0'; if (extraInfo[0] != '\0') diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c index 1d4236fc84..27604dcb4b 100644 --- a/src/backend/nodes/readfuncs.c +++ b/src/backend/nodes/readfuncs.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/nodes/readfuncs.c,v 1.120 2002/04/28 19:54:28 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/nodes/readfuncs.c,v 1.121 2002/05/12 20:10:03 tgl Exp $ * * NOTES * Most of the read functions for plan nodes are tested. (In fact, they @@ -651,6 +651,24 @@ _readSubqueryScan(void) return local_node; } +/* ---------------- + * _readFunctionScan + * + * FunctionScan is a subclass of Scan + * ---------------- + */ +static FunctionScan * +_readFunctionScan(void) +{ + FunctionScan *local_node; + + local_node = makeNode(FunctionScan); + + _getScan((Scan *) local_node); + + return local_node; +} + /* ---------------- * _readSort * @@ -1514,6 +1532,11 @@ _readRangeTblEntry(void) local_node->subquery = nodeRead(true); /* now read it */ break; + case RTE_FUNCTION: + token = pg_strtok(&length); /* eat :funcexpr */ + local_node->funcexpr = nodeRead(true); /* now read it */ + break; + case RTE_JOIN: token = pg_strtok(&length); /* eat :jointype */ token = pg_strtok(&length); /* get jointype */ @@ -2031,6 +2054,8 @@ parsePlanString(void) return_value = _readTidScan(); else if (length == 12 && strncmp(token, "SUBQUERYSCAN", length) == 0) return_value = _readSubqueryScan(); + else if (length == 12 && strncmp(token, "FUNCTIONSCAN", length) == 0) + return_value = _readFunctionScan(); else if (length == 4 && strncmp(token, "SORT", length) == 0) return_value = _readSort(); else if (length == 6 && strncmp(token, "AGGREG", length) == 0) diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c index 9368723188..1e9074186c 100644 --- a/src/backend/optimizer/path/allpaths.c +++ b/src/backend/optimizer/path/allpaths.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/optimizer/path/allpaths.c,v 1.83 2001/12/10 22:54:12 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/optimizer/path/allpaths.c,v 1.84 2002/05/12 20:10:03 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -42,6 +42,8 @@ static void set_inherited_rel_pathlist(Query *root, RelOptInfo *rel, List *inheritlist); static void set_subquery_pathlist(Query *root, RelOptInfo *rel, Index rti, RangeTblEntry *rte); +static void set_function_pathlist(Query *root, RelOptInfo *rel, + RangeTblEntry *rte); static RelOptInfo *make_one_rel_by_joins(Query *root, int levels_needed, List *initial_rels); @@ -98,11 +100,16 @@ set_base_rel_pathlists(Query *root) rti = lfirsti(rel->relids); rte = rt_fetch(rti, root->rtable); - if (rel->issubquery) + if (rel->rtekind == RTE_SUBQUERY) { /* Subquery --- generate a separate plan for it */ set_subquery_pathlist(root, rel, rti, rte); } + else if (rel->rtekind == RTE_FUNCTION) + { + /* RangeFunction --- generate a separate plan for it */ + set_function_pathlist(root, rel, rte); + } else if ((inheritlist = expand_inherted_rtentry(root, rti, true)) != NIL) { @@ -385,6 +392,23 @@ set_subquery_pathlist(Query *root, RelOptInfo *rel, set_cheapest(rel); } +/* + * set_function_pathlist + * Build the (single) access path for a function RTE + */ +static void +set_function_pathlist(Query *root, RelOptInfo *rel, RangeTblEntry *rte) +{ + /* Mark rel with estimated output rows, width, etc */ + set_function_size_estimates(root, rel); + + /* Generate appropriate path */ + add_path(rel, create_functionscan_path(root, rel)); + + /* Select cheapest path (pretty easy in this case...) */ + set_cheapest(rel); +} + /* * make_fromexpr_rel * Build access paths for a FromExpr jointree node. diff --git a/src/backend/optimizer/path/clausesel.c b/src/backend/optimizer/path/clausesel.c index 85584bfdcf..35a9803100 100644 --- a/src/backend/optimizer/path/clausesel.c +++ b/src/backend/optimizer/path/clausesel.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/optimizer/path/clausesel.c,v 1.49 2002/03/06 06:09:50 momjian Exp $ + * $Header: /cvsroot/pgsql/src/backend/optimizer/path/clausesel.c,v 1.50 2002/05/12 20:10:03 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -381,7 +381,7 @@ clause_selectivity(Query *root, { RangeTblEntry *rte = rt_fetch(var->varno, root->rtable); - if (rte->subquery) + if (rte->rtekind == RTE_SUBQUERY) { /* * XXX not smart about subquery references... any way to diff --git a/src/backend/optimizer/path/costsize.c b/src/backend/optimizer/path/costsize.c index 2bc0bb8a13..b14322f45b 100644 --- a/src/backend/optimizer/path/costsize.c +++ b/src/backend/optimizer/path/costsize.c @@ -42,7 +42,7 @@ * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/optimizer/path/costsize.c,v 1.83 2002/03/12 00:51:42 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/optimizer/path/costsize.c,v 1.84 2002/05/12 20:10:03 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -111,7 +111,7 @@ cost_seqscan(Path *path, Query *root, /* Should only be applied to base relations */ Assert(length(baserel->relids) == 1); - Assert(!baserel->issubquery); + Assert(baserel->rtekind == RTE_RELATION); if (!enable_seqscan) startup_cost += disable_cost; @@ -224,9 +224,10 @@ cost_index(Path *path, Query *root, b; /* Should only be applied to base relations */ - Assert(IsA(baserel, RelOptInfo) &&IsA(index, IndexOptInfo)); + Assert(IsA(baserel, RelOptInfo) && + IsA(index, IndexOptInfo)); Assert(length(baserel->relids) == 1); - Assert(!baserel->issubquery); + Assert(baserel->rtekind == RTE_RELATION); if (!enable_indexscan && !is_injoin) startup_cost += disable_cost; @@ -372,6 +373,10 @@ cost_tidscan(Path *path, Query *root, Cost cpu_per_tuple; int ntuples = length(tideval); + /* Should only be applied to base relations */ + Assert(length(baserel->relids) == 1); + Assert(baserel->rtekind == RTE_RELATION); + if (!enable_tidscan) startup_cost += disable_cost; @@ -386,6 +391,36 @@ cost_tidscan(Path *path, Query *root, path->total_cost = startup_cost + run_cost; } +/* + * cost_functionscan + * Determines and returns the cost of scanning a function RTE. + */ +void +cost_functionscan(Path *path, Query *root, RelOptInfo *baserel) +{ + Cost startup_cost = 0; + Cost run_cost = 0; + Cost cpu_per_tuple; + + /* Should only be applied to base relations that are functions */ + Assert(length(baserel->relids) == 1); + Assert(baserel->rtekind == RTE_FUNCTION); + + /* + * For now, estimate function's cost at one operator eval per function + * call. Someday we should revive the function cost estimate columns in + * pg_proc... + */ + cpu_per_tuple = cpu_operator_cost; + + /* Add scanning CPU costs */ + cpu_per_tuple += cpu_tuple_cost + baserel->baserestrictcost; + run_cost += cpu_per_tuple * baserel->tuples; + + path->startup_cost = startup_cost; + path->total_cost = startup_cost + run_cost; +} + /* * cost_sort * Determines and returns the cost of sorting a relation. @@ -1298,6 +1333,54 @@ set_joinrel_size_estimates(Query *root, RelOptInfo *rel, rel->width = outer_rel->width + inner_rel->width; } +/* + * set_function_size_estimates + * Set the size estimates for a base relation that is a function call. + * + * The rel's targetlist and restrictinfo list must have been constructed + * already. + * + * We set the following fields of the rel node: + * rows: the estimated number of output tuples (after applying + * restriction clauses). + * width: the estimated average output tuple width in bytes. + * baserestrictcost: estimated cost of evaluating baserestrictinfo clauses. + */ +void +set_function_size_estimates(Query *root, RelOptInfo *rel) +{ + /* Should only be applied to base relations that are functions */ + Assert(length(rel->relids) == 1); + Assert(rel->rtekind == RTE_FUNCTION); + + /* + * Estimate number of rows the function itself will return. + * + * XXX no idea how to do this yet; but should at least check whether + * function returns set or not... + */ + rel->tuples = 1000; + + /* Now estimate number of output rows */ + rel->rows = rel->tuples * + restrictlist_selectivity(root, + rel->baserestrictinfo, + lfirsti(rel->relids)); + + /* + * Force estimate to be at least one row, to make explain output look + * better and to avoid possible divide-by-zero when interpolating + * cost. + */ + if (rel->rows < 1.0) + rel->rows = 1.0; + + rel->baserestrictcost = cost_qual_eval(rel->baserestrictinfo); + + set_rel_width(root, rel); +} + + /* * set_rel_width * Set the estimated output width of the relation. diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c index c20abacca3..cd8b0e6612 100644 --- a/src/backend/optimizer/plan/createplan.c +++ b/src/backend/optimizer/plan/createplan.c @@ -10,7 +10,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/optimizer/plan/createplan.c,v 1.113 2002/04/28 19:54:28 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/optimizer/plan/createplan.c,v 1.114 2002/05/12 20:10:03 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -43,6 +43,8 @@ static TidScan *create_tidscan_plan(TidPath *best_path, List *tlist, List *scan_clauses); static SubqueryScan *create_subqueryscan_plan(Path *best_path, List *tlist, List *scan_clauses); +static FunctionScan *create_functionscan_plan(Path *best_path, + List *tlist, List *scan_clauses); static NestLoop *create_nestloop_plan(Query *root, NestPath *best_path, List *tlist, List *joinclauses, List *otherclauses, @@ -77,6 +79,8 @@ static IndexScan *make_indexscan(List *qptlist, List *qpqual, Index scanrelid, ScanDirection indexscandir); static TidScan *make_tidscan(List *qptlist, List *qpqual, Index scanrelid, List *tideval); +static FunctionScan *make_functionscan(List *qptlist, List *qpqual, + Index scanrelid); static NestLoop *make_nestloop(List *tlist, List *joinclauses, List *otherclauses, Plan *lefttree, Plan *righttree, @@ -119,6 +123,7 @@ create_plan(Query *root, Path *best_path) case T_SeqScan: case T_TidScan: case T_SubqueryScan: + case T_FunctionScan: plan = (Plan *) create_scan_plan(root, best_path); break; case T_HashJoin: @@ -200,6 +205,12 @@ create_scan_plan(Query *root, Path *best_path) scan_clauses); break; + case T_FunctionScan: + plan = (Scan *) create_functionscan_plan(best_path, + tlist, + scan_clauses); + break; + default: elog(ERROR, "create_scan_plan: unknown node type: %d", best_path->pathtype); @@ -353,7 +364,7 @@ create_seqscan_plan(Path *best_path, List *tlist, List *scan_clauses) /* there should be exactly one base rel involved... */ Assert(length(best_path->parent->relids) == 1); - Assert(!best_path->parent->issubquery); + Assert(best_path->parent->rtekind == RTE_RELATION); scan_relid = (Index) lfirsti(best_path->parent->relids); @@ -397,7 +408,7 @@ create_indexscan_plan(Query *root, /* there should be exactly one base rel involved... */ Assert(length(best_path->path.parent->relids) == 1); - Assert(!best_path->path.parent->issubquery); + Assert(best_path->path.parent->rtekind == RTE_RELATION); baserelid = lfirsti(best_path->path.parent->relids); @@ -515,7 +526,7 @@ create_tidscan_plan(TidPath *best_path, List *tlist, List *scan_clauses) /* there should be exactly one base rel involved... */ Assert(length(best_path->path.parent->relids) == 1); - Assert(!best_path->path.parent->issubquery); + Assert(best_path->path.parent->rtekind == RTE_RELATION); scan_relid = (Index) lfirsti(best_path->path.parent->relids); @@ -546,7 +557,7 @@ create_subqueryscan_plan(Path *best_path, List *tlist, List *scan_clauses) /* there should be exactly one base rel involved... */ Assert(length(best_path->parent->relids) == 1); /* and it must be a subquery */ - Assert(best_path->parent->issubquery); + Assert(best_path->parent->rtekind == RTE_SUBQUERY); scan_relid = (Index) lfirsti(best_path->parent->relids); @@ -558,6 +569,31 @@ create_subqueryscan_plan(Path *best_path, List *tlist, List *scan_clauses) return scan_plan; } +/* + * create_functionscan_plan + * Returns a functionscan plan for the base relation scanned by 'best_path' + * with restriction clauses 'scan_clauses' and targetlist 'tlist'. + */ +static FunctionScan * +create_functionscan_plan(Path *best_path, List *tlist, List *scan_clauses) +{ + FunctionScan *scan_plan; + Index scan_relid; + + /* there should be exactly one base rel involved... */ + Assert(length(best_path->parent->relids) == 1); + /* and it must be a function */ + Assert(best_path->parent->rtekind == RTE_FUNCTION); + + scan_relid = (Index) lfirsti(best_path->parent->relids); + + scan_plan = make_functionscan(tlist, scan_clauses, scan_relid); + + copy_path_costsize(&scan_plan->scan.plan, best_path); + + return scan_plan; +} + /***************************************************************************** * * JOIN METHODS @@ -791,6 +827,7 @@ create_mergejoin_plan(Query *root, { case T_SeqScan: case T_IndexScan: + case T_FunctionScan: case T_Material: case T_Sort: /* OK, these inner plans support mark/restore */ @@ -1305,6 +1342,26 @@ make_subqueryscan(List *qptlist, return node; } +static FunctionScan * +make_functionscan(List *qptlist, + List *qpqual, + Index scanrelid) +{ + FunctionScan *node = makeNode(FunctionScan); + Plan *plan = &node->scan.plan; + + /* cost should be inserted by caller */ + plan->state = (EState *) NULL; + plan->targetlist = qptlist; + plan->qual = qpqual; + plan->lefttree = NULL; + plan->righttree = NULL; + node->scan.scanrelid = scanrelid; + node->scan.scanstate = (CommonScanState *) NULL; + + return node; +} + Append * make_append(List *appendplans, bool isTarget, List *tlist) { diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c index 807876e4a7..3e07b2425e 100644 --- a/src/backend/optimizer/plan/setrefs.c +++ b/src/backend/optimizer/plan/setrefs.c @@ -9,7 +9,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/optimizer/plan/setrefs.c,v 1.75 2002/04/28 19:54:28 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/optimizer/plan/setrefs.c,v 1.76 2002/05/12 20:10:03 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -120,6 +120,10 @@ set_plan_references(Query *root, Plan *plan) /* Recurse into subplan too */ set_plan_references(root, ((SubqueryScan *) plan)->subplan); break; + case T_FunctionScan: + fix_expr_references(plan, (Node *) plan->targetlist); + fix_expr_references(plan, (Node *) plan->qual); + break; case T_NestLoop: set_join_references(root, (Join *) plan); fix_expr_references(plan, (Node *) plan->targetlist); diff --git a/src/backend/optimizer/plan/subselect.c b/src/backend/optimizer/plan/subselect.c index a67eb5d8e5..57930e9a50 100644 --- a/src/backend/optimizer/plan/subselect.c +++ b/src/backend/optimizer/plan/subselect.c @@ -7,7 +7,7 @@ * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/optimizer/plan/subselect.c,v 1.51 2002/04/16 23:08:11 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/optimizer/plan/subselect.c,v 1.52 2002/05/12 20:10:03 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -352,13 +352,13 @@ make_subplan(SubLink *slink) } break; case T_Material: + case T_FunctionScan: case T_Sort: /* * Don't add another Material node if there's one - * already, nor if the top node is a Sort, since Sort - * materializes its output anyway. (I doubt either - * case can happen in practice for a subplan, but...) + * already, nor if the top node is any other type that + * materializes its output anyway. */ use_material = false; break; @@ -686,6 +686,7 @@ SS_finalize_plan(Plan *plan) case T_SetOp: case T_Limit: case T_Group: + case T_FunctionScan: break; default: diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c index 1b7cc14e2e..1a278e9ca2 100644 --- a/src/backend/optimizer/util/clauses.c +++ b/src/backend/optimizer/util/clauses.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/optimizer/util/clauses.c,v 1.97 2002/04/28 19:54:28 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/optimizer/util/clauses.c,v 1.98 2002/05/12 20:10:03 tgl Exp $ * * HISTORY * AUTHOR DATE MAJOR EVENT @@ -1922,6 +1922,7 @@ query_tree_walker(Query *query, { case RTE_RELATION: case RTE_SPECIAL: + case RTE_FUNCTION: /* nothing to do */ break; case RTE_SUBQUERY: @@ -2309,6 +2310,7 @@ query_tree_mutator(Query *query, { case RTE_RELATION: case RTE_SPECIAL: + case RTE_FUNCTION: /* nothing to do, don't bother to make a copy */ break; case RTE_SUBQUERY: diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c index c86ee45008..07d05b38a8 100644 --- a/src/backend/optimizer/util/pathnode.c +++ b/src/backend/optimizer/util/pathnode.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/optimizer/util/pathnode.c,v 1.76 2001/10/25 05:49:34 momjian Exp $ + * $Header: /cvsroot/pgsql/src/backend/optimizer/util/pathnode.c,v 1.77 2002/05/12 20:10:03 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -454,6 +454,25 @@ create_subqueryscan_path(RelOptInfo *rel) return pathnode; } +/* + * create_functionscan_path + * Creates a path corresponding to a sequential scan of a function, + * returning the pathnode. + */ +Path * +create_functionscan_path(Query *root, RelOptInfo *rel) +{ + Path *pathnode = makeNode(Path); + + pathnode->pathtype = T_FunctionScan; + pathnode->parent = rel; + pathnode->pathkeys = NIL; /* for now, assume unordered result */ + + cost_functionscan(pathnode, root, rel); + + return pathnode; +} + /* * create_nestloop_path * Creates a pathnode corresponding to a nestloop join between two diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c index 0fad16fbdd..38e988b26f 100644 --- a/src/backend/optimizer/util/relnode.c +++ b/src/backend/optimizer/util/relnode.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/optimizer/util/relnode.c,v 1.36 2002/03/12 00:51:51 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/optimizer/util/relnode.c,v 1.37 2002/05/12 20:10:03 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -140,7 +140,7 @@ make_base_rel(Query *root, int relid) rel->cheapest_startup_path = NULL; rel->cheapest_total_path = NULL; rel->pruneable = true; - rel->issubquery = false; + rel->rtekind = rte->rtekind; rel->indexlist = NIL; rel->pages = 0; rel->tuples = 0; @@ -168,8 +168,8 @@ make_base_rel(Query *root, int relid) break; } case RTE_SUBQUERY: - /* Subquery --- mark it as such for later processing */ - rel->issubquery = true; + case RTE_FUNCTION: + /* Subquery or function --- nothing to do here */ break; case RTE_JOIN: /* Join --- must be an otherrel */ @@ -351,7 +351,7 @@ build_join_rel(Query *root, joinrel->cheapest_startup_path = NULL; joinrel->cheapest_total_path = NULL; joinrel->pruneable = true; - joinrel->issubquery = false; + joinrel->rtekind = RTE_JOIN; joinrel->indexlist = NIL; joinrel->pages = 0; joinrel->tuples = 0; diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c index a3acf29453..72a0c8be51 100644 --- a/src/backend/parser/analyze.c +++ b/src/backend/parser/analyze.c @@ -6,7 +6,7 @@ * Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $Header: /cvsroot/pgsql/src/backend/parser/analyze.c,v 1.233 2002/04/28 19:54:28 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/parser/analyze.c,v 1.234 2002/05/12 20:10:03 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -2679,7 +2679,7 @@ transformForUpdate(Query *qry, List *forUpdate) RangeTblEntry *rte = (RangeTblEntry *) lfirst(rt); ++i; - if (rte->subquery) + if (rte->rtekind == RTE_SUBQUERY) { /* FOR UPDATE of subquery is propagated to subquery's rels */ transformForUpdate(rte->subquery, makeList1(NULL)); @@ -2707,7 +2707,7 @@ transformForUpdate(Query *qry, List *forUpdate) ++i; if (strcmp(rte->eref->aliasname, relname) == 0) { - if (rte->subquery) + if (rte->rtekind == RTE_SUBQUERY) { /* propagate to subquery */ transformForUpdate(rte->subquery, makeList1(NULL)); diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 258a11c118..6b7469f2bd 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -11,7 +11,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/parser/gram.y,v 2.313 2002/05/06 19:47:30 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/parser/gram.y,v 2.314 2002/05/12 20:10:04 tgl Exp $ * * HISTORY * AUTHOR DATE MAJOR EVENT @@ -52,6 +52,7 @@ #include "access/htup.h" #include "catalog/index.h" +#include "catalog/namespace.h" #include "catalog/pg_type.h" #include "nodes/makefuncs.h" #include "nodes/params.h" @@ -250,7 +251,7 @@ static void doNegateFloat(Value *v); %type def_elem %type def_arg, columnElem, where_clause, insert_column_item, a_expr, b_expr, c_expr, AexprConst, - in_expr, having_clause + in_expr, having_clause, func_table %type row_descriptor, row_list, in_expr_nodes %type row_expr %type case_expr, case_arg, when_clause, case_default @@ -4074,6 +4075,19 @@ table_ref: relation_expr $1->alias = $2; $$ = (Node *) $1; } + | func_table + { + RangeFunction *n = makeNode(RangeFunction); + n->funccallnode = $1; + $$ = (Node *) n; + } + | func_table alias_clause + { + RangeFunction *n = makeNode(RangeFunction); + n->funccallnode = $1; + n->alias = $2; + $$ = (Node *) n; + } | select_with_parens { /* @@ -4109,6 +4123,7 @@ table_ref: relation_expr } ; + /* * It may seem silly to separate joined_table from table_ref, but there is * method in SQL92's madness: if you don't do it this way you get reduce- @@ -4280,6 +4295,28 @@ relation_expr: qualified_name } ; + +func_table: func_name '(' ')' + { + FuncCall *n = makeNode(FuncCall); + n->funcname = $1; + n->args = NIL; + n->agg_star = FALSE; + n->agg_distinct = FALSE; + $$ = (Node *)n; + } + | func_name '(' expr_list ')' + { + FuncCall *n = makeNode(FuncCall); + n->funcname = $1; + n->args = $3; + n->agg_star = FALSE; + n->agg_distinct = FALSE; + $$ = (Node *)n; + } + ; + + where_clause: WHERE a_expr { $$ = $2; } | /*EMPTY*/ { $$ = NULL; /* no qualifiers */ } ; @@ -5845,26 +5882,33 @@ qualified_name_list: qualified_name { $$ = lappend($1, $3); } ; -qualified_name: ColId +qualified_name: relation_name { $$ = makeNode(RangeVar); $$->catalogname = NULL; $$->schemaname = NULL; $$->relname = $1; } - | ColId '.' ColId + | dotted_name { $$ = makeNode(RangeVar); - $$->catalogname = NULL; - $$->schemaname = $1; - $$->relname = $3; - } - | ColId '.' ColId '.' ColId - { - $$ = makeNode(RangeVar); - $$->catalogname = $1; - $$->schemaname = $3; - $$->relname = $5; + switch (length($1)) + { + case 2: + $$->catalogname = NULL; + $$->schemaname = strVal(lfirst($1)); + $$->relname = strVal(lsecond($1)); + break; + case 3: + $$->catalogname = strVal(lfirst($1)); + $$->schemaname = strVal(lsecond($1)); + $$->relname = strVal(lfirst(lnext(lnext($1)))); + break; + default: + elog(ERROR, "Improper qualified name (too many dotted names): %s", + NameListToString($1)); + break; + } } ; diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c index 6df4a4fd7d..19aa688ff9 100644 --- a/src/backend/parser/parse_clause.c +++ b/src/backend/parser/parse_clause.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/parser/parse_clause.c,v 1.90 2002/04/28 19:54:28 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/parser/parse_clause.c,v 1.91 2002/05/12 20:10:04 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -17,6 +17,7 @@ #include "access/heapam.h" #include "nodes/makefuncs.h" +#include "optimizer/clauses.h" #include "optimizer/tlist.h" #include "optimizer/var.h" #include "parser/analyze.h" @@ -49,6 +50,8 @@ static Node *transformJoinOnClause(ParseState *pstate, JoinExpr *j, static RangeTblRef *transformTableEntry(ParseState *pstate, RangeVar *r); static RangeTblRef *transformRangeSubselect(ParseState *pstate, RangeSubselect *r); +static RangeTblRef *transformRangeFunction(ParseState *pstate, + RangeFunction *r); static Node *transformFromClauseItem(ParseState *pstate, Node *n, List **containedRels); static Node *buildMergedJoinVar(JoinType jointype, @@ -82,9 +85,9 @@ transformFromClause(ParseState *pstate, List *frmList) /* * The grammar will have produced a list of RangeVars, - * RangeSubselects, and/or JoinExprs. Transform each one (possibly - * adding entries to the rtable), check for duplicate refnames, and - * then add it to the joinlist and namespace. + * RangeSubselects, RangeFunctions, and/or JoinExprs. Transform each one + * (possibly adding entries to the rtable), check for duplicate refnames, + * and then add it to the joinlist and namespace. */ foreach(fl, frmList) { @@ -453,6 +456,71 @@ transformRangeSubselect(ParseState *pstate, RangeSubselect *r) } +/* + * transformRangeFunction --- transform a function call appearing in FROM + */ +static RangeTblRef * +transformRangeFunction(ParseState *pstate, RangeFunction *r) +{ + Node *funcexpr; + char *funcname; + RangeTblEntry *rte; + RangeTblRef *rtr; + + /* + * Transform the raw FuncCall node + */ + funcexpr = transformExpr(pstate, r->funccallnode); + + Assert(IsA(r->funccallnode, FuncCall)); + funcname = strVal(llast(((FuncCall *) r->funccallnode)->funcname)); + + /* + * Disallow aggregate functions and subselects in the expression. + * (Aggregates clearly make no sense; perhaps later we could support + * subselects, though.) + */ + if (contain_agg_clause(funcexpr)) + elog(ERROR, "cannot use aggregate function in FROM function expression"); + if (contain_subplans(funcexpr)) + elog(ERROR, "cannot use subselect in FROM function expression"); + + /* + * Remove any Iter nodes added by parse_func.c. We oughta get rid of + * Iter completely ... + */ + while (funcexpr && IsA(funcexpr, Iter)) + funcexpr = ((Iter *) funcexpr)->iterexpr; + + /* + * Insist we now have a bare function call (explain.c is the only place + * that depends on this, I think). If this fails, it's probably because + * transformExpr interpreted the function notation as a type coercion. + */ + if (!funcexpr || + !IsA(funcexpr, Expr) || + ((Expr *) funcexpr)->opType != FUNC_EXPR) + elog(ERROR, "Coercion function not allowed in FROM clause"); + + /* + * OK, build an RTE for the function. + */ + rte = addRangeTableEntryForFunction(pstate, funcname, funcexpr, + r->alias, true); + + /* + * We create a RangeTblRef, but we do not add it to the joinlist or + * namespace; our caller must do that if appropriate. + */ + rtr = makeNode(RangeTblRef); + /* assume new rte is at end */ + rtr->rtindex = length(pstate->p_rtable); + Assert(rte == rt_fetch(rtr->rtindex, pstate->p_rtable)); + + return rtr; +} + + /* * transformFromClauseItem - * Transform a FROM-clause item, adding any required entries to the @@ -486,6 +554,15 @@ transformFromClauseItem(ParseState *pstate, Node *n, List **containedRels) *containedRels = makeListi1(rtr->rtindex); return (Node *) rtr; } + else if (IsA(n, RangeFunction)) + { + /* function is like a plain relation */ + RangeTblRef *rtr; + + rtr = transformRangeFunction(pstate, (RangeFunction *) n); + *containedRels = makeListi1(rtr->rtindex); + return (Node *) rtr; + } else if (IsA(n, JoinExpr)) { /* A newfangled join expression */ diff --git a/src/backend/parser/parse_func.c b/src/backend/parser/parse_func.c index f74f5be2f7..339577f39c 100644 --- a/src/backend/parser/parse_func.c +++ b/src/backend/parser/parse_func.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/parser/parse_func.c,v 1.127 2002/05/03 20:15:02 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/parser/parse_func.c,v 1.128 2002/05/12 20:10:04 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -181,27 +181,32 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs, * sizeof(Pointer) to signal that the runtime representation * will be a pointer not an Oid. */ - if (rte->rtekind != RTE_RELATION) + switch (rte->rtekind) { - /* - * RTE is a join or subselect; must fail for lack of a - * named tuple type - */ - if (is_column) - elog(ERROR, "No such attribute %s.%s", - refname, strVal(lfirst(funcname))); - else - { - elog(ERROR, "Cannot pass result of sub-select or join %s to a function", - refname); - } + case RTE_RELATION: + toid = get_rel_type_id(rte->relid); + if (!OidIsValid(toid)) + elog(ERROR, "Cannot find type OID for relation %u", + rte->relid); + break; + case RTE_FUNCTION: + toid = exprType(rte->funcexpr); + break; + default: + /* + * RTE is a join or subselect; must fail for lack of a + * named tuple type + */ + if (is_column) + elog(ERROR, "No such attribute %s.%s", + refname, strVal(lfirst(funcname))); + else + elog(ERROR, "Cannot pass result of sub-select or join %s to a function", + refname); + toid = InvalidOid; /* keep compiler quiet */ + break; } - toid = get_rel_type_id(rte->relid); - if (!OidIsValid(toid)) - elog(ERROR, "Cannot find type OID for relation %u", - rte->relid); - /* replace RangeVar in the arg list */ lfirst(i) = makeVar(vnum, InvalidAttrNumber, diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c index b822a2378b..857adf0569 100644 --- a/src/backend/parser/parse_relation.c +++ b/src/backend/parser/parse_relation.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/parser/parse_relation.c,v 1.68 2002/04/28 19:54:28 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/parser/parse_relation.c,v 1.69 2002/05/12 20:10:04 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -672,6 +672,117 @@ addRangeTableEntryForSubquery(ParseState *pstate, return rte; } +/* + * Add an entry for a function to the pstate's range table (p_rtable). + * + * This is just like addRangeTableEntry() except that it makes a function RTE. + */ +RangeTblEntry * +addRangeTableEntryForFunction(ParseState *pstate, + char *funcname, + Node *funcexpr, + Alias *alias, + bool inFromCl) +{ + RangeTblEntry *rte = makeNode(RangeTblEntry); + Oid funcrettype = exprType(funcexpr); + Oid funcrelid; + Alias *eref; + int numaliases; + int varattno; + + rte->rtekind = RTE_FUNCTION; + rte->relid = InvalidOid; + rte->subquery = NULL; + rte->funcexpr = funcexpr; + rte->alias = alias; + + eref = alias ? (Alias *) copyObject(alias) : makeAlias(funcname, NIL); + rte->eref = eref; + + numaliases = length(eref->colnames); + + /* + * Now determine if the function returns a simple or composite type, + * and check/add column aliases. + */ + funcrelid = typeidTypeRelid(funcrettype); + + if (OidIsValid(funcrelid)) + { + /* + * Composite data type, i.e. a table's row type + * + * Get the rel's relcache entry. This access ensures that we have an + * up-to-date relcache entry for the rel. + */ + Relation rel; + int maxattrs; + + rel = heap_open(funcrelid, AccessShareLock); + + /* + * Since the rel is open anyway, let's check that the number of column + * aliases is reasonable. + */ + maxattrs = RelationGetNumberOfAttributes(rel); + if (maxattrs < numaliases) + elog(ERROR, "Table \"%s\" has %d columns available but %d columns specified", + RelationGetRelationName(rel), maxattrs, numaliases); + + /* fill in alias columns using actual column names */ + for (varattno = numaliases; varattno < maxattrs; varattno++) + { + char *attrname; + + attrname = pstrdup(NameStr(rel->rd_att->attrs[varattno]->attname)); + eref->colnames = lappend(eref->colnames, makeString(attrname)); + } + + /* + * Drop the rel refcount, but keep the access lock till end of + * transaction so that the table can't be deleted or have its schema + * modified underneath us. + */ + heap_close(rel, NoLock); + } + else + { + /* + * Must be a base data type, i.e. scalar. + * Just add one alias column named for the function. + */ + if (numaliases > 1) + elog(ERROR, "Too many column aliases specified for function %s", + funcname); + if (numaliases == 0) + eref->colnames = makeList1(makeString(funcname)); + } + + /*---------- + * Flags: + * - this RTE should be expanded to include descendant tables, + * - this RTE is in the FROM clause, + * - this RTE should be checked for read/write access rights. + *---------- + */ + rte->inh = false; /* never true for functions */ + rte->inFromCl = inFromCl; + rte->checkForRead = true; + rte->checkForWrite = false; + + rte->checkAsUser = InvalidOid; + + /* + * Add completed RTE to pstate's range table list, but not to join + * list nor namespace --- caller must do that if appropriate. + */ + if (pstate != NULL) + pstate->p_rtable = lappend(pstate->p_rtable, rte); + + return rte; +} + /* * Add an entry for a join to the pstate's range table (p_rtable). * @@ -834,124 +945,201 @@ expandRTE(ParseState *pstate, RangeTblEntry *rte, /* Need the RT index of the entry for creating Vars */ rtindex = RTERangeTablePosn(pstate, rte, &sublevels_up); - if (rte->rtekind == RTE_RELATION) + switch (rte->rtekind) { - /* Ordinary relation RTE */ - Relation rel; - int maxattrs; - int numaliases; - - rel = heap_open(rte->relid, AccessShareLock); - maxattrs = RelationGetNumberOfAttributes(rel); - numaliases = length(rte->eref->colnames); - - for (varattno = 0; varattno < maxattrs; varattno++) - { - Form_pg_attribute attr = rel->rd_att->attrs[varattno]; - - if (colnames) + case RTE_RELATION: { - char *label; + /* Ordinary relation RTE */ + Relation rel; + int maxattrs; + int numaliases; - if (varattno < numaliases) - label = strVal(nth(varattno, rte->eref->colnames)); + rel = heap_open(rte->relid, AccessShareLock); + maxattrs = RelationGetNumberOfAttributes(rel); + numaliases = length(rte->eref->colnames); + + for (varattno = 0; varattno < maxattrs; varattno++) + { + Form_pg_attribute attr = rel->rd_att->attrs[varattno]; + + if (colnames) + { + char *label; + + if (varattno < numaliases) + label = strVal(nth(varattno, rte->eref->colnames)); + else + label = NameStr(attr->attname); + *colnames = lappend(*colnames, makeString(pstrdup(label))); + } + + if (colvars) + { + Var *varnode; + + varnode = makeVar(rtindex, attr->attnum, + attr->atttypid, attr->atttypmod, + sublevels_up); + + *colvars = lappend(*colvars, varnode); + } + } + + heap_close(rel, AccessShareLock); + } + break; + case RTE_SUBQUERY: + { + /* Subquery RTE */ + List *aliasp = rte->eref->colnames; + List *tlistitem; + + varattno = 0; + foreach(tlistitem, rte->subquery->targetList) + { + TargetEntry *te = (TargetEntry *) lfirst(tlistitem); + + if (te->resdom->resjunk) + continue; + varattno++; + Assert(varattno == te->resdom->resno); + + if (colnames) + { + /* Assume there is one alias per target item */ + char *label = strVal(lfirst(aliasp)); + + *colnames = lappend(*colnames, makeString(pstrdup(label))); + aliasp = lnext(aliasp); + } + + if (colvars) + { + Var *varnode; + + varnode = makeVar(rtindex, varattno, + te->resdom->restype, + te->resdom->restypmod, + sublevels_up); + + *colvars = lappend(*colvars, varnode); + } + } + } + break; + case RTE_FUNCTION: + { + /* Function RTE */ + Oid funcrettype = exprType(rte->funcexpr); + Oid funcrelid = typeidTypeRelid(funcrettype); + + if (OidIsValid(funcrelid)) + { + /* + * Composite data type, i.e. a table's row type + * Same as ordinary relation RTE + */ + Relation rel; + int maxattrs; + int numaliases; + + rel = heap_open(funcrelid, AccessShareLock); + maxattrs = RelationGetNumberOfAttributes(rel); + numaliases = length(rte->eref->colnames); + + for (varattno = 0; varattno < maxattrs; varattno++) + { + Form_pg_attribute attr = rel->rd_att->attrs[varattno]; + + if (colnames) + { + char *label; + + if (varattno < numaliases) + label = strVal(nth(varattno, rte->eref->colnames)); + else + label = NameStr(attr->attname); + *colnames = lappend(*colnames, makeString(pstrdup(label))); + } + + if (colvars) + { + Var *varnode; + + varnode = makeVar(rtindex, attr->attnum, + attr->atttypid, attr->atttypmod, + sublevels_up); + + *colvars = lappend(*colvars, varnode); + } + } + + heap_close(rel, AccessShareLock); + } else - label = NameStr(attr->attname); - *colnames = lappend(*colnames, makeString(pstrdup(label))); - } + { + /* + * Must be a base data type, i.e. scalar + */ + if (colnames) + *colnames = lappend(*colnames, + lfirst(rte->eref->colnames)); - if (colvars) + if (colvars) + { + Var *varnode; + + varnode = makeVar(rtindex, 1, + funcrettype, -1, + sublevels_up); + + *colvars = lappend(*colvars, varnode); + } + } + } + break; + case RTE_JOIN: { - Var *varnode; + /* Join RTE */ + List *aliasp = rte->eref->colnames; + List *aliasvars = rte->joinaliasvars; - varnode = makeVar(rtindex, attr->attnum, - attr->atttypid, attr->atttypmod, - sublevels_up); + varattno = 0; + while (aliasp) + { + Assert(aliasvars); + varattno++; - *colvars = lappend(*colvars, varnode); + if (colnames) + { + char *label = strVal(lfirst(aliasp)); + + *colnames = lappend(*colnames, makeString(pstrdup(label))); + } + + if (colvars) + { + Node *aliasvar = (Node *) lfirst(aliasvars); + Var *varnode; + + varnode = makeVar(rtindex, varattno, + exprType(aliasvar), + exprTypmod(aliasvar), + sublevels_up); + + *colvars = lappend(*colvars, varnode); + } + + aliasp = lnext(aliasp); + aliasvars = lnext(aliasvars); + } + Assert(aliasvars == NIL); } - } - - heap_close(rel, AccessShareLock); + break; + default: + elog(ERROR, "expandRTE: unsupported RTE kind %d", + (int) rte->rtekind); } - else if (rte->rtekind == RTE_SUBQUERY) - { - /* Subquery RTE */ - List *aliasp = rte->eref->colnames; - List *tlistitem; - - varattno = 0; - foreach(tlistitem, rte->subquery->targetList) - { - TargetEntry *te = (TargetEntry *) lfirst(tlistitem); - - if (te->resdom->resjunk) - continue; - varattno++; - Assert(varattno == te->resdom->resno); - - if (colnames) - { - /* Assume there is one alias per target item */ - char *label = strVal(lfirst(aliasp)); - - *colnames = lappend(*colnames, makeString(pstrdup(label))); - aliasp = lnext(aliasp); - } - - if (colvars) - { - Var *varnode; - - varnode = makeVar(rtindex, varattno, - te->resdom->restype, - te->resdom->restypmod, - sublevels_up); - - *colvars = lappend(*colvars, varnode); - } - } - } - else if (rte->rtekind == RTE_JOIN) - { - /* Join RTE */ - List *aliasp = rte->eref->colnames; - List *aliasvars = rte->joinaliasvars; - - varattno = 0; - while (aliasp) - { - Assert(aliasvars); - varattno++; - - if (colnames) - { - char *label = strVal(lfirst(aliasp)); - - *colnames = lappend(*colnames, makeString(pstrdup(label))); - } - - if (colvars) - { - Node *aliasvar = (Node *) lfirst(aliasvars); - Var *varnode; - - varnode = makeVar(rtindex, varattno, - exprType(aliasvar), - exprTypmod(aliasvar), - sublevels_up); - - *colvars = lappend(*colvars, varnode); - } - - aliasp = lnext(aliasp); - aliasvars = lnext(aliasvars); - } - Assert(aliasvars == NIL); - } - else - elog(ERROR, "expandRTE: unsupported RTE kind %d", - (int) rte->rtekind); } /* @@ -1044,57 +1232,101 @@ void get_rte_attribute_type(RangeTblEntry *rte, AttrNumber attnum, Oid *vartype, int32 *vartypmod) { - if (rte->rtekind == RTE_RELATION) + switch (rte->rtekind) { - /* Plain relation RTE --- get the attribute's type info */ - HeapTuple tp; - Form_pg_attribute att_tup; + case RTE_RELATION: + { + /* Plain relation RTE --- get the attribute's type info */ + HeapTuple tp; + Form_pg_attribute att_tup; - tp = SearchSysCache(ATTNUM, - ObjectIdGetDatum(rte->relid), - Int16GetDatum(attnum), - 0, 0); - /* this shouldn't happen... */ - if (!HeapTupleIsValid(tp)) - elog(ERROR, "Relation %s does not have attribute %d", - get_rel_name(rte->relid), attnum); - att_tup = (Form_pg_attribute) GETSTRUCT(tp); - *vartype = att_tup->atttypid; - *vartypmod = att_tup->atttypmod; - ReleaseSysCache(tp); + tp = SearchSysCache(ATTNUM, + ObjectIdGetDatum(rte->relid), + Int16GetDatum(attnum), + 0, 0); + /* this shouldn't happen... */ + if (!HeapTupleIsValid(tp)) + elog(ERROR, "Relation %s does not have attribute %d", + get_rel_name(rte->relid), attnum); + att_tup = (Form_pg_attribute) GETSTRUCT(tp); + *vartype = att_tup->atttypid; + *vartypmod = att_tup->atttypmod; + ReleaseSysCache(tp); + } + break; + case RTE_SUBQUERY: + { + /* Subselect RTE --- get type info from subselect's tlist */ + List *tlistitem; + + foreach(tlistitem, rte->subquery->targetList) + { + TargetEntry *te = (TargetEntry *) lfirst(tlistitem); + + if (te->resdom->resjunk || te->resdom->resno != attnum) + continue; + *vartype = te->resdom->restype; + *vartypmod = te->resdom->restypmod; + return; + } + /* falling off end of list shouldn't happen... */ + elog(ERROR, "Subquery %s does not have attribute %d", + rte->eref->aliasname, attnum); + } + break; + case RTE_FUNCTION: + { + /* Function RTE */ + Oid funcrettype = exprType(rte->funcexpr); + Oid funcrelid = typeidTypeRelid(funcrettype); + + if (OidIsValid(funcrelid)) + { + /* + * Composite data type, i.e. a table's row type + * Same as ordinary relation RTE + */ + HeapTuple tp; + Form_pg_attribute att_tup; + + tp = SearchSysCache(ATTNUM, + ObjectIdGetDatum(funcrelid), + Int16GetDatum(attnum), + 0, 0); + /* this shouldn't happen... */ + if (!HeapTupleIsValid(tp)) + elog(ERROR, "Relation %s does not have attribute %d", + get_rel_name(funcrelid), attnum); + att_tup = (Form_pg_attribute) GETSTRUCT(tp); + *vartype = att_tup->atttypid; + *vartypmod = att_tup->atttypmod; + ReleaseSysCache(tp); + } + else + { + /* + * Must be a base data type, i.e. scalar + */ + *vartype = funcrettype; + *vartypmod = -1; + } + } + break; + case RTE_JOIN: + { + /* Join RTE --- get type info from join RTE's alias variable */ + Node *aliasvar; + + Assert(attnum > 0 && attnum <= length(rte->joinaliasvars)); + aliasvar = (Node *) nth(attnum-1, rte->joinaliasvars); + *vartype = exprType(aliasvar); + *vartypmod = exprTypmod(aliasvar); + } + break; + default: + elog(ERROR, "get_rte_attribute_type: unsupported RTE kind %d", + (int) rte->rtekind); } - else if (rte->rtekind == RTE_SUBQUERY) - { - /* Subselect RTE --- get type info from subselect's tlist */ - List *tlistitem; - - foreach(tlistitem, rte->subquery->targetList) - { - TargetEntry *te = (TargetEntry *) lfirst(tlistitem); - - if (te->resdom->resjunk || te->resdom->resno != attnum) - continue; - *vartype = te->resdom->restype; - *vartypmod = te->resdom->restypmod; - return; - } - /* falling off end of list shouldn't happen... */ - elog(ERROR, "Subquery %s does not have attribute %d", - rte->eref->aliasname, attnum); - } - else if (rte->rtekind == RTE_JOIN) - { - /* Join RTE --- get type info from join RTE's alias variable */ - Node *aliasvar; - - Assert(attnum > 0 && attnum <= length(rte->joinaliasvars)); - aliasvar = (Node *) nth(attnum-1, rte->joinaliasvars); - *vartype = exprType(aliasvar); - *vartypmod = exprTypmod(aliasvar); - } - else - elog(ERROR, "get_rte_attribute_type: unsupported RTE kind %d", - (int) rte->rtekind); } /* diff --git a/src/backend/parser/parse_type.c b/src/backend/parser/parse_type.c index 0901164389..a8a466ac3b 100644 --- a/src/backend/parser/parse_type.c +++ b/src/backend/parser/parse_type.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/parser/parse_type.c,v 1.40 2002/04/20 21:56:14 petere Exp $ + * $Header: /cvsroot/pgsql/src/backend/parser/parse_type.c,v 1.41 2002/05/12 20:10:04 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -56,7 +56,8 @@ LookupTypeName(const TypeName *typename) switch (length(typename->names)) { case 1: - elog(ERROR, "Improper %%TYPE reference (too few dotted names)"); + elog(ERROR, "Improper %%TYPE reference (too few dotted names): %s", + NameListToString(typename->names)); break; case 2: rel->relname = strVal(lfirst(typename->names)); @@ -74,7 +75,8 @@ LookupTypeName(const TypeName *typename) field = strVal(lfirst(lnext(lnext(lnext(typename->names))))); break; default: - elog(ERROR, "Improper %%TYPE reference (too many dotted names)"); + elog(ERROR, "Improper %%TYPE reference (too many dotted names): %s", + NameListToString(typename->names)); break; } @@ -121,7 +123,8 @@ LookupTypeName(const TypeName *typename) elog(ERROR, "Cross-database references are not implemented"); break; default: - elog(ERROR, "Improper type name (too many dotted names)"); + elog(ERROR, "Improper type name (too many dotted names): %s", + NameListToString(typename->names)); break; } diff --git a/src/backend/rewrite/rewriteDefine.c b/src/backend/rewrite/rewriteDefine.c index 6d4c81c70a..f35540128c 100644 --- a/src/backend/rewrite/rewriteDefine.c +++ b/src/backend/rewrite/rewriteDefine.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/rewrite/rewriteDefine.c,v 1.69 2002/04/27 03:45:03 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/rewrite/rewriteDefine.c,v 1.70 2002/05/12 20:10:04 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -388,7 +388,7 @@ setRuleCheckAsUser(Query *qry, Oid userid) { RangeTblEntry *rte = (RangeTblEntry *) lfirst(l); - if (rte->subquery) + if (rte->rtekind == RTE_SUBQUERY) { /* Recurse into subquery in FROM */ setRuleCheckAsUser(rte->subquery, userid); diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c index b1f012d4d9..b4bd3b2f51 100644 --- a/src/backend/utils/adt/ruleutils.c +++ b/src/backend/utils/adt/ruleutils.c @@ -3,7 +3,7 @@ * back to source text * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/utils/adt/ruleutils.c,v 1.102 2002/05/03 20:15:02 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/utils/adt/ruleutils.c,v 1.103 2002/05/12 20:10:04 tgl Exp $ * * This software is copyrighted by Jan Wieck - Hamburg. * @@ -705,25 +705,16 @@ deparse_context_for_plan(int outer_varno, Node *outercontext, } /* - * deparse_context_for_relation - Build deparse context for 1 relation + * deparse_context_for_rte - Build deparse context for 1 relation * * Helper routine to build one of the inputs for deparse_context_for_plan. - * Pass the reference name (alias) and OID of a relation. * - * The returned node is actually a RangeTblEntry, but we declare it as just - * Node to discourage callers from assuming anything. + * The returned node is actually the given RangeTblEntry, but we declare it + * as just Node to discourage callers from assuming anything. */ Node * -deparse_context_for_relation(const char *aliasname, Oid relid) +deparse_context_for_rte(RangeTblEntry *rte) { - RangeTblEntry *rte = makeNode(RangeTblEntry); - - rte->rtekind = RTE_RELATION; - rte->relid = relid; - rte->eref = makeAlias(aliasname, NIL); - rte->inh = false; - rte->inFromCl = true; - return (Node *) rte; } @@ -2398,6 +2389,10 @@ get_from_clause_item(Node *jtnode, Query *query, deparse_context *context) get_query_def(rte->subquery, buf, context->namespaces); appendStringInfoChar(buf, ')'); break; + case RTE_FUNCTION: + /* Function RTE */ + get_rule_expr(rte->funcexpr, context); + break; default: elog(ERROR, "unexpected rte kind %d", (int) rte->rtekind); break; diff --git a/src/backend/utils/fmgr/README b/src/backend/utils/fmgr/README index c50e33b9d1..043a3a7e8d 100644 --- a/src/backend/utils/fmgr/README +++ b/src/backend/utils/fmgr/README @@ -383,7 +383,7 @@ If a function is marked in pg_proc as returning a set, then it is called with fcinfo->resultinfo pointing to a node of type ReturnSetInfo. A function that desires to return a set should raise an error "called in context that does not accept a set result" if resultinfo is NULL or does -not point to a ReturnSetInfo node. ReturnSetInfo contains a single field +not point to a ReturnSetInfo node. ReturnSetInfo contains a field "isDone", which should be set to one of these values: ExprSingleResult /* expression does not return a set */ @@ -396,6 +396,11 @@ After all elements have been returned, the next call should set isDone to ExprEndResult and return a null result. (Note it is possible to return an empty set by doing this on the first call.) +As of 7.3, the ReturnSetInfo node also contains a link to the ExprContext +within which the function is being evaluated. This is useful for functions +that need to close down internal state when they are not run to completion: +they can register a shutdown callback function in the ExprContext. + Notes about function handlers ----------------------------- diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h index e135782bd5..3120fb5fae 100644 --- a/src/include/catalog/catversion.h +++ b/src/include/catalog/catversion.h @@ -37,7 +37,7 @@ * Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $Id: catversion.h,v 1.129 2002/04/28 19:54:28 tgl Exp $ + * $Id: catversion.h,v 1.130 2002/05/12 20:10:04 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -53,6 +53,6 @@ */ /* yyyymmddN */ -#define CATALOG_VERSION_NO 200204281 +#define CATALOG_VERSION_NO 200205121 #endif diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h index 63bc10f79d..6752d72ca7 100644 --- a/src/include/executor/executor.h +++ b/src/include/executor/executor.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $Id: executor.h,v 1.63 2002/02/27 19:35:59 tgl Exp $ + * $Id: executor.h,v 1.64 2002/05/12 20:10:04 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -172,4 +172,11 @@ extern void ExecCloseIndices(ResultRelInfo *resultRelInfo); extern void ExecInsertIndexTuples(TupleTableSlot *slot, ItemPointer tupleid, EState *estate, bool is_update); +extern void RegisterExprContextCallback(ExprContext *econtext, + ExprContextCallbackFunction function, + Datum arg); +extern void UnregisterExprContextCallback(ExprContext *econtext, + ExprContextCallbackFunction function, + Datum arg); + #endif /* EXECUTOR_H */ diff --git a/src/include/executor/nodeFunctionscan.h b/src/include/executor/nodeFunctionscan.h new file mode 100644 index 0000000000..4decbfe6b5 --- /dev/null +++ b/src/include/executor/nodeFunctionscan.h @@ -0,0 +1,27 @@ +/*------------------------------------------------------------------------- + * + * nodeFunctionscan.h + * + * + * + * Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * $Id: nodeFunctionscan.h,v 1.1 2002/05/12 20:10:04 tgl Exp $ + * + *------------------------------------------------------------------------- + */ +#ifndef NODEFUNCTIONSCAN_H +#define NODEFUNCTIONSCAN_H + +#include "nodes/plannodes.h" + +extern TupleTableSlot *ExecFunctionScan(FunctionScan *node); +extern void ExecEndFunctionScan(FunctionScan *node); +extern bool ExecInitFunctionScan(FunctionScan *node, EState *estate, Plan *parent); +extern int ExecCountSlotsFunctionScan(FunctionScan *node); +extern void ExecFunctionMarkPos(FunctionScan *node); +extern void ExecFunctionRestrPos(FunctionScan *node); +extern void ExecFunctionReScan(FunctionScan *node, ExprContext *exprCtxt, Plan *parent); + +#endif /* NODEFUNCTIONSCAN_H */ diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h index 97e76ccdfd..66f93df451 100644 --- a/src/include/nodes/execnodes.h +++ b/src/include/nodes/execnodes.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $Id: execnodes.h,v 1.67 2001/11/21 22:57:01 tgl Exp $ + * $Id: execnodes.h,v 1.68 2002/05/12 20:10:04 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -52,6 +52,21 @@ typedef struct IndexInfo bool ii_Unique; } IndexInfo; +/* ---------------- + * ExprContext_CB + * + * List of callbacks to be called at ExprContext shutdown. + * ---------------- + */ +typedef void (*ExprContextCallbackFunction) (Datum arg); + +typedef struct ExprContext_CB +{ + struct ExprContext_CB *next; + ExprContextCallbackFunction function; + Datum arg; +} ExprContext_CB; + /* ---------------- * ExprContext * @@ -77,20 +92,27 @@ typedef struct IndexInfo */ typedef struct ExprContext { - NodeTag type; + NodeTag type; + /* Tuples that Var nodes in expression may refer to */ TupleTableSlot *ecxt_scantuple; TupleTableSlot *ecxt_innertuple; TupleTableSlot *ecxt_outertuple; + /* Memory contexts for expression evaluation --- see notes above */ - MemoryContext ecxt_per_query_memory; - MemoryContext ecxt_per_tuple_memory; + MemoryContext ecxt_per_query_memory; + MemoryContext ecxt_per_tuple_memory; + /* Values to substitute for Param nodes in expression */ - ParamExecData *ecxt_param_exec_vals; /* for PARAM_EXEC params */ - ParamListInfo ecxt_param_list_info; /* for other param types */ + ParamExecData *ecxt_param_exec_vals; /* for PARAM_EXEC params */ + ParamListInfo ecxt_param_list_info; /* for other param types */ + /* Values to substitute for Aggref nodes in expression */ - Datum *ecxt_aggvalues; /* precomputed values for Aggref nodes */ - bool *ecxt_aggnulls; /* null flags for Aggref nodes */ + Datum *ecxt_aggvalues; /* precomputed values for Aggref nodes */ + bool *ecxt_aggnulls; /* null flags for Aggref nodes */ + + /* Functions to call back when ExprContext is shut down */ + ExprContext_CB *ecxt_callbacks; } ExprContext; /* @@ -107,7 +129,8 @@ typedef enum * When calling a function that might return a set (multiple rows), * a node of this type is passed as fcinfo->resultinfo to allow * return status to be passed back. A function returning set should - * raise an error if no such resultinfo is provided. + * raise an error if no such resultinfo is provided. The ExprContext + * in which the function is being called is also made available. * * XXX this mechanism is a quick hack and probably needs to be redesigned. */ @@ -115,9 +138,9 @@ typedef struct ReturnSetInfo { NodeTag type; ExprDoneCond isDone; + ExprContext *econtext; } ReturnSetInfo; - /* ---------------- * ProjectionInfo node information * @@ -481,6 +504,36 @@ typedef struct SubqueryScanState EState *sss_SubEState; } SubqueryScanState; +/* ---------------- + * FunctionScanState information + * + * Function nodes are used to scan the results of a + * function appearing in FROM (typically a function returning set). + * + * functionmode function operating mode: + * - repeated call + * - materialize + * - return query + * tuplestorestate private state of tuplestore.c + * ---------------- + */ +typedef enum FunctionMode +{ + PM_REPEATEDCALL, + PM_MATERIALIZE, + PM_QUERY +} FunctionMode; + +typedef struct FunctionScanState +{ + CommonScanState csstate; /* its first field is NodeTag */ + FunctionMode functionmode; + TupleDesc tupdesc; + void *tuplestorestate; + Node *funcexpr; /* function expression being evaluated */ + bool returnsTuple; /* does function return tuples? */ +} FunctionScanState; + /* ---------------------------------------------------------------- * Join State Information * ---------------------------------------------------------------- diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h index 1664e76900..492619e194 100644 --- a/src/include/nodes/nodes.h +++ b/src/include/nodes/nodes.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $Id: nodes.h,v 1.105 2002/04/18 20:01:11 tgl Exp $ + * $Id: nodes.h,v 1.106 2002/05/12 20:10:04 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -49,6 +49,7 @@ typedef enum NodeTag T_SubPlan, T_TidScan, T_SubqueryScan, + T_FunctionScan, /* * TAGS FOR PRIMITIVE NODES (primnodes.h) @@ -120,6 +121,7 @@ typedef enum NodeTag T_SubqueryScanState, T_SetOpState, T_LimitState, + T_FunctionScanState, /* * TAGS FOR MEMORY NODES (memnodes.h) @@ -212,6 +214,7 @@ typedef enum NodeTag T_Alias, T_RangeVar, T_RangeSubselect, + T_RangeFunction, T_TypeName, T_IndexElem, T_ColumnDef, diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 5bd0e89b18..0c5672dd67 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $Id: parsenodes.h,v 1.175 2002/04/28 19:54:28 tgl Exp $ + * $Id: parsenodes.h,v 1.176 2002/05/12 20:10:04 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -387,6 +387,16 @@ typedef struct RangeSubselect Alias *alias; /* table alias & optional column aliases */ } RangeSubselect; +/* + * RangeFunction - function call appearing in a FROM clause + */ +typedef struct RangeFunction +{ + NodeTag type; + Node *funccallnode; /* untransformed function call tree */ + Alias *alias; /* table alias & optional column aliases */ +} RangeFunction; + /* * IndexElem - index parameters (used in CREATE INDEX) * @@ -482,7 +492,8 @@ typedef enum RTEKind RTE_RELATION, /* ordinary relation reference */ RTE_SUBQUERY, /* subquery in FROM */ RTE_JOIN, /* join */ - RTE_SPECIAL /* special rule relation (NEW or OLD) */ + RTE_SPECIAL, /* special rule relation (NEW or OLD) */ + RTE_FUNCTION /* function in FROM */ } RTEKind; typedef struct RangeTblEntry @@ -507,6 +518,11 @@ typedef struct RangeTblEntry */ Query *subquery; /* the sub-query */ + /* + * Fields valid for a function RTE (else NULL): + */ + Node *funcexpr; /* expression tree for func call */ + /* * Fields valid for a join RTE (else NULL/zero): * diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h index 9ba1caca32..20413e08ae 100644 --- a/src/include/nodes/plannodes.h +++ b/src/include/nodes/plannodes.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $Id: plannodes.h,v 1.55 2002/04/28 19:54:28 tgl Exp $ + * $Id: plannodes.h,v 1.56 2002/05/12 20:10:05 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -32,6 +32,7 @@ * Scan *** CommonScanState scanstate; * IndexScan IndexScanState indxstate; * SubqueryScan SubqueryScanState subquerystate; + * FunctionScan FunctionScanState functionstate; * * (*** nodes which inherit Scan also inherit scanstate) * @@ -242,6 +243,17 @@ typedef struct SubqueryScan Plan *subplan; } SubqueryScan; +/* ---------------- + * FunctionScan node + * ---------------- + */ +typedef struct FunctionScan +{ + Scan scan; + /* no other fields needed at present */ + /* scan.scanstate actually points at a FunctionScanState node */ +} FunctionScan; + /* * ========== * Join nodes diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h index 903245cf51..6eed89a704 100644 --- a/src/include/nodes/primnodes.h +++ b/src/include/nodes/primnodes.h @@ -10,7 +10,7 @@ * Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $Id: primnodes.h,v 1.61 2002/04/11 20:00:15 tgl Exp $ + * $Id: primnodes.h,v 1.62 2002/05/12 20:10:05 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -514,10 +514,11 @@ typedef struct RelabelType * rows.) If all joins are inner joins then all the qual positions are * semantically interchangeable. * - * NOTE: in the raw output of gram.y, a join tree contains RangeVar and - * RangeSubselect nodes, which are both replaced by RangeTblRef nodes - * during the parse analysis phase. Also, the top-level FromExpr is added - * during parse analysis; the grammar regards FROM and WHERE as separate. + * NOTE: in the raw output of gram.y, a join tree contains RangeVar, + * RangeSubselect, and RangeFunction nodes, which are all replaced by + * RangeTblRef nodes during the parse analysis phase. Also, the top-level + * FromExpr is added during parse analysis; the grammar regards FROM and + * WHERE as separate. * ---------------------------------------------------------------- */ diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h index d26d60c71b..28ad8bc2eb 100644 --- a/src/include/nodes/relation.h +++ b/src/include/nodes/relation.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $Id: relation.h,v 1.63 2002/03/12 00:52:02 tgl Exp $ + * $Id: relation.h,v 1.64 2002/05/12 20:10:05 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -92,12 +92,12 @@ typedef enum CostSelector * * If the relation is a base relation it will have these fields set: * - * issubquery - true if baserel is a subquery RTE rather than a table + * rtekind - distinguishes plain relation, subquery, or function RTE * indexlist - list of IndexOptInfo nodes for relation's indexes - * (always NIL if it's a subquery) - * pages - number of disk pages in relation (zero if a subquery) + * (always NIL if it's not a table) + * pages - number of disk pages in relation (zero if not a table) * tuples - number of tuples in relation (not considering restrictions) - * subplan - plan for subquery (NULL if it's a plain table) + * subplan - plan for subquery (NULL if it's not a subquery) * * Note: for a subquery, tuples and subplan are not set immediately * upon creation of the RelOptInfo object; they are filled in when @@ -184,11 +184,11 @@ typedef struct RelOptInfo bool pruneable; /* information about a base rel (not set for join rels!) */ - bool issubquery; + RTEKind rtekind; /* RELATION, SUBQUERY, or FUNCTION */ List *indexlist; long pages; double tuples; - struct Plan *subplan; + struct Plan *subplan; /* if subquery */ /* information about a join rel (not set for base rels!) */ Index joinrti; diff --git a/src/include/optimizer/cost.h b/src/include/optimizer/cost.h index 7c4c22a595..690fa3f129 100644 --- a/src/include/optimizer/cost.h +++ b/src/include/optimizer/cost.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $Id: cost.h,v 1.43 2001/11/05 17:46:34 momjian Exp $ + * $Id: cost.h,v 1.44 2002/05/12 20:10:05 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -59,6 +59,8 @@ extern void cost_index(Path *path, Query *root, List *indexQuals, bool is_injoin); extern void cost_tidscan(Path *path, Query *root, RelOptInfo *baserel, List *tideval); +extern void cost_functionscan(Path *path, Query *root, + RelOptInfo *baserel); extern void cost_sort(Path *path, Query *root, List *pathkeys, double tuples, int width); extern void cost_nestloop(Path *path, Query *root, @@ -80,6 +82,7 @@ extern void set_joinrel_size_estimates(Query *root, RelOptInfo *rel, RelOptInfo *inner_rel, JoinType jointype, List *restrictlist); +extern void set_function_size_estimates(Query *root, RelOptInfo *rel); /* * prototypes for clausesel.c diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h index d9419df47d..5a6646db88 100644 --- a/src/include/optimizer/pathnode.h +++ b/src/include/optimizer/pathnode.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $Id: pathnode.h,v 1.42 2002/03/12 00:52:03 tgl Exp $ + * $Id: pathnode.h,v 1.43 2002/05/12 20:10:05 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -36,6 +36,7 @@ extern TidPath *create_tidscan_path(Query *root, RelOptInfo *rel, List *tideval); extern AppendPath *create_append_path(RelOptInfo *rel, List *subpaths); extern Path *create_subqueryscan_path(RelOptInfo *rel); +extern Path *create_functionscan_path(Query *root, RelOptInfo *rel); extern NestPath *create_nestloop_path(Query *root, RelOptInfo *joinrel, diff --git a/src/include/parser/parse_relation.h b/src/include/parser/parse_relation.h index 353ce8957c..be02997ba5 100644 --- a/src/include/parser/parse_relation.h +++ b/src/include/parser/parse_relation.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $Id: parse_relation.h,v 1.32 2002/04/28 19:54:29 tgl Exp $ + * $Id: parse_relation.h,v 1.33 2002/05/12 20:10:05 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -41,6 +41,11 @@ extern RangeTblEntry *addRangeTableEntryForSubquery(ParseState *pstate, Query *subquery, Alias *alias, bool inFromCl); +extern RangeTblEntry *addRangeTableEntryForFunction(ParseState *pstate, + char *funcname, + Node *funcexpr, + Alias *alias, + bool inFromCl); extern RangeTblEntry *addRangeTableEntryForJoin(ParseState *pstate, List *colnames, JoinType jointype, diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h index d46e6d8262..7a8d1fb387 100644 --- a/src/include/utils/builtins.h +++ b/src/include/utils/builtins.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $Id: builtins.h,v 1.180 2002/04/26 01:24:08 tgl Exp $ + * $Id: builtins.h,v 1.181 2002/05/12 20:10:05 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -354,7 +354,7 @@ extern char *deparse_expression(Node *expr, List *dpcontext, extern List *deparse_context_for(const char *aliasname, Oid relid); extern List *deparse_context_for_plan(int outer_varno, Node *outercontext, int inner_varno, Node *innercontext); -extern Node *deparse_context_for_relation(const char *aliasname, Oid relid); +extern Node *deparse_context_for_rte(RangeTblEntry *rte); extern Node *deparse_context_for_subplan(const char *name, List *tlist, List *rtable); extern const char *quote_identifier(const char *ident);