1334 lines
31 KiB
C
1334 lines
31 KiB
C
/*-------------------------------------------------------------------------
|
|
*
|
|
* recipe.c
|
|
* routines for handling execution of Tioga recipes
|
|
*
|
|
* Copyright (c) 1994, Regents of the University of California
|
|
*
|
|
*
|
|
* IDENTIFICATION
|
|
* $Header: /cvsroot/pgsql/src/backend/commands/_deadcode/Attic/recipe.c,v 1.4 1999/05/25 22:40:56 momjian Exp $
|
|
*
|
|
*-------------------------------------------------------------------------
|
|
*/
|
|
#include <postgres.h>
|
|
|
|
#include <nodes/parsenodes.h>
|
|
#include <nodes/plannodes.h>
|
|
#include <nodes/execnodes.h>
|
|
#include <nodes/makefuncs.h>
|
|
#include <catalog/pg_type.h>
|
|
#include <commands/recipe.h>
|
|
#include <libpq/libpq-be.h>
|
|
#include <parser/parse_node.h>
|
|
#include <utils/builtins.h>
|
|
#include <utils/relcache.h> /* for RelationNameGetRelation */
|
|
#include <rewrite/rewriteHandler.h>
|
|
#include <rewrite/rewriteManip.h>
|
|
#include <tcop/pquery.h>
|
|
#include <tcop/dest.h>
|
|
#include <optimizer/planner.h>
|
|
#include <executor/executor.h>
|
|
|
|
/* from tcop/postgres.c */
|
|
extern CommandDest whereToSendOutput;
|
|
|
|
#ifndef TIOGA
|
|
|
|
void
|
|
beginRecipe(RecipeStmt *stmt)
|
|
{
|
|
elog(NOTICE, "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(NOTICE,
|
|
"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(NOTICE, "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,
|
|
* setheapoverride() allows us to see the changes
|
|
*/
|
|
setheapoverride(true);
|
|
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(NOTICE, "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) */
|
|
|
|
setheapoverride(false);
|
|
|
|
/* 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(NOTICE, "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(NOTICE, "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(NOTICE, "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 < 8; i++)
|
|
typev[i] = 0;
|
|
for (j = 0; j < elem->inTypes->num; j++)
|
|
{
|
|
if (parameterCount == 8)
|
|
{
|
|
elog(ERROR,
|
|
"getParamTypes: Ingredients cannot take > 8 arguments");
|
|
}
|
|
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(NOTICE, "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[8], /* 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(NOTICE, "calling parser with %s", elem->src);
|
|
#endif /* DEBUG_RECIPE */
|
|
|
|
parameterCount = getParamTypes(elem, typev);
|
|
|
|
qList = parser(elem->src, typev, parameterCount);
|
|
|
|
if (qList->len > 1)
|
|
{
|
|
elog(NOTICE,
|
|
"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(NOTICE, "calling parser with %s", newquery);
|
|
#endif /* DEBUG_RECIPE */
|
|
|
|
qList = parser(newquery, typev, parameterCount);
|
|
if (qList->len > 1)
|
|
{
|
|
elog(NOTICE,
|
|
"tg_parseSubQuery: parser produced > 1 query tree");
|
|
}
|
|
}
|
|
break;
|
|
case TG_RECIPE_GRAPH:
|
|
elog(NOTICE, "tg_parseSubQuery: can't parse recipe graph ingredients yet!");
|
|
break;
|
|
case TG_COMPILED:
|
|
elog(NOTICE, "tg_parseSubQuery: can't parse compiled ingredients yet!");
|
|
break;
|
|
default:
|
|
elog(NOTICE, "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(NOTICE, "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(NOTICE, "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(NOTICE, "tg_parseSubQuery: can't handle embedded recipes yet!");
|
|
else
|
|
elog(NOTICE, "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(NOTICE, "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(NOTICE, "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 */
|