Commit 43515ba3 authored by Peter Eisentraut's avatar Peter Eisentraut

Remove _deadcode.

parent 739adf32
/*-------------------------------------------------------------------------
*
* recipe.c
* routines for handling execution of Tioga recipes
*
* Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/commands/_deadcode/Attic/recipe.c,v 1.17 2002/06/20 20:29:27 momjian Exp $
*
*-------------------------------------------------------------------------
*/
#include "postgres.h"
#include "catalog/pg_type.h"
#include "commands/recipe.h"
#include "executor/executor.h"
#include "libpq/libpq-be.h"
#include "nodes/execnodes.h"
#include "nodes/makefuncs.h"
#include "nodes/parsenodes.h"
#include "nodes/plannodes.h"
#include "optimizer/planner.h"
#include "parser/parse_node.h"
#include "rewrite/rewriteHandler.h"
#include "rewrite/rewriteManip.h"
#include "tcop/dest.h"
#include "tcop/pquery.h"
#include "utils/builtins.h"
#include "utils/relcache.h"
/* from tcop/postgres.c */
extern CommandDest whereToSendOutput;
#ifndef TIOGA
void
beginRecipe(RecipeStmt *stmt)
{
elog(WARNING, "You must compile with TIOGA defined in order to use recipes\n");
}
#else
#include "tioga/tgRecipe.h"
#define DEBUG_RECIPE 1
/* structure to keep track of the tee node plans */
typedef struct _teePlanInfo
{
char *tpi_relName;
Query *tpi_parsetree;
Plan *tpi_plan;
} TeePlanInfo;
typedef struct _teeInfo
{
int num;
TeePlanInfo *val;
} TeeInfo;
QueryTreeList *appendQlist(QueryTreeList * q1, QueryTreeList * q2);
void OffsetVarAttno(Node *node, int varno, int offset);
static void appendTeeQuery(TeeInfo * teeInfo,
QueryTreeList * q,
char *teeNodeName);
static Plan *replaceTeeScans(Plan *plan,
Query *parsetree,
TeeInfo * teeInfo);
static void replaceSeqScan(Plan *plan,
Plan *parent,
int rt_ind,
Plan *tplan);
static void tg_rewriteQuery(TgRecipe * r, TgNode * n,
QueryTreeList * q,
QueryTreeList * inputQlist);
static Node *tg_replaceNumberedParam(Node *expression,
int pnum,
int rt_ind,
char *teeRelName);
static Node *tg_rewriteParamsInExpr(Node *expression,
QueryTreeList * inputQlist);
static QueryTreeList *tg_parseSubQuery(TgRecipe * r,
TgNode * n,
TeeInfo * teeInfo);
static QueryTreeList *tg_parseTeeNode(TgRecipe * r,
TgNode * n,
int i,
QueryTreeList * qList,
TeeInfo * teeInfo);
/*
The Tioga recipe rewrite algorithm:
To parse a Tioga recipe, we start from an eye node and go backwards through
its input nodes. To rewrite a Tioga node, we do the following:
1) parse the node we're at in the standard way (calling parser() )
2) rewrite its input nodes recursively using Tioga rewrite
3) now, with the rewritten input parse trees and the original parse tree
of the node, we rewrite the the node.
To do the rewrite, we use the target lists, range tables, and
qualifications of the input parse trees
*/
/*
* beginRecipe:
* this is the main function to recipe execution
* this function is invoked for EXECUTE RECIPE ... statements
*
* takes in a RecipeStmt structure from the parser
* and returns a list of cursor names
*/
void
beginRecipe(RecipeStmt *stmt)
{
TgRecipe *r;
int i,
numTees;
QueryTreeList *qList;
char portalName[1024];
Plan *plan;
TupleDesc attinfo;
QueryDesc *queryDesc;
Query *parsetree;
TeeInfo *teeInfo;
/*
* retrieveRecipe() reads the recipe from the database and returns a
* TgRecipe* structure we can work with
*/
r = retrieveRecipe(stmt->recipeName);
if (r == NULL)
return;
/* find the number of tees in the recipe */
numTees = r->tees->num;
if (numTees > 0)
{
/* allocate a teePlan structure */
teeInfo = (TeeInfo *) malloc(sizeof(TeeInfo));
teeInfo->num = numTees;
teeInfo->val = (TeePlanInfo *) malloc(numTees * sizeof(TeePlanInfo));
for (i = 0; i < numTees; i++)
{
teeInfo->val[i].tpi_relName = r->tees->val[i]->nodeName;
teeInfo->val[i].tpi_parsetree = NULL;
teeInfo->val[i].tpi_plan = NULL;
}
}
else
teeInfo = NULL;
/*
* for each viewer in the recipe, go backwards from each viewer input
* and generate a plan. Attach the plan to cursors.
*/
for (i = 0; i < r->eyes->num; i++)
{
TgNodePtr e;
e = r->eyes->val[i];
if (e->inNodes->num > 1)
{
elog(WARNING,
"beginRecipe: Currently eyes cannot have more than one input");
}
if (e->inNodes->num == 0)
{
/* no input to this eye, skip it */
continue;
}
#ifdef DEBUG_RECIPE
elog(WARNING, "beginRecipe: eyes[%d] = %s\n", i, e->nodeName);
#endif /* DEBUG_RECIPE */
qList = tg_parseSubQuery(r, e->inNodes->val[0], teeInfo);
if (qList == NULL)
{
/* eye is directly connected to a tee node */
/* XXX TODO: handle this case */
}
/* now, plan the queries */
/*
* should really do everything pg_plan() does, but for now, we
* skip the rule rewrite and time qual stuff
*/
/*
* 1) plan the main query, everything from an eye node back to a
* Tee
*/
parsetree = qList->qtrees[0];
/*
* before we plan, we want to see all the changes we did, during
* the rewrite phase, such as creating the tee tables,
* CommandCounterIncrement() allows us to see the changes
*/
CommandCounterIncrement();
plan = planner(parsetree);
/*
* 2) plan the tee queries, (subgraphs rooted from a Tee) by the
* time the eye is processed, all tees that contribute to that eye
* will have been included in the teeInfo list
*/
if (teeInfo)
{
int t;
Plan *tplan;
Tee *newplan;
for (t = 0; t < teeInfo->num; t++)
{
if (teeInfo->val[t].tpi_plan == NULL)
{
/* plan it in the usual fashion */
tplan = planner(teeInfo->val[t].tpi_parsetree);
/* now add a tee node to the root of the plan */
elog(WARNING, "adding tee plan node to the root of the %s\n",
teeInfo->val[t].tpi_relName);
newplan = (Tee *) makeNode(Tee);
newplan->plan.targetlist = tplan->targetlist;
newplan->plan.qual = NULL; /* tplan->qual; */
newplan->plan.lefttree = tplan;
newplan->plan.righttree = NULL;
newplan->leftParent = NULL;
newplan->rightParent = NULL;
/*
* the range table of the tee is the range table of
* the tplan
*/
newplan->rtentries = teeInfo->val[t].tpi_parsetree->rtable;
strcpy(newplan->teeTableName,
teeInfo->val[t].tpi_relName);
teeInfo->val[t].tpi_plan = (Plan *) newplan;
}
}
/*
* 3) replace the tee table scans in the main plan with actual
* tee plannodes
*/
plan = replaceTeeScans(plan, parsetree, teeInfo);
} /* if (teeInfo) */
/* define a portal for this viewer input */
/* for now, eyes can only have one input */
snprintf(portalName, 1024, "%s%d", e->nodeName, 0);
queryDesc = CreateQueryDesc(parsetree,
plan,
whereToSendOutput);
/*
* call ExecStart to prepare the plan for execution
*/
attinfo = ExecutorStart(queryDesc, NULL);
ProcessPortal(portalName,
parsetree,
plan,
attinfo,
whereToSendOutput);
elog(WARNING, "beginRecipe: cursor named %s is now available", portalName);
}
}
/*
* tg_rewriteQuery -
* r - the recipe being rewritten
* n - the node that we're current at
* q - a QueryTree List containing the parse tree of the node
* inputQlist - the parsetrees of its input nodes,
* the size of inputQlist must be the same as the
* number of input nodes. Some elements in the inpuQlist
* may be null if the inputs to those nodes are unconnected
*
* this is the main routine for rewriting the recipe queries
* the original query tree 'q' is modified
*/
static void
tg_rewriteQuery(TgRecipe * r,
TgNode * n,
QueryTreeList * q,
QueryTreeList * inputQlist)
{
Query *orig;
Query *inputQ;
int i;
List *rtable;
List *input_rtable;
int rt_length;
/* orig is the original parse tree of the node */
orig = q->qtrees[0];
/*
* step 1:
*
* form a combined range table from all the range tables in the original
* query as well as the input nodes
*
* form a combined qualification from the qual in the original plus the
* quals of the input nodes
*/
/* start with the original range table */
rtable = orig->rtable;
rt_length = length(rtable);
for (i = 0; i < n->inNodes->num; i++)
{
if (n->inNodes->val[i] != NULL &&
n->inNodes->val[i]->nodeType != TG_TEE_NODE)
{
inputQ = inputQlist->qtrees[i];
input_rtable = inputQ->rtable;
/*
* need to offset the var nodes in the qual and targetlist
* because they are indexed off the original rtable
*/
OffsetVarNodes((Node *) inputQ->qual, rt_length, 0);
OffsetVarNodes((Node *) inputQ->targetList, rt_length, 0);
/* append the range tables from the children nodes */
rtable = nconc(rtable, input_rtable);
/*
* append the qualifications of the child node into the
* original qual list
*/
AddQual(orig, inputQ->qual);
}
}
orig->rtable = rtable;
/*
* step 2: rewrite the target list of the original parse tree if there
* are any references to params, replace them with the appropriate
* target list entry of the children node
*/
if (orig->targetList != NIL)
{
List *tl;
TargetEntry *tle;
foreach(tl, orig->targetList)
{
tle = lfirst(tl);
if (tle->resdom != NULL)
tle->expr = tg_rewriteParamsInExpr(tle->expr, inputQlist);
}
}
/*
* step 3: rewrite the qual of the original parse tree if there are
* any references to params, replace them with the appropriate target
* list entry of the children node
*/
if (orig->qual)
{
if (nodeTag(orig->qual) == T_List)
elog(ERROR, "tg_rewriteQuery: Whoa! why is my qual a List???");
orig->qual = tg_rewriteParamsInExpr(orig->qual, inputQlist);
}
/*
* at this point, we're done with the rewrite, the querytreelist q has
* been modified
*/
}
/* tg_replaceNumberedParam:
this procedure replaces the specified numbered param with a
reference to a range table
this procedure recursively calls itself
it returns a (possibly modified) Node*.
*/
static Node *
tg_replaceNumberedParam(Node *expression,
int pnum, /* the number of the parameter */
int rt_ind, /* the range table index */
char *teeRelName) /* the relname of the tee
* table */
{
TargetEntry *param_tle;
Param *p;
Var *newVar,
*oldVar;
if (expression == NULL)
return NULL;
switch (nodeTag(expression))
{
case T_Param:
{
/*
* the node is a parameter, substitute the entry from the
* target list of the child that corresponds to the
* parameter number
*/
p = (Param *) expression;
/* we only deal with the case of numbered parameters */
if (p->paramkind == PARAM_NUM && p->paramid == pnum)
{
if (p->param_tlist)
{
/*
* we have a parameter with an attribute like
* $N.foo so replace it with a new var node
*/
/* param tlist can only have one entry in them! */
param_tle = (TargetEntry *) (lfirst(p->param_tlist));
oldVar = (Var *) param_tle->expr;
oldVar->varno = rt_ind;
oldVar->varnoold = rt_ind;
return (Node *) oldVar;
}
else
{
/* we have $N without the .foo */
bool defined;
bool isRel;
/*
* TODO here, we need to check to see whether the
* type of the tee is a complex type (relation) or
* a simple type
*/
/*
* if it is a simple type, then we need to get the
* "result" attribute from the tee relation
*/
isRel = (typeidTypeRelid(p->paramtype) != 0);
if (isRel)
{
newVar = makeVar(rt_ind,
0, /* the whole tuple */
TypeGet(teeRelName, &defined),
-1,
0,
rt_ind,
0);
return (Node *) newVar;
}
else
newVar = makeVar(rt_ind,
1, /* just the first field,
* which is 'result' */
TypeGet(teeRelName, &defined),
-1,
0,
rt_ind,
0);
return (Node *) newVar;
}
}
else
elog(WARNING, "tg_replaceNumberedParam: unexpected paramkind value of %d", p->paramkind);
}
break;
case T_Expr:
{
/*
* the node is an expression, we need to recursively call
* ourselves until we find parameter nodes
*/
List *l;
Expr *expr = (Expr *) expression;
List *newArgs;
/*
* we have to make a new args lists because Params can be
* replaced by Var nodes in tg_replaceNumberedParam()
*/
newArgs = NIL;
/*
* we only care about argument to expressions, it doesn't
* matter when the opType is
*/
/* recursively rewrite the arguments of this expression */
foreach(l, expr->args)
{
newArgs = lappend(newArgs,
tg_replaceNumberedParam(lfirst(l),
pnum,
rt_ind,
teeRelName));
}
/* change the arguments of the expression */
expr->args = newArgs;
}
break;
default:
{
/* ignore other expr types */
}
}
return expression;
}
/* tg_rewriteParamsInExpr:
rewrite the params in expressions by using the targetlist entries
from the input parsetrees
this procedure recursively calls itself
it returns a (possibly modified) Node*.
*/
static Node *
tg_rewriteParamsInExpr(Node *expression, QueryTreeList * inputQlist)
{
List *tl;
TargetEntry *param_tle,
*tle;
Param *p;
int childno;
char *resname;
if (expression == NULL)
return NULL;
switch (nodeTag(expression))
{
case T_Param:
{
/*
* the node is a parameter, substitute the entry from the
* target list of the child that corresponds to the
* parameter number
*/
p = (Param *) expression;
/* we only deal with the case of numbered parameters */
if (p->paramkind == PARAM_NUM)
{
/* paramid's start from 1 */
childno = p->paramid - 1;
if (p->param_tlist)
{
/*
* we have a parameter with an attribute like
* $N.foo so match the resname "foo" against the
* target list of the (N-1)th inputQlist
*/
/* param tlist can only have one entry in them! */
param_tle = (TargetEntry *) (lfirst(p->param_tlist));
resname = param_tle->resdom->resname;
if (inputQlist->qtrees[childno])
{
foreach(tl, inputQlist->qtrees[childno]->targetList)
{
tle = lfirst(tl);
if (strcmp(resname, tle->resdom->resname) == 0)
return tle->expr;
}
}
else
elog(ERROR, "tg_rewriteParamsInExpr:can't substitute for parameter %d when that input is unconnected", p->paramid);
}
else
{
/* we have $N without the .foo */
/* use the first resdom in the targetlist of the */
/* appropriate child query */
tl = inputQlist->qtrees[childno]->targetList;
tle = lfirst(tl);
return tle->expr;
}
}
else
elog(WARNING, "tg_rewriteParamsInExpr: unexpected paramkind value of %d", p->paramkind);
}
break;
case T_Expr:
{
/*
* the node is an expression, we need to recursively call
* ourselves until we find parameter nodes
*/
List *l;
Expr *expr = (Expr *) expression;
List *newArgs;
/*
* we have to make a new args lists because Params can be
* replaced by Var nodes in tg_rewriteParamsInExpr()
*/
newArgs = NIL;
/*
* we only care about argument to expressions, it doesn't
* matter when the opType is
*/
/* recursively rewrite the arguments of this expression */
foreach(l, expr->args)
{
newArgs = lappend(newArgs,
tg_rewriteParamsInExpr(lfirst(l), inputQlist));
}
/* change the arguments of the expression */
expr->args = newArgs;
}
break;
default:
{
/* ignore other expr types */
}
}
return expression;
}
/*
getParamTypes:
given an element, finds its parameter types.
the typev array argument is set to the parameter types.
the parameterCount is returned
this code is very similar to ProcedureDefine() in pg_proc.c
*/
static int
getParamTypes(TgElement * elem, Oid *typev)
{
/* this code is similar to ProcedureDefine() */
int16 parameterCount;
bool defined;
Oid toid;
char *t;
int i,
j;
parameterCount = 0;
for (i = 0; i < FUNC_MAX_ARGS; i++)
typev[i] = 0;
for (j = 0; j < elem->inTypes->num; j++)
{
if (parameterCount == FUNC_MAX_ARGS)
{
elog(ERROR,
"getParamTypes: Ingredients cannot take > %d arguments", FUNC_MAX_ARGS);
}
t = elem->inTypes->val[j];
if (strcmp(t, "opaque") == 0)
{
elog(ERROR,
"getParamTypes: Ingredient functions cannot take type 'opaque'");
}
else
{
toid = TypeGet(elem->inTypes->val[j], &defined);
if (!OidIsValid(toid))
elog(ERROR, "getParamTypes: arg type '%s' is not defined", t);
if (!defined)
elog(WARNING, "getParamTypes: arg type '%s' is only a shell", t);
}
typev[parameterCount++] = toid;
}
return parameterCount;
}
/*
* tg_parseTeeNode
*
* handles the parsing of the tee node
*
*
*/
static QueryTreeList *
tg_parseTeeNode(TgRecipe * r,
TgNode * n, /* the tee node */
int i, /* which input this node is to its parent */
QueryTreeList * qList,
TeeInfo * teeInfo)
{
QueryTreeList *q;
char *tt;
int rt_ind;
Query *orig;
/*
* the input Node is a tee node, so we need to do the following: we
* need to parse the child of the tee node, we add that to our query
* tree list we need the name of the tee node table the tee node table
* is the table into which the tee node may materialize results. Call
* it TT we add a range table to our existing query with TT in it we
* need to replace the parameter $i with TT (otherwise the optimizer
* won't know to use the table on expression containining $i) After
* that rewrite, the optimizer will generate sequential scans of TT
*
* Later, in the glue phase, we replace all instances of TT sequential
* scans with the actual Tee node
*/
q = tg_parseSubQuery(r, n, teeInfo);
/* tt is the name of the tee node table */
tt = n->nodeName;
if (q)
appendTeeQuery(teeInfo, q, tt);
orig = qList->qtrees[0];
rt_ind = RangeTablePosn(orig->rtable, tt);
/*
* check to see that this table is not part of the range table
* already. This usually only happens if multiple inputs are
* connected to the same Tee.
*/
if (rt_ind == 0)
{
orig->rtable = lappend(orig->rtable,
addRangeTableEntry(NULL,
tt,
tt,
FALSE,
FALSE));
rt_ind = length(orig->rtable);
}
orig->qual = tg_replaceNumberedParam(orig->qual,
i + 1, /* params start at 1 */
rt_ind,
tt);
return qList;
}
/*
* tg_parseSubQuery:
* go backwards from a node and parse the query
*
* the result parse tree is passed back
*
* could return NULL if trying to parse a teeNode
* that's already been processed by another parent
*
*/
static QueryTreeList *
tg_parseSubQuery(TgRecipe * r, TgNode * n, TeeInfo * teeInfo)
{
TgElement *elem;
char *funcName;
Oid typev[FUNC_MAX_ARGS], /* eight arguments maximum */
relid;
int i,
parameterCount;
QueryTreeList *qList; /* the parse tree of the nodeElement */
QueryTreeList *inputQlist; /* the list of parse trees for the inputs
* to this node */
QueryTreeList *q;
TgNode *child;
Relation rel;
unsigned int len;
TupleDesc tupdesc;
qList = NULL;
if (n->nodeType == TG_INGRED_NODE)
{
/* parse each ingredient node in turn */
elem = n->nodeElem;
switch (elem->srcLang)
{
case TG_SQL:
{
/*
* for SQL ingredients, the SQL query is contained in
* the 'src' field
*/
#ifdef DEBUG_RECIPE
elog(WARNING, "calling parser with %s", elem->src);
#endif /* DEBUG_RECIPE */
parameterCount = getParamTypes(elem, typev);
qList = parser(elem->src, typev, parameterCount);
if (qList->len > 1)
{
elog(WARNING,
"tg_parseSubQuery: parser produced > 1 query tree");
}
}
break;
case TG_C:
{
/* C ingredients are registered functions in postgres */
/*
* we create a new query string by using the function
* name (found in the 'src' field) and adding
* parameters to it so if the function was FOOBAR and
* took in two arguments, we would create a string
* select FOOBAR($1,$2)
*/
char newquery[1000];
funcName = elem->src;
parameterCount = getParamTypes(elem, typev);
if (parameterCount > 0)
{
int i;
snprintf(newquery, 1000, "select %s($1", funcName);
for (i = 1; i < parameterCount; i++)
snprintf(newquery, 1000, "%s,$%d", pstrdup(newquery), i);
snprintf(newquery, 1000, "%s)", pstrdup(newquery));
}
else
snprintf(newquery, 1000, "select %s()", funcName);
#ifdef DEBUG_RECIPE
elog(WARNING, "calling parser with %s", newquery);
#endif /* DEBUG_RECIPE */
qList = parser(newquery, typev, parameterCount);
if (qList->len > 1)
{
elog(WARNING,
"tg_parseSubQuery: parser produced > 1 query tree");
}
}
break;
case TG_RECIPE_GRAPH:
elog(WARNING, "tg_parseSubQuery: can't parse recipe graph ingredients yet!");
break;
case TG_COMPILED:
elog(WARNING, "tg_parseSubQuery: can't parse compiled ingredients yet!");
break;
default:
elog(WARNING, "tg_parseSubQuery: unknown srcLang: %d", elem->srcLang);
}
/* parse each of the subrecipes that are input to this node */
if (n->inNodes->num > 0)
{
inputQlist = malloc(sizeof(QueryTreeList));
inputQlist->len = n->inNodes->num + 1;
inputQlist->qtrees = (Query **) malloc(inputQlist->len * sizeof(Query *));
for (i = 0; i < n->inNodes->num; i++)
{
inputQlist->qtrees[i] = NULL;
if (n->inNodes->val[i])
{
if (n->inNodes->val[i]->nodeType == TG_TEE_NODE)
{
qList = tg_parseTeeNode(r, n->inNodes->val[i],
i, qList, teeInfo);
}
else
{ /* input node is not a Tee */
q = tg_parseSubQuery(r, n->inNodes->val[i],
teeInfo);
Assert(q->len == 1);
inputQlist->qtrees[i] = q->qtrees[0];
}
}
}
/* now, we have all the query trees from our input nodes */
/* transform the original parse tree appropriately */
tg_rewriteQuery(r, n, qList, inputQlist);
}
}
else if (n->nodeType == TG_EYE_NODE)
{
/*
* if we hit an eye, we need to stop and make what we have into a
* subrecipe query block
*/
elog(WARNING, "tg_parseSubQuery: can't handle eye nodes yet");
}
else if (n->nodeType == TG_TEE_NODE)
{
/*
* if we hit a tee, check to see if the parsing has been done for
* this tee already by the other parent
*/
rel = RelationNameGetRelation(n->nodeName);
if (RelationIsValid(rel))
{
/*
* this tee has already been visited, no need to do any
* further processing
*/
return NULL;
}
else
{
/* we need to process the child of the tee first, */
child = n->inNodes->val[0];
if (child->nodeType == TG_TEE_NODE)
{
/* nested Tee nodes */
qList = tg_parseTeeNode(r, child, 0, qList, teeInfo);
return qList;
}
Assert(child != NULL);
/* parse the input node */
q = tg_parseSubQuery(r, child, teeInfo);
Assert(q->len == 1);
/* add the parsed query to the main list of queries */
qList = appendQlist(qList, q);
/* need to create the tee table here */
/*
* the tee table created is used both for materializing the
* values at the tee node, and for parsing and optimization.
* The optimization needs to have a real table before it will
* consider scans on it
*/
/*
* first, find the type of the tuples being produced by the
* tee. The type is the same as the output type of the child
* node.
*
* NOTE: we are assuming that the child node only has a single
* output here!
*/
getParamTypes(child->nodeElem, typev);
/*
* the output type is either a complex type, (and is thus a
* relation) or is a simple type
*/
rel = RelationNameGetRelation(child->nodeElem->outTypes->val[0]);
if (RelationIsValid(rel))
{
/*
* for complex types, create new relation with the same
* tuple descriptor as the output table type
*/
len = length(q->qtrees[0]->targetList);
tupdesc = rel->rd_att;
relid = heap_create_with_catalog(
child->nodeElem->outTypes->val[0],
tupdesc, RELKIND_RELATION, false);
}
else
{
/*
* we have to create a relation with one attribute of the
* simple base type. That attribute will have an attr
* name of "result"
*/
/* NOTE: ignore array types for the time being */
len = 1;
tupdesc = CreateTemplateTupleDesc(len);
if (!TupleDescInitEntry(tupdesc, 1,
"result",
InvalidOid,
-1, 0, false))
elog(WARNING, "tg_parseSubQuery: unexpected result from TupleDescInitEntry");
else
{
relid = heap_create_with_catalog(
child->nodeElem->outTypes->val[0],
tupdesc, RELKIND_RELATION, false);
}
}
}
}
else if (n->nodeType == TG_RECIPE_NODE)
elog(WARNING, "tg_parseSubQuery: can't handle embedded recipes yet!");
else
elog(WARNING, "unknown nodeType: %d", n->nodeType);
return qList;
}
/*
* OffsetVarAttno -
* recursively find all the var nodes with the specified varno
* and offset their varattno with the offset
*
* code is similar to OffsetVarNodes in rewriteManip.c
*/
void
OffsetVarAttno(Node *node, int varno, int offset)
{
if (node == NULL)
return;
switch (nodeTag(node))
{
case T_TargetEntry:
{
TargetEntry *tle = (TargetEntry *) node;
OffsetVarAttno(tle->expr, varno, offset);
}
break;
case T_Expr:
{
Expr *expr = (Expr *) node;
OffsetVarAttno((Node *) expr->args, varno, offset);
}
break;
case T_Var:
{
Var *var = (Var *) node;
if (var->varno == varno)
var->varattno += offset;
}
break;
case T_List:
{
List *l;
foreach(l, (List *) node)
OffsetVarAttno(lfirst(l), varno, offset);
}
break;
default:
/* ignore the others */
break;
}
}
/*
* appendQlist
* add the contents of a QueryTreeList q2 to the end of the QueryTreeList
* q1
*
* returns a new querytree list
*/
QueryTreeList *
appendQlist(QueryTreeList * q1, QueryTreeList * q2)
{
QueryTreeList *newq;
int i,
j;
int newlen;
if (q1 == NULL)
return q2;
if (q2 == NULL)
return q1;
newlen = q1->len + q2->len;
newq = (QueryTreeList *) malloc(sizeof(QueryTreeList));
newq->len = newlen;
newq->qtrees = (Query **) malloc(newlen * sizeof(Query *));
for (i = 0; i < q1->len; i++)
newq->qtrees[i] = q1->qtrees[i];
for (j = 0; j < q2->len; j++)
newq->qtrees[i + j] = q2->qtrees[j];
return newq;
}
/*
* appendTeeQuery
*
* modify the query field of the teeInfo list of the particular tee node
*/
static void
appendTeeQuery(TeeInfo * teeInfo, QueryTreeList * q, char *teeNodeName)
{
int i;
Assert(teeInfo);
for (i = 0; i < teeInfo->num; i++)
{
if (strcmp(teeInfo->val[i].tpi_relName, teeNodeName) == 0)
{
Assert(q->len == 1);
teeInfo->val[i].tpi_parsetree = q->qtrees[0];
return;
}
}
elog(WARNING, "appendTeeQuery: teeNodeName '%s' not found in teeInfo");
}
/*
* replaceSeqScan
* replaces sequential scans of a specified relation with the tee plan
* the relation is specified by its index in the range table, rt_ind
*
* returns the modified plan
* the offset_attno is the offset that needs to be added to the parent's
* qual or targetlist because the child plan has been replaced with a tee node
*/
static void
replaceSeqScan(Plan *plan, Plan *parent,
int rt_ind, Plan *tplan)
{
Scan *snode;
Tee *teePlan;
Result *newPlan;
if (plan == NULL)
return;
if (plan->type == T_SeqScan)
{
snode = (Scan *) plan;
if (snode->scanrelid == rt_ind)
{
/*
* found the sequential scan that should be replaced with the
* tplan.
*/
/* we replace the plan, but we also need to modify its parent */
/*
* replace the sequential scan with a Result node the reason
* we use a result node is so that we get the proper
* projection behavior. The Result node is simply (ab)used as
* a projection node
*/
newPlan = makeNode(Result);
newPlan->plan.cost = 0.0;
newPlan->plan.state = (EState *) NULL;
newPlan->plan.targetlist = plan->targetlist;
newPlan->plan.lefttree = tplan;
newPlan->plan.righttree = NULL;
newPlan->resconstantqual = NULL;
newPlan->resstate = NULL;
/* change all the varno's to 1 */
ChangeVarNodes((Node *) newPlan->plan.targetlist,
snode->scanrelid, 1);
if (parent)
{
teePlan = (Tee *) tplan;
if (parent->lefttree == plan)
parent->lefttree = (Plan *) newPlan;
else
parent->righttree = (Plan *) newPlan;
if (teePlan->leftParent == NULL)
teePlan->leftParent = (Plan *) newPlan;
else
teePlan->rightParent = (Plan *) newPlan;
/* comment for now to test out executor-stuff
if (parent->state) {
ExecInitNode((Plan*)newPlan, parent->state, (Plan*)newPlan);
}
*/
}
}
}
else
{
if (plan->lefttree)
replaceSeqScan(plan->lefttree, plan, rt_ind, tplan);
if (plan->righttree)
replaceSeqScan(plan->righttree, plan, rt_ind, tplan);
}
}
/*
* replaceTeeScans
* places the sequential scans of the Tee table with
* a connection to the actual tee plan node
*/
static Plan *
replaceTeeScans(Plan *plan, Query *parsetree, TeeInfo * teeInfo)
{
int i;
List *rtable;
RangeTblEntry *rte;
char prefix[5];
int rt_ind;
Plan *tplan;
rtable = parsetree->rtable;
if (rtable == NULL)
return plan;
/*
* look through the range table for the tee relation entry, that will
* give use the varno we need to detect which sequential scans need to
* be replaced with tee nodes
*/
rt_ind = 0;
while (rtable != NIL)
{
rte = lfirst(rtable);
rtable = lnext(rtable);
rt_ind++; /* range table references in varno fields
* start w/ 1 */
/*
* look for the "tee_" prefix in the refname, also check to see
* that the relname and the refname are the same this should
* eliminate any user-specified table and leave us with the tee
* table entries only
*/
if ((strlen(rte->refname) < 4) ||
(strcmp(rte->relname, rte->refname) != 0))
continue;
StrNCpy(prefix, rte->refname, 5);
if (strcmp(prefix, "tee_") == 0)
{
/* okay, we found a tee node entry in the range table */
/* find the appropriate plan in the teeInfo list */
tplan = NULL;
for (i = 0; i < teeInfo->num; i++)
{
if (strcmp(teeInfo->val[i].tpi_relName,
rte->refname) == 0)
tplan = teeInfo->val[i].tpi_plan;
}
if (tplan == NULL)
elog(WARNING, "replaceTeeScans didn't find the corresponding tee plan");
/*
* replace the sequential scan node with that var number with
* the tee plan node
*/
replaceSeqScan(plan, NULL, rt_ind, tplan);
}
}
return plan;
}
#endif /* TIOGA */
/*-------------------------------------------------------------------------
*
* version.c
* This file contains all the rules that govern all version semantics.
*
* Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* The version stuff has not been tested under postgres95 and probably
* doesn't work! - jolly 8/19/95
*
*
* $Id: version.c,v 1.30 2002/06/20 20:29:27 momjian Exp $
*
* NOTES
* At the point the version is defined, 2 physical relations are created
* <vname>_added and <vname>_deleted.
*
* In addition, 4 rules are defined which govern the semantics of
* versions w.r.t retrieves, appends, replaces and deletes.
*
*-------------------------------------------------------------------------
*/
#include "postgres.h"
#define MAX_QUERY_LEN 1024
char rule_buf[MAX_QUERY_LEN];
/*
* problem: the version system assumes that the rules it declares will
* be fired in the order of declaration, it also assumes
* goh's silly instead semantics. Unfortunately, it is a pain
* to make the version system work with the new semantics.
* However the whole problem can be solved, and some nice
* functionality can be achieved if we get multiple action rules
* to work. So thats what I did -- glass
*
* Well, at least they've been working for about 20 minutes.
*
* So any comments in this code about 1 rule per transction are false...:)
*
*/
/*
* This is needed because the rule system only allows
* *1* rule to be defined per transaction.
*
* NOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO
* OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO
* OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO
* OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO
* OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO
* OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO
* OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO
* OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO
* OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO
* OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO
* OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO
* OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO
* OOOOOOOOOOOOOOOOOOO!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
*
* DONT DO THAT!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
*
* If you commit the current Xact all the palloced memory GOES AWAY
* and could be re-palloced in the new Xact and the whole hell breaks
* loose and poor people like me spend 2 hours of their live chassing
* a strange memory bug instead of watching the "Get Smart" marathon
* in NICK !
* DO NOT COMMIT THE XACT, just increase the Cid counter!
* _sp.
*/
#ifdef NOT_USED
static void
eval_as_new_xact(char *query)
{
/*------
* WARNING! do not uncomment the following lines WARNING!
*
* CommitTransactionCommand();
* StartTransactionCommand();
*------
*/
CommandCounterIncrement();
pg_exec_query(query);
}
#endif
/*
* Define a version.
*/
#ifdef NOT_USED
void
DefineVersion(char *name, char *fromRelname, char *date)
{
char *bname;
static char saved_basename[512];
static char saved_snapshot[512];
if (date == NULL)
{
/* no time ranges */
bname = fromRelname;
strcpy(saved_basename, (char *) bname);
*saved_snapshot = (char) NULL;
}
else
{
/* version is a snapshot */
bname = fromRelname;
strcpy(saved_basename, (char *) bname);
sprintf(saved_snapshot, "['%s']", date);
}
/*
* Calls the routine ``GetAttrList'' get the list of attributes from
* the base relation. Code is put here so that we only need to look up
* the attribute once for both appends and replaces.
*/
setAttrList(bname);
VersionCreate(name, saved_basename);
VersionAppend(name, saved_basename);
VersionDelete(name, saved_basename, saved_snapshot);
VersionReplace(name, saved_basename, saved_snapshot);
VersionRetrieve(name, saved_basename, saved_snapshot);
}
#endif
/*
* Creates the deltas.
*/
#ifdef NOT_USED
void
VersionCreate(char *vname, char *bname)
{
static char query_buf[MAX_QUERY_LEN];
/*
* Creating the dummy version relation for triggering rules.
*/
sprintf(query_buf, "SELECT * INTO TABLE %s from %s where 1 =2",
vname, bname);
pg_exec_query(query_buf);
/*
* Creating the ``v_added'' relation
*/
sprintf(query_buf, "SELECT * INTO TABLE %s_added from %s where 1 = 2",
vname, bname);
eval_as_new_xact(query_buf);
/*
* Creating the ``v_deleted'' relation.
*/
sprintf(query_buf, "CREATE TABLE %s_del (DOID oid)", vname);
eval_as_new_xact(query_buf);
}
#endif
/*
* Given the relation name, does a catalog lookup for that relation and
* sets the global variable 'attr_list' with the list of attributes (names)
* for that relation.
*/
#ifdef NOT_USED
static void
setAttrList(char *bname)
{
Relation rel;
int i = 0;
int maxattrs = 0;
char *attrname;
char temp_buf[512];
int notfirst = 0;
rel = heap_openr(bname);
if (rel == NULL)
{
elog(ERROR, "Unable to expand all -- amopenr failed ");
return;
}
maxattrs = RelationGetNumberOfAttributes(rel);
attr_list[0] = '\0';
for (i = maxattrs - 1; i > -1; --i)
{
attrname = NameStr(rel->rd_att->attrs[i]->attname);
if (notfirst == 1)
sprintf(temp_buf, ", %s = new.%s", attrname, attrname);
else
{
sprintf(temp_buf, "%s = new.%s", attrname, attrname);
notfirst = 1;
}
strcat(attr_list, temp_buf);
}
heap_close(rel);
return;
}
#endif
/*
* This routine defines the rule governing the append semantics of
* versions. All tuples appended to a version gets appended to the
* <vname>_added relation.
*/
#ifdef NOT_USED
static void
VersionAppend(char *vname, char *bname)
{
sprintf(rule_buf,
"define rewrite rule %s_append is on INSERT to %s do instead append %s_added(%s)",
vname, vname, vname, attr_list);
eval_as_new_xact(rule_buf);
}
#endif
/*
* This routine defines the rule governing the retrieval semantics of
* versions. To retrieve tuples from a version , we need to:
*
* 1. Retrieve all tuples in the <vname>_added relation.
* 2. Retrieve all tuples in the base relation which are not in
* the <vname>_del relation.
*/
#ifdef NOT_USED
void
VersionRetrieve(char *vname, char *bname, char *snapshot)
{
sprintf(rule_buf,
"define rewrite rule %s_retrieve is on SELECT to %s do instead\n\
SELECT %s_1.oid, %s_1.* from _%s in %s%s, %s_1 in (%s_added | _%s) \
where _%s.oid !!= '%s_del.DOID'",
vname, vname, vname, vname, bname,
bname, snapshot,
vname, vname, bname, bname, vname);
eval_as_new_xact(rule_buf);
/* printf("%s\n",rule_buf); */
}
#endif
/*
* This routine defines the rules that govern the delete semantics of
* versions. Two things happens when we delete a tuple from a version:
*
* 1. If the tuple to be deleted was added to the version *after*
* the version was created, then we simply delete the tuple
* from the <vname>_added relation.
* 2. If the tuple to be deleted is actually in the base relation,
* then we have to mark that tuple as being deleted by adding
* it to the <vname>_del relation.
*/
#ifdef NOT_USED
void
VersionDelete(char *vname, char *bname, char *snapshot)
{
sprintf(rule_buf,
"define rewrite rule %s_delete1 is on delete to %s do instead\n \
[delete %s_added where current.oid = %s_added.oid\n \
append %s_del(DOID = current.oid) from _%s in %s%s \
where current.oid = _%s.oid] \n",
vname, vname, vname, vname, vname,
bname, bname, snapshot, bname);
eval_as_new_xact(rule_buf);
#ifdef OLD_REWRITE
sprintf(rule_buf,
"define rewrite rule %s_delete2 is on delete to %s do instead \n \
append %s_del(DOID = current.oid) from _%s in %s%s \
where current.oid = _%s.oid \n",
vname, vname, vname, bname, bname, snapshot, bname);
eval_as_new_xact(rule_buf);
#endif /* OLD_REWRITE */
}
#endif
/*
* This routine defines the rules that govern the update semantics
* of versions. To update a tuple in a version:
*
* 1. If the tuple is in <vname>_added, we simply ``replace''
* the tuple (as per postgres style).
* 2. if the tuple is in the base relation, then two things have to
* happen:
* 2.1 The tuple is marked ``deleted'' from the base relation by
* adding the tuple to the <vname>_del relation.
* 2.2 A copy of the tuple is appended to the <vname>_added relation
*/
#ifdef NOT_USED
void
VersionReplace(char *vname, char *bname, char *snapshot)
{
sprintf(rule_buf,
"define rewrite rule %s_replace1 is on replace to %s do instead \n\
[replace %s_added(%s) where current.oid = %s_added.oid \n\
append %s_del(DOID = current.oid) from _%s in %s%s \
where current.oid = _%s.oid\n\
append %s_added(%s) from _%s in %s%s \
where current.oid !!= '%s_added.oid' and current.oid = _%s.oid]\n",
vname, vname, vname, attr_list, vname,
vname, bname, bname, snapshot, bname,
vname, attr_list, bname, bname, snapshot, vname, bname);
eval_as_new_xact(rule_buf);
/* printf("%s\n",rule_buf); */
#ifdef OLD_REWRITE
sprintf(rule_buf,
"define rewrite rule %s_replace2 is on replace to %s do \n\
append %s_del(DOID = current.oid) from _%s in %s%s \
where current.oid = _%s.oid\n",
vname, vname, vname, bname, bname, snapshot, bname);
eval_as_new_xact(rule_buf);
sprintf(rule_buf,
"define rewrite rule %s_replace3 is on replace to %s do instead\n\
append %s_added(%s) from _%s in %s%s \
where current.oid !!= '%s_added.oid' and current.oid = \
_%s.oid\n",
vname, vname, vname, attr_list, bname, bname, snapshot, vname, bname);
eval_as_new_xact(rule_buf);
#endif /* OLD_REWRITE */
/* printf("%s\n",rule_buf); */
}
#endif
/*-------------------------------------------------------------------------
*
* nodeTee.c
*
*
* Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* DESCRIPTION
* This code provides support for a tee node, which allows
* multiple parent in a megaplan.
*
* INTERFACE ROUTINES
* ExecTee
* ExecInitTee
* ExecEndTee
*
* $Id: nodeTee.c,v 1.12 2002/06/20 20:29:28 momjian Exp $
*
*-------------------------------------------------------------------------
*/
#include <sys/types.h>
#include <sys/file.h>
#include "postgres.h"
#include "access/heapam.h"
#include "catalog/catalog.h"
#include "catalog/heap.h"
#include "executor/executor.h"
#include "executor/nodeTee.h"
#include "optimizer/internal.h"
#include "storage/bufmgr.h"
#include "storage/smgr.h"
#include "tcop/pquery.h"
#include "utils/relcache.h"
/* ------------------------------------------------------------------
* ExecInitTee
*
* Create tee state
*
* ------------------------------------------------------------------
*/
bool
ExecInitTee(Tee * node, EState *currentEstate, Plan *parent)
{
TeeState *teeState;
Plan *outerPlan;
int len;
Relation bufferRel;
TupleDesc tupType;
EState *estate;
/*
* it is possible that the Tee has already been initialized since it
* can be reached by multiple parents. If it is already initialized,
* simply return and do not initialize the children nodes again
*/
if (node->plan.state)
return TRUE;
/*
* assign the node's execution state
*/
/*
* make a new executor state, because we have a different
* es_range_table
*/
/* node->plan.state = estate;*/
estate = CreateExecutorState();
estate->es_direction = currentEstate->es_direction;
estate->es_BaseId = currentEstate->es_BaseId;
estate->es_BaseId = currentEstate->es_BaseId;
estate->es_tupleTable = currentEstate->es_tupleTable;
estate->es_refcount = currentEstate->es_refcount;
estate->es_junkFilter = currentEstate->es_junkFilter;
estate->es_snapshot = currentEstate->es_snapshot;
/*
* use the range table for Tee subplan since the range tables for the
* two parents may be different
*/
if (node->rtentries)
estate->es_range_table = node->rtentries;
else
estate->es_range_table = currentEstate->es_range_table;
node->plan.state = estate;
/*
* create teeState structure
*/
teeState = makeNode(TeeState);
teeState->tee_leftPlace = 0;
teeState->tee_rightPlace = 0;
teeState->tee_lastPlace = 0;
teeState->tee_bufferRel = NULL;
teeState->tee_leftScanDesc = NULL;
teeState->tee_rightScanDesc = NULL;
node->teestate = teeState;
/* ----------------
* Miscellanious initialization
*
* + assign node's base_id
* + assign debugging hooks and
* + create expression context for node
* ----------------
*/
ExecAssignNodeBaseInfo(estate, &(teeState->cstate), parent);
ExecAssignExprContext(estate, &(teeState->cstate));
#define TEE_NSLOTS 2
/*
* initialize tuple slots
*/
ExecInitResultTupleSlot(estate, &(teeState->cstate));
/* initialize child nodes */
outerPlan = outerPlan((Plan *) node);
ExecInitNode(outerPlan, estate, (Plan *) node);
/*
* the tuple type info is from the outer plan of this node the result
* type is also the same as the outerplan
*/
ExecAssignResultTypeFromOuterPlan((Plan *) node, &(teeState->cstate));
ExecAssignProjectionInfo((Plan *) node, &teeState->cstate);
/*
* initialize temporary relation to buffer tuples
*/
tupType = ExecGetResultType(&(teeState->cstate));
len = ExecTargetListLength(((Plan *) node)->targetlist);
/*
* create a catalogued relation even though this is a temporary
* relation
*/
/* cleanup of catalogued relations is easier to do */
if (node->teeTableName[0] != '\0')
{
Relation r;
teeState->tee_bufferRelname = pstrdup(node->teeTableName);
/*
* we are given an tee table name, if a relation by that name
* exists, then we open it, else we create it and then open it
*/
r = RelationNameGetRelation(teeState->tee_bufferRelname);
if (RelationIsValid(r))
bufferRel = heap_openr(teeState->tee_bufferRelname);
else
bufferRel = heap_open(
heap_create_with_catalog(teeState->tee_bufferRelname,
tupType, RELKIND_RELATION, false));
}
else
{
sprintf(teeState->tee_bufferRelname,
"ttemp_%d", /* 'ttemp' for 'tee' temporary */
newoid());
bufferRel = heap_open(
heap_create_with_catalog(teeState->tee_bufferRelname,
tupType, RELKIND_RELATION, false));
}
teeState->tee_bufferRel = bufferRel;
/*
* initialize a memory context for allocating thing like scan
* descriptors
*/
/*
* we do this so that on cleanup of the tee, we can free things. if we
* didn't have our own memory context, we would be in the memory
* context of the portal that we happen to be using at the moment
*/
teeState->tee_mcxt = (MemoryContext) CreateGlobalMemory(teeState->tee_bufferRelname);
/*
* don't initialize the scan descriptors here because it's not good to
* initialize scan descriptors on empty rels. Wait until the scan
* descriptors are needed before initializing them.
*/
teeState->tee_leftScanDesc = NULL;
teeState->tee_rightScanDesc = NULL;
return TRUE;
}
int
ExecCountSlotsTee(Tee * node)
{
/* Tee nodes can't have innerPlans */
return ExecCountSlotsNode(outerPlan(node)) + TEE_NSLOTS;
}
/* ----------------------------------------------------------------
initTeeScanDescs
initializes the left and right scandescs on the temporary
relation of a Tee node
must open two separate scan descriptors,
because the left and right scans may be at different points
* ----------------------------------------------------------------
*/
static void
initTeeScanDescs(Tee * node)
{
TeeState *teeState;
Relation bufferRel;
ScanDirection dir;
Snapshot snapshot;
MemoryContext orig;
teeState = node->teestate;
if (teeState->tee_leftScanDesc && teeState->tee_rightScanDesc)
return;
orig = CurrentMemoryContext;
MemoryContextSwitchTo(teeState->tee_mcxt);
bufferRel = teeState->tee_bufferRel;
dir = ((Plan *) node)->state->es_direction; /* backwards not handled
* yet XXX */
snapshot = ((Plan *) node)->state->es_snapshot;
if (teeState->tee_leftScanDesc == NULL)
{
teeState->tee_leftScanDesc = heap_beginscan(bufferRel,
ScanDirectionIsBackward(dir),
snapshot,
0, /* num scan keys */
NULL /* scan keys */
);
}
if (teeState->tee_rightScanDesc == NULL)
{
teeState->tee_rightScanDesc = heap_beginscan(bufferRel,
ScanDirectionIsBackward(dir),
snapshot,
0, /* num scan keys */
NULL /* scan keys */
);
}
MemoryContextSwitchTo(orig);
}
/* ----------------------------------------------------------------
* ExecTee(node)
*
*
* A Tee serves to connect a subplan to multiple parents.
* the subplan is always the outplan of the Tee node.
*
* The Tee gets requests from either leftParent or rightParent,
* fetches the result tuple from the child, and then
* stored the result into a temporary relation (serving as a queue).
* leftPlace and rightPlace keep track of where the left and rightParents
* are.
* If a parent requests a tuple and that parent is not at the end
* of the temporary relation, then the request is satisfied from
* the queue instead of by executing the child plan
*
* ----------------------------------------------------------------
*/
TupleTableSlot *
ExecTee(Tee * node, Plan *parent)
{
EState *estate;
TeeState *teeState;
int leftPlace,
rightPlace,
lastPlace;
int branch;
TupleTableSlot *result;
TupleTableSlot *slot;
Plan *childNode;
ScanDirection dir;
HeapTuple heapTuple;
Relation bufferRel;
HeapScanDesc scanDesc;
estate = ((Plan *) node)->state;
teeState = node->teestate;
leftPlace = teeState->tee_leftPlace;
rightPlace = teeState->tee_rightPlace;
lastPlace = teeState->tee_lastPlace;
bufferRel = teeState->tee_bufferRel;
childNode = outerPlan(node);
dir = estate->es_direction;
/* XXX doesn't handle backwards direction yet */
if (parent == node->leftParent)
branch = leftPlace;
else if ((parent == node->rightParent) || (parent == (Plan *) node))
/*
* the tee node could be the root node of the plan, in which case,
* we treat it like a right-parent pull
*/
branch = rightPlace;
else
{
elog(ERROR, "A Tee node can only be executed from its left or right parent\n");
return NULL;
}
if (branch == lastPlace)
{ /* we're at the end of the queue already,
* - get a new tuple from the child plan,
* - store it in the queue, - increment
* lastPlace, - increment leftPlace or
* rightPlace as appropriate, - and return
* result */
slot = ExecProcNode(childNode, (Plan *) node);
if (!TupIsNull(slot))
{
/*
* heap_insert changes something...
*/
if (slot->ttc_buffer != InvalidBuffer)
heapTuple = heap_copytuple(slot->val);
else
heapTuple = slot->val;
/* insert into temporary relation */
heap_insert(bufferRel, heapTuple);
if (slot->ttc_buffer != InvalidBuffer)
heap_freetuple(heapTuple);
/*
* once there is data in the temporary relation, ensure that
* the left and right scandescs are initialized
*/
initTeeScanDescs(node);
scanDesc = (parent == node->leftParent) ?
teeState->tee_leftScanDesc : teeState->tee_rightScanDesc;
{
/*
* move the scandesc forward so we don't re-read this
* tuple later
*/
HeapTuple throwAway;
/* Buffer buffer; */
throwAway = heap_getnext(scanDesc, ScanDirectionIsBackward(dir));
}
/*
* set the shouldFree field of the child's slot so that when
* the child's slot is free'd, this tuple isn't free'd also
*/
/*
* does this mean this tuple has to be garbage collected
* later??
*/
slot->ttc_shouldFree = false;
teeState->tee_lastPlace = lastPlace + 1;
}
result = slot;
}
else
{ /* the desired data already exists in the
* temporary relation */
scanDesc = (parent == node->leftParent) ?
teeState->tee_leftScanDesc : teeState->tee_rightScanDesc;
heapTuple = heap_getnext(scanDesc, ScanDirectionIsBackward(dir));
/*
* Increase the pin count on the buffer page, because the tuple
* stored in the slot also points to it (as well as the scan
* descriptor). If we don't, ExecStoreTuple will decrease the pin
* count on the next iteration.
*/
if (scanDesc->rs_cbuf != InvalidBuffer)
IncrBufferRefCount(scanDesc->rs_cbuf);
slot = teeState->cstate.cs_ResultTupleSlot;
slot->ttc_tupleDescriptor = RelationGetDescr(bufferRel);
result = ExecStoreTuple(heapTuple, /* tuple to store */
slot, /* slot to store in */
scanDesc->rs_cbuf, /* this tuple's buffer */
false); /* don't free stuff from
* heap_getnext */
}
if (parent == node->leftParent)
teeState->tee_leftPlace = leftPlace + 1;
else
teeState->tee_rightPlace = rightPlace + 1;
return result;
}
/* ---------------------------------------------------------------------
* ExecEndTee
*
* End the Tee node, and free up any storage
* since a Tee node can be downstream of multiple parent nodes,
* only free when both parents are done
* --------------------------------------------------------------------
*/
void
ExecEndTee(Tee * node, Plan *parent)
{
EState *estate;
TeeState *teeState;
int leftPlace,
rightPlace,
lastPlace;
Relation bufferRel;
MemoryContext orig;
estate = ((Plan *) node)->state;
teeState = node->teestate;
leftPlace = teeState->tee_leftPlace;
rightPlace = teeState->tee_rightPlace;
lastPlace = teeState->tee_lastPlace;
if (!node->leftParent || parent == node->leftParent)
leftPlace = -1;
if (!node->rightParent || parent == node->rightParent)
rightPlace = -1;
if (parent == (Plan *) node)
rightPlace = leftPlace = -1;
teeState->tee_leftPlace = leftPlace;
teeState->tee_rightPlace = rightPlace;
if ((leftPlace == -1) && (rightPlace == -1))
{
/* remove the temporary relations */
/* and close the scan descriptors */
bufferRel = teeState->tee_bufferRel;
if (bufferRel)
{
heap_drop(bufferRel);
teeState->tee_bufferRel = NULL;
if (teeState->tee_mcxt)
{
orig = CurrentMemoryContext;
MemoryContextSwitchTo(teeState->tee_mcxt);
}
else
orig = 0;
if (teeState->tee_leftScanDesc)
{
heap_endscan(teeState->tee_leftScanDesc);
teeState->tee_leftScanDesc = NULL;
}
if (teeState->tee_rightScanDesc)
{
heap_endscan(teeState->tee_rightScanDesc);
teeState->tee_rightScanDesc = NULL;
}
if (teeState->tee_mcxt)
{
MemoryContextSwitchTo(orig);
teeState->tee_mcxt = NULL;
}
}
}
}
/*-------------------------------------------------------------------------
*
* predmig.c
*
*
* Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/optimizer/path/_deadcode/Attic/predmig.c,v 1.15 2002/06/20 20:29:30 momjian Exp $
*
*-------------------------------------------------------------------------
*/
/*
** DESCRIPTION
** Main Routines to handle Predicate Migration (i.e. correct optimization
** of queries with expensive functions.)
**
** The reasoning behind some of these algorithms is rather detailed.
** Have a look at Sequoia Tech Report 92/13 for more info. Also
** see Monma and Sidney's paper "Sequencing with Series-Parallel
** Precedence Constraints", in "Mathematics of Operations Research",
** volume 4 (1979), pp. 215-224.
**
** The main thing that this code does that wasn't handled in xfunc.c is
** it considers the possibility that two joins in a stream may not
** be ordered by ascending rank -- in such a scenario, it may be optimal
** to pullup more restrictions than we did via xfunc_try_pullup.
**
** This code in some sense generalizes xfunc_try_pullup; if you
** run postgres -x noprune, you'll turn off xfunc_try_pullup, and this
** code will do everything that xfunc_try_pullup would have, and maybe
** more. However, this results in no pruning, which may slow down the
** optimizer and/or cause the system to run out of memory.
** -- JMH, 11/13/92
*/
#include "nodes/pg_list.h"
#include "nodes/nodes.h"
#include "nodes/primnodes.h"
#include "nodes/relation.h"
#include "optimizer/pathnode.h"
#include "optimizer/internal.h"
#include "optimizer/cost.h"
#include "optimizer/keys.h"
#include "optimizer/tlist.h"
#define is_clause(node) (get_cinfo(node)) /* a stream node
* represents a clause
* (not a join) iff it has
* a non-NULL cinfo field */
static void xfunc_predmig(JoinPath pathnode, Stream streamroot,
Stream laststream, bool *progressp);
static bool xfunc_series_llel(Stream stream);
static bool xfunc_llel_chains(Stream root, Stream bottom);
static Stream xfunc_complete_stream(Stream stream);
static bool xfunc_prdmig_pullup(Stream origstream, Stream pullme,
JoinPath joinpath);
static void xfunc_form_groups(Stream root, Stream bottom);
static void xfunc_free_stream(Stream root);
static Stream xfunc_add_clauses(Stream current);
static void xfunc_setup_group(Stream node, Stream bottom);
static Stream xfunc_streaminsert(RestrictInfo restrictinfo, Stream current,
int clausetype);
static int xfunc_num_relids(Stream node);
static StreamPtr xfunc_get_downjoin(Stream node);
static StreamPtr xfunc_get_upjoin(Stream node);
static Stream xfunc_stream_qsort(Stream root, Stream bottom);
static int xfunc_stream_compare(void *arg1, void *arg2);
static bool xfunc_check_stream(Stream node);
static bool xfunc_in_stream(Stream node, Stream stream);
/* ----------------- MAIN FUNCTIONS ------------------------ */
/*
** xfunc_do_predmig
** wrapper for Predicate Migration. It calls xfunc_predmig until no
** more progress is made.
** return value says if any changes were ever made.
*/
bool
xfunc_do_predmig(Path root)
{
bool progress,
changed = false;
if (is_join(root))
do
{
progress = false;
Assert(IsA(root, JoinPath));
xfunc_predmig((JoinPath) root, (Stream) NULL, (Stream) NULL,
&progress);
if (changed && progress)
elog(DEBUG, "Needed to do a second round of predmig!\n");
if (progress)
changed = true;
} while (progress);
return changed;
}
/*
** xfunc_predmig
** The main routine for Predicate Migration. It traverses a join tree,
** and for each root-to-leaf path in the plan tree it constructs a
** "Stream", which it passes to xfunc_series_llel for optimization.
** Destructively modifies the join tree (via predicate pullup).
*/
static void
xfunc_predmig(JoinPath pathnode, /* root of the join tree */
Stream streamroot,
Stream laststream,/* for recursive calls -- these are the
* root of the stream under construction,
* and the lowest node created so far */
bool *progressp)
{
Stream newstream;
/*
* * traverse the join tree dfs-style, constructing a stream as you
* go. * When you hit a scan node, pass the stream off to
* xfunc_series_llel.
*/
/* sanity check */
if ((!streamroot && laststream) ||
(streamroot && !laststream))
elog(ERROR, "called xfunc_predmig with bad inputs");
if (streamroot)
Assert(xfunc_check_stream(streamroot));
/* add path node to stream */
newstream = RMakeStream();
if (!streamroot)
streamroot = newstream;
set_upstream(newstream, (StreamPtr) laststream);
if (laststream)
set_downstream(laststream, (StreamPtr) newstream);
set_downstream(newstream, (StreamPtr) NULL);
set_pathptr(newstream, (pathPtr) pathnode);
set_cinfo(newstream, (RestrictInfo) NULL);
set_clausetype(newstream, XFUNC_UNKNOWN);
/* base case: we're at a leaf, call xfunc_series_llel */
if (!is_join(pathnode))
{
/* form a fleshed-out copy of the stream */
Stream fullstream = xfunc_complete_stream(streamroot);
/* sort it via series-llel */
if (xfunc_series_llel(fullstream))
*progressp = true;
/* free up the copy */
xfunc_free_stream(fullstream);
}
else
{
/* visit left child */
xfunc_predmig((JoinPath) get_outerjoinpath(pathnode),
streamroot, newstream, progressp);
/* visit right child */
xfunc_predmig((JoinPath) get_innerjoinpath(pathnode),
streamroot, newstream, progressp);
}
/* remove this node */
if (get_upstream(newstream))
set_downstream((Stream) get_upstream(newstream), (StreamPtr) NULL);
pfree(newstream);
}
/*
** xfunc_series_llel
** A flavor of Monma and Sidney's Series-Parallel algorithm.
** Traverse stream downwards. When you find a node with restrictions on it,
** call xfunc_llel_chains on the substream from root to that node.
*/
static bool
xfunc_series_llel(Stream stream)
{
Stream temp,
next;
bool progress = false;
for (temp = stream; temp != (Stream) NULL; temp = next)
{
next = (Stream) xfunc_get_downjoin(temp);
/*
* * if there are restrictions/secondary join clauses above this *
* node, call xfunc_llel_chains
*/
if (get_upstream(temp) && is_clause((Stream) get_upstream(temp)))
if (xfunc_llel_chains(stream, temp))
progress = true;
}
return progress;
}
/*
** xfunc_llel_chains
** A flavor of Monma and Sidney's Parallel Chains algorithm.
** Given a stream which has been well-ordered except for its lowermost
** restrictions/2-ary joins, pull up the restrictions/2-arys as appropriate.
** What that means here is to form groups in the chain above the lowest
** join node above bottom inclusive, and then take all the restrictions
** following bottom, and try to pull them up as far as possible.
*/
static bool
xfunc_llel_chains(Stream root, Stream bottom)
{
bool progress = false;
Stream origstream;
Stream tmpstream,
pathstream;
Stream rootcopy = root;
Assert(xfunc_check_stream(root));
/* xfunc_prdmig_pullup will need an unmodified copy of the stream */
origstream = (Stream) copyObject((Node) root);
/* form groups among ill-ordered nodes */
xfunc_form_groups(root, bottom);
/* sort chain by rank */
Assert(xfunc_in_stream(bottom, root));
rootcopy = xfunc_stream_qsort(root, bottom);
/*
* * traverse sorted stream -- if any restriction has moved above a
* join, * we must pull it up in the plan. That is, make plan tree *
* reflect order of sorted stream.
*/
for (tmpstream = rootcopy,
pathstream = (Stream) xfunc_get_downjoin(rootcopy);
tmpstream != (Stream) NULL && pathstream != (Stream) NULL;
tmpstream = (Stream) get_downstream(tmpstream))
{
if (is_clause(tmpstream)
&& get_pathptr(pathstream) != get_pathptr(tmpstream))
{
/*
* * If restriction moved above a Join after sort, we pull it *
* up in the join plan. * If restriction moved down, we
* ignore it. * This is because Joey's Sequoia paper proves
* that * restrictions should never move down. If this * one
* were moved down, it would violate "semantic correctness", *
* i.e. it would be lower than the attributes it references.
*/
Assert(xfunc_num_relids(pathstream) > xfunc_num_relids(tmpstream));
progress = xfunc_prdmig_pullup(origstream, tmpstream,
(JoinPath) get_pathptr(pathstream));
}
if (get_downstream(tmpstream))
pathstream = (Stream) xfunc_get_downjoin((Stream) get_downstream(tmpstream));
}
/* free up origstream */
xfunc_free_stream(origstream);
return progress;
}
/*
** xfunc_complete_stream
** Given a stream composed of join nodes only, make a copy containing the
** join nodes along with the associated restriction nodes.
*/
static Stream
xfunc_complete_stream(Stream stream)
{
Stream tmpstream,
copystream,
curstream = (Stream) NULL;
copystream = (Stream) copyObject((Node) stream);
Assert(xfunc_check_stream(copystream));
curstream = copystream;
Assert(!is_clause(curstream));
/* curstream = (Stream)xfunc_get_downjoin(curstream); */
while (curstream != (Stream) NULL)
{
xfunc_add_clauses(curstream);
curstream = (Stream) xfunc_get_downjoin(curstream);
}
/* find top of stream and return it */
for (tmpstream = copystream; get_upstream(tmpstream) != (StreamPtr) NULL;
tmpstream = (Stream) get_upstream(tmpstream))
/* no body in for loop */ ;
return tmpstream;
}
/*
** xfunc_prdmig_pullup
** pullup a clause in a path above joinpath. Since the JoinPath tree
** doesn't have upward pointers, it's difficult to deal with. Thus we
** require the original stream, which maintains pointers to all the path
** nodes. We use the original stream to find out what joins are
** above the clause.
*/
static bool
xfunc_prdmig_pullup(Stream origstream, Stream pullme, JoinPath joinpath)
{
RestrictInfo restrictinfo = get_cinfo(pullme);
bool progress = false;
Stream upjoin,
orignode,
temp;
int whichchild;
/* find node in origstream that contains clause */
for (orignode = origstream;
orignode != (Stream) NULL
&& get_cinfo(orignode) != restrictinfo;
orignode = (Stream) get_downstream(orignode))
/* empty body in for loop */ ;
if (!orignode)
elog(ERROR, "Didn't find matching node in original stream");
/* pull up this node as far as it should go */
for (upjoin = (Stream) xfunc_get_upjoin(orignode);
upjoin != (Stream) NULL
&& (JoinPath) get_pathptr((Stream) xfunc_get_downjoin(upjoin))
!= joinpath;
upjoin = (Stream) xfunc_get_upjoin(upjoin))
{
#ifdef DEBUG
elog(DEBUG, "pulling up in xfunc_predmig_pullup!");
#endif
/* move clause up in path */
if (get_pathptr((Stream) get_downstream(upjoin))
== (pathPtr) get_outerjoinpath((JoinPath) get_pathptr(upjoin)))
whichchild = OUTER;
else
whichchild = INNER;
restrictinfo = xfunc_pullup((Path) get_pathptr((Stream) get_downstream(upjoin)),
(JoinPath) get_pathptr(upjoin),
restrictinfo,
whichchild,
get_clausetype(orignode));
set_pathptr(pullme, get_pathptr(upjoin));
/* pullme has been moved into locrestrictinfo */
set_clausetype(pullme, XFUNC_LOCPRD);
/*
* * xfunc_pullup makes new path nodes for children of *
* get_pathptr(current). We must modify the stream nodes to point *
* to these path nodes
*/
if (whichchild == OUTER)
{
for (temp = (Stream) get_downstream(upjoin); is_clause(temp);
temp = (Stream) get_downstream(temp))
set_pathptr
(temp, (pathPtr)
get_outerjoinpath((JoinPath) get_pathptr(upjoin)));
set_pathptr
(temp,
(pathPtr) get_outerjoinpath((JoinPath) get_pathptr(upjoin)));
}
else
{
for (temp = (Stream) get_downstream(upjoin); is_clause(temp);
temp = (Stream) get_downstream(temp))
set_pathptr
(temp, (pathPtr)
get_innerjoinpath((JoinPath) get_pathptr(upjoin)));
set_pathptr
(temp, (pathPtr)
get_innerjoinpath((JoinPath) get_pathptr(upjoin)));
}
progress = true;
}
if (!progress)
elog(DEBUG, "didn't succeed in pulling up in xfunc_prdmig_pullup");
return progress;
}
/*
** xfunc_form_groups
** A group is a pair of stream nodes a,b such that a is constrained to
** precede b (for instance if a and b are both joins), but rank(a) > rank(b).
** In such a situation, Monma and Sidney prove that no clauses should end
** up between a and b, and therefore we may treat them as a group, with
** selectivity equal to the product of their selectivities, and cost
** equal to the cost of the first plus the selectivity of the first times the
** cost of the second. We define each node to be in a group by itself,
** and then repeatedly find adjacent groups which are ordered by descending
** rank, and make larger groups. You know that two adjacent nodes are in a
** group together if the lower has groupup set to true. They will both have
** the same groupcost and groupsel (since they're in the same group!)
*/
static void
xfunc_form_groups(Query *queryInfo, Stream root, Stream bottom)
{
Stream temp,
parent;
int lowest = xfunc_num_relids((Stream) xfunc_get_upjoin(bottom));
bool progress;
LispValue primjoin;
int whichchild;
if (!lowest)
return; /* no joins in stream, so no groups */
/* initialize groups to be single nodes */
for (temp = root;
temp != (Stream) NULL && temp != bottom;
temp = (Stream) get_downstream(temp))
{
/* if a Join node */
if (!is_clause(temp))
{
if (get_pathptr((Stream) get_downstream(temp))
== (pathPtr) get_outerjoinpath((JoinPath) get_pathptr(temp)))
whichchild = OUTER;
else
whichchild = INNER;
set_groupcost(temp,
xfunc_join_expense((JoinPath) get_pathptr(temp),
whichchild));
if (primjoin = xfunc_primary_join((JoinPath) get_pathptr(temp)))
{
set_groupsel(temp,
compute_clause_selec(queryInfo,
primjoin, NIL));
}
else
set_groupsel(temp, 1.0);
}
else
/* a restriction, or 2-ary join pred */
{
set_groupcost(temp,
xfunc_expense(queryInfo,
get_clause(get_cinfo(temp))));
set_groupsel(temp,
compute_clause_selec(queryInfo,
get_clause(get_cinfo(temp)),
NIL));
}
set_groupup(temp, false);
}
/* make passes upwards, forming groups */
do
{
progress = false;
for (temp = (Stream) get_upstream(bottom);
temp != (Stream) NULL;
temp = (Stream) get_upstream(temp))
{
/* check for grouping with node upstream */
if (!get_groupup(temp) && /* not already grouped */
(parent = (Stream) get_upstream(temp)) != (Stream) NULL &&
/* temp is a join or temp is the top of a group */
(is_join((Path) get_pathptr(temp)) ||
get_downstream(temp) &&
get_groupup((Stream) get_downstream(temp))) &&
get_grouprank(parent) < get_grouprank(temp))
{
progress = true; /* we formed a new group */
set_groupup(temp, true);
set_groupcost(temp,
get_groupcost(temp) +
get_groupsel(temp) * get_groupcost(parent));
set_groupsel(temp, get_groupsel(temp) * get_groupsel(parent));
/* fix costs and sels of all members of group */
xfunc_setup_group(temp, bottom);
}
}
} while (progress);
}
/* ------------------- UTILITY FUNCTIONS ------------------------- */
/*
** xfunc_free_stream
** walk down a stream and pfree it
*/
static void
xfunc_free_stream(Stream root)
{
Stream cur,
next;
Assert(xfunc_check_stream(root));
if (root != (Stream) NULL)
for (cur = root; cur != (Stream) NULL; cur = next)
{
next = (Stream) get_downstream(cur);
pfree(cur);
}
}
/*
** xfunc_add<_clauses
** find any clauses above current, and insert them into stream as
** appropriate. Return uppermost clause inserted, or current if none.
*/
static Stream
xfunc_add_clauses(Stream current)
{
Stream topnode = current;
LispValue temp;
LispValue primjoin;
/* first add in the local clauses */
foreach(temp, get_loc_restrictinfo((Path) get_pathptr(current)))
{
topnode = xfunc_streaminsert((RestrictInfo) lfirst(temp), topnode,
XFUNC_LOCPRD);
}
/* and add in the join clauses */
if (IsA(get_pathptr(current), JoinPath))
{
primjoin = xfunc_primary_join((JoinPath) get_pathptr(current));
foreach(temp, get_pathrestrictinfo((JoinPath) get_pathptr(current)))
{
if (!equal(get_clause((RestrictInfo) lfirst(temp)), primjoin))
topnode = xfunc_streaminsert((RestrictInfo) lfirst(temp), topnode,
XFUNC_JOINPRD);
}
}
return topnode;
}
/*
** xfunc_setup_group
** find all elements of stream that are grouped with node and are above
** bottom, and set their groupcost and groupsel to be the same as node's.
*/
static void
xfunc_setup_group(Stream node, Stream bottom)
{
Stream temp;
if (node != bottom)
/* traverse downwards */
for (temp = (Stream) get_downstream(node);
temp != (Stream) NULL && temp != bottom;
temp = (Stream) get_downstream(temp))
{
if (!get_groupup(temp))
break;
else
{
set_groupcost(temp, get_groupcost(node));
set_groupsel(temp, get_groupsel(node));
}
}
/* traverse upwards */
for (temp = (Stream) get_upstream(node); temp != (Stream) NULL;
temp = (Stream) get_upstream(temp))
{
if (!get_groupup((Stream) get_downstream(temp)))
break;
else
{
set_groupcost(temp, get_groupcost(node));
set_groupsel(temp, get_groupsel(node));
}
}
}
/*
** xfunc_streaminsert
** Make a new Stream node to hold clause, and insert it above current.
** Return new node.
*/
static Stream
xfunc_streaminsert(RestrictInfo restrictinfo,
Stream current,
int clausetype) /* XFUNC_LOCPRD or XFUNC_JOINPRD */
{
Stream newstream = RMakeStream();
set_upstream(newstream, get_upstream(current));
if (get_upstream(current))
set_downstream((Stream) (get_upstream(current)), (StreamPtr) newstream);
set_upstream(current, (StreamPtr) newstream);
set_downstream(newstream, (StreamPtr) current);
set_pathptr(newstream, get_pathptr(current));
set_cinfo(newstream, restrictinfo);
set_clausetype(newstream, clausetype);
return newstream;
}
/*
** Given a Stream node, find the number of relids referenced in the pathnode
** associated with the stream node. The number of relids gives a unique
** ordering on the joins in a stream, which we use to compare the height of
** join nodes.
*/
static int
xfunc_num_relids(Stream node)
{
if (!node || !IsA(get_pathptr(node), JoinPath))
return 0;
else
return (length
(get_relids(get_parent((JoinPath) get_pathptr(node)))));
}
/*
** xfunc_get_downjoin
** Given a stream node, find the next lowest node which points to a
** join predicate or a scan node.
*/
static StreamPtr
xfunc_get_downjoin(Stream node)
{
Stream temp;
if (!is_clause(node)) /* if this is a join */
node = (Stream) get_downstream(node);
for (temp = node; temp && is_clause(temp);
temp = (Stream) get_downstream(temp))
/* empty body in for loop */ ;
return (StreamPtr) temp;
}
/*
** xfunc_get_upjoin
** same as above, but upwards.
*/
static StreamPtr
xfunc_get_upjoin(Stream node)
{
Stream temp;
if (!is_clause(node)) /* if this is a join */
node = (Stream) get_upstream(node);
for (temp = node; temp && is_clause(temp);
temp = (Stream) get_upstream(temp))
/* empty body in for loop */ ;
return (StreamPtr) temp;
}
/*
** xfunc_stream_qsort
** Given a stream, sort by group rank the elements in the stream from the
** node "bottom" up. DESTRUCTIVELY MODIFIES STREAM! Returns new root.
*/
static Stream
xfunc_stream_qsort(Stream root, Stream bottom)
{
int i;
size_t num;
Stream *nodearray,
output;
Stream tmp;
/* find size of list */
for (num = 0, tmp = root; tmp != bottom;
tmp = (Stream) get_downstream(tmp))
num++;
if (num <= 1)
return root;
/* copy elements of the list into an array */
nodearray = (Stream *) palloc(num * sizeof(Stream));
for (tmp = root, i = 0; tmp != bottom;
tmp = (Stream) get_downstream(tmp), i++)
nodearray[i] = tmp;
/* sort the array */
qsort(nodearray, num, sizeof(LispValue), xfunc_stream_compare);
/* paste together the array elements */
output = nodearray[num - 1];
set_upstream(output, (StreamPtr) NULL);
for (i = num - 2; i >= 0; i--)
{
set_downstream(nodearray[i + 1], (StreamPtr) nodearray[i]);
set_upstream(nodearray[i], (StreamPtr) nodearray[i + 1]);
}
set_downstream(nodearray[0], (StreamPtr) bottom);
if (bottom)
set_upstream(bottom, (StreamPtr) nodearray[0]);
Assert(xfunc_check_stream(output));
return output;
}
/*
** xfunc_stream_compare
** comparison function for xfunc_stream_qsort.
** Compare nodes by group rank. If group ranks are equal, ensure that
** join nodes appear in same order as in plan tree.
*/
static int
xfunc_stream_compare(void *arg1, void *arg2)
{
Stream stream1 = *(Stream *) arg1;
Stream stream2 = *(Stream *) arg2;
Cost rank1,
rank2;
rank1 = get_grouprank(stream1);
rank2 = get_grouprank(stream2);
if (rank1 > rank2)
return 1;
else if (rank1 < rank2)
return -1;
else
{
if (is_clause(stream1) && is_clause(stream2))
return 0; /* doesn't matter what order if both are
* restrictions */
else if (!is_clause(stream1) && !is_clause(stream2))
{
if (xfunc_num_relids(stream1) < xfunc_num_relids(stream2))
return -1;
else
return 1;
}
else if (is_clause(stream1) && !is_clause(stream2))
{
if (xfunc_num_relids(stream1) == xfunc_num_relids(stream2))
/* stream1 is a restriction over stream2 */
return 1;
else
return -1;
}
else if (!is_clause(stream1) && is_clause(stream2))
{
/* stream2 is a restriction over stream1: never push down */
return -1;
}
}
}
/* ------------------ DEBUGGING ROUTINES ---------------------------- */
/*
** Make sure all pointers in stream make sense. Make sure no joins are
** out of order.
*/
static bool
xfunc_check_stream(Stream node)
{
Stream temp;
int numrelids,
tmp;
/* set numrelids higher than max */
if (!is_clause(node))
numrelids = xfunc_num_relids(node) + 1;
else if (xfunc_get_downjoin(node))
numrelids = xfunc_num_relids((Stream) xfunc_get_downjoin(node)) + 1;
else
numrelids = 1;
for (temp = node; get_downstream(temp); temp = (Stream) get_downstream(temp))
{
if ((Stream) get_upstream((Stream) get_downstream(temp)) != temp)
{
elog(ERROR, "bad pointers in stream");
return false;
}
if (!is_clause(temp))
{
if ((tmp = xfunc_num_relids(temp)) >= numrelids)
{
elog(ERROR, "Joins got reordered!");
return false;
}
numrelids = tmp;
}
}
return true;
}
/*
** xfunc_in_stream
** check if node is in stream
*/
static bool
xfunc_in_stream(Stream node, Stream stream)
{
Stream temp;
for (temp = stream; temp; temp = (Stream) get_downstream(temp))
if (temp == node)
return 1;
return 0;
}
/*-------------------------------------------------------------------------
*
* xfunc.c
* Utility routines to handle expensive function optimization.
* Includes xfunc_trypullup(), which attempts early pullup of predicates
* to allow for maximal pruning.
*
* Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/optimizer/path/_deadcode/Attic/xfunc.c,v 1.19 2002/06/20 20:29:30 momjian Exp $
*
*-------------------------------------------------------------------------
*/
#include <math.h>
#ifdef HAVE_VALUES_H
#include <values.h>
#endif
#include "postgres.h"
#include "access/heapam.h"
#include "access/htup.h"
#include "catalog/pg_language.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_type.h"
#include "lib/lispsort.h"
#include "nodes/nodes.h"
#include "nodes/pg_list.h"
#include "nodes/primnodes.h"
#include "nodes/relation.h"
#include "optimizer/clauses.h"
#include "optimizer/cost.h"
#include "optimizer/internal.h"
#include "optimizer/keys.h"
#include "optimizer/pathnode.h"
#include "optimizer/tlist.h"
#include "storage/buf_internals.h"
#include "tcop/dest.h"
#include "utils/syscache.h"
#define ever ; 1 ;
/* local funcs */
static int xfunc_card_unreferenced(Query *queryInfo,
Expr *clause, Relids referenced);
*/
/*
** xfunc_trypullup
** Preliminary pullup of predicates, to allow for maximal pruning.
** Given a relation, check each of its paths and see if you can
** pullup clauses from its inner and outer.
*/
void
xfunc_trypullup(RelOptInfo rel)
{
LispValue y; /* list ptr */
RestrictInfo maxcinfo; /* The RestrictInfo to pull up, as
* calculated by xfunc_shouldpull() */
JoinPath curpath; /* current path in list */
int progress; /* has progress been made this time
* through? */
int clausetype;
do
{
progress = false; /* no progress yet in this iteration */
foreach(y, get_pathlist(rel))
{
curpath = (JoinPath) lfirst(y);
/*
* * for each operand, attempt to pullup predicates until
* first * failure.
*/
for (ever)
{
/* No, the following should NOT be '==' !! */
if (clausetype = xfunc_shouldpull((Path) get_innerjoinpath(curpath),
curpath, INNER, &maxcinfo))
{
xfunc_pullup((Path) get_innerjoinpath(curpath),
curpath, maxcinfo, INNER, clausetype);
progress = true;
}
else
break;
}
for (ever)
{
/* No, the following should NOT be '==' !! */
if (clausetype = xfunc_shouldpull((Path) get_outerjoinpath(curpath),
curpath, OUTER, &maxcinfo))
{
xfunc_pullup((Path) get_outerjoinpath(curpath),
curpath, maxcinfo, OUTER, clausetype);
progress = true;
}
else
break;
}
/*
* * make sure the unpruneable flag bubbles up, i.e. * if
* anywhere below us in the path pruneable is false, * then
* pruneable should be false here
*/
if (get_pruneable(get_parent(curpath)) &&
(!get_pruneable(get_parent
((Path) get_innerjoinpath(curpath))) ||
!get_pruneable(get_parent((Path)
get_outerjoinpath(curpath)))))
{
set_pruneable(get_parent(curpath), false);
progress = true;
}
}
} while (progress);
}
/*
** xfunc_shouldpull
** find clause with highest rank, and decide whether to pull it up
** from child to parent. Currently we only pullup secondary join clauses
** that are in the pathrestrictinfo. Secondary hash and sort clauses are
** left where they are.
** If we find an expensive function but decide *not* to pull it up,
** we'd better set the unpruneable flag. -- JMH, 11/11/92
**
** Returns: 0 if nothing left to pullup
** XFUNC_LOCPRD if a local predicate is to be pulled up
** XFUNC_JOINPRD if a secondary join predicate is to be pulled up
*/
int
xfunc_shouldpull(Query *queryInfo,
Path childpath,
JoinPath parentpath,
int whichchild,
RestrictInfo *maxcinfopt) /* Out: pointer to clause
* to pullup */
{
LispValue clauselist,
tmplist; /* lists of clauses */
RestrictInfo maxcinfo; /* clause to pullup */
LispValue primjoinclause /* primary join clause */
= xfunc_primary_join(parentpath);
Cost tmprank,
maxrank = (-1 * MAXFLOAT); /* ranks of clauses */
Cost joinselec = 0; /* selectivity of the join predicate */
Cost joincost = 0; /* join cost + primjoinclause cost */
int retval = XFUNC_LOCPRD;
clauselist = get_loc_restrictinfo(childpath);
if (clauselist != LispNil)
{
/* find local predicate with maximum rank */
for (tmplist = clauselist,
maxcinfo = (RestrictInfo) lfirst(tmplist),
maxrank = xfunc_rank(get_clause(maxcinfo));
tmplist != LispNil;
tmplist = lnext(tmplist))
{
if ((tmprank = xfunc_rank(get_clause((RestrictInfo) lfirst(tmplist))))
> maxrank)
{
maxcinfo = (RestrictInfo) lfirst(tmplist);
maxrank = tmprank;
}
}
}
/*
* * If child is a join path, and there are multiple join clauses, *
* see if any join clause has even higher rank than the highest *
* local predicate
*/
if (is_join(childpath) && xfunc_num_join_clauses((JoinPath) childpath) > 1)
for (tmplist = get_pathrestrictinfo((JoinPath) childpath);
tmplist != LispNil;
tmplist = lnext(tmplist))
{
if (tmplist != LispNil &&
(tmprank = xfunc_rank(get_clause((RestrictInfo) lfirst(tmplist))))
> maxrank)
{
maxcinfo = (RestrictInfo) lfirst(tmplist);
maxrank = tmprank;
retval = XFUNC_JOINPRD;
}
}
if (maxrank == (-1 * MAXFLOAT)) /* no expensive clauses */
return 0;
/*
* * Pullup over join if clause is higher rank than join, or if * join
* is nested loop and current path is inner child (note that *
* restrictions on the inner of a nested loop don't buy you anything
* -- * you still have to scan the entire inner relation each time). *
* Note that the cost of a secondary join clause is only what's *
* calculated by xfunc_expense(), since the actual joining * (i.e. the
* usual path_cost) is paid for by the primary join clause.
*/
if (primjoinclause != LispNil)
{
joinselec = compute_clause_selec(queryInfo, primjoinclause, LispNil);
joincost = xfunc_join_expense(parentpath, whichchild);
if (XfuncMode == XFUNC_PULLALL ||
(XfuncMode != XFUNC_WAIT &&
((joincost != 0 &&
(maxrank = xfunc_rank(get_clause(maxcinfo))) >
((joinselec - 1.0) / joincost))
|| (joincost == 0 && joinselec < 1)
|| (!is_join(childpath)
&& (whichchild == INNER)
&& IsA(parentpath, NestPath)
&&!IsA(parentpath, HashPath)
&&!IsA(parentpath, MergePath)))))
{
*maxcinfopt = maxcinfo;
return retval;
}
else if (maxrank != -(MAXFLOAT))
{
/*
* * we've left an expensive restriction below a join. Since *
* we may pullup this restriction in predmig.c, we'd best *
* set the RelOptInfo of this join to be unpruneable
*/
set_pruneable(get_parent(parentpath), false);
/* and fall through */
}
}
return 0;
}
/*
** xfunc_pullup
** move clause from child pathnode to parent pathnode. This operation
** makes the child pathnode produce a larger relation than it used to.
** This means that we must construct a new RelOptInfo just for the childpath,
** although this RelOptInfo will not be added to the list of Rels to be joined up
** in the query; it's merely a parent for the new childpath.
** We also have to fix up the path costs of the child and parent.
**
** Now returns a pointer to the new pulled-up RestrictInfo. -- JMH, 11/18/92
*/
RestrictInfo
xfunc_pullup(Query *queryInfo,
Path childpath,
JoinPath parentpath,
RestrictInfo cinfo, /* clause to pull up */
int whichchild, /* whether child is INNER or OUTER of join */
int clausetype) /* whether clause to pull is join or local */
{
Path newkid;
RelOptInfo newrel;
Cost pulled_selec;
Cost cost;
RestrictInfo newinfo;
/* remove clause from childpath */
newkid = (Path) copyObject((Node) childpath);
if (clausetype == XFUNC_LOCPRD)
{
set_locrestrictinfo(newkid,
xfunc_LispRemove((LispValue) cinfo,
(List) get_loc_restrictinfo(newkid)));
}
else
{
set_pathrestrictinfo
((JoinPath) newkid,
xfunc_LispRemove((LispValue) cinfo,
(List) get_pathrestrictinfo((JoinPath) newkid)));
}
/*
* * give the new child path its own RelOptInfo node that reflects the *
* lack of the pulled-up predicate
*/
pulled_selec = compute_clause_selec(queryInfo,
get_clause(cinfo), LispNil);
xfunc_copyrel(get_parent(newkid), &newrel);
set_parent(newkid, newrel);
set_pathlist(newrel, makeList1(newkid));
set_unorderedpath(newrel, (PathPtr) newkid);
set_cheapestpath(newrel, (PathPtr) newkid);
set_size(newrel,
(Count) ((Cost) get_size(get_parent(childpath)) / pulled_selec));
/*
* * fix up path cost of newkid. To do this we subtract away all the *
* xfunc_costs of childpath, then recompute the xfunc_costs of newkid
*/
cost = get_path_cost(newkid) - xfunc_get_path_cost(childpath);
Assert(cost >= 0);
set_path_cost(newkid, cost);
cost = get_path_cost(newkid) + xfunc_get_path_cost(newkid);
set_path_cost(newkid, cost);
/*
* * We copy the cinfo, since it may appear in other plans, and we're
* going * to munge it. -- JMH, 7/22/92
*/
newinfo = (RestrictInfo) copyObject((Node) cinfo);
/*
* * Fix all vars in the clause * to point to the right varno and
* varattno in parentpath
*/
xfunc_fixvars(get_clause(newinfo), newrel, whichchild);
/* add clause to parentpath, and fix up its cost. */
set_locrestrictinfo(parentpath,
lispCons((LispValue) newinfo,
(LispValue) get_loc_restrictinfo(parentpath)));
/* put new childpath into the path tree */
if (whichchild == INNER)
set_innerjoinpath(parentpath, (pathPtr) newkid);
else
set_outerjoinpath(parentpath, (pathPtr) newkid);
/*
* * recompute parentpath cost from scratch -- the cost * of the join
* method has changed
*/
cost = xfunc_total_path_cost(parentpath);
set_path_cost(parentpath, cost);
return newinfo;
}
/*
** calculate (selectivity-1)/cost.
*/
Cost
xfunc_rank(Query *queryInfo, LispValue clause)
{
Cost selec = compute_clause_selec(queryInfo, clause, LispNil);
Cost cost = xfunc_expense(queryInfo, clause);
if (cost == 0)
if (selec > 1)
return MAXFLOAT;
else
return -(MAXFLOAT);
return (selec - 1) / cost;
}
/*
** Find the "global" expense of a clause; i.e. the local expense divided
** by the cardinalities of all the base relations of the query that are *not*
** referenced in the clause.
*/
Cost
xfunc_expense(Query *queryInfo, clause)
LispValue clause;
{
Cost cost = xfunc_local_expense(clause);
if (cost)
{
Count card = xfunc_card_unreferenced(queryInfo, clause, LispNil);
if (card)
cost /= card;
}
return cost;
}
/*
** xfunc_join_expense
** Find global expense of a join clause
*/
Cost
xfunc_join_expense(Query *queryInfo, JoinPath path, int whichchild)
{
LispValue primjoinclause = xfunc_primary_join(path);
/*
* * the second argument to xfunc_card_unreferenced reflects all the *
* relations involved in the join clause, i.e. all the relids in the
* RelOptInfo * of the join clause
*/
Count card = 0;
Cost cost = xfunc_expense_per_tuple(path, whichchild);
card = xfunc_card_unreferenced(queryInfo,
primjoinclause,
get_relids(get_parent(path)));
if (primjoinclause)
cost += xfunc_local_expense(primjoinclause);
if (card)
cost /= card;
return cost;
}
/*
** Recursively find the per-tuple expense of a clause. See
** xfunc_func_expense for more discussion.
*/
Cost
xfunc_local_expense(LispValue clause)
{
Cost cost = 0; /* running expense */
LispValue tmpclause;
/* First handle the base case */
if (IsA(clause, Const) ||IsA(clause, Var) ||IsA(clause, Param))
return 0;
/* now other stuff */
else if (IsA(clause, Iter))
/* Too low. Should multiply by the expected number of iterations. */
return xfunc_local_expense(get_iterexpr((Iter) clause));
else if (IsA(clause, ArrayRef))
return xfunc_local_expense(get_refexpr((ArrayRef) clause));
else if (fast_is_clause(clause))
return (xfunc_func_expense((LispValue) get_op(clause),
(LispValue) get_opargs(clause)));
else if (fast_is_funcclause(clause))
return (xfunc_func_expense((LispValue) get_function(clause),
(LispValue) get_funcargs(clause)));
else if (fast_not_clause(clause))
return xfunc_local_expense(lsecond(clause));
else if (fast_or_clause(clause) || fast_and_clause(clause))
{
/* find cost of evaluating each disjunct */
for (tmpclause = lnext(clause); tmpclause != LispNil;
tmpclause = lnext(tmpclause))
cost += xfunc_local_expense(lfirst(tmpclause));
return cost;
}
else
{
elog(ERROR, "Clause node of undetermined type");
return -1;
}
}
/*
** xfunc_func_expense
** given a Func or Oper and its args, find its expense.
** Note: in Stonebraker's SIGMOD '91 paper, he uses a more complicated metric
** than the one here. We can ignore the expected number of tuples for
** our calculations; we just need the per-tuple expense. But he also
** proposes components to take into account the costs of accessing disk and
** archive. We didn't adopt that scheme here; eventually the vacuum
** cleaner should be able to tell us what percentage of bytes to find on
** which storage level, and that should be multiplied in appropriately
** in the cost function below. Right now we don't model the cost of
** accessing secondary or tertiary storage, since we don't have sufficient
** stats to do it right.
*/
Cost
xfunc_func_expense(LispValue node, LispValue args)
{
HeapTuple tupl; /* the pg_proc tuple for each function */
Form_pg_proc proc; /* a data structure to hold the pg_proc
* tuple */
int width = 0; /* byte width of the field referenced by
* each clause */
RegProcedure funcid; /* ID of function associate with node */
Cost cost = 0; /* running expense */
LispValue tmpclause;
LispValue operand; /* one operand of an operator */
if (IsA(node, Oper))
{
/* don't trust the opid in the Oper node. Use the opno. */
if (!(funcid = get_opcode(get_opno((Oper) node))))
elog(ERROR, "Oper's function is undefined");
}
else
funcid = get_funcid((Func) node);
/* look up tuple in cache */
tupl = SearchSysCacheTuple(PROCOID,
ObjectIdGetDatum(funcid),
0, 0, 0);
if (!HeapTupleIsValid(tupl))
elog(ERROR, "Cache lookup failed for procedure %u", funcid);
proc = (Form_pg_proc) GETSTRUCT(tupl);
/*
* * if it's a Postquel function, its cost is stored in the *
* associated plan.
*/
if (proc->prolang == SQLlanguageId)
{
LispValue tmpplan;
List planlist;
if (IsA(node, Oper) ||get_func_planlist((Func) node) == LispNil)
{
Oid *argOidVect; /* vector of argtypes */
char *pq_src; /* text of PQ function */
int nargs; /* num args to PQ function */
QueryTreeList *queryTree_list; /* dummy variable */
/*
* * plan the function, storing it in the Func node for later *
* use by the executor.
*/
pq_src = (char *) textout(&(proc->prosrc));
nargs = proc->pronargs;
if (nargs > 0)
argOidVect = proc->proargtypes;
planlist = (List) pg_parse_and_plan(pq_src, argOidVect, nargs,
&parseTree_list, None, FALSE);
if (IsA(node, Func))
set_func_planlist((Func) node, planlist);
}
else
{ /* plan has been cached inside the Func
* node already */
planlist = get_func_planlist((Func) node);
}
/*
* * Return the sum of the costs of the plans (the PQ function *
* may have many queries in its body).
*/
foreach(tmpplan, planlist)
cost += get_cost((Plan) lfirst(tmpplan));
return cost;
}
else
{ /* it's a C function */
/*
* * find the cost of evaluating the function's arguments * and
* the width of the operands
*/
for (tmpclause = args; tmpclause != LispNil;
tmpclause = lnext(tmpclause))
{
if ((operand = lfirst(tmpclause)) != LispNil)
{
cost += xfunc_local_expense(operand);
width += xfunc_width(operand);
}
}
/*
* * when stats become available, add in cost of accessing
* secondary * and tertiary storage here.
*/
return (cost +
(Cost) proc->propercall_cpu +
(Cost) proc->properbyte_cpu * (Cost) proc->probyte_pct / 100.00 *
(Cost) width
/*
* Pct_of_obj_in_mem DISK_COST * proc->probyte_pct/100.00 * width
* Pct_of_obj_on_disk + ARCH_COST * proc->probyte_pct/100.00 *
* width Pct_of_obj_on_arch
*/
);
}
}
/*
** xfunc_width
** recursively find the width of a expression
*/
int
xfunc_width(LispValue clause)
{
Relation rd; /* Relation Descriptor */
HeapTuple tupl; /* structure to hold a cached tuple */
Form_pg_type type; /* structure to hold a type tuple */
int retval = 0;
if (IsA(clause, Const))
{
/* base case: width is the width of this constant */
retval = get_constlen((Const) clause);
goto exit;
}
else if (IsA(clause, ArrayRef))
{
/* base case: width is width of the refelem within the array */
retval = get_refelemlength((ArrayRef) clause);
goto exit;
}
else if (IsA(clause, Var))
{
/* base case: width is width of this attribute */
tupl = SearchSysCacheTuple(TYPEOID,
ObjectIdGetDatum(get_vartype((Var) clause)),
0, 0, 0);
if (!HeapTupleIsValid(tupl))
elog(ERROR, "Cache lookup failed for type %u",
get_vartype((Var) clause));
type = (Form_pg_type) GETSTRUCT(tupl);
if (get_varattno((Var) clause) == 0)
{
/* clause is a tuple. Get its width */
rd = heap_open(type->typrelid);
retval = xfunc_tuple_width(rd);
heap_close(rd);
}
else
{
/* attribute is a base type */
retval = type->typlen;
}
goto exit;
}
else if (IsA(clause, Param))
{
if (typeidTypeRelids(get_paramtype((Param) clause)))
{
/* Param node returns a tuple. Find its width */
rd = heap_open(typeidTypeRelids(get_paramtype((Param) clause)));
retval = xfunc_tuple_width(rd);
heap_close(rd);
}
else if (get_param_tlist((Param) clause) != LispNil)
{
/* Param node projects a complex type */
Assert(length(get_param_tlist((Param) clause)) == 1); /* sanity */
retval = xfunc_width((LispValue)
get_expr(lfirst(get_param_tlist((Param) clause))));
}
else
{
/* Param node returns a base type */
retval = typeLen(typeidType(get_paramtype((Param) clause)));
}
goto exit;
}
else if (IsA(clause, Iter))
{
/*
* * An Iter returns a setof things, so return the width of a
* single * thing. * Note: THIS MAY NOT WORK RIGHT WHEN AGGS GET
* FIXED, * SINCE AGG FUNCTIONS CHEW ON THE WHOLE SETOF THINGS!!!! *
* This whole Iter business is bogus, anyway.
*/
retval = xfunc_width(get_iterexpr((Iter) clause));
goto exit;
}
else if (fast_is_clause(clause))
{
/*
* * get function associated with this Oper, and treat this as * a
* Func
*/
tupl = SearchSysCacheTuple(OPEROID,
ObjectIdGetDatum(get_opno((Oper) get_op(clause))),
0, 0, 0);
if (!HeapTupleIsValid(tupl))
elog(ERROR, "Cache lookup failed for procedure %u",
get_opno((Oper) get_op(clause)));
return (xfunc_func_width
((RegProcedure) (((Form_pg_operator) (GETSTRUCT(tupl)))->oprcode),
(LispValue) get_opargs(clause)));
}
else if (fast_is_funcclause(clause))
{
Func func = (Func) get_function(clause);
if (get_func_tlist(func) != LispNil)
{
/*
* this function has a projection on it. Get the length of
* the projected attribute
*/
Assert(length(get_func_tlist(func)) == 1); /* sanity */
retval = xfunc_width((LispValue)
get_expr(lfirst(get_func_tlist(func))));
goto exit;
}
else
{
return (xfunc_func_width((RegProcedure) get_funcid(func),
(LispValue) get_funcargs(clause)));
}
}
else
{
elog(ERROR, "Clause node of undetermined type");
return -1;
}
exit:
if (retval == -1)
retval = VARLEN_DEFAULT;
return retval;
}
/*
** xfunc_card_unreferenced:
** find all relations not referenced in clause, and multiply their
** cardinalities. Ignore relation of cardinality 0.
** User may pass in referenced list, if they know it (useful
** for joins).
*/
static Count
xfunc_card_unreferenced(Query *queryInfo,
LispValue clause, Relids referenced)
{
Relids unreferenced,
allrelids = LispNil;
LispValue temp;
/* find all relids of base relations referenced in query */
foreach(temp, queryInfo->base_rel_list)
{
Assert(lnext(get_relids((RelOptInfo) lfirst(temp))) == LispNil);
allrelids = lappend(allrelids,
lfirst(get_relids((RelOptInfo) lfirst(temp))));
}
/* find all relids referenced in query but not in clause */
if (!referenced)
referenced = xfunc_find_references(clause);
unreferenced = set_difference(allrelids, referenced);
return xfunc_card_product(unreferenced);
}
/*
** xfunc_card_product
** multiple together cardinalities of a list relations.
*/
Count
xfunc_card_product(Query *queryInfo, Relids relids)
{
LispValue cinfonode;
LispValue temp;
RelOptInfo currel;
Cost tuples;
Count retval = 0;
foreach(temp, relids)
{
currel = get_rel(lfirst(temp));
tuples = get_tuples(currel);
if (tuples)
{ /* not of cardinality 0 */
/* factor in the selectivity of all zero-cost clauses */
foreach(cinfonode, get_restrictinfo(currel))
{
if (!xfunc_expense(queryInfo, get_clause((RestrictInfo) lfirst(cinfonode))))
tuples *= compute_clause_selec(queryInfo,
get_clause((RestrictInfo) lfirst(cinfonode)),
LispNil);
}
if (retval == 0)
retval = tuples;
else
retval *= tuples;
}
}
if (retval == 0)
retval = 1; /* saves caller from dividing by zero */
return retval;
}
/*
** xfunc_find_references:
** Traverse a clause and find all relids referenced in the clause.
*/
List
xfunc_find_references(LispValue clause)
{
List retval = (List) LispNil;
LispValue tmpclause;
/* Base cases */
if (IsA(clause, Var))
return lispCons(lfirst(get_varid((Var) clause)), LispNil);
else if (IsA(clause, Const) ||IsA(clause, Param))
return (List) LispNil;
/* recursion */
else if (IsA(clause, Iter))
/*
* Too low. Should multiply by the expected number of iterations.
* maybe
*/
return xfunc_find_references(get_iterexpr((Iter) clause));
else if (IsA(clause, ArrayRef))
return xfunc_find_references(get_refexpr((ArrayRef) clause));
else if (fast_is_clause(clause))
{
/* string together result of all operands of Oper */
for (tmpclause = (LispValue) get_opargs(clause); tmpclause != LispNil;
tmpclause = lnext(tmpclause))
retval = nconc(retval, xfunc_find_references(lfirst(tmpclause)));
return retval;
}
else if (fast_is_funcclause(clause))
{
/* string together result of all args of Func */
for (tmpclause = (LispValue) get_funcargs(clause);
tmpclause != LispNil;
tmpclause = lnext(tmpclause))
retval = nconc(retval, xfunc_find_references(lfirst(tmpclause)));
return retval;
}
else if (fast_not_clause(clause))
return xfunc_find_references(lsecond(clause));
else if (fast_or_clause(clause) || fast_and_clause(clause))
{
/* string together result of all operands of OR */
for (tmpclause = lnext(clause); tmpclause != LispNil;
tmpclause = lnext(tmpclause))
retval = nconc(retval, xfunc_find_references(lfirst(tmpclause)));
return retval;
}
else
{
elog(ERROR, "Clause node of undetermined type");
return (List) LispNil;
}
}
/*
** xfunc_primary_join:
** Find the primary join clause: for Hash and Merge Joins, this is the
** min rank Hash or Merge clause, while for Nested Loop it's the
** min rank pathclause
*/
LispValue
xfunc_primary_join(JoinPath pathnode)
{
LispValue joinclauselist = get_pathrestrictinfo(pathnode);
RestrictInfo mincinfo;
LispValue tmplist;
LispValue minclause = LispNil;
Cost minrank,
tmprank;
if (IsA(pathnode, MergePath))
{
for (tmplist = get_path_mergeclauses((MergePath) pathnode),
minclause = lfirst(tmplist),
minrank = xfunc_rank(minclause);
tmplist != LispNil;
tmplist = lnext(tmplist))
if ((tmprank = xfunc_rank(lfirst(tmplist)))
< minrank)
{
minrank = tmprank;
minclause = lfirst(tmplist);
}
return minclause;
}
else if (IsA(pathnode, HashPath))
{
for (tmplist = get_path_hashclauses((HashPath) pathnode),
minclause = lfirst(tmplist),
minrank = xfunc_rank(minclause);
tmplist != LispNil;
tmplist = lnext(tmplist))
if ((tmprank = xfunc_rank(lfirst(tmplist)))
< minrank)
{
minrank = tmprank;
minclause = lfirst(tmplist);
}
return minclause;
}
/* if we drop through, it's nested loop join */
if (joinclauselist == LispNil)
return LispNil;
for (tmplist = joinclauselist, mincinfo = (RestrictInfo) lfirst(joinclauselist),
minrank = xfunc_rank(get_clause((RestrictInfo) lfirst(tmplist)));
tmplist != LispNil;
tmplist = lnext(tmplist))
if ((tmprank = xfunc_rank(get_clause((RestrictInfo) lfirst(tmplist))))
< minrank)
{
minrank = tmprank;
mincinfo = (RestrictInfo) lfirst(tmplist);
}
return (LispValue) get_clause(mincinfo);
}
/*
** xfunc_get_path_cost
** get the expensive function costs of the path
*/
Cost
xfunc_get_path_cost(Query *queryInfo, Path pathnode)
{
Cost cost = 0;
LispValue tmplist;
Cost selec = 1.0;
/*
* * first add in the expensive local function costs. * We ensure that
* the clauses are sorted by rank, so that we * know (via
* selectivities) the number of tuples that will be checked * by each
* function. If we're not doing any optimization of expensive *
* functions, we don't sort.
*/
if (XfuncMode != XFUNC_OFF)
set_locrestrictinfo(pathnode, lisp_qsort(get_loc_restrictinfo(pathnode),
xfunc_cinfo_compare));
for (tmplist = get_loc_restrictinfo(pathnode), selec = 1.0;
tmplist != LispNil;
tmplist = lnext(tmplist))
{
cost += (Cost) (xfunc_local_expense(get_clause((RestrictInfo) lfirst(tmplist)))
* (Cost) get_tuples(get_parent(pathnode)) * selec);
selec *= compute_clause_selec(queryInfo,
get_clause((RestrictInfo) lfirst(tmplist)),
LispNil);
}
/*
* * Now add in any node-specific expensive function costs. * Again,
* we must ensure that the clauses are sorted by rank.
*/
if (IsA(pathnode, JoinPath))
{
if (XfuncMode != XFUNC_OFF)
set_pathrestrictinfo((JoinPath) pathnode, lisp_qsort
(get_pathrestrictinfo((JoinPath) pathnode),
xfunc_cinfo_compare));
for (tmplist = get_pathrestrictinfo((JoinPath) pathnode), selec = 1.0;
tmplist != LispNil;
tmplist = lnext(tmplist))
{
cost += (Cost) (xfunc_local_expense(get_clause((RestrictInfo) lfirst(tmplist)))
* (Cost) get_tuples(get_parent(pathnode)) * selec);
selec *= compute_clause_selec(queryInfo,
get_clause((RestrictInfo) lfirst(tmplist)),
LispNil);
}
}
if (IsA(pathnode, HashPath))
{
if (XfuncMode != XFUNC_OFF)
set_path_hashclauses
((HashPath) pathnode,
lisp_qsort(get_path_hashclauses((HashPath) pathnode),
xfunc_clause_compare));
for (tmplist = get_path_hashclauses((HashPath) pathnode), selec = 1.0;
tmplist != LispNil;
tmplist = lnext(tmplist))
{
cost += (Cost) (xfunc_local_expense(lfirst(tmplist))
* (Cost) get_tuples(get_parent(pathnode)) * selec);
selec *= compute_clause_selec(queryInfo,
lfirst(tmplist), LispNil);
}
}
if (IsA(pathnode, MergePath))
{
if (XfuncMode != XFUNC_OFF)
set_path_mergeclauses
((MergePath) pathnode,
lisp_qsort(get_path_mergeclauses((MergePath) pathnode),
xfunc_clause_compare));
for (tmplist = get_path_mergeclauses((MergePath) pathnode), selec = 1.0;
tmplist != LispNil;
tmplist = lnext(tmplist))
{
cost += (Cost) (xfunc_local_expense(lfirst(tmplist))
* (Cost) get_tuples(get_parent(pathnode)) * selec);
selec *= compute_clause_selec(queryInfo,
lfirst(tmplist), LispNil);
}
}
Assert(cost >= 0);
return cost;
}
/*
** Recalculate the cost of a path node. This includes the basic cost of the
** node, as well as the cost of its expensive functions.
** We need to do this to the parent after pulling a clause from a child into a
** parent. Thus we should only be calling this function on JoinPaths.
*/
Cost
xfunc_total_path_cost(JoinPath pathnode)
{
Cost cost = xfunc_get_path_cost((Path) pathnode);
Assert(IsA(pathnode, JoinPath));
if (IsA(pathnode, MergePath))
{
MergePath mrgnode = (MergePath) pathnode;
cost += cost_mergejoin(get_path_cost((Path) get_outerjoinpath(mrgnode)),
get_path_cost((Path) get_innerjoinpath(mrgnode)),
get_outersortkeys(mrgnode),
get_innersortkeys(mrgnode),
get_tuples(get_parent((Path) get_outerjoinpath
(mrgnode))),
get_tuples(get_parent((Path) get_innerjoinpath
(mrgnode))),
get_width(get_parent((Path) get_outerjoinpath
(mrgnode))),
get_width(get_parent((Path) get_innerjoinpath
(mrgnode))));
Assert(cost >= 0);
return cost;
}
else if (IsA(pathnode, HashPath))
{
HashPath hashnode = (HashPath) pathnode;
cost += cost_hashjoin(get_path_cost((Path) get_outerjoinpath(hashnode)),
get_path_cost((Path) get_innerjoinpath(hashnode)),
get_outerhashkeys(hashnode),
get_innerhashkeys(hashnode),
get_tuples(get_parent((Path) get_outerjoinpath
(hashnode))),
get_tuples(get_parent((Path) get_innerjoinpath
(hashnode))),
get_width(get_parent((Path) get_outerjoinpath
(hashnode))),
get_width(get_parent((Path) get_innerjoinpath
(hashnode))));
Assert(cost >= 0);
return cost;
}
else
/* Nested Loop Join */
{
cost += cost_nestloop(get_path_cost((Path) get_outerjoinpath(pathnode)),
get_path_cost((Path) get_innerjoinpath(pathnode)),
get_tuples(get_parent((Path) get_outerjoinpath
(pathnode))),
get_tuples(get_parent((Path) get_innerjoinpath
(pathnode))),
get_pages(get_parent((Path) get_outerjoinpath
(pathnode))),
IsA(get_innerjoinpath(pathnode), IndexPath));
Assert(cost >= 0);
return cost;
}
}
/*
** xfunc_expense_per_tuple
** return the expense of the join *per-tuple* of the input relation.
** The cost model here is that a join costs
** k*card(outer)*card(inner) + l*card(outer) + m*card(inner) + n
**
** We treat the l and m terms by considering them to be like restrictions
** constrained to be right under the join. Thus the cost per inner and
** cost per outer of the join is different, reflecting these virtual nodes.
**
** The cost per tuple of outer is k + l/referenced(inner). Cost per tuple
** of inner is k + m/referenced(outer).
** The constants k, l, m and n depend on the join method. Measures here are
** based on the costs in costsize.c, with fudging for HashJoin and Sorts to
** make it fit our model (the 'q' in HashJoin results in a
** card(outer)/card(inner) term, and sorting results in a log term.
*/
Cost
xfunc_expense_per_tuple(JoinPath joinnode, int whichchild)
{
RelOptInfo outerrel = get_parent((Path) get_outerjoinpath(joinnode));
RelOptInfo innerrel = get_parent((Path) get_innerjoinpath(joinnode));
Count outerwidth = get_width(outerrel);
Count outers_per_page = ceil(BLCKSZ / (outerwidth + MinTupleSize));
if (IsA(joinnode, HashPath))
{
if (whichchild == INNER)
return (1 + cpu_page_weight) * outers_per_page / NBuffers;
else
return (((1 + cpu_page_weight) * outers_per_page / NBuffers)
+ cpu_page_weight
/ xfunc_card_product(get_relids(innerrel)));
}
else if (IsA(joinnode, MergePath))
{
/* assumes sort exists, and costs one (I/O + CPU) per tuple */
if (whichchild == INNER)
return ((2 * cpu_page_weight + 1)
/ xfunc_card_product(get_relids(outerrel)));
else
return ((2 * cpu_page_weight + 1)
/ xfunc_card_product(get_relids(innerrel)));
}
else
/* nestloop */
{
Assert(IsA(joinnode, JoinPath));
return cpu_page_weight;
}
}
/*
** xfunc_fixvars
** After pulling up a clause, we must walk its expression tree, fixing Var
** nodes to point to the correct varno (either INNER or OUTER, depending
** on which child the clause was pulled from), and the right varattno in the
** target list of the child's former relation. If the target list of the
** child RelOptInfo does not contain the attribute we need, we add it.
*/
void
xfunc_fixvars(LispValue clause, /* clause being pulled up */
RelOptInfo rel, /* rel it's being pulled from */
int varno) /* whether rel is INNER or OUTER of join */
{
LispValue tmpclause; /* temporary variable */
TargetEntry *tle; /* tlist member corresponding to var */
if (IsA(clause, Const) ||IsA(clause, Param))
return;
else if (IsA(clause, Var))
{
/* here's the meat */
tle = tlistentry_member((Var) clause, get_targetlist(rel));
if (tle == LispNil)
{
/*
* * The attribute we need is not in the target list, * so we
* have to add it. *
*
*/
add_var_to_tlist(rel, (Var) clause);
tle = tlistentry_member((Var) clause, get_targetlist(rel));
}
set_varno(((Var) clause), varno);
set_varattno(((Var) clause), get_resno(get_resdom(get_entry(tle))));
}
else if (IsA(clause, Iter))
xfunc_fixvars(get_iterexpr((Iter) clause), rel, varno);
else if (fast_is_clause(clause))
{
xfunc_fixvars(lfirst(lnext(clause)), rel, varno);
xfunc_fixvars(lfirst(lnext(lnext(clause))), rel, varno);
}
else if (fast_is_funcclause(clause))
for (tmpclause = lnext(clause); tmpclause != LispNil;
tmpclause = lnext(tmpclause))
xfunc_fixvars(lfirst(tmpclause), rel, varno);
else if (fast_not_clause(clause))
xfunc_fixvars(lsecond(clause), rel, varno);
else if (fast_or_clause(clause) || fast_and_clause(clause))
for (tmpclause = lnext(clause); tmpclause != LispNil;
tmpclause = lnext(tmpclause))
xfunc_fixvars(lfirst(tmpclause), rel, varno);
else
elog(ERROR, "Clause node of undetermined type");
}
/*
** Comparison function for lisp_qsort() on a list of RestrictInfo's.
** arg1 and arg2 should really be of type (RestrictInfo *).
*/
int
xfunc_cinfo_compare(void *arg1, void *arg2)
{
RestrictInfo info1 = *(RestrictInfo *) arg1;
RestrictInfo info2 = *(RestrictInfo *) arg2;
LispValue clause1 = (LispValue) get_clause(info1),
clause2 = (LispValue) get_clause(info2);
return xfunc_clause_compare((void *) &clause1, (void *) &clause2);
}
/*
** xfunc_clause_compare: comparison function for lisp_qsort() that compares two
** clauses based on expense/(1 - selectivity)
** arg1 and arg2 are really pointers to clauses.
*/
int
xfunc_clause_compare(void *arg1, void *arg2)
{
LispValue clause1 = *(LispValue *) arg1;
LispValue clause2 = *(LispValue *) arg2;
Cost rank1, /* total xfunc rank of clause1 */
rank2; /* total xfunc rank of clause2 */
rank1 = xfunc_rank(clause1);
rank2 = xfunc_rank(clause2);
if (rank1 < rank2)
return -1;
else if (rank1 == rank2)
return 0;
else
return 1;
}
/*
** xfunc_disjunct_sort
** given a list of clauses, for each clause sort the disjuncts by cost
** (this assumes the predicates have been converted to Conjunctive NF)
** Modifies the clause list!
*/
void
xfunc_disjunct_sort(LispValue clause_list)
{
LispValue temp;
foreach(temp, clause_list)
if (or_clause(lfirst(temp)))
lnext(lfirst(temp)) = lisp_qsort(lnext(lfirst(temp)), xfunc_disjunct_compare);
}
/*
** xfunc_disjunct_compare: comparison function for qsort() that compares two
** disjuncts based on cost/selec.
** arg1 and arg2 are really pointers to disjuncts
*/
int
xfunc_disjunct_compare(Query *queryInfo, void *arg1, void *arg2)
{
LispValue disjunct1 = *(LispValue *) arg1;
LispValue disjunct2 = *(LispValue *) arg2;
Cost cost1, /* total cost of disjunct1 */
cost2, /* total cost of disjunct2 */
selec1,
selec2;
Cost rank1,
rank2;
cost1 = xfunc_expense(queryInfo, disjunct1);
cost2 = xfunc_expense(queryInfo, disjunct2);
selec1 = compute_clause_selec(queryInfo,
disjunct1, LispNil);
selec2 = compute_clause_selec(queryInfo,
disjunct2, LispNil);
if (selec1 == 0)
rank1 = MAXFLOAT;
else if (cost1 == 0)
rank1 = 0;
else
rank1 = cost1 / selec1;
if (selec2 == 0)
rank2 = MAXFLOAT;
else if (cost2 == 0)
rank2 = 0;
else
rank2 = cost2 / selec2;
if (rank1 < rank2)
return -1;
else if (rank1 == rank2)
return 0;
else
return 1;
}
/* ------------------------ UTILITY FUNCTIONS ------------------------------- */
/*
** xfunc_func_width
** Given a function OID and operands, find the width of the return value.
*/
int
xfunc_func_width(RegProcedure funcid, LispValue args)
{
Relation rd; /* Relation Descriptor */
HeapTuple tupl; /* structure to hold a cached tuple */
Form_pg_proc proc; /* structure to hold the pg_proc tuple */
Form_pg_type type; /* structure to hold the pg_type tuple */
LispValue tmpclause;
int retval;
/* lookup function and find its return type */
Assert(RegProcedureIsValid(funcid));
tupl = SearchSysCacheTuple(PROCOID,
ObjectIdGetDatum(funcid),
0, 0, 0);
if (!HeapTupleIsValid(tupl))
elog(ERROR, "Cache lookup failed for procedure %u", funcid);
proc = (Form_pg_proc) GETSTRUCT(tupl);
/* if function returns a tuple, get the width of that */
if (typeidTypeRelids(proc->prorettype))
{
rd = heap_open(typeidTypeRelids(proc->prorettype));
retval = xfunc_tuple_width(rd);
heap_close(rd);
goto exit;
}
else
/* function returns a base type */
{
tupl = SearchSysCacheTuple(TYPEOID,
ObjectIdGetDatum(proc->prorettype),
0, 0, 0);
if (!HeapTupleIsValid(tupl))
elog(ERROR, "Cache lookup failed for type %u", proc->prorettype);
type = (Form_pg_type) GETSTRUCT(tupl);
/* if the type length is known, return that */
if (type->typlen != -1)
{
retval = type->typlen;
goto exit;
}
else
/* estimate the return size */
{
/* find width of the function's arguments */
for (tmpclause = args; tmpclause != LispNil;
tmpclause = lnext(tmpclause))
retval += xfunc_width(lfirst(tmpclause));
/* multiply by outin_ratio */
retval = (int) (proc->prooutin_ratio / 100.0 * retval);
goto exit;
}
}
exit:
return retval;
}
/*
** xfunc_tuple_width
** Return the sum of the lengths of all the attributes of a given relation
*/
int
xfunc_tuple_width(Relation rd)
{
int i;
int retval = 0;
TupleDesc tdesc = RelationGetDescr(rd);
for (i = 0; i < tdesc->natts; i++)
{
if (tdesc->attrs[i]->attlen != -1)
retval += tdesc->attrs[i]->attlen;
else
retval += VARLEN_DEFAULT;
}
return retval;
}
/*
** xfunc_num_join_clauses
** Find the number of join clauses associated with this join path
*/
int
xfunc_num_join_clauses(JoinPath path)
{
int num = length(get_pathrestrictinfo(path));
if (IsA(path, MergePath))
return num + length(get_path_mergeclauses((MergePath) path));
else if (IsA(path, HashPath))
return num + length(get_path_hashclauses((HashPath) path));
else
return num;
}
/*
** xfunc_LispRemove
** Just like LispRemove, but it whines if the item to be removed ain't there
*/
LispValue
xfunc_LispRemove(LispValue foo, List bar)
{
LispValue temp = LispNil;
LispValue result = LispNil;
int sanity = false;
for (temp = bar; !null(temp); temp = lnext(temp))
if (!equal((Node) (foo), (Node) (lfirst(temp))))
result = lappend(result, lfirst(temp));
else
sanity = true; /* found a matching item to remove! */
if (!sanity)
elog(ERROR, "xfunc_LispRemove: didn't find a match!");
return result;
}
#define Node_Copy(a, b, c, d) \
do { \
if (NodeCopy((Node)((a)->d), (Node*)&((b)->d), c) != true) \
{ \
return false; \
} \
} while(0)
/*
** xfunc_copyrel
** Just like _copyRel, but doesn't copy the paths
*/
bool
xfunc_copyrel(RelOptInfo from, RelOptInfo *to)
{
RelOptInfo newnode;
Pointer (*alloc) () = palloc;
/* COPY_CHECKARGS() */
if (to == NULL)
return false;
/* COPY_CHECKNULL() */
if (from == NULL)
{
(*to) = NULL;
return true;
}
/* COPY_NEW(c) */
newnode = (RelOptInfo) (*alloc) (classSize(RelOptInfo));
if (newnode == NULL)
return false;
/*
* copy node superclass fields
*/
CopyNodeFields((Node) from, (Node) newnode, alloc);
/*
* copy remainder of node
*/
Node_Copy(from, newnode, alloc, relids);
newnode->indexed = from->indexed;
newnode->pages = from->pages;
newnode->tuples = from->tuples;
newnode->size = from->size;
newnode->width = from->width;
Node_Copy(from, newnode, alloc, targetlist);
/*
* No!!!! Node_Copy(from, newnode, alloc, pathlist);
* Node_Copy(from, newnode, alloc, unorderedpath); Node_Copy(from,
* newnode, alloc, cheapestpath);
*/
#if 0 /* can't use Node_copy now. 2/95 -ay */
Node_Copy(from, newnode, alloc, classlist);
Node_Copy(from, newnode, alloc, indexkeys);
Node_Copy(from, newnode, alloc, ordering);
#endif
Node_Copy(from, newnode, alloc, restrictinfo);
Node_Copy(from, newnode, alloc, joininfo);
Node_Copy(from, newnode, alloc, innerjoin);
(*to) = newnode;
return true;
}
/*-------------------------------------------------------------------------
*
* xfunc.h
* prototypes for xfunc.c and predmig.c.
*
*
* Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $Id: xfunc.h,v 1.9 2002/06/20 20:29:51 momjian Exp $
*
*-------------------------------------------------------------------------
*/
#ifndef XFUNC_H
#define XFUNC_H
#include "nodes/relation.h"
#include "utils/rel.h"
/* command line arg flags */
#define XFUNC_OFF -1 /* do no optimization of expensive preds */
#define XFUNC_NOR 2 /* do no optimization of OR clauses */
#define XFUNC_NOPULL 4 /* never pull restrictions above joins */
#define XFUNC_NOPM 8 /* don't do predicate migration */
#define XFUNC_WAIT 16 /* don't do pullup until predicate
* migration */
#define XFUNC_PULLALL 32 /* pull all expensive restrictions up,
* always */
/* constants for local and join predicates */
#define XFUNC_LOCPRD 1
#define XFUNC_JOINPRD 2
#define XFUNC_UNKNOWN 0
extern int XfuncMode; /* defined in tcop/postgres.c */
/* default width assumed for variable length attributes */
#define VARLEN_DEFAULT 128;
/* Macro to get group rank out of group cost and group sel */
#define get_grouprank(a) ((get_groupsel(a) - 1) / get_groupcost(a))
/* Macro to see if a path node is actually a Join */
#define is_join(pathnode) (length(get_relids(get_parent(pathnode))) > 1 ? 1 : 0)
/* function prototypes from planner/path/xfunc.c */
extern void xfunc_trypullup(RelOptInfo *rel);
extern int xfunc_shouldpull(Path *childpath, JoinPath *parentpath,
int whichchild, RestrictInfo *maxcinfopt);
extern RestrictInfo *xfunc_pullup(Path *childpath, JoinPath *parentpath, RestrictInfo *cinfo,
int whichchild, int clausetype);
extern Cost xfunc_rank(Expr *clause);
extern Cost xfunc_expense(Query *queryInfo, Expr *clause);
extern Cost xfunc_join_expense(JoinPath *path, int whichchild);
extern Cost xfunc_local_expense(Expr *clause);
extern Cost xfunc_func_expense(Expr *node, List *args);
extern int xfunc_width(Expr *clause);
/* static, moved to xfunc.c */
/* extern int xfunc_card_unreferenced(Expr *clause, Relids referenced); */
extern int xfunc_card_product(Relids relids);
extern List *xfunc_find_references(List *clause);
extern List *xfunc_primary_join(JoinPath *pathnode);
extern Cost xfunc_get_path_cost(Path *pathnode);
extern Cost xfunc_total_path_cost(JoinPath *pathnode);
extern Cost xfunc_expense_per_tuple(JoinPath *joinnode, int whichchild);
extern void xfunc_fixvars(Expr *clause, RelOptInfo *rel, int varno);
extern int xfunc_cinfo_compare(void *arg1, void *arg2);
extern int xfunc_clause_compare(void *arg1, void *arg2);
extern void xfunc_disjunct_sort(List *clause_list);
extern int xfunc_disjunct_compare(void *arg1, void *arg2);
extern int xfunc_func_width(RegProcedure funcid, List *args);
extern int xfunc_tuple_width(Relation rd);
extern int xfunc_num_join_clauses(JoinPath *path);
extern List *xfunc_LispRemove(List *foo, List *bar);
extern bool xfunc_copyrel(RelOptInfo *from, RelOptInfo **to);
/*
* function prototypes for path/predmig.c
*/
extern bool xfunc_do_predmig(Path root);
#endif /* XFUNC_H */
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment