postgresql/src/backend/executor/functions.c

458 lines
10 KiB
C
Raw Normal View History

/*-------------------------------------------------------------------------
*
* functions.c--
* Routines to handle functions called from the executor
* Putting this stuff in fmgr makes the postmaster a mess....
*
* Copyright (c) 1994, Regents of the University of California
*
*
* IDENTIFICATION
1998-11-27 20:52:36 +01:00
* $Header: /cvsroot/pgsql/src/backend/executor/functions.c,v 1.21 1998/11/27 19:52:01 vadim Exp $
*
*-------------------------------------------------------------------------
*/
#include <string.h>
#include "postgres.h"
#include "nodes/primnodes.h"
#include "nodes/relation.h"
#include "nodes/execnodes.h"
#include "nodes/plannodes.h"
#include "catalog/pg_proc.h"
#include "tcop/pquery.h"
#include "tcop/tcopprot.h"
1996-10-26 06:15:05 +02:00
#include "tcop/utility.h"
#include "nodes/params.h"
#include "fmgr.h"
#include "utils/fcache.h"
#include "utils/datum.h"
#include "utils/elog.h"
#include "utils/palloc.h"
#include "utils/syscache.h"
#include "catalog/pg_language.h"
#include "access/heapam.h"
#include "access/xact.h"
#include "executor/executor.h"
#include "executor/execdefs.h"
#include "executor/functions.h"
#undef new
typedef enum
{
F_EXEC_START, F_EXEC_RUN, F_EXEC_DONE
} ExecStatus;
typedef struct local_es
{
QueryDesc *qd;
EState *estate;
struct local_es *next;
ExecStatus status;
} execution_state;
#define LAST_POSTQUEL_COMMAND(es) ((es)->next == (execution_state *)NULL)
/* non-export function prototypes */
static TupleDesc postquel_start(execution_state *es);
static execution_state *init_execution_state(FunctionCachePtr fcache,
char *args[]);
static TupleTableSlot *postquel_getnext(execution_state *es);
static void postquel_end(execution_state *es);
static void postquel_sub_params(execution_state *es, int nargs,
char *args[], bool *nullV);
static Datum postquel_execute(execution_state *es, FunctionCachePtr fcache,
List *fTlist, char **args, bool *isNull);
Datum
ProjectAttribute(TupleDesc TD,
TargetEntry *tlist,
HeapTuple tup,
bool *isnullP)
{
Datum val,
valueP;
Var *attrVar = (Var *) tlist->expr;
AttrNumber attrno = attrVar->varattno;
val = heap_getattr(tup, attrno, TD, isnullP);
if (*isnullP)
return (Datum) NULL;
valueP = datumCopy(val,
TD->attrs[attrno - 1]->atttypid,
TD->attrs[attrno - 1]->attbyval,
(Size) TD->attrs[attrno - 1]->attlen);
return valueP;
}
static execution_state *
init_execution_state(FunctionCachePtr fcache,
char *args[])
{
execution_state *newes;
execution_state *nextes;
execution_state *preves;
QueryTreeList *queryTree_list;
int i;
List *planTree_list;
int nargs;
nargs = fcache->nargs;
newes = (execution_state *) palloc(sizeof(execution_state));
nextes = newes;
preves = (execution_state *) NULL;
planTree_list = (List *)
This is the final state of the rule system for 6.4 after the patch is applied: Rewrite rules on relation level work fine now. Event qualifications on insert/update/delete rules work fine now. I added the new keyword OLD to reference the CURRENT tuple. CURRENT will be removed in 6.5. Update rules can reference NEW and OLD in the rule qualification and the actions. Insert/update/delete rules on views can be established to let them behave like real tables. For insert/update/delete rules multiple actions are supported now. The actions can also be surrounded by parantheses to make psql happy. Multiple actions are required if update to a view requires updates to multiple tables. Regular users are permitted to create/drop rules on tables they have RULE permissions for (DefineQueryRewrite() is now able to get around the access restrictions on pg_rewrite). This enables view creation for regular users too. This required an extra boolean parameter to pg_parse_and_plan() that tells to set skipAcl on all rangetable entries of the resulting queries. There is a new function pg_exec_query_acl_override() that could be used by backend utilities to use this facility. All rule actions (not only views) inherit the permissions of the event relations owner. Sample: User A creates tables T1 and T2, creates rules that log INSERT/UPDATE/DELETE on T1 in T2 (like in the regression tests for rules I created) and grants ALL but RULE on T1 to user B. User B can now fully access T1 and the logging happens in T2. But user B cannot access T2 at all, only the rule actions can. And due to missing RULE permissions on T1, user B cannot disable logging. Rules on the attribute level are disabled (they don't work properly and since regular users are now permitted to create rules I decided to disable them). Rules on select must have exactly one action that is a select (so select rules must be a view definition). UPDATE NEW/OLD rules are disabled (still broken, but triggers can do it). There are two new system views (pg_rule and pg_view) that show the definition of the rules or views so the db admin can see what the users do. They use two new functions pg_get_ruledef() and pg_get_viewdef() that are builtins. The functions pg_get_ruledef() and pg_get_viewdef() could be used to implement rule and view support in pg_dump. PostgreSQL is now the only database system I know, that has rewrite rules on the query level. All others (where I found a rule statement at all) use stored database procedures or the like (triggers as we call them) for active rules (as some call them). Future of the rule system: The now disabled parts of the rule system (attribute level, multiple actions on select and update new stuff) require a complete new rewrite handler from scratch. The old one is too badly wired up. After 6.4 I'll start to work on a new rewrite handler, that fully supports the attribute level rules, multiple actions on select and update new. This will be available for 6.5 so we get full rewrite rule capabilities. Jan
1998-08-24 03:38:11 +02:00
pg_parse_and_plan(fcache->src, fcache->argOidVect, nargs, &queryTree_list, None, FALSE);
for (i = 0; i < queryTree_list->len; i++)
{
EState *estate;
Query *queryTree = (Query *) (queryTree_list->qtrees[i]);
Plan *planTree = lfirst(planTree_list);
if (!nextes)
nextes = (execution_state *) palloc(sizeof(execution_state));
if (preves)
preves->next = nextes;
nextes->next = NULL;
nextes->status = F_EXEC_START;
nextes->qd = CreateQueryDesc(queryTree,
planTree,
None);
estate = CreateExecutorState();
if (nargs > 0)
{
int i;
ParamListInfo paramLI;
paramLI =
(ParamListInfo) palloc((nargs + 1) * sizeof(ParamListInfoData));
1997-09-18 22:22:58 +02:00
MemSet(paramLI, 0, nargs * sizeof(ParamListInfoData));
estate->es_param_list_info = paramLI;
for (i = 0; i < nargs; paramLI++, i++)
{
paramLI->kind = PARAM_NUM;
paramLI->id = i + 1;
paramLI->isnull = false;
paramLI->value = (Datum) NULL;
}
paramLI->kind = PARAM_INVALID;
}
else
estate->es_param_list_info = (ParamListInfo) NULL;
nextes->estate = estate;
preves = nextes;
nextes = (execution_state *) NULL;
planTree_list = lnext(planTree_list);
}
return newes;
}
static TupleDesc
postquel_start(execution_state *es)
{
#ifdef FUNC_UTIL_PATCH
/*
* Do nothing for utility commands. (create, destroy...) DZ -
* 30-8-1996
*/
if (es->qd->operation == CMD_UTILITY)
return (TupleDesc) NULL;
#endif
return ExecutorStart(es->qd, es->estate);
}
static TupleTableSlot *
postquel_getnext(execution_state *es)
{
int feature;
#ifdef FUNC_UTIL_PATCH
if (es->qd->operation == CMD_UTILITY)
{
/*
* Process an utility command. (create, destroy...) DZ -
* 30-8-1996
*/
ProcessUtility(es->qd->parsetree->utilityStmt, es->qd->dest);
if (!LAST_POSTQUEL_COMMAND(es))
CommandCounterIncrement();
return (TupleTableSlot *) NULL;
}
#endif
feature = (LAST_POSTQUEL_COMMAND(es)) ? EXEC_RETONE : EXEC_RUN;
return ExecutorRun(es->qd, es->estate, feature, 0);
}
static void
postquel_end(execution_state *es)
{
#ifdef FUNC_UTIL_PATCH
/*
* Do nothing for utility commands. (create, destroy...) DZ -
* 30-8-1996
*/
if (es->qd->operation == CMD_UTILITY)
return;
#endif
ExecutorEnd(es->qd, es->estate);
}
static void
postquel_sub_params(execution_state *es,
int nargs,
char *args[],
bool *nullV)
{
ParamListInfo paramLI;
EState *estate;
estate = es->estate;
paramLI = estate->es_param_list_info;
while (paramLI->kind != PARAM_INVALID)
{
if (paramLI->kind == PARAM_NUM)
{
Assert(paramLI->id <= nargs);
paramLI->value = (Datum) args[(paramLI->id - 1)];
paramLI->isnull = nullV[(paramLI->id - 1)];
}
paramLI++;
}
}
static TupleTableSlot *
copy_function_result(FunctionCachePtr fcache,
TupleTableSlot *resultSlot)
{
TupleTableSlot *funcSlot;
TupleDesc resultTd;
HeapTuple newTuple;
HeapTuple oldTuple;
Assert(!TupIsNull(resultSlot));
oldTuple = resultSlot->val;
funcSlot = (TupleTableSlot *) fcache->funcSlot;
if (funcSlot == (TupleTableSlot *) NULL)
return resultSlot;
resultTd = resultSlot->ttc_tupleDescriptor;
/*
* When the funcSlot is NULL we have to initialize the funcSlot's
* tuple descriptor.
*/
if (TupIsNull(funcSlot))
{
int i = 0;
TupleDesc funcTd = funcSlot->ttc_tupleDescriptor;
1998-11-27 20:52:36 +01:00
while (i < oldTuple->t_data->t_natts)
{
funcTd->attrs[i] =
1998-09-01 05:29:17 +02:00
(Form_pg_attribute) palloc(ATTRIBUTE_TUPLE_SIZE);
memmove(funcTd->attrs[i],
resultTd->attrs[i],
ATTRIBUTE_TUPLE_SIZE);
i++;
}
}
newTuple = heap_copytuple(oldTuple);
return ExecStoreTuple(newTuple, funcSlot, InvalidBuffer, true);
}
static Datum
postquel_execute(execution_state *es,
FunctionCachePtr fcache,
List *fTlist,
char **args,
bool *isNull)
{
TupleTableSlot *slot;
Datum value;
/*
* It's more right place to do it (before
* postquel_start->ExecutorStart). Now
* ExecutorStart->ExecInitIndexScan->ExecEvalParam works ok. (But
* note: I HOPE we can do it here). - vadim 01/22/97
*/
if (fcache->nargs > 0)
postquel_sub_params(es, fcache->nargs, args, fcache->nullVect);
if (es->status == F_EXEC_START)
{
postquel_start(es);
es->status = F_EXEC_RUN;
}
slot = postquel_getnext(es);
if (TupIsNull(slot))
{
postquel_end(es);
es->status = F_EXEC_DONE;
*isNull = true;
/*
* If this isn't the last command for the function we have to
* increment the command counter so that subsequent commands can
* see changes made by previous ones.
*/
if (!LAST_POSTQUEL_COMMAND(es))
CommandCounterIncrement();
return (Datum) NULL;
}
if (LAST_POSTQUEL_COMMAND(es))
{
TupleTableSlot *resSlot;
/*
* Copy the result. copy_function_result is smart enough to do
* nothing when no action is called for. This helps reduce the
* logic and code redundancy here.
*/
resSlot = copy_function_result(fcache, slot);
if (fTlist != NIL)
{
TargetEntry *tle = lfirst(fTlist);
value = ProjectAttribute(resSlot->ttc_tupleDescriptor,
tle,
1998-11-27 20:52:36 +01:00
resSlot->val,
isNull);
}
else
{
value = (Datum) resSlot;
*isNull = false;
}
/*
* If this is a single valued function we have to end the function
* execution now.
*/
if (fcache->oneResult)
{
postquel_end(es);
es->status = F_EXEC_DONE;
}
return value;
}
/*
* If this isn't the last command for the function, we don't return
* any results, but we have to increment the command counter so that
* subsequent commands can see changes made by previous ones.
*/
CommandCounterIncrement();
return (Datum) NULL;
}
Datum
postquel_function(Func *funcNode, char **args, bool *isNull, bool *isDone)
{
execution_state *es;
Datum result = 0;
FunctionCachePtr fcache = funcNode->func_fcache;
CommandId savedId;
/*
* Before we start do anything we must save CurrentScanCommandId to
* restore it before return to upper Executor. Also, we have to set
* CurrentScanCommandId equal to CurrentCommandId. - vadim 08/29/97
*/
savedId = GetScanCommandId();
SetScanCommandId(GetCurrentCommandId());
es = (execution_state *) fcache->func_state;
if (es == NULL)
{
es = init_execution_state(fcache, args);
fcache->func_state = (char *) es;
}
while (es && es->status == F_EXEC_DONE)
es = es->next;
Assert(es);
/*
* Execute each command in the function one after another until we're
* executing the final command and get a result or we run out of
* commands.
*/
while (es != (execution_state *) NULL)
{
result = postquel_execute(es,
fcache,
funcNode->func_tlist,
args,
isNull);
if (es->status != F_EXEC_DONE)
break;
es = es->next;
}
1997-08-29 11:05:57 +02:00
/*
* If we've gone through every command in this function, we are done.
1997-08-29 11:05:57 +02:00
*/
if (es == (execution_state *) NULL)
{
/*
* Reset the execution states to start over again
*/
es = (execution_state *) fcache->func_state;
while (es)
{
es->status = F_EXEC_START;
es = es->next;
}
/*
* Let caller know we're finished.
*/
*isDone = true;
SetScanCommandId(savedId);
return (fcache->oneResult) ? result : (Datum) NULL;
}
1997-08-29 11:05:57 +02:00
/*
* If we got a result from a command within the function it has to be
* the final command. All others shouldn't be returing anything.
1997-08-29 11:05:57 +02:00
*/
Assert(LAST_POSTQUEL_COMMAND(es));
*isDone = false;
SetScanCommandId(savedId);
return result;
}