diff options
author | Marc G. Fournier <scrappy@hub.org> | 1996-07-09 06:22:35 +0000 |
---|---|---|
committer | Marc G. Fournier <scrappy@hub.org> | 1996-07-09 06:22:35 +0000 |
commit | d31084e9d1118b25fd16580d9d8c2924b5740dff (patch) | |
tree | 3179e66307d54df9c7b966543550e601eb55e668 /src/backend/executor | |
download | postgresql-PG95-1_01.tar.gz postgresql-PG95-1_01.zip |
Postgres95 1.01 Distribution - Virgin SourcesPG95-1_01
Diffstat (limited to 'src/backend/executor')
47 files changed, 15993 insertions, 0 deletions
diff --git a/src/backend/executor/Makefile.inc b/src/backend/executor/Makefile.inc new file mode 100644 index 00000000000..211e725cec4 --- /dev/null +++ b/src/backend/executor/Makefile.inc @@ -0,0 +1,29 @@ +#------------------------------------------------------------------------- +# +# Makefile.inc-- +# Makefile for the executor module +# +# Copyright (c) 1994, Regents of the University of California +# +# +# IDENTIFICATION +# $Header: /cvsroot/pgsql/src/backend/executor/Attic/Makefile.inc,v 1.1.1.1 1996/07/09 06:21:24 scrappy Exp $ +# +#------------------------------------------------------------------------- + +VPATH:= $(VPATH):$(CURDIR)/executor + +SRCS_EXECUTOR= execAmi.c execFlatten.c execJunk.c execMain.c \ + execProcnode.c execQual.c execScan.c execTuples.c \ + execUtils.c functions.c nodeAppend.c nodeAgg.c nodeHash.c \ + nodeHashjoin.c nodeIndexscan.c nodeMaterial.c nodeMergejoin.c \ + nodeNestloop.c nodeResult.c nodeSeqscan.c nodeSort.c \ + nodeUnique.c nodeTee.c nodeGroup.c + +HEADERS+= execFlatten.h execdebug.h execdefs.h execdesc.h \ + executor.h functions.h hashjoin.h nodeAgg.h nodeAppend.h \ + nodeHash.h nodeHashjoin.h nodeIndexscan.h nodeMaterial.h \ + nodeMergejoin.h nodeNestloop.h nodeResult.h \ + nodeSeqscan.h nodeSort.h nodeUnique.h tuptable.h nodeTee.h \ + nodeGroup.h + diff --git a/src/backend/executor/execAmi.c b/src/backend/executor/execAmi.c new file mode 100644 index 00000000000..08d3e70d8b9 --- /dev/null +++ b/src/backend/executor/execAmi.c @@ -0,0 +1,439 @@ +/*------------------------------------------------------------------------- + * + * execAmi.c-- + * miscellanious executor access method routines + * + * Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * $Header: /cvsroot/pgsql/src/backend/executor/execAmi.c,v 1.1.1.1 1996/07/09 06:21:24 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +/* + * INTERFACE ROUTINES + * + * ExecOpenScanR \ / amopen + * ExecBeginScan \ / ambeginscan + * ExecCloseR \ / amclose + * ExecInsert \ executor interface / aminsert + * ExecReScanNode / to access methods \ amrescan + * ExecReScanR / \ amrescan + * ExecMarkPos / \ ammarkpos + * ExecRestrPos / \ amrestpos + * + * ExecCreatR function to create temporary relations + * + */ +#include <stdio.h> /* for sprintf() */ +#include "executor/executor.h" +#include "storage/smgr.h" +#include "executor/nodeSeqscan.h" +#include "executor/nodeIndexscan.h" +#include "executor/nodeSort.h" +#include "executor/nodeTee.h" +#include "optimizer/internal.h" /* for _TEMP_RELATION_ID_ */ + +/* ---------------------------------------------------------------- + * ExecOpenScanR + * + * old comments: + * Parameters: + * relation -- relation to be opened and scanned. + * nkeys -- number of keys + * skeys -- keys to restrict scanning + * isindex -- if this is true, the relation is the relid of + * an index relation, else it is an index into the + * range table. + * Returns the relation as(relDesc scanDesc) + * If this structure is changed, need to modify the access macros + * defined in execInt.h. + * ---------------------------------------------------------------- + */ +void +ExecOpenScanR(Oid relOid, + int nkeys, + ScanKey skeys, + bool isindex, + ScanDirection dir, + TimeQual timeRange, + Relation *returnRelation, /* return */ + Pointer *returnScanDesc) /* return */ +{ + Relation relation; + Pointer scanDesc; + + /* ---------------- + * note: scanDesc returned by ExecBeginScan can be either + * a HeapScanDesc or an IndexScanDesc so for now we + * make it a Pointer. There should be a better scan + * abstraction someday -cim 9/9/89 + * ---------------- + */ + relation = ExecOpenR(relOid, isindex); + scanDesc = ExecBeginScan(relation, + nkeys, + skeys, + isindex, + dir, + timeRange); + + if (returnRelation != NULL) + *returnRelation = relation; + if (scanDesc != NULL) + *returnScanDesc = scanDesc; +} + +/* ---------------------------------------------------------------- + * ExecOpenR + * + * returns a relation descriptor given an object id. + * ---------------------------------------------------------------- + */ +Relation +ExecOpenR(Oid relationOid, bool isindex) +{ + Relation relation; + relation = (Relation) NULL; + + /* ---------------- + * open the relation with the correct call depending + * on whether this is a heap relation or an index relation. + * ---------------- + */ + if (isindex) { + relation = index_open(relationOid); + } else + relation = heap_open(relationOid); + + if (relation == NULL) + elog(DEBUG, "ExecOpenR: relation == NULL, heap_open failed."); + + return relation; +} + +/* ---------------------------------------------------------------- + * ExecBeginScan + * + * beginscans a relation in current direction. + * + * XXX fix parameters to AMbeginscan (and btbeginscan) + * currently we need to pass a flag stating whether + * or not the scan should begin at an endpoint of + * the relation.. Right now we always pass false + * -cim 9/14/89 + * ---------------------------------------------------------------- + */ +Pointer +ExecBeginScan(Relation relation, + int nkeys, + ScanKey skeys, + bool isindex, + ScanDirection dir, + TimeQual time_range) +{ + Pointer scanDesc; + + scanDesc = NULL; + + /* ---------------- + * open the appropriate type of scan. + * + * Note: ambeginscan()'s second arg is a boolean indicating + * that the scan should be done in reverse.. That is, + * if you pass it true, then the scan is backward. + * ---------------- + */ + if (isindex) { + scanDesc = (Pointer) index_beginscan(relation, + false, /* see above comment */ + nkeys, + skeys); + } else { + scanDesc = (Pointer) heap_beginscan(relation, + ScanDirectionIsBackward(dir), + time_range, + nkeys, + skeys); + } + + if (scanDesc == NULL) + elog(DEBUG, "ExecBeginScan: scanDesc = NULL, heap_beginscan failed."); + + + return scanDesc; +} + +/* ---------------------------------------------------------------- + * ExecCloseR + * + * closes the relation and scan descriptor for a scan or sort + * node. Also closes index relations and scans for index scans. + * + * old comments + * closes the relation indicated in 'relID' + * ---------------------------------------------------------------- + */ +void +ExecCloseR(Plan *node) +{ + CommonScanState *state; + Relation relation; + HeapScanDesc scanDesc; + + /* ---------------- + * shut down the heap scan and close the heap relation + * ---------------- + */ + switch (nodeTag(node)) { + + case T_SeqScan: + state = ((SeqScan *)node)->scanstate; + break; + + case T_IndexScan: + state = ((IndexScan *)node)->scan.scanstate; + break; + + case T_Material: + state = &(((Material *)node)->matstate->csstate); + break; + + case T_Sort: + state = &(((Sort *)node)->sortstate->csstate); + break; + + case T_Agg: + state = &(((Agg *)node)->aggstate->csstate); + break; + + default: + elog(DEBUG, "ExecCloseR: not a scan, material, or sort node!"); + return; + } + + relation = state->css_currentRelation; + scanDesc = state->css_currentScanDesc; + + if (scanDesc != NULL) + heap_endscan(scanDesc); + + if (relation != NULL) + heap_close(relation); + + /* ---------------- + * if this is an index scan then we have to take care + * of the index relations as well.. + * ---------------- + */ + if (nodeTag(node) == T_IndexScan) { + IndexScan *iscan= (IndexScan *)node; + IndexScanState *indexstate; + int numIndices; + RelationPtr indexRelationDescs; + IndexScanDescPtr indexScanDescs; + int i; + + indexstate = iscan->indxstate; + numIndices = indexstate->iss_NumIndices; + indexRelationDescs = indexstate->iss_RelationDescs; + indexScanDescs = indexstate->iss_ScanDescs; + + for (i = 0; i<numIndices; i++) { + /* ---------------- + * shut down each of the scans and + * close each of the index relations + * ---------------- + */ + if (indexScanDescs[i] != NULL) + index_endscan(indexScanDescs[i]); + + if (indexRelationDescs[i] != NULL) + index_close(indexRelationDescs[i]); + } + } +} + +/* ---------------------------------------------------------------- + * ExecReScan + * + * XXX this should be extended to cope with all the node types.. + * + * takes the new expression context as an argument, so that + * index scans needn't have their scan keys updated separately + * - marcel 09/20/94 + * ---------------------------------------------------------------- + */ +void +ExecReScan(Plan *node, ExprContext *exprCtxt, Plan *parent) +{ + switch(nodeTag(node)) { + case T_SeqScan: + ExecSeqReScan((SeqScan *) node, exprCtxt, parent); + return; + + case T_IndexScan: + ExecIndexReScan((IndexScan *) node, exprCtxt, parent); + return; + + case T_Material: + /* the first call to ExecReScan should have no effect because + * everything is initialized properly already. the following + * calls will be handled by ExecSeqReScan() because the nodes + * below the Material node have already been materialized into + * a temp relation. + */ + return; + + case T_Tee: + ExecTeeReScan((Tee*) node, exprCtxt, parent); + break; + + default: + elog(WARN, "ExecReScan: not a seqscan or indexscan node."); + return; + } +} + +/* ---------------------------------------------------------------- + * ExecReScanR + * + * XXX this does not do the right thing with indices yet. + * ---------------------------------------------------------------- + */ +HeapScanDesc +ExecReScanR(Relation relDesc, /* LLL relDesc unused */ + HeapScanDesc scanDesc, + ScanDirection direction, + int nkeys, /* LLL nkeys unused */ + ScanKey skeys) +{ + if (scanDesc != NULL) + heap_rescan(scanDesc, /* scan desc */ + ScanDirectionIsBackward(direction), /* backward flag */ + skeys); /* scan keys */ + + return scanDesc; +} + +/* ---------------------------------------------------------------- + * ExecMarkPos + * + * Marks the current scan position. + * + * XXX Needs to be extended to include all the node types. + * ---------------------------------------------------------------- + */ +void +ExecMarkPos(Plan *node) +{ + switch(nodeTag(node)) { + case T_SeqScan: + ExecSeqMarkPos((SeqScan *) node); + break; + + case T_IndexScan: + ExecIndexMarkPos((IndexScan *) node); + break; + + case T_Sort: + ExecSortMarkPos((Sort *) node); + break; + + default: + /* elog(DEBUG, "ExecMarkPos: unsupported node type"); */ + break; + } + return; +} + +/* ---------------------------------------------------------------- + * ExecRestrPos + * + * restores the scan position previously saved with ExecMarkPos() + * ---------------------------------------------------------------- + */ +void +ExecRestrPos(Plan *node) +{ + switch(nodeTag(node)) { + case T_SeqScan: + ExecSeqRestrPos((SeqScan *) node); + return; + + case T_IndexScan: + ExecIndexRestrPos((IndexScan *) node); + return; + + case T_Sort: + ExecSortRestrPos((Sort *) node); + return; + + default: + /* elog(DEBUG, "ExecRestrPos: node type not supported"); */ + return; + } +} + +/* ---------------------------------------------------------------- + * ExecCreatR + * + * old comments + * Creates a relation. + * + * Parameters: + * attrType -- type information on the attributes. + * accessMtd -- access methods used to access the created relation. + * relation -- optional. Either an index to the range table or + * negative number indicating a temporary relation. + * A temporary relation is assume is this field is absent. + * ---------------------------------------------------------------- + */ + +Relation +ExecCreatR(TupleDesc tupType, + Oid relationOid) +{ + Relation relDesc; + + EU4_printf("ExecCreatR: %s numatts=%d type=%d oid=%d\n", + "entering: ", numberAttributes, tupType, relationOid); + CXT1_printf("ExecCreatR: context is %d\n", CurrentMemoryContext); + + relDesc = NULL; + + if (relationOid == _TEMP_RELATION_ID_ ) { + /* ---------------- + * create a temporary relation + * (currently the planner always puts a _TEMP_RELATION_ID + * in the relation argument so we expect this to be the case although + * it's possible that someday we'll get the name from + * from the range table.. -cim 10/12/89) + * ---------------- + */ +/* + sprintf(tempname, "temp_%d.%d", getpid(), tmpcnt++); + EU1_printf("ExecCreatR: attempting to create %s\n", tempname); +*/ + /* heap_creatr creates a name if the argument to heap_creatr is '\0 ' */ + relDesc = heap_creatr("", + DEFAULT_SMGR, + tupType); + } else { + /* ---------------- + * use a relation from the range table + * ---------------- + */ + elog(DEBUG, "ExecCreatR: %s", + "stuff using range table id's is not functional"); + } + + if (relDesc == NULL) + elog(DEBUG, "ExecCreatR: failed to create relation."); + + EU1_printf("ExecCreatR: returning relDesc=%d\n", relDesc); + + return relDesc; +} + diff --git a/src/backend/executor/execFlatten.c b/src/backend/executor/execFlatten.c new file mode 100644 index 00000000000..646c571506e --- /dev/null +++ b/src/backend/executor/execFlatten.c @@ -0,0 +1,236 @@ +/*------------------------------------------------------------------------- + * + * execFlatten.c-- + * This file handles the nodes associated with flattening sets in the + * target list of queries containing functions returning sets. + * + * Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * $Header: /cvsroot/pgsql/src/backend/executor/Attic/execFlatten.c,v 1.1.1.1 1996/07/09 06:21:24 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ + +/* + * ExecEvalIter() - + * Iterate through the all return tuples/base types from a function one + * at time (i.e. one per ExecEvalIter call). Not really needed for + * postquel functions, but for reasons of orthogonality, these nodes + * exist above pq functions as well as c functions. + * + * ExecEvalFjoin() - + * Given N Iter nodes return a vector of all combinations of results + * one at a time (i.e. one result vector per ExecEvalFjoin call). This + * node does the actual flattening work. + */ +#include "postgres.h" +#include "nodes/primnodes.h" +#include "nodes/relation.h" +#include "nodes/execnodes.h" +#include "executor/executor.h" +#include "executor/execFlatten.h" + +Datum +ExecEvalIter(Iter *iterNode, + ExprContext *econtext, + bool *resultIsNull, + bool *iterIsDone) +{ + Node *expression; + + expression = iterNode->iterexpr; + + /* + * Really Iter nodes are only needed for C functions, postquel function + * by their nature return 1 result at a time. For now we are only worrying + * about postquel functions, c functions will come later. + */ + return ExecEvalExpr(expression, econtext, resultIsNull, iterIsDone); +} + +void +ExecEvalFjoin(TargetEntry *tlist, + ExprContext *econtext, + bool *isNullVect, + bool *fj_isDone) +{ + +#ifdef SETS_FIXED + bool isDone; + int curNode; + List *tlistP; + + Fjoin *fjNode = tlist->fjoin; + DatumPtr resVect = fjNode->fj_results; + BoolPtr alwaysDone = fjNode->fj_alwaysDone; + + if (fj_isDone) *fj_isDone = false; + /* + * For the next tuple produced by the plan, we need to re-initialize + * the Fjoin node. + */ + if (!fjNode->fj_initialized) + { + /* + * Initialize all of the Outer nodes + */ + curNode = 1; + foreach(tlistP, lnext(tlist)) + { + TargetEntry *tle = lfirst(tlistP); + + resVect[curNode] = ExecEvalIter((Iter*)tle->expr, + econtext, + &isNullVect[curNode], + &isDone); + if (isDone) + isNullVect[curNode] = alwaysDone[curNode] = true; + else + alwaysDone[curNode] = false; + + curNode++; + } + + /* + * Initialize the inner node + */ + resVect[0] = ExecEvalIter((Iter*)fjNode->fj_innerNode->expr, + econtext, + &isNullVect[0], + &isDone); + if (isDone) + isNullVect[0] = alwaysDone[0] = true; + else + alwaysDone[0] = false; + + /* + * Mark the Fjoin as initialized now. + */ + fjNode->fj_initialized = TRUE; + + /* + * If the inner node is always done, then we are done for now + */ + if (isDone) + return; + } + else + { + /* + * If we're already initialized, all we need to do is get the + * next inner result and pair it up with the existing outer node + * result vector. Watch out for the degenerate case, where the + * inner node never returns results. + */ + + /* + * Fill in nulls for every function that is always done. + */ + for (curNode=fjNode->fj_nNodes-1; curNode >= 0; curNode--) + isNullVect[curNode] = alwaysDone[curNode]; + + if (alwaysDone[0] == true) + { + *fj_isDone = FjoinBumpOuterNodes(tlist, + econtext, + resVect, + isNullVect); + return; + } + else + resVect[0] = ExecEvalIter((Iter*)fjNode->fj_innerNode->expr, + econtext, + &isNullVect[0], + &isDone); + } + + /* + * if the inner node is done + */ + if (isDone) + { + *fj_isDone = FjoinBumpOuterNodes(tlist, + econtext, + resVect, + isNullVect); + if (*fj_isDone) + return; + + resVect[0] = ExecEvalIter((Iter*)fjNode->fj_innerNode->expr, + econtext, + &isNullVect[0], + &isDone); + + } +#endif + return; +} + +bool +FjoinBumpOuterNodes(TargetEntry *tlist, + ExprContext *econtext, + DatumPtr results, + char *nulls) +{ +#ifdef SETS_FIXED + bool funcIsDone = true; + Fjoin *fjNode = tlist->fjoin; + char *alwaysDone = fjNode->fj_alwaysDone; + List *outerList = lnext(tlist); + List *trailers = lnext(tlist); + int trailNode = 1; + int curNode = 1; + + /* + * Run through list of functions until we get to one that isn't yet + * done returning values. Watch out for funcs that are always done. + */ + while ((funcIsDone == true) && (outerList != NIL)) + { + TargetEntry *tle = lfirst(outerList); + + if (alwaysDone[curNode] == true) + nulls[curNode] = 'n'; + else + results[curNode] = ExecEvalIter((Iter)tle->expr, + econtext, + &nulls[curNode], + &funcIsDone); + curNode++; + outerList = lnext(outerList); + } + + /* + * If every function is done, then we are done flattening. + * Mark the Fjoin node unitialized, it is time to get the + * next tuple from the plan and redo all of the flattening. + */ + if (funcIsDone) + { + set_fj_initialized(fjNode, false); + return (true); + } + + /* + * We found a function that wasn't done. Now re-run every function + * before it. As usual watch out for functions that are always done. + */ + trailNode = 1; + while (trailNode != curNode-1) + { + TargetEntry *tle = lfirst(trailers); + + if (alwaysDone[trailNode] != true) + results[trailNode] = ExecEvalIter((Iter)tle->expr, + econtext, + &nulls[trailNode], + &funcIsDone); + trailNode++; + trailers = lnext(trailers); + } + return false; +#endif + return false; +} diff --git a/src/backend/executor/execFlatten.h b/src/backend/executor/execFlatten.h new file mode 100644 index 00000000000..fe06823619f --- /dev/null +++ b/src/backend/executor/execFlatten.h @@ -0,0 +1,26 @@ +/*------------------------------------------------------------------------- + * + * execFlatten.h-- + * prototypes for execFlatten.c. + * + * + * Copyright (c) 1994, Regents of the University of California + * + * $Id: execFlatten.h,v 1.1.1.1 1996/07/09 06:21:24 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +#ifndef EXECFLATTEN_H +#define EXECFLATTEN_H + +extern Datum ExecEvalIter(Iter *iterNode, ExprContext *econtext, bool *resultIsNull, bool *iterIsDone); + +extern void ExecEvalFjoin(TargetEntry *tlist, ExprContext *econtext, bool *isNullVect, bool *fj_isDone); + +extern bool FjoinBumpOuterNodes(TargetEntry *tlist, ExprContext *econtext, DatumPtr results, char *nulls); + + +#endif /* EXECFLATTEN_H */ + + + diff --git a/src/backend/executor/execJunk.c b/src/backend/executor/execJunk.c new file mode 100644 index 00000000000..7ee1543299a --- /dev/null +++ b/src/backend/executor/execJunk.c @@ -0,0 +1,389 @@ +/*------------------------------------------------------------------------- + * + * junk.c-- + * Junk attribute support stuff.... + * + * Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * $Header: /cvsroot/pgsql/src/backend/executor/execJunk.c,v 1.1.1.1 1996/07/09 06:21:24 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +#include "utils/palloc.h" +#include "executor/executor.h" +#include "nodes/relation.h" +#include "optimizer/tlist.h" /* for MakeTLE */ + +/*------------------------------------------------------------------------- + * XXX this stuff should be rewritten to take advantage + * of ExecProject() and the ProjectionInfo node. + * -cim 6/3/91 + * + * An attribute of a tuple living inside the executor, can be + * either a normal attribute or a "junk" attribute. "junk" attributes + * never make it out of the executor, i.e. they are never printed, + * returned or stored in disk. Their only purpose in life is to + * store some information useful only to the executor, mainly the values + * of some system attributes like "ctid" or rule locks. + * + * The general idea is the following: A target list consists of a list of + * Resdom nodes & expression pairs. Each Resdom node has an attribute + * called 'resjunk'. If the value of this attribute is 1 then the + * corresponding attribute is a "junk" attribute. + * + * When we initialize a plan we call 'ExecInitJunkFilter' to create + * and store the appropriate information in the 'es_junkFilter' attribute of + * EState. + * + * We then execute the plan ignoring the "resjunk" attributes. + * + * Finally, when at the top level we get back a tuple, we can call + * 'ExecGetJunkAttribute' to retrieve the value of the junk attributes we + * are interested in, and 'ExecRemoveJunk' to remove all the junk attributes + * from a tuple. This new "clean" tuple is then printed, replaced, deleted + * or inserted. + * + *------------------------------------------------------------------------- + */ + +/*------------------------------------------------------------------------- + * ExecInitJunkFilter + * + * Initialize the Junk filter. + *------------------------------------------------------------------------- + */ +JunkFilter * +ExecInitJunkFilter(List *targetList) +{ + JunkFilter *junkfilter; + List *cleanTargetList; + int len, cleanLength; + TupleDesc tupType, cleanTupType; + List *t; + TargetEntry *tle; + Resdom *resdom, *cleanResdom; + int resjunk; + AttrNumber cleanResno; + AttrNumber *cleanMap; + Size size; + Node *expr; + + /* --------------------- + * First find the "clean" target list, i.e. all the entries + * in the original target list which have a zero 'resjunk' + * NOTE: make copy of the Resdom nodes, because we have + * to change the 'resno's... + * --------------------- + */ + cleanTargetList = NIL; + cleanResno = 1; + + foreach (t, targetList) { + TargetEntry *rtarget = lfirst(t); + if (rtarget->resdom != NULL) { + resdom = rtarget->resdom; + expr = rtarget->expr; + resjunk = resdom->resjunk; + if (resjunk == 0) { + /* + * make a copy of the resdom node, changing its resno. + */ + cleanResdom = (Resdom *) copyObject(resdom); + cleanResdom->resno = cleanResno; + cleanResno ++; + /* + * create a new target list entry + */ + tle = makeNode(TargetEntry); + tle->resdom = cleanResdom; + tle->expr = expr; + cleanTargetList = lappend(cleanTargetList, tle); + } + } + else { +#ifdef SETS_FIXED + List *fjListP; + Fjoin *cleanFjoin; + List *cleanFjList; + List *fjList = lfirst(t); + Fjoin *fjNode = (Fjoin *)tl_node(fjList); + + cleanFjoin = (Fjoin)copyObject((Node) fjNode); + cleanFjList = lcons(cleanFjoin, NIL); + + resdom = (Resdom) lfirst(get_fj_innerNode(fjNode)); + expr = lsecond(get_fj_innerNode(fjNode)); + cleanResdom = (Resdom) copyObject((Node) resdom); + set_resno(cleanResdom, cleanResno); + cleanResno++; + tle = (List) MakeTLE(cleanResdom, (Expr) expr); + set_fj_innerNode(cleanFjoin, tle); + + foreach(fjListP, lnext(fjList)) { + TargetEntry *tle = lfirst(fjListP); + + resdom = tle->resdom; + expr = tle->expr; + cleanResdom = (Resdom*) copyObject((Node) resdom); + cleanResno++; + cleanResdom->Resno = cleanResno; + /* + * create a new target list entry + */ + tle = (List) MakeTLE(cleanResdom, (Expr) expr); + cleanFjList = lappend(cleanFjList, tle); + } + lappend(cleanTargetList, cleanFjList); +#endif + } + } + + /* --------------------- + * Now calculate the tuple types for the original and the clean tuple + * + * XXX ExecTypeFromTL should be used sparingly. Don't we already + * have the tupType corresponding to the targetlist we are passed? + * -cim 5/31/91 + * --------------------- + */ + tupType = (TupleDesc) ExecTypeFromTL(targetList); + cleanTupType = (TupleDesc) ExecTypeFromTL(cleanTargetList); + + len = ExecTargetListLength(targetList); + cleanLength = ExecTargetListLength(cleanTargetList); + + /* --------------------- + * Now calculate the "map" between the original tuples attributes + * and the "clean" tuple's attributes. + * + * The "map" is an array of "cleanLength" attribute numbers, i.e. + * one entry for every attribute of the "clean" tuple. + * The value of this entry is the attribute number of the corresponding + * attribute of the "original" tuple. + * --------------------- + */ + if (cleanLength > 0) { + size = cleanLength * sizeof(AttrNumber); + cleanMap = (AttrNumber*) palloc(size); + cleanResno = 1; + foreach (t, targetList) { + TargetEntry *tle = lfirst(t); + if (tle->resdom != NULL) { + resdom = tle->resdom; + expr = tle->expr; + resjunk = resdom->resjunk; + if (resjunk == 0) { + cleanMap[cleanResno-1] = resdom->resno; + cleanResno ++; + } + } else { +#ifdef SETS_FIXED + List fjListP; + List fjList = lfirst(t); + Fjoin fjNode = (Fjoin)lfirst(fjList); + + /* what the hell is this????? */ + resdom = (Resdom) lfirst(get_fj_innerNode(fjNode)); +#endif + + cleanMap[cleanResno-1] = tle->resdom->resno; + cleanResno++; + +#ifdef SETS_FIXED + foreach(fjListP, lnext(fjList)) { + TargetEntry *tle = lfirst(fjListP); + + resdom = tle->resdom; + cleanMap[cleanResno-1] = resdom->resno; + cleanResno++; + } +#endif + } + } + } else { + cleanMap = NULL; + } + + /* --------------------- + * Finally create and initialize the JunkFilter. + * --------------------- + */ + junkfilter = makeNode(JunkFilter); + + junkfilter->jf_targetList = targetList; + junkfilter->jf_length = len; + junkfilter->jf_tupType = tupType; + junkfilter->jf_cleanTargetList = cleanTargetList; + junkfilter->jf_cleanLength = cleanLength; + junkfilter->jf_cleanTupType = cleanTupType; + junkfilter->jf_cleanMap = cleanMap; + + return(junkfilter); + +} + +/*------------------------------------------------------------------------- + * ExecGetJunkAttribute + * + * Given a tuple (slot), the junk filter and a junk attribute's name, + * extract & return the value of this attribute. + * + * It returns false iff no junk attribute with such name was found. + * + * NOTE: isNull might be NULL ! + *------------------------------------------------------------------------- + */ +bool +ExecGetJunkAttribute(JunkFilter *junkfilter, + TupleTableSlot *slot, + char *attrName, + Datum *value, + bool *isNull) +{ + List *targetList; + List *t; + Resdom *resdom; + AttrNumber resno; + char *resname; + int resjunk; + TupleDesc tupType; + HeapTuple tuple; + + /* --------------------- + * first look in the junkfilter's target list for + * an attribute with the given name + * --------------------- + */ + resno = InvalidAttrNumber; + targetList = junkfilter->jf_targetList; + + foreach (t, targetList) { + TargetEntry *tle = lfirst(t); + resdom = tle->resdom; + resname = resdom->resname; + resjunk = resdom->resjunk; + if (resjunk != 0 && (strcmp(resname, attrName) == 0)) { + /* We found it ! */ + resno = resdom->resno; + break; + } + } + + if (resno == InvalidAttrNumber) { + /* Ooops! We couldn't find this attribute... */ + return(false); + } + + /* --------------------- + * Now extract the attribute value from the tuple. + * --------------------- + */ + tuple = slot->val; + tupType = (TupleDesc) junkfilter->jf_tupType; + + *value = (Datum) + heap_getattr(tuple, InvalidBuffer, resno, tupType, isNull); + + return true; +} + +/*------------------------------------------------------------------------- + * ExecRemoveJunk + * + * Construct and return a tuple with all the junk attributes removed. + *------------------------------------------------------------------------- + */ +HeapTuple +ExecRemoveJunk(JunkFilter *junkfilter, TupleTableSlot *slot) +{ + HeapTuple tuple; + HeapTuple cleanTuple; + AttrNumber *cleanMap; + TupleDesc cleanTupType; + TupleDesc tupType; + int cleanLength; + bool isNull; + int i; + Size size; + Datum *values; + char *nulls; + Datum values_array[64]; + char nulls_array[64]; + + /* ---------------- + * get info from the slot and the junk filter + * ---------------- + */ + tuple = slot->val; + + tupType = (TupleDesc) junkfilter->jf_tupType; + cleanTupType = (TupleDesc) junkfilter->jf_cleanTupType; + cleanLength = junkfilter->jf_cleanLength; + cleanMap = junkfilter->jf_cleanMap; + + /* --------------------- + * Handle the trivial case first. + * --------------------- + */ + if (cleanLength == 0) + return (HeapTuple) NULL; + + /* --------------------- + * Create the arrays that will hold the attribute values + * and the null information for the new "clean" tuple. + * + * Note: we use memory on the stack to optimize things when + * we are dealing with a small number of tuples. + * for large tuples we just use palloc. + * --------------------- + */ + if (cleanLength > 64) { + size = cleanLength * sizeof(Datum); + values = (Datum *) palloc(size); + + size = cleanLength * sizeof(char); + nulls = (char *) palloc(size); + } else { + values = values_array; + nulls = nulls_array; + } + + /* --------------------- + * Exctract one by one all the values of the "clean" tuple. + * --------------------- + */ + for (i=0; i<cleanLength; i++) { + Datum d = (Datum) + heap_getattr(tuple, InvalidBuffer, cleanMap[i], tupType, &isNull); + + values[i] = d; + + if (isNull) + nulls[i] = 'n'; + else + nulls[i] = ' '; + } + + /* --------------------- + * Now form the new tuple. + * --------------------- + */ + cleanTuple = heap_formtuple(cleanTupType, + values, + nulls); + + /* --------------------- + * We are done. Free any space allocated for 'values' and 'nulls' + * and return the new tuple. + * --------------------- + */ + if (cleanLength > 64) { + pfree(values); + pfree(nulls); + } + + return(cleanTuple); +} + diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c new file mode 100644 index 00000000000..07aac34accb --- /dev/null +++ b/src/backend/executor/execMain.c @@ -0,0 +1,1023 @@ +/*------------------------------------------------------------------------- + * + * execMain.c-- + * top level executor interface routines + * + * INTERFACE ROUTINES + * ExecutorStart() + * ExecutorRun() + * ExecutorEnd() + * + * The old ExecutorMain() has been replaced by ExecutorStart(), + * ExecutorRun() and ExecutorEnd() + * + * These three procedures are the external interfaces to the executor. + * In each case, the query descriptor and the execution state is required + * as arguments + * + * ExecutorStart() must be called at the beginning of any execution of any + * query plan and ExecutorEnd() should always be called at the end of + * execution of a plan. + * + * ExecutorRun accepts 'feature' and 'count' arguments that specify whether + * the plan is to be executed forwards, backwards, and for how many tuples. + * + * Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * $Header: /cvsroot/pgsql/src/backend/executor/execMain.c,v 1.1.1.1 1996/07/09 06:21:25 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +#include "executor/executor.h" +#include "utils/builtins.h" +#include "utils/palloc.h" +#include "utils/acl.h" +#include "parser/parsetree.h" /* rt_fetch() */ +#include "storage/bufmgr.h" +#include "commands/async.h" +/* #include "access/localam.h" */ +#include "optimizer/var.h" + + +/* decls for local routines only used within this module */ +static void ExecCheckPerms(CmdType operation, int resultRelation, List *rangeTable, + Query *parseTree); +static TupleDesc InitPlan(CmdType operation, Query *parseTree, + Plan *plan, EState *estate); +static void EndPlan(Plan *plan, EState *estate); +static TupleTableSlot *ExecutePlan(EState *estate, Plan *plan, + Query *parseTree, CmdType operation, + int numberTuples, int direction, + void (*printfunc)()); +static void ExecRetrieve(TupleTableSlot *slot, void (*printfunc)(), + Relation intoRelationDesc); +static void ExecAppend(TupleTableSlot *slot,ItemPointer tupleid, + EState *estate); +static void ExecDelete(TupleTableSlot *slot, ItemPointer tupleid, + EState *estate); +static void ExecReplace(TupleTableSlot *slot, ItemPointer tupleid, + EState *estate, Query *parseTree); + +/* end of local decls */ + +/* ---------------------------------------------------------------- + * ExecutorStart + * + * This routine must be called at the beginning of any execution of any + * query plan + * + * returns (AttrInfo*) which describes the attributes of the tuples to + * be returned by the query. + * + * ---------------------------------------------------------------- + */ +TupleDesc +ExecutorStart(QueryDesc *queryDesc, EState *estate) +{ + TupleDesc result; + + /* sanity checks */ + Assert(queryDesc!=NULL); + + result = InitPlan(queryDesc->operation, + queryDesc->parsetree, + queryDesc->plantree, + estate); + + /* reset buffer refcount. the current refcounts + * are saved and will be restored when ExecutorEnd is called + * + * this makes sure that when ExecutorRun's are + * called recursively as for postquel functions, + * the buffers pinned by one ExecutorRun will not be + * unpinned by another ExecutorRun. + */ + BufferRefCountReset(estate->es_refcount); + + return result; +} + +/* ---------------------------------------------------------------- + * ExecutorRun + * + * This is the main routine of the executor module. It accepts + * the query descriptor from the traffic cop and executes the + * query plan. + * + * ExecutorStart must have been called already. + * + * the different features supported are: + * EXEC_RUN: retrieve all tuples in the forward direction + * EXEC_FOR: retrieve 'count' number of tuples in the forward dir + * EXEC_BACK: retrieve 'count' number of tuples in the backward dir + * EXEC_RETONE: return one tuple but don't 'retrieve' it + * used in postquel function processing + * + * + * ---------------------------------------------------------------- + */ +TupleTableSlot* +ExecutorRun(QueryDesc *queryDesc, EState *estate, int feature, int count) +{ + CmdType operation; + Query *parseTree; + Plan *plan; + TupleTableSlot *result; + CommandDest dest; + void (*destination)(); + + /* ---------------- + * sanity checks + * ---------------- + */ + Assert(queryDesc!=NULL); + + /* ---------------- + * extract information from the query descriptor + * and the query feature. + * ---------------- + */ + operation = queryDesc->operation; + parseTree = queryDesc->parsetree; + plan = queryDesc->plantree; + dest = queryDesc->dest; + destination = (void (*)()) DestToFunction(dest); + + switch(feature) { + + case EXEC_RUN: + result = ExecutePlan(estate, + plan, + parseTree, + operation, + ALL_TUPLES, + EXEC_FRWD, + destination); + break; + case EXEC_FOR: + result = ExecutePlan(estate, + plan, + parseTree, + operation, + count, + EXEC_FRWD, + destination); + break; + + /* ---------------- + * retrieve next n "backward" tuples + * ---------------- + */ + case EXEC_BACK: + result = ExecutePlan(estate, + plan, + parseTree, + operation, + count, + EXEC_BKWD, + destination); + break; + + /* ---------------- + * return one tuple but don't "retrieve" it. + * (this is used by the rule manager..) -cim 9/14/89 + * ---------------- + */ + case EXEC_RETONE: + result = ExecutePlan(estate, + plan, + parseTree, + operation, + ONE_TUPLE, + EXEC_FRWD, + destination); + break; + default: + elog(DEBUG, "ExecutorRun: Unknown feature %d", feature); + break; + } + + return result; +} + +/* ---------------------------------------------------------------- + * ExecutorEnd + * + * This routine must be called at the end of any execution of any + * query plan + * + * returns (AttrInfo*) which describes the attributes of the tuples to + * be returned by the query. + * + * ---------------------------------------------------------------- + */ +void +ExecutorEnd(QueryDesc *queryDesc, EState *estate) +{ + /* sanity checks */ + Assert(queryDesc!=NULL); + + EndPlan(queryDesc->plantree, estate); + + /* restore saved refcounts. */ + BufferRefCountRestore(estate->es_refcount); +} + +/* =============================================================== + * =============================================================== + static routines follow + * =============================================================== + * =============================================================== + */ + +static void +ExecCheckPerms(CmdType operation, + int resultRelation, + List *rangeTable, + Query *parseTree) +{ + int i = 1; + Oid relid; + HeapTuple htp; + List *lp; + List *qvars, *tvars; + int32 ok = 1; + char *opstr; + NameData rname; + char *userName; + +#define CHECK(MODE) pg_aclcheck(rname.data, userName, MODE) + + userName = GetPgUserName(); + + foreach (lp, rangeTable) { + RangeTblEntry *rte = lfirst(lp); + + relid = rte->relid; + htp = SearchSysCacheTuple(RELOID, + ObjectIdGetDatum(relid), + 0,0,0); + if (!HeapTupleIsValid(htp)) + elog(WARN, "ExecCheckPerms: bogus RT relid: %d", + relid); + strncpy(rname.data, + ((Form_pg_class) GETSTRUCT(htp))->relname.data, + NAMEDATALEN); + if (i == resultRelation) { /* this is the result relation */ + qvars = pull_varnos(parseTree->qual); + tvars = pull_varnos((Node*)parseTree->targetList); + if (intMember(resultRelation, qvars) || + intMember(resultRelation, tvars)) { + /* result relation is scanned */ + ok = CHECK(ACL_RD); + opstr = "read"; + if (!ok) + break; + } + switch (operation) { + case CMD_INSERT: + ok = CHECK(ACL_AP) || + CHECK(ACL_WR); + opstr = "append"; + break; + case CMD_NOTIFY: /* what does this mean?? -- jw, 1/6/94 */ + case CMD_DELETE: + case CMD_UPDATE: + ok = CHECK(ACL_WR); + opstr = "write"; + break; + default: + elog(WARN, "ExecCheckPerms: bogus operation %d", + operation); + } + } else { + /* XXX NOTIFY?? */ + ok = CHECK(ACL_RD); + opstr = "read"; + } + if (!ok) + break; + ++i; + } + if (!ok) { +/* + elog(WARN, "%s on \"%-.*s\": permission denied", opstr, + NAMEDATALEN, rname.data); +*/ + elog(WARN, "%s %s", rname.data, ACL_NO_PRIV_WARNING); + } +} + + +/* ---------------------------------------------------------------- + * InitPlan + * + * Initializes the query plan: open files, allocate storage + * and start up the rule manager + * ---------------------------------------------------------------- + */ +static TupleDesc +InitPlan(CmdType operation, Query *parseTree, Plan *plan, EState *estate) +{ + List *rangeTable; + int resultRelation; + Relation intoRelationDesc; + + TupleDesc tupType; + List *targetList; + int len; + + /* ---------------- + * get information from query descriptor + * ---------------- + */ + rangeTable = parseTree->rtable; + resultRelation = parseTree->resultRelation; + + /* ---------------- + * initialize the node's execution state + * ---------------- + */ + estate->es_range_table = rangeTable; + + /* ---------------- + * initialize the BaseId counter so node base_id's + * are assigned correctly. Someday baseid's will have to + * be stored someplace other than estate because they + * should be unique per query planned. + * ---------------- + */ + estate->es_BaseId = 1; + + /* ---------------- + * initialize result relation stuff + * ---------------- + */ + + if (resultRelation != 0 && operation != CMD_SELECT) { + /* ---------------- + * if we have a result relation, open it and + * initialize the result relation info stuff. + * ---------------- + */ + RelationInfo *resultRelationInfo; + Index resultRelationIndex; + RangeTblEntry *rtentry; + Oid resultRelationOid; + Relation resultRelationDesc; + + resultRelationIndex = resultRelation; + rtentry = rt_fetch(resultRelationIndex, rangeTable); + resultRelationOid = rtentry->relid; + resultRelationDesc = heap_open(resultRelationOid); + + /* Write-lock the result relation right away: if the relation + is used in a subsequent scan, we won't have to elevate the + read-lock set by heap_beginscan to a write-lock (needed by + heap_insert, heap_delete and heap_replace). + This will hopefully prevent some deadlocks. - 01/24/94 */ + RelationSetLockForWrite(resultRelationDesc); + + resultRelationInfo = makeNode(RelationInfo); + resultRelationInfo->ri_RangeTableIndex = resultRelationIndex; + resultRelationInfo->ri_RelationDesc = resultRelationDesc; + resultRelationInfo->ri_NumIndices = 0; + resultRelationInfo->ri_IndexRelationDescs = NULL; + resultRelationInfo->ri_IndexRelationInfo = NULL; + + /* ---------------- + * open indices on result relation and save descriptors + * in the result relation information.. + * ---------------- + */ + ExecOpenIndices(resultRelationOid, resultRelationInfo); + + estate->es_result_relation_info = resultRelationInfo; + } else { + /* ---------------- + * if no result relation, then set state appropriately + * ---------------- + */ + estate->es_result_relation_info = NULL; + } + +#ifndef NO_SECURITY + ExecCheckPerms(operation, resultRelation, rangeTable, parseTree); +#endif + + /* ---------------- + * initialize the executor "tuple" table. + * ---------------- + */ + { + int nSlots = ExecCountSlotsNode(plan); + TupleTable tupleTable = ExecCreateTupleTable(nSlots+10); /* why add ten? - jolly */ + + estate->es_tupleTable = tupleTable; + } + + /* ---------------- + * initialize the private state information for + * all the nodes in the query tree. This opens + * files, allocates storage and leaves us ready + * to start processing tuples.. + * ---------------- + */ + ExecInitNode(plan, estate, NULL); + + /* ---------------- + * get the tuple descriptor describing the type + * of tuples to return.. (this is especially important + * if we are creating a relation with "retrieve into") + * ---------------- + */ + tupType = ExecGetTupType(plan); /* tuple descriptor */ + targetList = plan->targetlist; + len = ExecTargetListLength(targetList); /* number of attributes */ + + /* ---------------- + * now that we have the target list, initialize the junk filter + * if this is a REPLACE or a DELETE query. + * We also init the junk filter if this is an append query + * (there might be some rule lock info there...) + * NOTE: in the future we might want to initialize the junk + * filter for all queries. + * ---------------- + */ + if (operation == CMD_UPDATE || operation == CMD_DELETE || + operation == CMD_INSERT) { + + JunkFilter *j = (JunkFilter*) ExecInitJunkFilter(targetList); + estate->es_junkFilter = j; + } else + estate->es_junkFilter = NULL; + + /* ---------------- + * initialize the "into" relation + * ---------------- + */ + intoRelationDesc = (Relation) NULL; + + if (operation == CMD_SELECT) { + char *intoName; + char archiveMode; + Oid intoRelationId; + + if (!parseTree->isPortal) { + /* + * a select into table + */ + if (parseTree->into != NULL) { + /* ---------------- + * create the "into" relation + * + * note: there is currently no way for the user to + * specify the desired archive mode of the + * "into" relation... + * ---------------- + */ + intoName = parseTree->into; + archiveMode = 'n'; + + intoRelationId = heap_create(intoName, + intoName, /* not used */ + archiveMode, + DEFAULT_SMGR, + tupType); + + /* ---------------- + * XXX rather than having to call setheapoverride(true) + * and then back to false, we should change the + * arguments to heap_open() instead.. + * ---------------- + */ + setheapoverride(true); + + intoRelationDesc = heap_open(intoRelationId); + + setheapoverride(false); + } + } + } + + estate->es_into_relation_descriptor = intoRelationDesc; + + /* ---------------- + * return the type information.. + * ---------------- + */ +/* + attinfo = (AttrInfo *)palloc(sizeof(AttrInfo)); + attinfo->numAttr = len; + attinfo->attrs = tupType->attrs; +*/ + + return tupType; +} + +/* ---------------------------------------------------------------- + * EndPlan + * + * Cleans up the query plan -- closes files and free up storages + * ---------------------------------------------------------------- + */ +static void +EndPlan(Plan *plan, EState *estate) +{ + RelationInfo *resultRelationInfo; + Relation intoRelationDesc; + + /* ---------------- + * get information from state + * ---------------- + */ + resultRelationInfo = estate->es_result_relation_info; + intoRelationDesc = estate->es_into_relation_descriptor; + + /* ---------------- + * shut down the query + * ---------------- + */ + ExecEndNode(plan, plan); + + /* ---------------- + * destroy the executor "tuple" table. + * ---------------- + */ + { + TupleTable tupleTable = (TupleTable) estate->es_tupleTable; + ExecDestroyTupleTable(tupleTable,true); /* was missing last arg */ + estate->es_tupleTable = NULL; + } + + /* ---------------- + * close the result relations if necessary + * ---------------- + */ + if (resultRelationInfo != NULL) { + Relation resultRelationDesc; + + resultRelationDesc = resultRelationInfo->ri_RelationDesc; + heap_close(resultRelationDesc); + + /* ---------------- + * close indices on the result relation + * ---------------- + */ + ExecCloseIndices(resultRelationInfo); + } + + /* ---------------- + * close the "into" relation if necessary + * ---------------- + */ + if (intoRelationDesc != NULL) { + heap_close(intoRelationDesc); + } +} + +/* ---------------------------------------------------------------- + * ExecutePlan + * + * processes the query plan to retrieve 'tupleCount' tuples in the + * direction specified. + * Retrieves all tuples if tupleCount is 0 + * + * result is either a slot containing a tuple in the case + * of a RETRIEVE or NULL otherwise. + * + * ---------------------------------------------------------------- + */ + +/* the ctid attribute is a 'junk' attribute that is removed before the + user can see it*/ + +static TupleTableSlot * +ExecutePlan(EState *estate, + Plan *plan, + Query *parseTree, + CmdType operation, + int numberTuples, + int direction, + void (*printfunc)()) +{ + Relation intoRelationDesc; + JunkFilter *junkfilter; + + TupleTableSlot *slot; + ItemPointer tupleid = NULL; + ItemPointerData tuple_ctid; + int current_tuple_count; + TupleTableSlot *result; + + /* ---------------- + * get information + * ---------------- + */ + intoRelationDesc = estate->es_into_relation_descriptor; + + /* ---------------- + * initialize local variables + * ---------------- + */ + slot = NULL; + current_tuple_count = 0; + result = NULL; + + /* ---------------- + * Set the direction. + * ---------------- + */ + estate->es_direction = direction; + + /* ---------------- + * Loop until we've processed the proper number + * of tuples from the plan.. + * ---------------- + */ + + for(;;) { + if (operation != CMD_NOTIFY) { + /* ---------------- + * Execute the plan and obtain a tuple + * ---------------- + */ + /* at the top level, the parent of a plan (2nd arg) is itself */ + slot = ExecProcNode(plan,plan); + + /* ---------------- + * if the tuple is null, then we assume + * there is nothing more to process so + * we just return null... + * ---------------- + */ + if (TupIsNull(slot)) { + result = NULL; + break; + } + } + + /* ---------------- + * if we have a junk filter, then project a new + * tuple with the junk removed. + * + * Store this new "clean" tuple in the place of the + * original tuple. + * + * Also, extract all the junk ifnormation we need. + * ---------------- + */ + if ((junkfilter = estate->es_junkFilter) != (JunkFilter*)NULL) { + Datum datum; +/* NameData attrName; */ + HeapTuple newTuple; + bool isNull; + + /* --------------- + * extract the 'ctid' junk attribute. + * --------------- + */ + if (operation == CMD_UPDATE || operation == CMD_DELETE) { + if (! ExecGetJunkAttribute(junkfilter, + slot, + "ctid", + &datum, + &isNull)) + elog(WARN,"ExecutePlan: NO (junk) `ctid' was found!"); + + if (isNull) + elog(WARN,"ExecutePlan: (junk) `ctid' is NULL!"); + + tupleid = (ItemPointer) DatumGetPointer(datum); + tuple_ctid = *tupleid; /* make sure we don't free the ctid!! */ + tupleid = &tuple_ctid; + } + + /* --------------- + * Finally create a new "clean" tuple with all junk attributes + * removed + * --------------- + */ + newTuple = ExecRemoveJunk(junkfilter, slot); + + slot = ExecStoreTuple(newTuple, /* tuple to store */ + slot, /* destination slot */ + InvalidBuffer,/* this tuple has no buffer */ + true); /* tuple should be pfreed */ + } /* if (junkfilter... */ + + /* ---------------- + * now that we have a tuple, do the appropriate thing + * with it.. either return it to the user, add + * it to a relation someplace, delete it from a + * relation, or modify some of it's attributes. + * ---------------- + */ + + switch(operation) { + case CMD_SELECT: + ExecRetrieve(slot, /* slot containing tuple */ + printfunc, /* print function */ + intoRelationDesc); /* "into" relation */ + result = slot; + break; + + case CMD_INSERT: + ExecAppend(slot, tupleid, estate); + result = NULL; + break; + + case CMD_DELETE: + ExecDelete(slot, tupleid, estate); + result = NULL; + break; + + case CMD_UPDATE: + ExecReplace(slot, tupleid, estate, parseTree); + result = NULL; + break; + + /* Total hack. I'm ignoring any accessor functions for + Relation, RelationTupleForm, NameData. + Assuming that NameData.data has offset 0. + */ + case CMD_NOTIFY: { + RelationInfo *rInfo = estate->es_result_relation_info; + Relation rDesc = rInfo->ri_RelationDesc; + Async_Notify(rDesc->rd_rel->relname.data); + result = NULL; + current_tuple_count = 0; + numberTuples = 1; + elog(DEBUG, "ExecNotify %s",&rDesc->rd_rel->relname); + } + break; + + default: + elog(DEBUG, "ExecutePlan: unknown operation in queryDesc"); + result = NULL; + break; + } + /* ---------------- + * check our tuple count.. if we've returned the + * proper number then return, else loop again and + * process more tuples.. + * ---------------- + */ + current_tuple_count += 1; + if (numberTuples == current_tuple_count) + break; + } + + /* ---------------- + * here, result is either a slot containing a tuple in the case + * of a RETRIEVE or NULL otherwise. + * ---------------- + */ + return result; +} + +/* ---------------------------------------------------------------- + * ExecRetrieve + * + * RETRIEVEs are easy.. we just pass the tuple to the appropriate + * print function. The only complexity is when we do a + * "retrieve into", in which case we insert the tuple into + * the appropriate relation (note: this is a newly created relation + * so we don't need to worry about indices or locks.) + * ---------------------------------------------------------------- + */ +static void +ExecRetrieve(TupleTableSlot *slot, + void (*printfunc)(), + Relation intoRelationDesc) +{ + HeapTuple tuple; + TupleDesc attrtype; + + /* ---------------- + * get the heap tuple out of the tuple table slot + * ---------------- + */ + tuple = slot->val; + attrtype = slot->ttc_tupleDescriptor; + + /* ---------------- + * insert the tuple into the "into relation" + * ---------------- + */ + if (intoRelationDesc != NULL) { + heap_insert (intoRelationDesc, tuple); + IncrAppended(); + } + + /* ---------------- + * send the tuple to the front end (or the screen) + * ---------------- + */ + (*printfunc)(tuple, attrtype); + IncrRetrieved(); +} + +/* ---------------------------------------------------------------- + * ExecAppend + * + * APPENDs are trickier.. we have to insert the tuple into + * the base relation and insert appropriate tuples into the + * index relations. + * ---------------------------------------------------------------- + */ + +static void +ExecAppend(TupleTableSlot *slot, + ItemPointer tupleid, + EState *estate) +{ + HeapTuple tuple; + RelationInfo *resultRelationInfo; + Relation resultRelationDesc; + int numIndices; + Oid newId; + + /* ---------------- + * get the heap tuple out of the tuple table slot + * ---------------- + */ + tuple = slot->val; + + /* ---------------- + * get information on the result relation + * ---------------- + */ + resultRelationInfo = estate->es_result_relation_info; + resultRelationDesc = resultRelationInfo->ri_RelationDesc; + + /* ---------------- + * have to add code to preform unique checking here. + * cim -12/1/89 + * ---------------- + */ + + /* ---------------- + * insert the tuple + * ---------------- + */ + newId = heap_insert(resultRelationDesc, /* relation desc */ + tuple); /* heap tuple */ + IncrAppended(); + UpdateAppendOid(newId); + + /* ---------------- + * process indices + * + * Note: heap_insert adds a new tuple to a relation. As a side + * effect, the tupleid of the new tuple is placed in the new + * tuple's t_ctid field. + * ---------------- + */ + numIndices = resultRelationInfo->ri_NumIndices; + if (numIndices > 0) { + ExecInsertIndexTuples(slot, &(tuple->t_ctid), estate); + } +} + +/* ---------------------------------------------------------------- + * ExecDelete + * + * DELETE is like append, we delete the tuple and its + * index tuples. + * ---------------------------------------------------------------- + */ +static void +ExecDelete(TupleTableSlot *slot, + ItemPointer tupleid, + EState *estate) +{ + RelationInfo *resultRelationInfo; + Relation resultRelationDesc; + + /* ---------------- + * get the result relation information + * ---------------- + */ + resultRelationInfo = estate->es_result_relation_info; + resultRelationDesc = resultRelationInfo->ri_RelationDesc; + + /* ---------------- + * delete the tuple + * ---------------- + */ + (void) heap_delete(resultRelationDesc, /* relation desc */ + tupleid); /* item pointer to tuple */ + + IncrDeleted(); + + /* ---------------- + * Note: Normally one would think that we have to + * delete index tuples associated with the + * heap tuple now.. + * + * ... but in POSTGRES, we have no need to do this + * because the vacuum daemon automatically + * opens an index scan and deletes index tuples + * when it finds deleted heap tuples. -cim 9/27/89 + * ---------------- + */ + +} + +/* ---------------------------------------------------------------- + * ExecReplace + * + * note: we can't run replace queries with transactions + * off because replaces are actually appends and our + * scan will mistakenly loop forever, replacing the tuple + * it just appended.. This should be fixed but until it + * is, we don't want to get stuck in an infinite loop + * which corrupts your database.. + * ---------------------------------------------------------------- + */ +static void +ExecReplace(TupleTableSlot *slot, + ItemPointer tupleid, + EState *estate, + Query *parseTree) +{ + HeapTuple tuple; + RelationInfo *resultRelationInfo; + Relation resultRelationDesc; + int numIndices; + + /* ---------------- + * abort the operation if not running transactions + * ---------------- + */ + if (IsBootstrapProcessingMode()) { + elog(DEBUG, "ExecReplace: replace can't run without transactions"); + return; + } + + /* ---------------- + * get the heap tuple out of the tuple table slot + * ---------------- + */ + tuple = slot->val; + + /* ---------------- + * get the result relation information + * ---------------- + */ + resultRelationInfo = estate->es_result_relation_info; + resultRelationDesc = resultRelationInfo->ri_RelationDesc; + + /* ---------------- + * have to add code to preform unique checking here. + * in the event of unique tuples, this becomes a deletion + * of the original tuple affected by the replace. + * cim -12/1/89 + * ---------------- + */ + + /* ---------------- + * replace the heap tuple + * + * Don't want to continue if our heap_replace didn't actually + * do a replace. This would be the case if heap_replace + * detected a non-functional update. -kw 12/30/93 + * ---------------- + */ + if (heap_replace(resultRelationDesc, /* relation desc */ + tupleid, /* item ptr of tuple to replace */ + tuple)) { /* replacement heap tuple */ + return; + } + + IncrReplaced(); + + /* ---------------- + * Note: instead of having to update the old index tuples + * associated with the heap tuple, all we do is form + * and insert new index tuples.. This is because + * replaces are actually deletes and inserts and + * index tuple deletion is done automagically by + * the vaccuum deamon.. All we do is insert new + * index tuples. -cim 9/27/89 + * ---------------- + */ + + /* ---------------- + * process indices + * + * heap_replace updates a tuple in the base relation by invalidating + * it and then appending a new tuple to the relation. As a side + * effect, the tupleid of the new tuple is placed in the new + * tuple's t_ctid field. So we now insert index tuples using + * the new tupleid stored there. + * ---------------- + */ + numIndices = resultRelationInfo->ri_NumIndices; + if (numIndices > 0) { + ExecInsertIndexTuples(slot, &(tuple->t_ctid), estate); + } +} diff --git a/src/backend/executor/execProcnode.c b/src/backend/executor/execProcnode.c new file mode 100644 index 00000000000..11a6f63a778 --- /dev/null +++ b/src/backend/executor/execProcnode.c @@ -0,0 +1,477 @@ +/*------------------------------------------------------------------------- + * + * execProcnode.c-- + * contains dispatch functions which call the appropriate "initialize", + * "get a tuple", and "cleanup" routines for the given node type. + * If the node has children, then it will presumably call ExecInitNode, + * ExecProcNode, or ExecEndNode on it's subnodes and do the appropriate + * processing.. + * + * Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * $Header: /cvsroot/pgsql/src/backend/executor/execProcnode.c,v 1.1.1.1 1996/07/09 06:21:25 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +/* + * INTERFACE ROUTINES + * ExecInitNode - initialize a plan node and it's subplans + * ExecProcNode - get a tuple by executing the plan node + * ExecEndNode - shut down a plan node and it's subplans + * + * NOTES + * This used to be three files. It is now all combined into + * one file so that it is easier to keep ExecInitNode, ExecProcNode, + * and ExecEndNode in sync when new nodes are added. + * + * EXAMPLE + * suppose we want the age of the manager of the shoe department and + * the number of employees in that department. so we have the query: + * + * retrieve (DEPT.no_emps, EMP.age) + * where EMP.name = DEPT.mgr and + * DEPT.name = "shoe" + * + * Suppose the planner gives us the following plan: + * + * Nest Loop (DEPT.mgr = EMP.name) + * / \ + * / \ + * Seq Scan Seq Scan + * DEPT EMP + * (name = "shoe") + * + * ExecStart() is called first. + * It calls InitPlan() which calls ExecInitNode() on + * the root of the plan -- the nest loop node. + * + * * ExecInitNode() notices that it is looking at a nest loop and + * as the code below demonstrates, it calls ExecInitNestLoop(). + * Eventually this calls ExecInitNode() on the right and left subplans + * and so forth until the entire plan is initialized. + * + * * Then when ExecRun() is called, it calls ExecutePlan() which + * calls ExecProcNode() repeatedly on the top node of the plan. + * Each time this happens, ExecProcNode() will end up calling + * ExecNestLoop(), which calls ExecProcNode() on its subplans. + * Each of these subplans is a sequential scan so ExecSeqScan() is + * called. The slots returned by ExecSeqScan() may contain + * tuples which contain the attributes ExecNestLoop() uses to + * form the tuples it returns. + * + * * Eventually ExecSeqScan() stops returning tuples and the nest + * loop join ends. Lastly, ExecEnd() calls ExecEndNode() which + * calls ExecEndNestLoop() which in turn calls ExecEndNode() on + * its subplans which result in ExecEndSeqScan(). + * + * This should show how the executor works by having + * ExecInitNode(), ExecProcNode() and ExecEndNode() dispatch + * their work to the appopriate node support routines which may + * in turn call these routines themselves on their subplans. + * + */ +#include "executor/executor.h" +#include "executor/nodeResult.h" +#include "executor/nodeAppend.h" +#include "executor/nodeSeqscan.h" +#include "executor/nodeIndexscan.h" +#include "executor/nodeNestloop.h" +#include "executor/nodeMergejoin.h" +#include "executor/nodeMaterial.h" +#include "executor/nodeSort.h" +#include "executor/nodeUnique.h" +#include "executor/nodeGroup.h" +#include "executor/nodeAgg.h" +#include "executor/nodeHash.h" +#include "executor/nodeHashjoin.h" +#include "executor/nodeTee.h" + +/* ------------------------------------------------------------------------ + * ExecInitNode + * + * Recursively initializes all the nodes in the plan rooted + * at 'node'. + * + * Initial States: + * 'node' is the plan produced by the query planner + * + * returns TRUE/FALSE on whether the plan was successfully initialized + * ------------------------------------------------------------------------ + */ +bool +ExecInitNode(Plan *node, EState *estate, Plan *parent) +{ + bool result; + + /* ---------------- + * do nothing when we get to the end + * of a leaf on tree. + * ---------------- + */ + if (node == NULL) + return FALSE; + + switch(nodeTag(node)) { + /* ---------------- + * control nodes + * ---------------- + */ + case T_Result: + result = ExecInitResult((Result *)node, estate, parent); + break; + + case T_Append: + result = ExecInitAppend((Append *)node, estate, parent); + break; + + /* ---------------- + * scan nodes + * ---------------- + */ + case T_SeqScan: + result = ExecInitSeqScan((SeqScan *)node, estate, parent); + break; + + case T_IndexScan: + result = ExecInitIndexScan((IndexScan *)node, estate, parent); + break; + + /* ---------------- + * join nodes + * ---------------- + */ + case T_NestLoop: + result = ExecInitNestLoop((NestLoop *)node, estate, parent); + break; + + case T_MergeJoin: + result = ExecInitMergeJoin((MergeJoin *)node, estate, parent); + break; + + /* ---------------- + * materialization nodes + * ---------------- + */ + case T_Material: + result = ExecInitMaterial((Material *)node, estate, parent); + break; + + case T_Sort: + result = ExecInitSort((Sort *)node, estate, parent); + break; + + case T_Unique: + result = ExecInitUnique((Unique *)node, estate, parent); + break; + + case T_Group: + result = ExecInitGroup((Group *)node, estate, parent); + break; + + case T_Agg: + result = ExecInitAgg((Agg *)node, estate, parent); + break; + + case T_Hash: + result = ExecInitHash((Hash *)node, estate, parent); + break; + + case T_HashJoin: + result = ExecInitHashJoin((HashJoin *)node, estate, parent); + break; + + case T_Tee: + result = ExecInitTee((Tee*)node, estate, parent); + break; + + default: + elog(DEBUG, "ExecInitNode: node not yet supported: %d", + nodeTag(node)); + result = FALSE; + } + + return result; +} + + +/* ---------------------------------------------------------------- + * ExecProcNode + * + * Initial States: + * the query tree must be initialized once by calling ExecInit. + * ---------------------------------------------------------------- + */ +TupleTableSlot * +ExecProcNode(Plan *node, Plan *parent) +{ + TupleTableSlot *result; + + /* ---------------- + * deal with NULL nodes.. + * ---------------- + */ + if (node == NULL) + return NULL; + + switch(nodeTag(node)) { + /* ---------------- + * control nodes + * ---------------- + */ + case T_Result: + result = ExecResult((Result *)node); + break; + + case T_Append: + result = ExecProcAppend((Append *)node); + break; + + /* ---------------- + * scan nodes + * ---------------- + */ + case T_SeqScan: + result = ExecSeqScan((SeqScan *)node); + break; + + case T_IndexScan: + result = ExecIndexScan((IndexScan *)node); + break; + + /* ---------------- + * join nodes + * ---------------- + */ + case T_NestLoop: + result = ExecNestLoop((NestLoop *)node, parent); + break; + + case T_MergeJoin: + result = ExecMergeJoin((MergeJoin *)node); + break; + + /* ---------------- + * materialization nodes + * ---------------- + */ + case T_Material: + result = ExecMaterial((Material *)node); + break; + + case T_Sort: + result = ExecSort((Sort *)node); + break; + + case T_Unique: + result = ExecUnique((Unique *)node); + break; + + case T_Group: + result = ExecGroup((Group *)node); + break; + + case T_Agg: + result = ExecAgg((Agg *)node); + break; + + case T_Hash: + result = ExecHash((Hash *)node); + break; + + case T_HashJoin: + result = ExecHashJoin((HashJoin *)node); + break; + + case T_Tee: + result = ExecTee((Tee*)node, parent); + break; + + default: + elog(DEBUG, "ExecProcNode: node not yet supported: %d", + nodeTag(node)); + result = FALSE; + } + + return result; +} + +int +ExecCountSlotsNode(Plan *node) +{ + if (node == (Plan *)NULL) + return 0; + + switch(nodeTag(node)) { + /* ---------------- + * control nodes + * ---------------- + */ + case T_Result: + return ExecCountSlotsResult((Result *)node); + + case T_Append: + return ExecCountSlotsAppend((Append *)node); + + /* ---------------- + * scan nodes + * ---------------- + */ + case T_SeqScan: + return ExecCountSlotsSeqScan((SeqScan *)node); + + case T_IndexScan: + return ExecCountSlotsIndexScan((IndexScan *)node); + + /* ---------------- + * join nodes + * ---------------- + */ + case T_NestLoop: + return ExecCountSlotsNestLoop((NestLoop *)node); + + case T_MergeJoin: + return ExecCountSlotsMergeJoin((MergeJoin *)node); + + /* ---------------- + * materialization nodes + * ---------------- + */ + case T_Material: + return ExecCountSlotsMaterial((Material *)node); + + case T_Sort: + return ExecCountSlotsSort((Sort *)node); + + case T_Unique: + return ExecCountSlotsUnique((Unique *)node); + + case T_Group: + return ExecCountSlotsGroup((Group *)node); + + case T_Agg: + return ExecCountSlotsAgg((Agg *)node); + + case T_Hash: + return ExecCountSlotsHash((Hash *)node); + + case T_HashJoin: + return ExecCountSlotsHashJoin((HashJoin *)node); + + case T_Tee: + return ExecCountSlotsTee((Tee*)node); + + default: + elog(WARN, "ExecCountSlotsNode: node not yet supported: %d", + nodeTag(node)); + break; + } + return 0; +} + +/* ---------------------------------------------------------------- + * ExecEndNode + * + * Recursively cleans up all the nodes in the plan rooted + * at 'node'. + * + * After this operation, the query plan will not be able to + * processed any further. This should be called only after + * the query plan has been fully executed. + * ---------------------------------------------------------------- + */ +void +ExecEndNode(Plan *node, Plan *parent) +{ + /* ---------------- + * do nothing when we get to the end + * of a leaf on tree. + * ---------------- + */ + if (node == NULL) + return; + + switch(nodeTag(node)) { + /* ---------------- + * control nodes + * ---------------- + */ + case T_Result: + ExecEndResult((Result *)node); + break; + + case T_Append: + ExecEndAppend((Append *)node); + break; + + /* ---------------- + * scan nodes + * ---------------- + */ + case T_SeqScan: + ExecEndSeqScan((SeqScan *)node); + break; + + case T_IndexScan: + ExecEndIndexScan((IndexScan *)node); + break; + + /* ---------------- + * join nodes + * ---------------- + */ + case T_NestLoop: + ExecEndNestLoop((NestLoop *)node); + break; + + case T_MergeJoin: + ExecEndMergeJoin((MergeJoin *)node); + break; + + /* ---------------- + * materialization nodes + * ---------------- + */ + case T_Material: + ExecEndMaterial((Material *)node); + break; + + case T_Sort: + ExecEndSort((Sort *)node); + break; + + case T_Unique: + ExecEndUnique((Unique *)node); + break; + + case T_Group: + ExecEndGroup((Group *)node); + break; + + case T_Agg: + ExecEndAgg((Agg *)node); + break; + + /* ---------------- + * XXX add hooks to these + * ---------------- + */ + case T_Hash: + ExecEndHash((Hash *) node); + break; + + case T_HashJoin: + ExecEndHashJoin((HashJoin *) node); + break; + + case T_Tee: + ExecEndTee((Tee*) node, parent); + break; + + default: + elog(DEBUG, "ExecEndNode: node not yet supported", + nodeTag(node)); + break; + } +} diff --git a/src/backend/executor/execQual.c b/src/backend/executor/execQual.c new file mode 100644 index 00000000000..104c1f2f506 --- /dev/null +++ b/src/backend/executor/execQual.c @@ -0,0 +1,1504 @@ +/*------------------------------------------------------------------------- + * + * execQual.c-- + * Routines to evaluate qualification and targetlist expressions + * + * Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * $Header: /cvsroot/pgsql/src/backend/executor/execQual.c,v 1.1.1.1 1996/07/09 06:21:25 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +/* + * INTERFACE ROUTINES + * ExecEvalExpr - evaluate an expression and return a datum + * ExecQual - return true/false if qualification is satisified + * ExecTargetList - form a new tuple by projecting the given tuple + * + * NOTES + * ExecEvalExpr() and ExecEvalVar() are hotspots. making these faster + * will speed up the entire system. Unfortunately they are currently + * implemented recursively.. Eliminating the recursion is bound to + * improve the speed of the executor. + * + * ExecTargetList() is used to make tuple projections. Rather then + * trying to speed it up, the execution plan should be pre-processed + * to facilitate attribute sharing between nodes wherever possible, + * instead of doing needless copying. -cim 5/31/91 + * + */ +#include "nodes/primnodes.h" +#include "nodes/relation.h" + +#include "optimizer/clauses.h" + +#include "nodes/memnodes.h" +#include "catalog/pg_language.h" +#include "executor/executor.h" +#include "executor/execFlatten.h" +#include "executor/functions.h" +#include "access/heapam.h" +#include "utils/memutils.h" +#include "utils/builtins.h" +#include "utils/palloc.h" +#include "utils/fcache.h" +#include "utils/fcache2.h" +#include "utils/array.h" + +/* ---------------- + * externs and constants + * ---------------- + */ + +/* + * XXX Used so we can get rid of use of Const nodes in the executor. + * Currently only used by ExecHashGetBucket and set only by ExecMakeVarConst + * and by ExecEvalArrayRef. + */ +bool execConstByVal; +int execConstLen; + +/* static functions decls */ +static Datum ExecEvalAggreg(Aggreg *agg, ExprContext *econtext, bool *isNull); +static Datum ExecEvalArrayRef(ArrayRef *arrayRef, ExprContext *econtext, + bool *isNull, bool *isDone); + +/* -------------------------------- + * ExecEvalArrayRef + * + * This function takes an ArrayRef and returns a Const Node if it + * is an array reference or returns the changed Array Node if it is + * an array assignment. + * + * -------------------------------- + */ +static Datum +ExecEvalArrayRef(ArrayRef *arrayRef, + ExprContext *econtext, + bool *isNull, + bool *isDone) +{ + bool dummy; + int i = 0, j = 0; + ArrayType *array_scanner; + List *upperIndexpr, *lowerIndexpr; + Node *assgnexpr; + List *elt; + IntArray upper, lower; + int *lIndex; + char *dataPtr; + + execConstByVal = arrayRef->refelembyval; + *isNull = false; + array_scanner = (ArrayType*)ExecEvalExpr(arrayRef->refexpr, + econtext, + isNull, + isDone); + if (*isNull) return (Datum)NULL; + + upperIndexpr = arrayRef->refupperindexpr; + + foreach (elt, upperIndexpr) { + upper.indx[i++] = (int32)ExecEvalExpr((Node*)lfirst(elt), + econtext, + isNull, + &dummy); + if (*isNull) return (Datum)NULL; + } + + lowerIndexpr = arrayRef->reflowerindexpr; + lIndex = NULL; + if (lowerIndexpr != NIL) { + foreach (elt, lowerIndexpr) { + lower.indx[j++] = (int32)ExecEvalExpr((Node*)lfirst(elt), + econtext, + isNull, + &dummy); + if (*isNull) return (Datum)NULL; + } + if (i != j) + elog(WARN, + "ExecEvalArrayRef: upper and lower indices mismatch"); + lIndex = lower.indx; + } + + assgnexpr = arrayRef->refassgnexpr; + if (assgnexpr != NULL) { + dataPtr = (char*)ExecEvalExpr((Node *) + assgnexpr, econtext, + isNull, &dummy); + if (*isNull) return (Datum)NULL; + if (lIndex == NULL) + return (Datum) array_set(array_scanner, i, upper.indx, dataPtr, + arrayRef->refelembyval, + arrayRef->refelemlength, + arrayRef->refattrlength, isNull); + return (Datum) array_assgn(array_scanner, i, upper.indx, + lower.indx, + (ArrayType*)dataPtr, + arrayRef->refelembyval, + arrayRef->refelemlength, isNull); + } + if (lIndex == NULL) + return (Datum) array_ref(array_scanner, i, upper.indx, + arrayRef->refelembyval, + arrayRef->refelemlength, + arrayRef->refattrlength, isNull); + return (Datum) array_clip(array_scanner, i, upper.indx, lower.indx, + arrayRef->refelembyval, + arrayRef->refelemlength, isNull); +} + + +/* ---------------------------------------------------------------- + * ExecEvalAggreg + * + * Returns a Datum whose value is the value of the precomputed + * aggregate found in the given expression context. + * ---------------------------------------------------------------- + */ +static Datum +ExecEvalAggreg(Aggreg *agg, ExprContext *econtext, bool *isNull) +{ + + *isNull = econtext->ecxt_nulls[agg->aggno]; + return econtext->ecxt_values[agg->aggno]; +} + +/* ---------------------------------------------------------------- + * ExecEvalVar + * + * Returns a Datum whose value is the value of a range + * variable with respect to given expression context. + * ---------------------------------------------------------------- + */ +Datum +ExecEvalVar(Var *variable, ExprContext *econtext, bool *isNull) +{ + Datum result; + TupleTableSlot *slot; + AttrNumber attnum; + HeapTuple heapTuple; + TupleDesc tuple_type; + Buffer buffer; + bool byval; + int16 len; + + /* ---------------- + * get the slot we want + * ---------------- + */ + switch(variable->varno) { + case INNER: /* get the tuple from the inner node */ + slot = econtext->ecxt_innertuple; + break; + + case OUTER: /* get the tuple from the outer node */ + slot = econtext->ecxt_outertuple; + break; + + default: /* get the tuple from the relation being scanned */ + slot = econtext->ecxt_scantuple; + break; + } + + /* ---------------- + * extract tuple information from the slot + * ---------------- + */ + heapTuple = slot->val; + tuple_type = slot->ttc_tupleDescriptor; + buffer = slot->ttc_buffer; + + attnum = variable->varattno; + + /* + * If the attribute number is invalid, then we are supposed to + * return the entire tuple, we give back a whole slot so that + * callers know what the tuple looks like. + */ + if (attnum == InvalidAttrNumber) + { + TupleTableSlot *tempSlot; + TupleDesc td; + HeapTuple tup; + + tempSlot = makeNode(TupleTableSlot); + tempSlot->ttc_shouldFree = false; + tempSlot->ttc_descIsNew = true; + tempSlot->ttc_tupleDescriptor = (TupleDesc)NULL, + tempSlot->ttc_buffer = InvalidBuffer; + tempSlot->ttc_whichplan = -1; + + tup = heap_copytuple(slot->val); + td = CreateTupleDescCopy(slot->ttc_tupleDescriptor); + + ExecSetSlotDescriptor(tempSlot, td); + + ExecStoreTuple(tup, tempSlot, InvalidBuffer, true); + return (Datum) tempSlot; + } + + result = (Datum) + heap_getattr(heapTuple, /* tuple containing attribute */ + buffer, /* buffer associated with tuple */ + attnum, /* attribute number of desired attribute */ + tuple_type, /* tuple descriptor of tuple */ + isNull); /* return: is attribute null? */ + + /* ---------------- + * return null if att is null + * ---------------- + */ + if (*isNull) + return (Datum) NULL; + + /* ---------------- + * get length and type information.. + * ??? what should we do about variable length attributes + * - variable length attributes have their length stored + * in the first 4 bytes of the memory pointed to by the + * returned value.. If we can determine that the type + * is a variable length type, we can do the right thing. + * -cim 9/15/89 + * ---------------- + */ + if (attnum < 0) { + /* ---------------- + * If this is a pseudo-att, we get the type and fake the length. + * There ought to be a routine to return the real lengths, so + * we'll mark this one ... XXX -mao + * ---------------- + */ + len = heap_sysattrlen(attnum); /* XXX see -mao above */ + byval = heap_sysattrbyval(attnum); /* XXX see -mao above */ + } else { + len = tuple_type->attrs[ attnum-1 ]->attlen; + byval = tuple_type->attrs[ attnum-1 ]->attbyval ? true : false ; + } + + execConstByVal = byval; + execConstLen = len; + + return result; +} + +/* ---------------------------------------------------------------- + * ExecEvalParam + * + * Returns the value of a parameter. A param node contains + * something like ($.name) and the expression context contains + * the current parameter bindings (name = "sam") (age = 34)... + * so our job is to replace the param node with the datum + * containing the appropriate information ("sam"). + * + * Q: if we have a parameter ($.foo) without a binding, i.e. + * there is no (foo = xxx) in the parameter list info, + * is this a fatal error or should this be a "not available" + * (in which case we shoud return a Const node with the + * isnull flag) ? -cim 10/13/89 + * + * Minor modification: Param nodes now have an extra field, + * `paramkind' which specifies the type of parameter + * (see params.h). So while searching the paramList for + * a paramname/value pair, we have also to check for `kind'. + * + * NOTE: The last entry in `paramList' is always an + * entry with kind == PARAM_INVALID. + * ---------------------------------------------------------------- + */ +Datum +ExecEvalParam(Param *expression, ExprContext *econtext, bool *isNull) +{ + + char *thisParameterName; + int thisParameterKind; + AttrNumber thisParameterId; + int matchFound; + ParamListInfo paramList; + + thisParameterName = expression->paramname; + thisParameterKind = expression->paramkind; + thisParameterId = expression->paramid; + paramList = econtext->ecxt_param_list_info; + + *isNull = false; + /* + * search the list with the parameter info to find a matching name. + * An entry with an InvalidName denotes the last element in the array. + */ + matchFound = 0; + if (paramList != NULL) { + /* + * search for an entry in 'paramList' that matches + * the `expression'. + */ + while(paramList->kind != PARAM_INVALID && !matchFound) { + switch (thisParameterKind) { + case PARAM_NAMED: + if (thisParameterKind == paramList->kind && + strcmp(paramList->name, thisParameterName) == 0){ + matchFound = 1; + } + break; + case PARAM_NUM: + if (thisParameterKind == paramList->kind && + paramList->id == thisParameterId) { + matchFound = 1; + } + break; + case PARAM_OLD: + case PARAM_NEW: + if (thisParameterKind == paramList->kind && + paramList->id == thisParameterId) + { + matchFound = 1; + /* + * sanity check + */ + if (strcmp(paramList->name, thisParameterName) != 0){ + elog(WARN, + "ExecEvalParam: new/old params with same id & diff names"); + } + } + break; + default: + /* + * oops! this is not supposed to happen! + */ + elog(WARN, "ExecEvalParam: invalid paramkind %d", + thisParameterKind); + } + if (! matchFound) { + paramList++; + } + } /*while*/ + } /*if*/ + + if (!matchFound) { + /* + * ooops! we couldn't find this parameter + * in the parameter list. Signal an error + */ + elog(WARN, "ExecEvalParam: Unknown value for parameter %s", + thisParameterName); + } + + /* + * return the value. + */ + if (paramList->isnull) + { + *isNull = true; + return (Datum)NULL; + } + + if (expression->param_tlist != NIL) + { + HeapTuple tup; + Datum value; + List *tlist = expression->param_tlist; + TargetEntry *tle = (TargetEntry *)lfirst(tlist); + TupleTableSlot *slot = (TupleTableSlot *)paramList->value; + + tup = slot->val; + value = ProjectAttribute(slot->ttc_tupleDescriptor, + tle, tup, isNull); + return value; + } + return(paramList->value); +} + + +/* ---------------------------------------------------------------- + * ExecEvalOper / ExecEvalFunc support routines + * ---------------------------------------------------------------- + */ + +/* ---------------- + * GetAttributeByName + * GetAttributeByNum + * + * These are functions which return the value of the + * named attribute out of the tuple from the arg slot. User defined + * C functions which take a tuple as an argument are expected + * to use this. Ex: overpaid(EMP) might call GetAttributeByNum(). + * ---------------- + */ +char * +GetAttributeByNum(TupleTableSlot *slot, + AttrNumber attrno, + bool *isNull) +{ + Datum retval; + + if (!AttributeNumberIsValid(attrno)) + elog(WARN, "GetAttributeByNum: Invalid attribute number"); + + if (!AttrNumberIsForUserDefinedAttr(attrno)) + elog(WARN, "GetAttributeByNum: cannot access system attributes here"); + + if (isNull == (bool *)NULL) + elog(WARN, "GetAttributeByNum: a NULL isNull flag was passed"); + + if (TupIsNull(slot)) + { + *isNull = true; + return (char *) NULL; + } + + retval = (Datum) + heap_getattr(slot->val, + slot->ttc_buffer, + attrno, + slot->ttc_tupleDescriptor, + isNull); + if (*isNull) + return (char *) NULL; + return (char *) retval; +} + +/* XXX char16 name for catalogs */ +char * +att_by_num(TupleTableSlot *slot, + AttrNumber attrno, + bool *isNull) +{ + return(GetAttributeByNum(slot, attrno, isNull)); +} + +char * +GetAttributeByName(TupleTableSlot *slot, char *attname, bool *isNull) +{ + AttrNumber attrno; + TupleDesc tupdesc; + HeapTuple tuple; + Datum retval; + int natts; + int i; + + if (attname == NULL) + elog(WARN, "GetAttributeByName: Invalid attribute name"); + + if (isNull == (bool *)NULL) + elog(WARN, "GetAttributeByName: a NULL isNull flag was passed"); + + if (TupIsNull(slot)) + { + *isNull = true; + return (char *) NULL; + } + + tupdesc = slot->ttc_tupleDescriptor; + tuple = slot->val; + + natts = tuple->t_natts; + + attrno = InvalidAttrNumber; + for (i=0;i<tupdesc->natts;i++) { + if (namestrcmp(&(tupdesc->attrs[i]->attname), attname) == 0) { + attrno = tupdesc->attrs[i]->attnum; + break; + } + } + + if (attrno == InvalidAttrNumber) + elog(WARN, "GetAttributeByName: attribute %s not found", attname); + + retval = (Datum) + heap_getattr(slot->val, + slot->ttc_buffer, + attrno, + tupdesc, + isNull); + if (*isNull) + return (char *) NULL; + return (char *) retval; +} + +/* XXX char16 name for catalogs */ +char * +att_by_name(TupleTableSlot *slot, char *attname, bool *isNull) +{ + return(GetAttributeByName(slot, attname, isNull)); +} + +void +ExecEvalFuncArgs(FunctionCachePtr fcache, + ExprContext *econtext, + List *argList, + Datum argV[], + bool *argIsDone) +{ + int i; + bool argIsNull, *nullVect; + List *arg; + + nullVect = fcache->nullVect; + + i = 0; + foreach (arg, argList) { + /* ---------------- + * evaluate the expression, in general functions cannot take + * sets as arguments but we make an exception in the case of + * nested dot expressions. We have to watch out for this case + * here. + * ---------------- + */ + argV[i] = (Datum) + ExecEvalExpr((Node *) lfirst(arg), + econtext, + &argIsNull, + argIsDone); + if (! (*argIsDone)) + { + Assert(i == 0); + fcache->setArg = (char *)argV[0]; + fcache->hasSetArg = true; + } + if (argIsNull) + nullVect[i] = true; + else + nullVect[i] = false; + i++; + } +} + +/* ---------------- + * ExecMakeFunctionResult + * ---------------- + */ +Datum +ExecMakeFunctionResult(Node *node, + List *arguments, + ExprContext *econtext, + bool *isNull, + bool *isDone) +{ + Datum argv[MAXFMGRARGS]; + FunctionCachePtr fcache; + Func *funcNode = NULL; + Oper *operNode = NULL; + bool funcisset = false; + + /* + * This is kind of ugly, Func nodes now have targetlists so that + * we know when and what to project out from postquel function results. + * This means we have to pass the func node all the way down instead + * of using only the fcache struct as before. ExecMakeFunctionResult + * becomes a little bit more of a dual personality as a result. + */ + if (IsA(node,Func)) + { + funcNode = (Func *)node; + fcache = funcNode->func_fcache; + } + else + { + operNode = (Oper *)node; + fcache = operNode->op_fcache; + } + + /* ---------------- + * arguments is a list of expressions to evaluate + * before passing to the function manager. + * We collect the results of evaluating the expressions + * into a datum array (argv) and pass this array to arrayFmgr() + * ---------------- + */ + if (fcache->nargs != 0) { + bool argDone; + + if (fcache->nargs > MAXFMGRARGS) + elog(WARN, "ExecMakeFunctionResult: too many arguments"); + + /* + * If the setArg in the fcache is set we have an argument + * returning a set of tuples (i.e. a nested dot expression). We + * don't want to evaluate the arguments again until the function + * is done. hasSetArg will always be false until we eval the args + * for the first time. We should set this in the parser. + */ + if ((fcache->hasSetArg) && fcache->setArg != NULL) + { + argv[0] = (Datum)fcache->setArg; + argDone = false; + } + else + ExecEvalFuncArgs(fcache, econtext, arguments, argv, &argDone); + + if ((fcache->hasSetArg) && (argDone)) { + if (isDone) *isDone = true; + return (Datum)NULL; + } + } + + /* If this function is really a set, we have to diddle with things. + * If the function has already been called at least once, then the + * setArg field of the fcache holds + * the OID of this set in pg_proc. (This is not quite legit, since + * the setArg field is really for functions which take sets of tuples + * as input - set functions take no inputs at all. But it's a nice + * place to stash this value, for now.) + * + * If this is the first call of the set's function, then + * the call to ExecEvalFuncArgs above just returned the OID of + * the pg_proc tuple which defines this set. So replace the existing + * funcid in the funcnode with the set's OID. Also, we want a new + * fcache which points to the right function, so get that, now that + * we have the right OID. Also zero out the argv, since the real + * set doesn't take any arguments. + */ + if (((Func *)node)->funcid == SetEvalRegProcedure) { + funcisset = true; + if (fcache->setArg) { + argv[0] = 0; + + ((Func *)node)->funcid = (Oid) PointerGetDatum(fcache->setArg); + + } else { + ((Func *)node)->funcid = (Oid) argv[0]; + setFcache(node, argv[0], NIL,econtext); + fcache = ((Func *)node)->func_fcache; + fcache->setArg = (char*)argv[0]; + argv[0] = (Datum)0; + } + } + + /* ---------------- + * now return the value gotten by calling the function manager, + * passing the function the evaluated parameter values. + * ---------------- + */ + if (fcache->language == SQLlanguageId) { + Datum result; + + Assert(funcNode); + result = postquel_function (funcNode, (char **) argv, isNull, isDone); + /* + * finagle the situation where we are iterating through all results + * in a nested dot function (whose argument function returns a set + * of tuples) and the current function finally finishes. We need to + * get the next argument in the set and run the function all over + * again. This is getting unclean. + */ + if ((*isDone) && (fcache->hasSetArg)) { + bool argDone; + + ExecEvalFuncArgs(fcache, econtext, arguments, argv, &argDone); + + if (argDone) { + fcache->setArg = (char *)NULL; + *isDone = true; + result = (Datum)NULL; + } + else + result = postquel_function(funcNode, + (char **) argv, + isNull, + isDone); + } + if (funcisset) { + /* reset the funcid so that next call to this routine will + * still recognize this func as a set. + * Note that for now we assume that the set function in + * pg_proc must be a Postquel function - the funcid is + * not reset below for C functions. + */ + ((Func *)node)->funcid = SetEvalRegProcedure; + /* If we're done with the results of this function, get rid + * of its func cache. + */ + if (*isDone) { + ((Func *)node)->func_fcache = NULL; + } + } + return result; + } + else + { + int i; + + if (isDone) *isDone = true; + for (i = 0; i < fcache->nargs; i++) + if (fcache->nullVect[i] == true) *isNull = true; + + return((Datum) fmgr_c(fcache->func, fcache->foid, fcache->nargs, + (FmgrValues *) argv, isNull)); + } +} + + +/* ---------------------------------------------------------------- + * ExecEvalOper + * ExecEvalFunc + * + * Evaluate the functional result of a list of arguments by calling the + * function manager. Note that in the case of operator expressions, the + * optimizer had better have already replaced the operator OID with the + * appropriate function OID or we're hosed. + * + * old comments + * Presumably the function manager will not take null arguments, so we + * check for null arguments before sending the arguments to (fmgr). + * + * Returns the value of the functional expression. + * ---------------------------------------------------------------- + */ + +/* ---------------------------------------------------------------- + * ExecEvalOper + * ---------------------------------------------------------------- + */ +Datum +ExecEvalOper(Expr *opClause, ExprContext *econtext, bool *isNull) +{ + Oper *op; + List *argList; + FunctionCachePtr fcache; + bool isDone; + + /* ---------------- + * an opclause is a list (op args). (I think) + * + * we extract the oid of the function associated with + * the op and then pass the work onto ExecMakeFunctionResult + * which evaluates the arguments and returns the result of + * calling the function on the evaluated arguments. + * ---------------- + */ + op = (Oper *) opClause->oper; + argList = opClause->args; + + /* + * get the fcache from the Oper node. + * If it is NULL, then initialize it + */ + fcache = op->op_fcache; + if (fcache == NULL) { + setFcache((Node*)op, op->opid, argList, econtext); + fcache = op->op_fcache; + } + + /* ----------- + * call ExecMakeFunctionResult() with a dummy isDone that we ignore. + * We don't have operator whose arguments are sets. + * ----------- + */ + return + ExecMakeFunctionResult((Node *)op, argList, econtext, isNull, &isDone); +} + +/* ---------------------------------------------------------------- + * ExecEvalFunc + * ---------------------------------------------------------------- + */ + +Datum +ExecEvalFunc(Expr *funcClause, + ExprContext *econtext, + bool *isNull, + bool *isDone) +{ + Func *func; + List *argList; + FunctionCachePtr fcache; + + /* ---------------- + * an funcclause is a list (func args). (I think) + * + * we extract the oid of the function associated with + * the func node and then pass the work onto ExecMakeFunctionResult + * which evaluates the arguments and returns the result of + * calling the function on the evaluated arguments. + * + * this is nearly identical to the ExecEvalOper code. + * ---------------- + */ + func = (Func *)funcClause->oper; + argList = funcClause->args; + + /* + * get the fcache from the Func node. + * If it is NULL, then initialize it + */ + fcache = func->func_fcache; + if (fcache == NULL) { + setFcache((Node*)func, func->funcid, argList, econtext); + fcache = func->func_fcache; + } + + return + ExecMakeFunctionResult((Node*)func, argList, econtext, isNull, isDone); +} + +/* ---------------------------------------------------------------- + * ExecEvalNot + * ExecEvalOr + * ExecEvalAnd + * + * Evaluate boolean expressions. Evaluation of 'or' is + * short-circuited when the first true (or null) value is found. + * + * The query planner reformulates clause expressions in the + * qualification to conjunctive normal form. If we ever get + * an AND to evaluate, we can be sure that it's not a top-level + * clause in the qualification, but appears lower (as a function + * argument, for example), or in the target list. Not that you + * need to know this, mind you... + * ---------------------------------------------------------------- + */ +Datum +ExecEvalNot(Expr *notclause, ExprContext *econtext, bool *isNull) +{ + Datum expr_value; + Node *clause; + bool isDone; + + clause = lfirst(notclause->args); + + /* ---------------- + * We don't iterate over sets in the quals, so pass in an isDone + * flag, but ignore it. + * ---------------- + */ + expr_value = ExecEvalExpr(clause, econtext, isNull, &isDone); + + /* ---------------- + * if the expression evaluates to null, then we just + * cascade the null back to whoever called us. + * ---------------- + */ + if (*isNull) + return expr_value; + + /* ---------------- + * evaluation of 'not' is simple.. expr is false, then + * return 'true' and vice versa. + * ---------------- + */ + if (DatumGetInt32(expr_value) == 0) + return (Datum) true; + + return (Datum) false; +} + +/* ---------------------------------------------------------------- + * ExecEvalOr + * ---------------------------------------------------------------- + */ +Datum +ExecEvalOr(Expr *orExpr, ExprContext *econtext, bool *isNull) +{ + List *clauses; + List *clause; + bool isDone; + bool IsNull; + Datum const_value; + + IsNull = false; + clauses = orExpr->args; + + /* ---------------- + * we use three valued logic functions here... + * we evaluate each of the clauses in turn, + * as soon as one is true we return that + * value. If none is true and none of the + * clauses evaluate to NULL we return + * the value of the last clause evaluated (which + * should be false) with *isNull set to false else + * if none is true and at least one clause evaluated + * to NULL we set *isNull flag to true - + * ---------------- + */ + foreach (clause, clauses) { + + /* ---------------- + * We don't iterate over sets in the quals, so pass in an isDone + * flag, but ignore it. + * ---------------- + */ + const_value = ExecEvalExpr((Node *) lfirst(clause), + econtext, + isNull, + &isDone); + + /* ---------------- + * if the expression evaluates to null, then we + * remember it in the local IsNull flag, if none of the + * clauses are true then we need to set *isNull + * to true again. + * ---------------- + */ + if (*isNull) + IsNull = *isNull; + + /* ---------------- + * if we have a true result, then we return it. + * ---------------- + */ + if (DatumGetInt32(const_value) != 0) + return const_value; + } + + /* IsNull is true if at least one clause evaluated to NULL */ + *isNull = IsNull; + return const_value; +} + +/* ---------------------------------------------------------------- + * ExecEvalAnd + * ---------------------------------------------------------------- + */ +Datum +ExecEvalAnd(Expr *andExpr, ExprContext *econtext, bool *isNull) +{ + List *clauses; + List *clause; + Datum const_value; + bool isDone; + bool IsNull; + + IsNull = false; + + clauses = andExpr->args; + + /* ---------------- + * we evaluate each of the clauses in turn, + * as soon as one is false we return that + * value. If none are false or NULL then we return + * the value of the last clause evaluated, which + * should be true. + * ---------------- + */ + foreach (clause, clauses) { + + /* ---------------- + * We don't iterate over sets in the quals, so pass in an isDone + * flag, but ignore it. + * ---------------- + */ + const_value = ExecEvalExpr((Node *) lfirst(clause), + econtext, + isNull, + &isDone); + + /* ---------------- + * if the expression evaluates to null, then we + * remember it in IsNull, if none of the clauses after + * this evaluates to false we will have to set *isNull + * to true again. + * ---------------- + */ + if (*isNull) + IsNull = *isNull; + + /* ---------------- + * if we have a false result, then we return it, since the + * conjunction must be false. + * ---------------- + */ + if (DatumGetInt32(const_value) == 0) + return const_value; + } + + *isNull = IsNull; + return const_value; +} + +/* ---------------------------------------------------------------- + * ExecEvalExpr + * + * Recursively evaluate a targetlist or qualification expression. + * + * This routine is an inner loop routine and should be as fast + * as possible. + * + * Node comparison functions were replaced by macros for speed and to plug + * memory leaks incurred by using the planner's Lispy stuff for + * comparisons. Order of evaluation of node comparisons IS IMPORTANT; + * the macros do no checks. Order of evaluation: + * + * o an isnull check, largely to avoid coredumps since greg doubts this + * routine is called with a null ptr anyway in proper operation, but is + * not completely sure... + * o ExactNodeType checks. + * o clause checks or other checks where we look at the lfirst of something. + * ---------------------------------------------------------------- + */ +Datum +ExecEvalExpr(Node *expression, + ExprContext *econtext, + bool *isNull, + bool *isDone) +{ + Datum retDatum; + + *isNull = false; + + /* + * Some callers don't care about is done and only want 1 result. They + * indicate this by passing NULL + */ + if (isDone) + *isDone = true; + + /* ---------------- + * here we dispatch the work to the appropriate type + * of function given the type of our expression. + * ---------------- + */ + if (expression == NULL) { + *isNull = true; + return (Datum) true; + } + + switch(nodeTag(expression)) { + case T_Var: + retDatum = (Datum) ExecEvalVar((Var *) expression, econtext, isNull); + break; + case T_Const: { + Const *con = (Const *)expression; + + if (con->constisnull) + *isNull = true; + retDatum = con->constvalue; + break; + } + case T_Param: + retDatum = (Datum)ExecEvalParam((Param *)expression, econtext, isNull); + break; + case T_Iter: + retDatum = (Datum) ExecEvalIter((Iter *) expression, + econtext, + isNull, + isDone); + break; + case T_Aggreg: + retDatum = (Datum) ExecEvalAggreg((Aggreg *)expression, + econtext, + isNull); + break; + case T_ArrayRef: + retDatum = (Datum) ExecEvalArrayRef((ArrayRef *) expression, + econtext, + isNull, + isDone); + break; + case T_Expr: { + Expr *expr = (Expr *)expression; + switch (expr->opType) { + case OP_EXPR: + retDatum = (Datum) ExecEvalOper(expr, econtext, isNull); + break; + case FUNC_EXPR: + retDatum = (Datum) ExecEvalFunc(expr, econtext, isNull, isDone); + break; + case OR_EXPR: + retDatum = (Datum) ExecEvalOr(expr, econtext, isNull); + break; + case AND_EXPR: + retDatum = (Datum) ExecEvalAnd(expr, econtext, isNull); + break; + case NOT_EXPR: + retDatum = (Datum) ExecEvalNot(expr, econtext, isNull); + break; + default: + elog(WARN, "ExecEvalExpr: unknown expression type"); + break; + } + break; + } + default: + elog(WARN, "ExecEvalExpr: unknown expression type"); + break; + } + + return retDatum; +} + +/* ---------------------------------------------------------------- + * ExecQual / ExecTargetList + * ---------------------------------------------------------------- + */ + +/* ---------------------------------------------------------------- + * ExecQualClause + * + * this is a workhorse for ExecQual. ExecQual has to deal + * with a list of qualifications, so it passes each qualification + * in the list to this function one at a time. ExecQualClause + * returns true when the qualification *fails* and false if + * the qualification succeeded (meaning we have to test the + * rest of the qualification) + * ---------------------------------------------------------------- + */ +bool +ExecQualClause(Node *clause, ExprContext *econtext) +{ + Datum expr_value; + bool isNull; + bool isDone; + + /* when there is a null clause, consider the qualification to be true */ + if (clause == NULL) + return true; + + /* + * pass isDone, but ignore it. We don't iterate over multiple + * returns in the qualifications. + */ + expr_value = (Datum) + ExecEvalExpr(clause, econtext, &isNull, &isDone); + + /* ---------------- + * this is interesting behaviour here. When a clause evaluates + * to null, then we consider this as passing the qualification. + * it seems kind of like, if the qual is NULL, then there's no + * qual.. + * ---------------- + */ + if (isNull) + return true; + + /* ---------------- + * remember, we return true when the qualification fails.. + * ---------------- + */ + if (DatumGetInt32(expr_value) == 0) + return true; + + return false; +} + +/* ---------------------------------------------------------------- + * ExecQual + * + * Evaluates a conjunctive boolean expression and returns t + * iff none of the subexpressions are false (or null). + * ---------------------------------------------------------------- + */ +bool +ExecQual(List *qual, ExprContext *econtext) +{ + List *clause; + bool result; + + /* ---------------- + * debugging stuff + * ---------------- + */ + EV_printf("ExecQual: qual is "); + EV_nodeDisplay(qual); + EV_printf("\n"); + + IncrProcessed(); + + /* ---------------- + * return true immediately if no qual + * ---------------- + */ + if (qual == NIL) + return true; + + /* ---------------- + * a "qual" is a list of clauses. To evaluate the + * qual, we evaluate each of the clauses in the list. + * + * ExecQualClause returns true when we know the qualification + * *failed* so we just pass each clause in qual to it until + * we know the qual failed or there are no more clauses. + * ---------------- + */ + result = false; + foreach (clause, qual) { + result = ExecQualClause((Node *)lfirst(clause), econtext); + if (result == true) + break; + } + + /* ---------------- + * if result is true, then it means a clause failed so we + * return false. if result is false then it means no clause + * failed so we return true. + * ---------------- + */ + if (result == true) + return false; + + return true; +} + +int +ExecTargetListLength(List *targetlist) +{ + int len; + List *tl; + TargetEntry *curTle; + + len = 0; + foreach (tl, targetlist) { + curTle = lfirst(tl); + + if (curTle->resdom != NULL) + len++; + else + len += curTle->fjoin->fj_nNodes; + } + return len; +} + +/* ---------------------------------------------------------------- + * ExecTargetList + * + * Evaluates a targetlist with respect to the current + * expression context and return a tuple. + * ---------------------------------------------------------------- + */ +static HeapTuple +ExecTargetList(List *targetlist, + int nodomains, + TupleDesc targettype, + Datum *values, + ExprContext *econtext, + bool *isDone) +{ + char nulls_array[64]; + bool fjNullArray[64]; + bool *fjIsNull; + char *null_head; + List *tl; + TargetEntry *tle; + Node *expr; + Resdom *resdom; + AttrNumber resind; + Datum constvalue; + HeapTuple newTuple; + bool isNull; + + /* ---------------- + * debugging stuff + * ---------------- + */ + EV_printf("ExecTargetList: tl is "); + EV_nodeDisplay(targetlist); + EV_printf("\n"); + + /* ---------------- + * Return a dummy tuple if the targetlist is empty . + * the dummy tuple is necessary to differentiate + * between passing and failing the qualification. + * ---------------- + */ + if (targetlist == NIL) { + /* ---------------- + * I now think that the only time this makes + * any sence is when we run a delete query. Then + * we need to return something other than nil + * so we know to delete the tuple associated + * with the saved tupleid.. see what ExecutePlan + * does with the returned tuple.. -cim 9/21/89 + * + * It could also happen in queries like: + * retrieve (foo.all) where bar.a = 3 + * + * is this a new phenomenon? it might cause bogus behavior + * if we try to free this tuple later!! I put a hook in + * ExecProject to watch out for this case -mer 24 Aug 1992 + * ---------------- + */ + CXT1_printf("ExecTargetList: context is %d\n", CurrentMemoryContext); + *isDone = true; + return (HeapTuple) true; + } + + /* ---------------- + * allocate an array of char's to hold the "null" information + * only if we have a really large targetlist. otherwise we use + * the stack. + * ---------------- + */ + if (nodomains > 64) { + null_head = (char *) palloc(nodomains+1); + fjIsNull = (bool *) palloc(nodomains+1); + } else { + null_head = &nulls_array[0]; + fjIsNull = &fjNullArray[0]; + } + + /* ---------------- + * evaluate all the expressions in the target list + * ---------------- + */ + EV_printf("ExecTargetList: setting target list values\n"); + + *isDone = true; + foreach (tl, targetlist) { + /* ---------------- + * remember, a target list is a list of lists: + * + * ((<resdom | fjoin> expr) (<resdom | fjoin> expr) ...) + * + * tl is a pointer to successive cdr's of the targetlist + * tle is a pointer to the target list entry in tl + * ---------------- + */ + tle = lfirst(tl); + + if (tle->resdom != NULL) { + expr = tle->expr; + resdom = tle->resdom; + resind = resdom->resno - 1; + constvalue = (Datum) ExecEvalExpr(expr, + econtext, + &isNull, + isDone); + + if ((IsA(expr,Iter)) && (*isDone)) + return (HeapTuple)NULL; + + values[resind] = constvalue; + + if (!isNull) + null_head[resind] = ' '; + else + null_head[resind] = 'n'; + }else { + int curNode; + Resdom *fjRes; + List *fjTlist = (List *)tle->expr; + Fjoin *fjNode = tle->fjoin; + int nNodes = fjNode->fj_nNodes; + DatumPtr results = fjNode->fj_results; + + ExecEvalFjoin(tle, econtext, fjIsNull, isDone); + if (*isDone) + return (HeapTuple)NULL; + + /* + * get the result from the inner node + */ + fjRes = (Resdom *)fjNode->fj_innerNode; + resind = fjRes->resno - 1; + if (fjIsNull[0]) + null_head[resind] = 'n'; + else { + null_head[resind] = ' '; + values[resind] = results[0]; + } + + /* + * Get results from all of the outer nodes + */ + for (curNode = 1; + curNode < nNodes; + curNode++, fjTlist = lnext(fjTlist)) + { +#if 0 /* what is this?? */ + Node *outernode = lfirst(fjTlist); + fjRes = (Resdom *)outernode->iterexpr; +#endif + resind = fjRes->resno - 1; + if (fjIsNull[curNode]) { + null_head[resind] = 'n'; + }else { + null_head[resind] = ' '; + values[resind] = results[curNode]; + } + } + } + } + + /* ---------------- + * form the new result tuple (in the "normal" context) + * ---------------- + */ + newTuple = (HeapTuple) + heap_formtuple(targettype, values, null_head); + + /* ---------------- + * free the nulls array if we allocated one.. + * ---------------- + */ + if (nodomains > 64) pfree(null_head); + + return + newTuple; +} + +/* ---------------------------------------------------------------- + * ExecProject + * + * projects a tuple based in projection info and stores + * it in the specified tuple table slot. + * + * Note: someday soon the executor can be extended to eliminate + * redundant projections by storing pointers to datums + * in the tuple table and then passing these around when + * possible. this should make things much quicker. + * -cim 6/3/91 + * ---------------------------------------------------------------- + */ +TupleTableSlot * +ExecProject(ProjectionInfo *projInfo, bool *isDone) +{ + TupleTableSlot *slot; + List *targetlist; + int len; + TupleDesc tupType; + Datum *tupValue; + ExprContext *econtext; + HeapTuple newTuple; + + /* ---------------- + * sanity checks + * ---------------- + */ + if (projInfo == NULL) + return (TupleTableSlot *) NULL; + + /* ---------------- + * get the projection info we want + * ---------------- + */ + slot = projInfo->pi_slot; + targetlist = projInfo->pi_targetlist; + len = projInfo->pi_len; + tupType = slot->ttc_tupleDescriptor; + + tupValue = projInfo->pi_tupValue; + econtext = projInfo->pi_exprContext; + + if (targetlist == NIL) { + *isDone = true; + return (TupleTableSlot *) NULL; + } + + /* ---------------- + * form a new (result) tuple + * ---------------- + */ + newTuple = ExecTargetList(targetlist, + len, + tupType, + tupValue, + econtext, + isDone); + + /* ---------------- + * store the tuple in the projection slot and return the slot. + * + * If there's no projection target list we don't want to pfree + * the bogus tuple that ExecTargetList passes back to us. + * -mer 24 Aug 1992 + * ---------------- + */ + return (TupleTableSlot *) + ExecStoreTuple(newTuple, /* tuple to store */ + slot, /* slot to store in */ + InvalidBuffer, /* tuple has no buffer */ + true); +} + diff --git a/src/backend/executor/execScan.c b/src/backend/executor/execScan.c new file mode 100644 index 00000000000..96dd5551289 --- /dev/null +++ b/src/backend/executor/execScan.c @@ -0,0 +1,136 @@ +/*------------------------------------------------------------------------- + * + * execScan.c-- + * This code provides support for generalized relation scans. ExecScan + * is passed a node and a pointer to a function to "do the right thing" + * and return a tuple from the relation. ExecScan then does the tedious + * stuff - checking the qualification and projecting the tuple + * appropriately. + * + * Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * $Header: /cvsroot/pgsql/src/backend/executor/execScan.c,v 1.1.1.1 1996/07/09 06:21:25 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ + +#include <sys/file.h> +#include "executor/executor.h" + +/* ---------------------------------------------------------------- + * ExecScan + * + * Scans the relation using the 'access method' indicated and + * returns the next qualifying tuple in the direction specified + * in the global variable ExecDirection. + * The access method returns the next tuple and execScan() is + * responisble for checking the tuple returned against the qual-clause. + * + * Conditions: + * -- the "cursor" maintained by the AMI is positioned at the tuple + * returned previously. + * + * Initial States: + * -- the relation indicated is opened for scanning so that the + * "cursor" is positioned before the first qualifying tuple. + * + * May need to put startmmgr and endmmgr in here. + * ---------------------------------------------------------------- + */ +TupleTableSlot * +ExecScan(Scan *node, + TupleTableSlot* (*accessMtd)()) /* function returning a tuple */ +{ + CommonScanState *scanstate; + EState *estate; + List *qual; + bool isDone; + + TupleTableSlot *slot; + TupleTableSlot *resultSlot; + HeapTuple newTuple; + + ExprContext *econtext; + ProjectionInfo *projInfo; + + + /* ---------------- + * initialize misc variables + * ---------------- + */ + newTuple = NULL; + slot = NULL; + + estate = node->plan.state; + scanstate = node->scanstate; + + /* ---------------- + * get the expression context + * ---------------- + */ + econtext = scanstate->cstate.cs_ExprContext; + + /* ---------------- + * initialize fields in ExprContext which don't change + * in the course of the scan.. + * ---------------- + */ + qual = node->plan.qual; + econtext->ecxt_relation = scanstate->css_currentRelation; + econtext->ecxt_relid = node->scanrelid; + + if (scanstate->cstate.cs_TupFromTlist) { + projInfo = scanstate->cstate.cs_ProjInfo; + resultSlot = ExecProject(projInfo, &isDone); + if (!isDone) + return resultSlot; + } + /* + * get a tuple from the access method + * loop until we obtain a tuple which passes the qualification. + */ + for(;;) { + slot = (TupleTableSlot *) (*accessMtd)(node); + + /* ---------------- + * if the slot returned by the accessMtd contains + * NULL, then it means there is nothing more to scan + * so we just return the empty slot. + * ---------------- + */ + if (TupIsNull(slot)) return slot; + + /* ---------------- + * place the current tuple into the expr context + * ---------------- + */ + econtext->ecxt_scantuple = slot; + + /* ---------------- + * check that the current tuple satisfies the qual-clause + * if our qualification succeeds then we + * leave the loop. + * ---------------- + */ + + /* add a check for non-nil qual here to avoid a + function call to ExecQual() when the qual is nil */ + if (!qual || ExecQual(qual, econtext) == true) + break; + } + + /* ---------------- + * form a projection tuple, store it in the result tuple + * slot and return it. + * ---------------- + */ + projInfo = scanstate->cstate.cs_ProjInfo; + + resultSlot = ExecProject(projInfo, &isDone); + scanstate->cstate.cs_TupFromTlist = !isDone; + + return resultSlot; +} + diff --git a/src/backend/executor/execTuples.c b/src/backend/executor/execTuples.c new file mode 100644 index 00000000000..8e7b5283dc6 --- /dev/null +++ b/src/backend/executor/execTuples.c @@ -0,0 +1,1013 @@ +/*------------------------------------------------------------------------- + * + * execTuples.c-- + * Routines dealing with the executor tuple tables. These are used to + * ensure that the executor frees copies of tuples (made by + * ExecTargetList) properly. + * + * Routines dealing with the type information for tuples. Currently, + * the type information for a tuple is an array of FormData_pg_attribute. + * This information is needed by routines manipulating tuples + * (getattribute, formtuple, etc.). + * + * Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * $Header: /cvsroot/pgsql/src/backend/executor/execTuples.c,v 1.1.1.1 1996/07/09 06:21:25 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +/* + * INTERFACE ROUTINES + * + * TABLE CREATE/DELETE + * ExecCreateTupleTable - create a new tuple table + * ExecDestroyTupleTable - destroy a table + * + * SLOT RESERVERATION + * ExecAllocTableSlot - find an available slot in the table + * + * SLOT ACCESSORS + * ExecStoreTuple - store a tuple in the table + * ExecFetchTuple - fetch a tuple from the table + * ExecClearTuple - clear contents of a table slot + * ExecSlotPolicy - return slot's tuple pfree policy + * ExecSetSlotPolicy - diddle the slot policy + * ExecSlotDescriptor - type of tuple in a slot + * ExecSetSlotDescriptor - set a slot's tuple descriptor + * ExecSetSlotDescriptorIsNew - diddle the slot-desc-is-new flag + * ExecSetNewSlotDescriptor - set a desc and the is-new-flag all at once + * ExecSlotBuffer - return buffer of tuple in slot + * ExecSetSlotBuffer - set the buffer for tuple in slot + * ExecIncrSlotBufferRefcnt - bump the refcnt of the slot buffer + * + * SLOT STATUS PREDICATES + * TupIsNull - true when slot contains no tuple + * ExecSlotDescriptorIsNew - true if we're now storing a different + * type of tuple in a slot + * + * CONVENIENCE INITIALIZATION ROUTINES + * ExecInitResultTupleSlot \ convience routines to initialize + * ExecInitScanTupleSlot \ the various tuple slots for nodes + * ExecInitMarkedTupleSlot / which store copies of tuples. + * ExecInitOuterTupleSlot / + * ExecInitHashTupleSlot / + * + * old routines: + * ExecGetTupType - get type of tuple returned by this node + * ExecTypeFromTL - form a TupleDesc from a target list + * + * EXAMPLE OF HOW TABLE ROUTINES WORK + * Suppose we have a query such as retrieve (EMP.name) and we have + * a single SeqScan node in the query plan. + * + * At ExecStart() + * ---------------- + * - InitPlan() calls ExecCreateTupleTable() to create the tuple + * table which will hold tuples processed by the executor. + * + * - ExecInitSeqScan() calls ExecInitScanTupleSlot() and + * ExecInitResultTupleSlot() to reserve places in the tuple + * table for the tuples returned by the access methods and the + * tuples resulting from preforming target list projections. + * + * During ExecRun() + * ---------------- + * - SeqNext() calls ExecStoreTuple() to place the tuple returned + * by the access methods into the scan tuple slot. + * + * - ExecSeqScan() calls ExecStoreTuple() to take the result + * tuple from ExecTargetList() and place it into the result tuple + * slot. + * + * - ExecutePlan() calls ExecRetrieve() which gets the tuple out of + * the slot passed to it by calling ExecFetchTuple(). this tuple + * is then returned. + * + * At ExecEnd() + * ---------------- + * - EndPlan() calls ExecDestroyTupleTable() to clean up any remaining + * tuples left over from executing the query. + * + * The important thing to watch in the executor code is how pointers + * to the slots containing tuples are passed instead of the tuples + * themselves. This facilitates the communication of related information + * (such as whether or not a tuple should be pfreed, what buffer contains + * this tuple, the tuple's tuple descriptor, etc). Note that much of + * this information is also kept in the ExprContext of each node. + * Soon the executor will be redesigned and ExprContext's will contain + * only slot pointers. -cim 3/14/91 + * + * NOTES + * The tuple table stuff is relatively new, put here to alleviate + * the process growth problems in the executor. The other routines + * are old (from the original lisp system) and may someday become + * obsolete. -cim 6/23/90 + * + * In the implementation of nested-dot queries such as + * "retrieve (EMP.hobbies.all)", a single scan may return tuples + * of many types, so now we return pointers to tuple descriptors + * along with tuples returned via the tuple table. This means + * we now have a bunch of routines to diddle the slot descriptors + * too. -cim 1/18/90 + * + * The tuple table stuff depends on the executor/tuptable.h macros, + * and the TupleTableSlot node in execnodes.h. + * + */ + +#include "executor/executor.h" +#undef ExecStoreTuple + +#include "access/tupdesc.h" +#include "utils/palloc.h" +#include "utils/lsyscache.h" +#include "storage/bufmgr.h" +#include "parser/catalog_utils.h" + +/* ---------------------------------------------------------------- + * tuple table create/delete functions + * ---------------------------------------------------------------- + */ +/* -------------------------------- + * ExecCreateTupleTable + * + * This creates a new tuple table of the specified initial + * size. If the size is insufficient, ExecAllocTableSlot() + * will grow the table as necessary. + * + * This should be used by InitPlan() to allocate the table. + * The table's address will be stored in the EState structure. + * -------------------------------- + */ +TupleTable /* return: address of table */ +ExecCreateTupleTable(int initialSize) /* initial number of slots in table */ +{ + TupleTable newtable; /* newly allocated table */ + TupleTableSlot* array; /* newly allocated slot array */ + + /* ---------------- + * sanity checks + * ---------------- + */ + Assert(initialSize >= 1); + + /* ---------------- + * Now allocate our new table along with space for the pointers + * to the tuples. + */ + + newtable = (TupleTable) palloc(sizeof(TupleTableData)); + array = (TupleTableSlot*) palloc(initialSize * sizeof(TupleTableSlot)); + + /* ---------------- + * clean out the slots we just allocated + * ---------------- + */ + memset(array, 0, initialSize * sizeof(TupleTableSlot)); + + /* ---------------- + * initialize the new table and return it to the caller. + * ---------------- + */ + newtable->size = initialSize; + newtable->next = 0; + newtable->array = array; + + return newtable; +} + +/* -------------------------------- + * ExecDestroyTupleTable + * + * This pfrees the storage assigned to the tuple table and + * optionally pfrees the contents of the table also. + * It is expected that this routine be called by EndPlan(). + * -------------------------------- + */ +void +ExecDestroyTupleTable(TupleTable table, /* tuple table */ + bool shouldFree) /* true if we should free slot contents */ +{ + int next; /* next avaliable slot */ + TupleTableSlot *array; /* start of table array */ + int i; /* counter */ + + /* ---------------- + * sanity checks + * ---------------- + */ + Assert(table != NULL); + + /* ---------------- + * get information from the table + * ---------------- + */ + array = table->array; + next = table->next; + + /* ---------------- + * first free all the valid pointers in the tuple array + * if that's what the caller wants.. + * + * Note: we do nothing about the Buffer and Tuple Descriptor's + * we store in the slots. This may have to change (ex: we should + * probably worry about pfreeing tuple descs too) -cim 3/14/91 + * ---------------- + */ + if (shouldFree) + for (i = 0; i < next; i++) { + TupleTableSlot slot; + HeapTuple tuple; + + slot = array[i]; + tuple = slot.val; + + if (tuple != NULL) { + slot.val = (HeapTuple)NULL; + if (slot.ttc_shouldFree) { + /* ---------------- + * since a tuple may contain a pointer to + * lock information allocated along with the + * tuple, we have to be careful to free any + * rule locks also -cim 1/17/90 + * ---------------- + */ + pfree(tuple); + } + } + } + + /* ---------------- + * finally free the tuple array and the table itself. + * ---------------- + */ + pfree(array); + pfree(table); + +} + + +/* ---------------------------------------------------------------- + * tuple table slot reservation functions + * ---------------------------------------------------------------- + */ +/* -------------------------------- + * ExecAllocTableSlot + * + * This routine is used to reserve slots in the table for + * use by the various plan nodes. It is expected to be + * called by the node init routines (ex: ExecInitNestLoop). + * once per slot needed by the node. Not all nodes need + * slots (some just pass tuples around). + * -------------------------------- + */ +TupleTableSlot* /* return: the slot allocated in the tuple table */ +ExecAllocTableSlot(TupleTable table) +{ + int slotnum; /* new slot number */ + + /* ---------------- + * sanity checks + * ---------------- + */ + Assert(table != NULL); + + /* ---------------- + * if our table is full we have to allocate a larger + * size table. Since ExecAllocTableSlot() is only called + * before the table is ever used to store tuples, we don't + * have to worry about the contents of the old table. + * If this changes, then we will have to preserve the contents. + * -cim 6/23/90 + * + * Unfortunately, we *cannot* do this. All of the nodes in + * the plan that have already initialized their slots will have + * pointers into _freed_ memory. This leads to bad ends. We + * now count the number of slots we will need and create all the + * slots we will need ahead of time. The if below should never + * happen now. Give a WARN if it does. -mer 4 Aug 1992 + * ---------------- + */ + if (table->next >= table->size) { + /* + * int newsize = NewTableSize(table->size); + * + * pfree(table->array); + * table->array = (Pointer) palloc(newsize * TableSlotSize); + * bzero(table->array, newsize * TableSlotSize); + * table->size = newsize; + */ + elog(NOTICE, "Plan requires more slots than are available"); + elog(WARN, "send mail to your local executor guru to fix this"); + } + + /* ---------------- + * at this point, space in the table is guaranteed so we + * reserve the next slot, initialize and return it. + * ---------------- + */ + slotnum = table->next; + table->next++; + + table->array[slotnum].type = T_TupleTableSlot; + + return &(table->array[slotnum]); +} + +/* ---------------------------------------------------------------- + * tuple table slot accessor functions + * ---------------------------------------------------------------- + */ + +/* -------------------------------- + * ExecStoreTuple + * + * This function is used to store a tuple into a specified + * slot in the tuple table. Note: the only slots which should + * be called with shouldFree == false are those slots used to + * store tuples not allocated with pfree(). Currently the + * seqscan and indexscan nodes use this for the tuples returned + * by amgetattr, which are actually pointers onto disk pages. + * -------------------------------- + */ +TupleTableSlot* /* return: slot passed */ +ExecStoreTuple(HeapTuple tuple, /* tuple to store */ + TupleTableSlot* slot, /* slot in which to store tuple */ + Buffer buffer, /* buffer associated with tuple */ + bool shouldFree) /* true if we call pfree() when we gc. */ +{ + /* ---------------- + * sanity checks + * ---------------- + */ + Assert(slot != NULL); + + /* clear out the slot first */ + ExecClearTuple(slot); + + /* ---------------- + * store the new tuple into the specified slot and + * return the slot into which we stored the tuple. + * ---------------- + */ + slot->val = tuple; + slot->ttc_buffer = buffer; + slot->ttc_shouldFree = shouldFree; + + return slot; +} + +/* -------------------------------- + * ExecClearTuple + * + * This function is used to clear out a slot in the tuple table. + * -------------------------------- + */ +TupleTableSlot* /* return: slot passed */ +ExecClearTuple(TupleTableSlot* slot) /* slot in which to store tuple */ +{ + HeapTuple oldtuple; /* prior contents of slot */ + + /* ---------------- + * sanity checks + * ---------------- + */ + Assert(slot != NULL); + + /* ---------------- + * get information from the tuple table + * ---------------- + */ + oldtuple = slot->val; + + /* ---------------- + * free the old contents of the specified slot if necessary. + * ---------------- + */ + if (slot->ttc_shouldFree && oldtuple != NULL) { + /* ---------------- + * since a tuple may contain a pointer to + * lock information allocated along with the + * tuple, we have to be careful to free any + * rule locks also -cim 1/17/90 + * ---------------- + */ + pfree(oldtuple); + } + + /* ---------------- + * store NULL into the specified slot and return the slot. + * - also set buffer to InvalidBuffer -cim 3/14/91 + * ---------------- + */ + slot->val = (HeapTuple)NULL; + + if (BufferIsValid(slot->ttc_buffer)) + ReleaseBuffer(slot->ttc_buffer); + + slot->ttc_buffer = InvalidBuffer; + slot->ttc_shouldFree = true; + + return slot; +} + + +/* -------------------------------- + * ExecSlotPolicy + * + * This function is used to get the call/don't call pfree + * setting of a slot. Most executor routines don't need this. + * It's only when you do tricky things like marking tuples for + * merge joins that you need to diddle the slot policy. + * -------------------------------- + */ +bool /* return: slot policy */ +ExecSlotPolicy(TupleTableSlot* slot) /* slot to inspect */ +{ + return slot->ttc_shouldFree; +} + +/* -------------------------------- + * ExecSetSlotPolicy + * + * This function is used to change the call/don't call pfree + * setting of a slot. Most executor routines don't need this. + * It's only when you do tricky things like marking tuples for + * merge joins that you need to diddle the slot policy. + * -------------------------------- + */ +bool /* return: old slot policy */ +ExecSetSlotPolicy(TupleTableSlot* slot, /* slot to change */ + bool shouldFree) /* true if we call pfree() when we gc. */ +{ + bool old_shouldFree = slot->ttc_shouldFree; + slot->ttc_shouldFree = shouldFree; + + return old_shouldFree; +} + +/* -------------------------------- + * ExecSlotDescriptor + * + * This function is used to get the tuple descriptor associated + * with the slot's tuple. + * + * Now a macro in tuptable.h -mer 5 March 1992 + * -------------------------------- + */ + +/* -------------------------------- + * ExecSetSlotDescriptor + * + * This function is used to set the tuple descriptor associated + * with the slot's tuple. + * -------------------------------- + */ +TupleDesc /* return: old slot tuple descriptor */ +ExecSetSlotDescriptor(TupleTableSlot *slot, /* slot to change */ + TupleDesc tupdesc) /* tuple descriptor */ +{ + TupleDesc old_tupdesc = slot->ttc_tupleDescriptor; + + slot->ttc_tupleDescriptor = tupdesc; + return old_tupdesc; +} + +/* -------------------------------- + * ExecSetSlotDescriptorIsNew + * + * This function is used to change the setting of the "isNew" flag + * -------------------------------- + */ +void +ExecSetSlotDescriptorIsNew(TupleTableSlot *slot,/* slot to change */ + bool isNew) /* "isNew" setting */ +{ + slot->ttc_descIsNew = isNew; +} + +/* -------------------------------- + * ExecSetNewSlotDescriptor + * + * This function is used to set the tuple descriptor associated + * with the slot's tuple, and set the "isNew" flag at the same time. + * -------------------------------- + */ +TupleDesc /* return: old slot tuple descriptor */ +ExecSetNewSlotDescriptor(TupleTableSlot *slot, /* slot to change */ + TupleDesc tupdesc) /* tuple descriptor */ +{ + TupleDesc old_tupdesc = slot->ttc_tupleDescriptor; + slot->ttc_tupleDescriptor = tupdesc; + slot->ttc_descIsNew = true; + + return old_tupdesc; +} + +/* -------------------------------- + * ExecSlotBuffer + * + * This function is used to get the tuple descriptor associated + * with the slot's tuple. Be very careful with this as it does not + * balance the reference counts. If the buffer returned is stored + * someplace else, then also use ExecIncrSlotBufferRefcnt(). + * + * Now a macro in tuptable.h + * -------------------------------- + */ + +/* -------------------------------- + * ExecSetSlotBuffer + * + * This function is used to set the tuple descriptor associated + * with the slot's tuple. Be very careful with this as it does not + * balance the reference counts. If we're using this then we should + * also use ExecIncrSlotBufferRefcnt(). + * -------------------------------- + */ +Buffer /* return: old slot buffer */ +ExecSetSlotBuffer(TupleTableSlot *slot, /* slot to change */ + Buffer b) /* tuple descriptor */ +{ + Buffer oldb = slot->ttc_buffer; + slot->ttc_buffer = b; + + return oldb; +} + +/* -------------------------------- + * ExecIncrSlotBufferRefcnt + * + * When we pass around buffers in the tuple table, we have to + * be careful to increment reference counts appropriately. + * This is used mainly in the mergejoin code. + * -------------------------------- + */ +void +ExecIncrSlotBufferRefcnt(TupleTableSlot *slot) /* slot to bump refcnt */ +{ +/* Buffer b = SlotBuffer((TupleTableSlot*) slot); */ + Buffer b = slot->ttc_buffer; + if (BufferIsValid(b)) + IncrBufferRefCount(b); +} + +/* ---------------------------------------------------------------- + * tuple table slot status predicates + * ---------------------------------------------------------------- + */ + +/* ---------------- + * TupIsNull + * + * This is used mainly to detect when there are no more + * tuples to process. + * ---------------- + */ +bool /* return: true if tuple in slot is NULL */ +TupIsNull(TupleTableSlot* slot) /* slot to check */ +{ + HeapTuple tuple; /* contents of slot (returned) */ + + /* ---------------- + * if the slot itself is null then we return true + * ---------------- + */ + if (slot == NULL) + return true; + + /* ---------------- + * get information from the slot and return true or + * false depending on the contents of the slot. + * ---------------- + */ + tuple = slot->val; + + return + (tuple == NULL ? true : false); +} + +/* -------------------------------- + * ExecSlotDescriptorIsNew + * + * This function is used to check if the tuple descriptor + * associated with this slot has just changed. ie: we are + * now storing a new type of tuple in this slot + * -------------------------------- + */ +bool /* return: descriptor "is new" */ +ExecSlotDescriptorIsNew(TupleTableSlot *slot) /* slot to inspect */ +{ +/* bool isNew = SlotTupleDescriptorIsNew((TupleTableSlot*) slot); + return isNew; */ + return slot->ttc_descIsNew; +} + +/* ---------------------------------------------------------------- + * convenience initialization routines + * ---------------------------------------------------------------- + */ +/* -------------------------------- + * ExecInit{Result,Scan,Raw,Marked,Outer,Hash}TupleSlot + * + * These are convenience routines to initialize the specfied slot + * in nodes inheriting the appropriate state. + * -------------------------------- + */ +#define INIT_SLOT_DEFS \ + TupleTable tupleTable; \ + TupleTableSlot* slot + +#define INIT_SLOT_ALLOC \ + tupleTable = (TupleTable) estate->es_tupleTable; \ + slot = ExecAllocTableSlot(tupleTable); \ + slot->val = (HeapTuple)NULL; \ + slot->ttc_shouldFree = true; \ + slot->ttc_tupleDescriptor = (TupleDesc)NULL; \ + slot->ttc_whichplan = -1;\ + slot->ttc_descIsNew = true; + +/* ---------------- + * ExecInitResultTupleSlot + * ---------------- + */ +void +ExecInitResultTupleSlot(EState *estate, CommonState *commonstate) +{ + INIT_SLOT_DEFS; + INIT_SLOT_ALLOC; + commonstate->cs_ResultTupleSlot = (TupleTableSlot *) slot; +} + +/* ---------------- + * ExecInitScanTupleSlot + * ---------------- + */ +void +ExecInitScanTupleSlot(EState *estate, CommonScanState *commonscanstate) +{ + INIT_SLOT_DEFS; + INIT_SLOT_ALLOC; + commonscanstate->css_ScanTupleSlot = (TupleTableSlot *)slot; +} + +/* ---------------- + * ExecInitMarkedTupleSlot + * ---------------- + */ +void +ExecInitMarkedTupleSlot(EState *estate, MergeJoinState *mergestate) +{ + INIT_SLOT_DEFS; + INIT_SLOT_ALLOC; + mergestate->mj_MarkedTupleSlot = (TupleTableSlot *) slot; +} + +/* ---------------- + * ExecInitOuterTupleSlot + * ---------------- + */ +void +ExecInitOuterTupleSlot(EState *estate, HashJoinState *hashstate) +{ + INIT_SLOT_DEFS; + INIT_SLOT_ALLOC; + hashstate->hj_OuterTupleSlot = slot; +} + +/* ---------------- + * ExecInitHashTupleSlot + * ---------------- + */ +void +ExecInitHashTupleSlot(EState *estate, HashJoinState *hashstate) +{ + INIT_SLOT_DEFS; + INIT_SLOT_ALLOC; + hashstate->hj_HashTupleSlot = slot; +} + +TupleTableSlot * +NodeGetResultTupleSlot(Plan *node) +{ + TupleTableSlot *slot; + + switch(nodeTag(node)) { + + case T_Result: + { + ResultState *resstate = ((Result *)node)->resstate; + slot = resstate->cstate.cs_ResultTupleSlot; + } + break; + + case T_SeqScan: + { + CommonScanState *scanstate = ((SeqScan *)node)->scanstate; + slot = scanstate->cstate.cs_ResultTupleSlot; + } + break; + + case T_NestLoop: + { + NestLoopState *nlstate = ((NestLoop *)node)->nlstate; + slot = nlstate->jstate.cs_ResultTupleSlot; + } + break; + + case T_Append: + { + Append *n = (Append *)node; + AppendState *unionstate; + List *unionplans; + int whichplan; + Plan *subplan; + + unionstate = n->unionstate; + unionplans = n->unionplans; + whichplan = unionstate->as_whichplan; + + subplan = (Plan*) nth(whichplan, unionplans); + slot = NodeGetResultTupleSlot(subplan); + break; + } + + case T_IndexScan: + { + CommonScanState *scanstate = ((IndexScan *)node)->scan.scanstate; + slot = scanstate->cstate.cs_ResultTupleSlot; + } + break; + + case T_Material: + { + MaterialState *matstate = ((Material *)node)->matstate; + slot = matstate->csstate.css_ScanTupleSlot; + } + break; + + case T_Sort: + { + SortState *sortstate = ((Sort *)node)->sortstate; + slot = sortstate->csstate.css_ScanTupleSlot; + } + break; + + case T_Agg: + { + AggState *aggstate = ((Agg *)node)->aggstate; + slot = aggstate->csstate.cstate.cs_ResultTupleSlot; + } + break; + + case T_Group: + { + GroupState *grpstate = ((Group *)node)->grpstate; + slot = grpstate->csstate.cstate.cs_ResultTupleSlot; + } + break; + + case T_Hash: + { + HashState *hashstate = ((Hash *)node)->hashstate; + slot = hashstate->cstate.cs_ResultTupleSlot; + } + break; + + case T_Unique: + { + UniqueState *uniquestate = ((Unique *)node)->uniquestate; + slot = uniquestate->cs_ResultTupleSlot; + } + break; + + case T_MergeJoin: + { + MergeJoinState *mergestate = ((MergeJoin *)node)->mergestate; + slot = mergestate->jstate.cs_ResultTupleSlot; + } + break; + + case T_HashJoin: + { + HashJoinState *hashjoinstate = ((HashJoin *)node)->hashjoinstate; + slot = hashjoinstate->jstate.cs_ResultTupleSlot; + } + break; + + case T_Tee: + { + TeeState *teestate = ((Tee*)node)->teestate; + slot = teestate->cstate.cs_ResultTupleSlot; + } + break; + + default: + /* ---------------- + * should never get here + * ---------------- + */ + elog(WARN, "NodeGetResultTupleSlot: node not yet supported: %d ", + nodeTag(node)); + + return NULL; + } + return slot; +} + +/* ---------------------------------------------------------------- + * ExecGetTupType + * + * this gives you the tuple descriptor for tuples returned + * by this node. I really wish I could ditch this routine, + * but since not all nodes store their type info in the same + * place, we have to do something special for each node type. + * + * Soon, the system will have to adapt to deal with changing + * tuple descriptors as we deal with dynamic tuple types + * being returned from procedure nodes. Perhaps then this + * routine can be retired. -cim 6/3/91 + * + * old comments + * This routine just gets the type information out of the + * node's state. If you already have a node's state, you + * can get this information directly, but this is a useful + * routine if you want to get the type information from + * the node's inner or outer subplan easily without having + * to inspect the subplan.. -cim 10/16/89 + * + * Assume that for existential nodes, we get the targetlist out + * of the right node's targetlist + * ---------------------------------------------------------------- + */ + +TupleDesc +ExecGetTupType(Plan *node) +{ + TupleTableSlot *slot; + TupleDesc tupType; + + if (node == NULL) + return NULL; + + slot = NodeGetResultTupleSlot(node); + tupType = slot->ttc_tupleDescriptor; + return tupType; +} + +/* +TupleDesc +ExecCopyTupType(TupleDesc td, int natts) +{ + TupleDesc newTd; + int i; + + newTd = CreateTemplateTupleDesc(natts); + i = 0; + while (i < natts) + { + newTd[i] = + (AttributeTupleForm)palloc(sizeof(FormData_pg_attribute)); + memmove(newTd[i], td[i], sizeof(FormData_pg_attribute)); + i++; + } + return newTd; +} +*/ + +/* ---------------------------------------------------------------- + * ExecTypeFromTL + * + * Currently there are about 4 different places where we create + * TupleDescriptors. They should all be merged, or perhaps + * be rewritten to call BuildDesc(). + * + * old comments + * Forms attribute type info from the target list in the node. + * It assumes all domains are individually specified in the target list. + * It fails if the target list contains something like Emp.all + * which represents all the attributes from EMP relation. + * + * Conditions: + * The inner and outer subtrees should be initialized because it + * might be necessary to know the type infos of the subtrees. + * ---------------------------------------------------------------- + */ +TupleDesc +ExecTypeFromTL(List *targetList) +{ + List *tlcdr; + TupleDesc typeInfo; + Resdom *resdom; + Oid restype; + int len; + + /* ---------------- + * examine targetlist - if empty then return NULL + * ---------------- + */ + len = ExecTargetListLength(targetList); + + if (len == 0) + return NULL; + + /* ---------------- + * allocate a new typeInfo + * ---------------- + */ + typeInfo = CreateTemplateTupleDesc(len); + + /* ---------------- + * notes: get resdom from (resdom expr) + * get_typbyval comes from src/lib/l-lisp/lsyscache.c + * ---------------- + */ + tlcdr = targetList; + while (tlcdr != NIL) { + TargetEntry *tle = lfirst(tlcdr); + if (tle->resdom != NULL) { + resdom = tle->resdom; + restype = resdom->restype; + + TupleDescInitEntry(typeInfo, + resdom->resno, + resdom->resname, + get_id_typname(restype), + 0, + false); + +/* + ExecSetTypeInfo(resdom->resno - 1, + typeInfo, + (Oid) restype, + resdom->resno, + resdom->reslen, + resdom->resname->data, + get_typbyval(restype), + get_typalign(restype)); +*/ + } + else { + Resdom *fjRes; + List *fjTlistP; + List *fjList = lfirst(tlcdr); +#ifdef SETS_FIXED + TargetEntry *tle; + Fjoin *fjNode = ((TargetEntry *)lfirst(fjList))->fjoin; + + tle = fjNode->fj_innerNode; /* ??? */ +#endif + fjRes = tle->resdom; + restype = fjRes->restype; + + TupleDescInitEntry(typeInfo, + fjRes->resno, + fjRes->resname, + get_id_typname(restype), + 0, + false); +/* + ExecSetTypeInfo(fjRes->resno - 1, + typeInfo, + (Oid) restype, + fjRes->resno, + fjRes->reslen, + (char *) fjRes->resname, + get_typbyval(restype), + get_typalign(restype)); +*/ + + foreach(fjTlistP, lnext(fjList)) { + TargetEntry *fjTle = lfirst(fjTlistP); + + fjRes = fjTle->resdom; + + TupleDescInitEntry(typeInfo, + fjRes->resno, + fjRes->resname, + get_id_typname(restype), + 0, + false); + +/* + ExecSetTypeInfo(fjRes->resno - 1, + typeInfo, + (Oid) fjRes->restype, + fjRes->resno, + fjRes->reslen, + (char *) fjRes->resname, + get_typbyval(fjRes->restype), + get_typalign(fjRes->restype)); +*/ + } + } + + tlcdr = lnext(tlcdr); + } + + return typeInfo; +} + + diff --git a/src/backend/executor/execUtils.c b/src/backend/executor/execUtils.c new file mode 100644 index 00000000000..8d1108aca25 --- /dev/null +++ b/src/backend/executor/execUtils.c @@ -0,0 +1,1092 @@ +/*------------------------------------------------------------------------- + * + * execUtils.c-- + * miscellanious executor utility routines + * + * Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * $Header: /cvsroot/pgsql/src/backend/executor/execUtils.c,v 1.1.1.1 1996/07/09 06:21:25 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +/* + * INTERFACE ROUTINES + * ExecAssignNodeBaseInfo \ + * ExecAssignDebugHooks > preforms misc work done in all the + * ExecAssignExprContext / init node routines. + * + * ExecGetTypeInfo | old execCStructs interface + * ExecMakeTypeInfo | code from the version 1 + * ExecOrderTypeInfo | lisp system. These should + * ExecSetTypeInfo | go away or be updated soon. + * ExecFreeTypeInfo | -cim 11/1/89 + * ExecTupleAttributes / + * + + * QueryDescGetTypeInfo - moved here from main.c + * am not sure what uses it -cim 10/12/89 + * + * ExecGetIndexKeyInfo \ + * ExecOpenIndices | referenced by InitPlan, EndPlan, + * ExecCloseIndices | ExecAppend, ExecReplace + * ExecFormIndexTuple | + * ExecInsertIndexTuple / + * + * NOTES + * This file has traditionally been the place to stick misc. + * executor support stuff that doesn't really go anyplace else. + * + */ + +#include "executor/executor.h" +#include "access/itup.h" +#include "optimizer/clauses.h" +#include "utils/palloc.h" +#include "commands/command.h" +#include "catalog/index.h" + +/* ---------------------------------------------------------------- + * global counters for number of tuples processed, retrieved, + * appended, replaced, deleted. + * ---------------------------------------------------------------- + */ +int NTupleProcessed; +int NTupleRetrieved; +int NTupleReplaced; +int NTupleAppended; +int NTupleDeleted; +int NIndexTupleInserted; +extern int NIndexTupleProcessed; /* have to be defined in the access + method level so that the cinterface.a + will link ok. */ + +/* ---------------------------------------------------------------- + * statistic functions + * ---------------------------------------------------------------- + */ + +/* ---------------------------------------------------------------- + * ResetTupleCount + * ---------------------------------------------------------------- + */ +void +ResetTupleCount() +{ + NTupleProcessed = 0; + NTupleRetrieved = 0; + NTupleAppended = 0; + NTupleDeleted = 0; + NTupleReplaced = 0; + NIndexTupleProcessed = 0; +} + +/* ---------------------------------------------------------------- + * PrintTupleCount + * ---------------------------------------------------------------- + */ +void +DisplayTupleCount(FILE *statfp) +{ + if (NTupleProcessed > 0) + fprintf(statfp, "!\t%d tuple%s processed, ", NTupleProcessed, + (NTupleProcessed == 1) ? "" : "s"); + else { + fprintf(statfp, "!\tno tuples processed.\n"); + return; + } + if (NIndexTupleProcessed > 0) + fprintf(statfp, "%d indextuple%s processed, ", NIndexTupleProcessed, + (NIndexTupleProcessed == 1) ? "" : "s"); + if (NIndexTupleInserted > 0) + fprintf(statfp, "%d indextuple%s inserted, ", NIndexTupleInserted, + (NIndexTupleInserted == 1) ? "" : "s"); + if (NTupleRetrieved > 0) + fprintf(statfp, "%d tuple%s retrieved. ", NTupleRetrieved, + (NTupleRetrieved == 1) ? "" : "s"); + if (NTupleAppended > 0) + fprintf(statfp, "%d tuple%s appended. ", NTupleAppended, + (NTupleAppended == 1) ? "" : "s"); + if (NTupleDeleted > 0) + fprintf(statfp, "%d tuple%s deleted. ", NTupleDeleted, + (NTupleDeleted == 1) ? "" : "s"); + if (NTupleReplaced > 0) + fprintf(statfp, "%d tuple%s replaced. ", NTupleReplaced, + (NTupleReplaced == 1) ? "" : "s"); + fprintf(statfp, "\n"); +} + +/* ---------------------------------------------------------------- + * miscellanious init node support functions + * + * ExecAssignNodeBaseInfo - assigns the baseid field of the node + * ExecAssignDebugHooks - assigns the node's debugging hooks + * ExecAssignExprContext - assigns the node's expression context + * ---------------------------------------------------------------- + */ + +/* ---------------- + * ExecAssignNodeBaseInfo + * + * as it says, this assigns the baseid field of the node and + * increments the counter in the estate. In addition, it initializes + * the base_parent field of the basenode. + * ---------------- + */ +void +ExecAssignNodeBaseInfo(EState *estate, CommonState *cstate, Plan *parent) +{ + int baseId; + + baseId = estate->es_BaseId; + cstate->cs_base_id = baseId; + estate->es_BaseId = baseId + 1; +} + +/* ---------------- + * ExecAssignExprContext + * + * This initializes the ExprContext field. It is only necessary + * to do this for nodes which use ExecQual or ExecTargetList + * because those routines depend on econtext. Other nodes which + * dont have to evaluate expressions don't need to do this. + * ---------------- + */ +void +ExecAssignExprContext(EState *estate, CommonState *commonstate) +{ + ExprContext *econtext; + ParamListInfo paraminfo; + List *rangeTable; + + paraminfo = estate->es_param_list_info; + rangeTable = estate->es_range_table; + + econtext = makeNode(ExprContext); + econtext->ecxt_scantuple = NULL; /* scan tuple slot */ + econtext->ecxt_innertuple = NULL; /* inner tuple slot */ + econtext->ecxt_outertuple = NULL; /* outer tuple slot */ + econtext->ecxt_relation = NULL; /* relation */ + econtext->ecxt_relid = 0; /* relid */ + econtext->ecxt_param_list_info = paraminfo; /* param list info */ + econtext->ecxt_range_table = rangeTable; /* range table */ + + commonstate->cs_ExprContext = econtext; +} + +/* ---------------------------------------------------------------- + * Result slot tuple type and ProjectionInfo support + * ---------------------------------------------------------------- + */ + +/* ---------------- + * ExecAssignResultType + * ---------------- + */ +void +ExecAssignResultType(CommonState *commonstate, + TupleDesc tupDesc) +{ + TupleTableSlot *slot; + + slot = commonstate->cs_ResultTupleSlot; + slot->ttc_tupleDescriptor = tupDesc; +} + +/* ---------------- + * ExecAssignResultTypeFromOuterPlan + * ---------------- + */ +void +ExecAssignResultTypeFromOuterPlan(Plan *node, CommonState *commonstate) +{ + Plan *outerPlan; + TupleDesc tupDesc; + + outerPlan = outerPlan(node); + tupDesc = ExecGetTupType(outerPlan); + + ExecAssignResultType(commonstate, tupDesc); +} + +/* ---------------- + * ExecAssignResultTypeFromTL + * ---------------- + */ +void +ExecAssignResultTypeFromTL(Plan *node, CommonState *commonstate) +{ + List *targetList; + int i; + int len; + List *tl; + TargetEntry *tle; + List *fjtl; + TupleDesc origTupDesc; + + targetList = node->targetlist; + origTupDesc = ExecTypeFromTL(targetList); + len = ExecTargetListLength(targetList); + + fjtl = NIL; + tl = targetList; + i = 0; + while (tl != NIL || fjtl != NIL) { + if (fjtl != NIL) { + tle = lfirst(fjtl); + fjtl = lnext(fjtl); + } + else { + tle = lfirst(tl); + tl = lnext(tl); + } +#ifdef SETS_FIXED + if (!tl_is_resdom(tle)) { + Fjoin *fj = (Fjoin *)lfirst(tle); + /* it is a FJoin */ + fjtl = lnext(tle); + tle = fj->fj_innerNode; + } +#endif + i++; + } + if (len > 0) { + ExecAssignResultType(commonstate, + origTupDesc); + } + else + ExecAssignResultType(commonstate, + (TupleDesc)NULL); +} + +/* ---------------- + * ExecGetResultType + * ---------------- + */ +TupleDesc +ExecGetResultType(CommonState *commonstate) +{ + TupleTableSlot *slot = commonstate->cs_ResultTupleSlot; + + return slot->ttc_tupleDescriptor; +} + +/* ---------------- + * ExecFreeResultType + * ---------------- + */ +void +ExecFreeResultType(CommonState *commonstate) +{ + TupleTableSlot *slot; + TupleDesc tupType; + + slot = commonstate->cs_ResultTupleSlot; + tupType = slot->ttc_tupleDescriptor; + +/* ExecFreeTypeInfo(tupType); */ + pfree(tupType); +} + + +/* ---------------- + * ExecAssignProjectionInfo + forms the projection information from the node's targetlist + * ---------------- + */ +void +ExecAssignProjectionInfo(Plan *node, CommonState *commonstate) +{ + ProjectionInfo *projInfo; + List *targetList; + int len; + + targetList = node->targetlist; + len = ExecTargetListLength(targetList); + + projInfo = makeNode(ProjectionInfo); + projInfo->pi_targetlist = targetList; + projInfo->pi_len = len; + projInfo->pi_tupValue = + (len <= 0) ? NULL : (Datum *) palloc(sizeof(Datum) * len); + projInfo->pi_exprContext = commonstate->cs_ExprContext; + projInfo->pi_slot = commonstate->cs_ResultTupleSlot; + + commonstate->cs_ProjInfo = projInfo; +} + + +/* ---------------- + * ExecFreeProjectionInfo + * ---------------- + */ +void +ExecFreeProjectionInfo(CommonState *commonstate) +{ + ProjectionInfo *projInfo; + + /* ---------------- + * get projection info. if NULL then this node has + * none so we just return. + * ---------------- + */ + projInfo = commonstate->cs_ProjInfo; + if (projInfo == NULL) + return; + + /* ---------------- + * clean up memory used. + * ---------------- + */ + if (projInfo->pi_tupValue != NULL) + pfree(projInfo->pi_tupValue); + + pfree(projInfo); + commonstate->cs_ProjInfo = NULL; +} + +/* ---------------------------------------------------------------- + * the following scan type support functions are for + * those nodes which are stubborn and return tuples in + * their Scan tuple slot instead of their Result tuple + * slot.. luck fur us, these nodes do not do projections + * so we don't have to worry about getting the ProjectionInfo + * right for them... -cim 6/3/91 + * ---------------------------------------------------------------- + */ + +/* ---------------- + * ExecGetScanType + * ---------------- + */ +TupleDesc +ExecGetScanType(CommonScanState *csstate) +{ + TupleTableSlot *slot = csstate->css_ScanTupleSlot; + return slot->ttc_tupleDescriptor; +} + +/* ---------------- + * ExecFreeScanType + * ---------------- + */ +void +ExecFreeScanType(CommonScanState *csstate) +{ + TupleTableSlot *slot; + TupleDesc tupType; + + slot = csstate->css_ScanTupleSlot; + tupType = slot->ttc_tupleDescriptor; + +/* ExecFreeTypeInfo(tupType); */ + pfree(tupType); +} + +/* ---------------- + * ExecAssignScanType + * ---------------- + */ +void +ExecAssignScanType(CommonScanState *csstate, + TupleDesc tupDesc) +{ + TupleTableSlot *slot; + + slot = (TupleTableSlot *) csstate->css_ScanTupleSlot; + slot->ttc_tupleDescriptor = tupDesc; +} + +/* ---------------- + * ExecAssignScanTypeFromOuterPlan + * ---------------- + */ +void +ExecAssignScanTypeFromOuterPlan(Plan *node, CommonScanState *csstate) +{ + Plan *outerPlan; + TupleDesc tupDesc; + + outerPlan = outerPlan(node); + tupDesc = ExecGetTupType(outerPlan); + + ExecAssignScanType(csstate, tupDesc); +} + + +/* ---------------------------------------------------------------- + * ExecTypeFromTL support routines. + * + * these routines are used mainly from ExecTypeFromTL. + * -cim 6/12/90 + * + * old comments + * Routines dealing with the structure 'attribute' which conatains + * the type information about attributes in a tuple: + * + * ExecMakeTypeInfo(noType) -- + * returns pointer to array of 'noType' structure 'attribute'. + * ExecSetTypeInfo(index, typeInfo, attNum, attLen) -- + * sets the element indexed by 'index' in typeInfo with + * the values: attNum, attLen. + * ExecFreeTypeInfo(typeInfo) -- + * frees the structure 'typeInfo'. + * ---------------------------------------------------------------- + */ + +/* ---------------- + * ExecSetTypeInfo + * + * This initializes fields of a single attribute in a + * tuple descriptor from the specified parameters. + * + * XXX this duplicates much of the functionality of TupleDescInitEntry. + * the routines should be moved to the same place and be rewritten + * to share common code. + * ---------------- + */ +#if 0 +void +ExecSetTypeInfo(int index, + TupleDesc typeInfo, + Oid typeID, + int attNum, + int attLen, + char *attName, + bool attbyVal, + char attalign) +{ + AttributeTupleForm att; + + /* ---------------- + * get attribute pointer and preform a sanity check.. + * ---------------- + */ + att = typeInfo[index]; + if (att == NULL) + elog(WARN, "ExecSetTypeInfo: trying to assign through NULL"); + + /* ---------------- + * assign values to the tuple descriptor, being careful not + * to copy a null attName.. + * + * XXX it is unknown exactly what information is needed to + * initialize the attribute struct correctly so for now + * we use 0. this should be fixed -- otherwise we run the + * risk of using garbage data. -cim 5/5/91 + * ---------------- + */ + att->attrelid = 0; /* dummy value */ + + if (attName != (char *) NULL) + strncpy(att->attname.data, attName, NAMEDATALEN); + else + memset(att->attname.data,0,NAMEDATALEN); + + att->atttypid = typeID; + att->attdefrel = 0; /* dummy value */ + att->attnvals = 0; /* dummy value */ + att->atttyparg = 0; /* dummy value */ + att->attlen = attLen; + att->attnum = attNum; + att->attbound = 0; /* dummy value */ + att->attbyval = attbyVal; + att->attcanindex = 0; /* dummy value */ + att->attproc = 0; /* dummy value */ + att->attnelems = 0; /* dummy value */ + att->attcacheoff = -1; + att->attisset = false; + att->attalign = attalign; +} + +/* ---------------- + * ExecFreeTypeInfo frees the array of attrbutes + * created by ExecMakeTypeInfo and returned by ExecTypeFromTL... + * ---------------- + */ +void +ExecFreeTypeInfo(TupleDesc typeInfo) +{ + /* ---------------- + * do nothing if asked to free a null pointer + * ---------------- + */ + if (typeInfo == NULL) + return; + + /* ---------------- + * the entire array of typeinfo pointers created by + * ExecMakeTypeInfo was allocated with a single palloc() + * so we can deallocate the whole array with a single pfree(). + * (we should not try and free all the elements in the array) + * -cim 6/12/90 + * ---------------- + */ + pfree(typeInfo); +} + + +/* ---------------------------------------------------------------- + * QueryDescGetTypeInfo + * + *| I don't know how this is used, all I know is that it + *| appeared one day in main.c so I moved it here. -cim 11/1/89 + * ---------------------------------------------------------------- + */ +TupleDesc +QueryDescGetTypeInfo(QueryDesc *queryDesc) +{ + Plan *plan; + TupleDesc tupleType; + List *targetList; + AttrInfo *attinfo = (AttrInfo *)palloc(sizeof(AttrInfo)); + + plan = queryDesc->plantree; + tupleType = (TupleDesc) ExecGetTupType(plan); +/* + targetList = plan->targetlist; + + attinfo->numAttr = ExecTargetListLength(targetList); + attinfo->attrs = tupleType; +*/ + attinfo->numAttr = tupleType->natts; + attinfo->attrs = tupleType->attrs; + return attinfo; +} +#endif + +/* ---------------------------------------------------------------- + * ExecInsertIndexTuples support + * ---------------------------------------------------------------- + */ +/* ---------------------------------------------------------------- + * ExecGetIndexKeyInfo + * + * Extracts the index key attribute numbers from + * an index tuple form (i.e. a tuple from the pg_index relation) + * into an array of attribute numbers. The array and the + * size of the array are returned to the caller via return + * parameters. + * ---------------------------------------------------------------- + */ +void +ExecGetIndexKeyInfo(IndexTupleForm indexTuple, + int *numAttsOutP, + AttrNumber **attsOutP, + FuncIndexInfoPtr fInfoP) +{ + int i; + int numKeys; + AttrNumber *attKeys; + + /* ---------------- + * check parameters + * ---------------- + */ + if (numAttsOutP == NULL && attsOutP == NULL) { + elog(DEBUG, "ExecGetIndexKeyInfo: %s", + "invalid parameters: numAttsOutP and attsOutP must be non-NULL"); + } + + /* ---------------- + * set the procid for a possible functional index. + * ---------------- + */ + FIsetProcOid(fInfoP, indexTuple->indproc); + + /* ---------------- + * count the number of keys.. + * ---------------- + */ + numKeys = 0; + for (i=0; i<8 && indexTuple->indkey[i] != 0; i++) + numKeys++; + + /* ---------------- + * place number keys in callers return area + * or the number of arguments for a functional index. + * + * If we have a functional index then the number of + * attributes defined in the index must 1 (the function's + * single return value). + * ---------------- + */ + if (FIgetProcOid(fInfoP) != InvalidOid) { + FIsetnArgs(fInfoP, numKeys); + (*numAttsOutP) = 1; + } + else + (*numAttsOutP) = numKeys; + + if (numKeys < 1) { + elog(DEBUG, "ExecGetIndexKeyInfo: %s", + "all index key attribute numbers are zero!"); + (*attsOutP) = NULL; + return; + } + + /* ---------------- + * allocate and fill in array of key attribute numbers + * ---------------- + */ + CXT1_printf("ExecGetIndexKeyInfo: context is %d\n", CurrentMemoryContext); + + attKeys = (AttrNumber*) + palloc(numKeys * sizeof(AttrNumber)); + + for (i=0; i<numKeys; i++) + attKeys[i] = indexTuple->indkey[i]; + + /* ---------------- + * return array to caller. + * ---------------- + */ + (*attsOutP) = attKeys; +} + +/* ---------------------------------------------------------------- + * ExecOpenIndices + * + * Here we scan the pg_index relation to find indices + * associated with a given heap relation oid. Since we + * don't know in advance how many indices we have, we + * form lists containing the information we need from + * pg_index and then process these lists. + * + * Note: much of this code duplicates effort done by + * the IndexCatalogInformation function in plancat.c + * because IndexCatalogInformation is poorly written. + * + * It would be much better the functionality provided + * by this function and IndexCatalogInformation was + * in the form of a small set of orthogonal routines.. + * If you are trying to understand this, I suggest you + * look at the code to IndexCatalogInformation and + * FormIndexTuple.. -cim 9/27/89 + * ---------------------------------------------------------------- + */ +void +ExecOpenIndices(Oid resultRelationOid, + RelationInfo *resultRelationInfo) +{ + Relation indexRd; + HeapScanDesc indexSd; + ScanKeyData key; + HeapTuple tuple; + IndexTupleForm indexStruct; + Oid indexOid; + List *oidList; + List *nkeyList; + List *keyList; + List *fiList; + char *predString; + List *predList; + List *indexoid; + List *numkeys; + List *indexkeys; + List *indexfuncs; + List *indexpreds; + int len; + + RelationPtr relationDescs; + IndexInfo **indexInfoArray; + FuncIndexInfoPtr fInfoP; + int numKeyAtts; + AttrNumber *indexKeyAtts; + PredInfo *predicate; + int i; + + /* ---------------- + * open pg_index + * ---------------- + */ + indexRd = heap_openr(IndexRelationName); + + /* ---------------- + * form a scan key + * ---------------- + */ + ScanKeyEntryInitialize(&key, 0, Anum_pg_index_indrelid, + ObjectIdEqualRegProcedure, + ObjectIdGetDatum(resultRelationOid)); + + /* ---------------- + * scan the index relation, looking for indices for our + * result relation.. + * ---------------- + */ + indexSd = heap_beginscan(indexRd, /* scan desc */ + false, /* scan backward flag */ + NowTimeQual, /* time qual */ + 1, /* number scan keys */ + &key); /* scan keys */ + + oidList = NIL; + nkeyList = NIL; + keyList = NIL; + fiList = NIL; + predList = NIL; + + while(tuple = heap_getnext(indexSd, /* scan desc */ + false, /* scan backward flag */ + NULL), /* return: buffer */ + HeapTupleIsValid(tuple)) { + + /* ---------------- + * For each index relation we find, extract the information + * we need and store it in a list.. + * + * first get the oid of the index relation from the tuple + * ---------------- + */ + indexStruct = (IndexTupleForm) GETSTRUCT(tuple); + indexOid = indexStruct->indexrelid; + + /* ---------------- + * allocate space for functional index information. + * ---------------- + */ + fInfoP = (FuncIndexInfoPtr)palloc( sizeof(*fInfoP) ); + + /* ---------------- + * next get the index key information from the tuple + * ---------------- + */ + ExecGetIndexKeyInfo(indexStruct, + &numKeyAtts, + &indexKeyAtts, + fInfoP); + + /* ---------------- + * next get the index predicate from the tuple + * ---------------- + */ + if (VARSIZE(&indexStruct->indpred) != 0) { + predString = fmgr(F_TEXTOUT, &indexStruct->indpred); + predicate = (PredInfo*)stringToNode(predString); + pfree(predString); + } else { + predicate = NULL; + } + + /* ---------------- + * save the index information into lists + * ---------------- + */ + oidList = lconsi(indexOid, oidList); + nkeyList = lconsi(numKeyAtts, nkeyList); + keyList = lcons(indexKeyAtts, keyList); + fiList = lcons(fInfoP, fiList); + predList = lcons(predicate, predList); + } + + /* ---------------- + * we have the info we need so close the pg_index relation.. + * ---------------- + */ + heap_endscan(indexSd); + heap_close(indexRd); + + /* ---------------- + * Now that we've collected the index information into three + * lists, we open the index relations and store the descriptors + * and the key information into arrays. + * ---------------- + */ + len = length(oidList); + if (len > 0) { + /* ---------------- + * allocate space for relation descs + * ---------------- + */ + CXT1_printf("ExecOpenIndices: context is %d\n", CurrentMemoryContext); + relationDescs = (RelationPtr) + palloc(len * sizeof(Relation)); + + /* ---------------- + * initialize index info array + * ---------------- + */ + CXT1_printf("ExecOpenIndices: context is %d\n", CurrentMemoryContext); + indexInfoArray = (IndexInfo**) + palloc(len * sizeof(IndexInfo*)); + + for (i=0; i<len; i++) { + IndexInfo *ii = makeNode(IndexInfo); + ii->ii_NumKeyAttributes = 0; + ii->ii_KeyAttributeNumbers = (AttrNumber*) NULL; + ii->ii_FuncIndexInfo = (FuncIndexInfoPtr) NULL; + ii->ii_Predicate = NULL; + indexInfoArray[i] = ii; + } + + /* ---------------- + * attempt to open each of the indices. If we succeed, + * then store the index relation descriptor into the + * relation descriptor array. + * ---------------- + */ + i = 0; + foreach (indexoid, oidList) { + Relation indexDesc; + + indexOid = lfirsti(indexoid); + indexDesc = index_open(indexOid); + if (indexDesc != NULL) + relationDescs[i++] = indexDesc; + } + + /* ---------------- + * store the relation descriptor array and number of + * descs into the result relation info. + * ---------------- + */ + resultRelationInfo->ri_NumIndices = i; + resultRelationInfo->ri_IndexRelationDescs = relationDescs; + + /* ---------------- + * store the index key information collected in our + * lists into the index info array + * ---------------- + */ + i = 0; + foreach (numkeys, nkeyList) { + numKeyAtts = lfirsti(numkeys); + indexInfoArray[i++]->ii_NumKeyAttributes = numKeyAtts; + } + + i = 0; + foreach (indexkeys, keyList) { + indexKeyAtts = (AttrNumber*) lfirst(indexkeys); + indexInfoArray[i++]->ii_KeyAttributeNumbers = indexKeyAtts; + } + + i = 0; + foreach (indexfuncs, fiList) { + FuncIndexInfoPtr fiP = (FuncIndexInfoPtr)lfirst(indexfuncs); + indexInfoArray[i++]->ii_FuncIndexInfo = fiP; + } + + i = 0; + foreach (indexpreds, predList) { + indexInfoArray[i++]->ii_Predicate = lfirst(indexpreds); + } + /* ---------------- + * store the index info array into relation info + * ---------------- + */ + resultRelationInfo->ri_IndexRelationInfo = indexInfoArray; + } + + /* ---------------- + * All done, resultRelationInfo now contains complete information + * on the indices associated with the result relation. + * ---------------- + */ + + /* should free oidList, nkeyList and keyList here */ + /* OK - let's do it -jolly */ + freeList(oidList); + freeList(nkeyList); + freeList(keyList); + freeList(fiList); + freeList(predList); +} + +/* ---------------------------------------------------------------- + * ExecCloseIndices + * + * Close the index relations stored in resultRelationInfo + * ---------------------------------------------------------------- + */ +void +ExecCloseIndices(RelationInfo *resultRelationInfo) +{ + int i; + int numIndices; + RelationPtr relationDescs; + + numIndices = resultRelationInfo->ri_NumIndices; + relationDescs = resultRelationInfo->ri_IndexRelationDescs; + + for (i=0; i<numIndices; i++) + if (relationDescs[i] != NULL) + index_close(relationDescs[i]); + /* + * XXX should free indexInfo array here too. + */ +} + +/* ---------------------------------------------------------------- + * ExecFormIndexTuple + * + * Most of this code is cannabilized from DefaultBuild(). + * As said in the comments for ExecOpenIndices, most of + * this functionality should be rearranged into a proper + * set of routines.. + * ---------------------------------------------------------------- + */ +IndexTuple +ExecFormIndexTuple(HeapTuple heapTuple, + Relation heapRelation, + Relation indexRelation, + IndexInfo *indexInfo) +{ + IndexTuple indexTuple; + TupleDesc heapDescriptor; + TupleDesc indexDescriptor; + Datum *datum; + char *nulls; + + int numberOfAttributes; + AttrNumber *keyAttributeNumbers; + FuncIndexInfoPtr fInfoP; + + /* ---------------- + * get information from index info structure + * ---------------- + */ + numberOfAttributes = indexInfo->ii_NumKeyAttributes; + keyAttributeNumbers = indexInfo->ii_KeyAttributeNumbers; + fInfoP = indexInfo->ii_FuncIndexInfo; + + /* ---------------- + * datum and null are arrays in which we collect the index attributes + * when forming a new index tuple. + * ---------------- + */ + CXT1_printf("ExecFormIndexTuple: context is %d\n", CurrentMemoryContext); + datum = (Datum *) palloc(numberOfAttributes * sizeof *datum); + nulls = (char *) palloc(numberOfAttributes * sizeof *nulls); + + /* ---------------- + * get the tuple descriptors from the relations so we know + * how to form the index tuples.. + * ---------------- + */ + heapDescriptor = RelationGetTupleDescriptor(heapRelation); + indexDescriptor = RelationGetTupleDescriptor(indexRelation); + + /* ---------------- + * FormIndexDatum fills in its datum and null parameters + * with attribute information taken from the given heap tuple. + * ---------------- + */ + FormIndexDatum(numberOfAttributes, /* num attributes */ + keyAttributeNumbers, /* array of att nums to extract */ + heapTuple, /* tuple from base relation */ + heapDescriptor, /* heap tuple's descriptor */ + InvalidBuffer, /* buffer associated with heap tuple */ + datum, /* return: array of attributes */ + nulls, /* return: array of char's */ + fInfoP); /* functional index information */ + + indexTuple = index_formtuple(indexDescriptor, + datum, + nulls); + + /* ---------------- + * free temporary arrays + * + * XXX should store these in the IndexInfo instead of allocating + * and freeing on every insertion, but efficency here is not + * that important and FormIndexTuple is wasteful anyways.. + * -cim 9/27/89 + * ---------------- + */ + pfree(nulls); + pfree(datum); + + return indexTuple; +} + +/* ---------------------------------------------------------------- + * ExecInsertIndexTuples + * + * This routine takes care of inserting index tuples + * into all the relations indexing the result relation + * when a heap tuple is inserted into the result relation. + * Much of this code should be moved into the genam + * stuff as it only exists here because the genam stuff + * doesn't provide the functionality needed by the + * executor.. -cim 9/27/89 + * ---------------------------------------------------------------- + */ +void +ExecInsertIndexTuples(TupleTableSlot *slot, + ItemPointer tupleid, + EState *estate) +{ + HeapTuple heapTuple; + RelationInfo *resultRelationInfo; + int i; + int numIndices; + RelationPtr relationDescs; + Relation heapRelation; + IndexInfo **indexInfoArray; + Node *predicate; + bool satisfied; + ExprContext *econtext; + IndexTuple indexTuple; + InsertIndexResult result; + + heapTuple = slot->val; + + /* ---------------- + * get information from the result relation info structure. + * ---------------- + */ + resultRelationInfo = estate->es_result_relation_info; + numIndices = resultRelationInfo->ri_NumIndices; + relationDescs = resultRelationInfo->ri_IndexRelationDescs; + indexInfoArray = resultRelationInfo->ri_IndexRelationInfo; + heapRelation = resultRelationInfo->ri_RelationDesc; + + /* ---------------- + * for each index, form and insert the index tuple + * ---------------- + */ + econtext = NULL; + for (i=0; i<numIndices; i++) { + if (relationDescs[i] == NULL) continue; + + predicate = indexInfoArray[i]->ii_Predicate; + if (predicate != NULL) { + if (econtext == NULL) { + econtext = makeNode(ExprContext); + } + econtext->ecxt_scantuple = slot; + + /* Skip this index-update if the predicate isn't satisfied */ + satisfied = ExecQual((List*)predicate, econtext); + if (satisfied == false) + continue; + } + + indexTuple = ExecFormIndexTuple(heapTuple, + heapRelation, + relationDescs[i], + indexInfoArray[i]); + + indexTuple->t_tid = (*tupleid); /* structure assignment */ + + result = index_insert(relationDescs[i], /* index relation */ + indexTuple); /* index tuple */ + + /* ---------------- + * keep track of index inserts for debugging + * ---------------- + */ + IncrIndexInserted(); + + /* ---------------- + * free index tuple after insertion + * ---------------- + */ + if (result) pfree(result); + pfree(indexTuple); + } + if (econtext != NULL) pfree(econtext); +} + diff --git a/src/backend/executor/execdebug.h b/src/backend/executor/execdebug.h new file mode 100644 index 00000000000..b5200ca2577 --- /dev/null +++ b/src/backend/executor/execdebug.h @@ -0,0 +1,377 @@ +/*------------------------------------------------------------------------- + * + * execdebug.h-- + * #defines governing debugging behaviour in the executor + * + * + * Copyright (c) 1994, Regents of the University of California + * + * $Id: execdebug.h,v 1.1.1.1 1996/07/09 06:21:25 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +#ifndef EXECDEBUG_H +#define EXECDEBUG_H + +/* ---------------------------------------------------------------- + * debugging defines. + * + * If you want certain debugging behaviour, then #define + * the variable to 1, else #undef it. -cim 10/26/89 + * ---------------------------------------------------------------- + */ + +/* ---------------- + * EXEC_DEBUGSTORETUP is for tuple table debugging - this + * will print a message every time we call ExecStoreTuple. + * -cim 3/20/91 + * ---------------- + */ +#undef EXEC_DEBUGSTORETUP + +/* ---------------- + * EXEC_TUPLECOUNT is a #define which causes the + * executor keep track of tuple counts. This might be + * causing some problems with the decstation stuff so + * you might want to undefine this if you are doing work + * on the decs - cim 10/20/89 + * ---------------- + */ +#undef EXEC_TUPLECOUNT + +/* ---------------- + * EXEC_SHOWBUFSTATS controls whether or not buffer statistics + * are shown for each query. -cim 2/9/89 + * ---------------- + */ +#undef EXEC_SHOWBUFSTATS + +/* ---------------- + * EXEC_CONTEXTDEBUG turns on the printing of debugging information + * by CXT_printf() calls regarding which memory context is the + * CurrentMemoryContext for palloc() calls. + * ---------------- + */ +#undef EXEC_CONTEXTDEBUG + +/* ---------------- + * EXEC_RETURNSIZE is a compile flag governing the + * behaviour of lispFmgr.. See ExecMakeFunctionResult(). + * Undefining this avoids a problem in the system cache. + * + * Note: undefining this means that there is incorrect + * information in the const nodes corresponding + * to function (or operator) results. The thing is, + * 99% of the time this is fine because when you do + * something like x = emp.sal + 1, you already know + * the type and size of x so the fact that + didn't + * return the correct size doesn't matter. + * With variable length stuff the size is stored in + * the first few bytes of the data so again, it's + * not likely to matter. + * ---------------- + */ +#undef EXEC_RETURNSIZE + +/* ---------------- + * EXEC_UTILSDEBUG is a flag which turns on debugging of the + * executor utilities by EU_printf() in eutils.c + * ---------------- + */ +#undef EXEC_UTILSDEBUG + +/* ---------------- + * EXEC_NESTLOOPDEBUG is a flag which turns on debugging of the + * nest loop node by NL_printf() and ENL_printf() in nestloop.c + * ---------------- + */ +#undef EXEC_NESTLOOPDEBUG + +/* ---------------- + * EXEC_PROCDEBUG is a flag which turns on debugging of + * ExecProcNode() by PN_printf() in procnode.c + * ---------------- + */ +#undef EXEC_PROCDEBUG + +/* ---------------- + * EXEC_EVALDEBUG is a flag which turns on debugging of + * ExecEval and ExecTargetList() stuff by EV_printf() in qual.c + * ---------------- + */ +#undef EXEC_EVALDEBUG + +/* ---------------- + * EXEC_SCANDEBUG is a flag which turns on debugging of + * the ExecSeqScan() stuff by S_printf() in seqscan.c + * ---------------- + */ +#undef EXEC_SCANDEBUG + +/* ---------------- + * EXEC_SORTDEBUG is a flag which turns on debugging of + * the ExecSort() stuff by SO_printf() in sort.c + * ---------------- + */ +#undef EXEC_SORTDEBUG + +/* ---------------- + * EXEC_MERGEJOINDEBUG is a flag which turns on debugging of + * the ExecMergeJoin() stuff by MJ_printf() in mergejoin.c + * ---------------- + */ +#undef EXEC_MERGEJOINDEBUG + +/* ---------------- + * EXEC_MERGEJOINPFREE is a flag which causes merge joins + * to pfree intermittant tuples (which is the proper thing) + * Not defining this means we avoid menory management problems + * at the cost of doing deallocation of stuff only at the + * end of the transaction + * ---------------- + */ +#undef EXEC_MERGEJOINPFREE + +/* ---------------- + * EXEC_DEBUGINTERACTIVE is a flag which enables the + * user to issue "DEBUG" commands from an interactive + * backend. + * ---------------- + */ +#undef EXEC_DEBUGINTERACTIVE + +/* ---------------- + * EXEC_DEBUGVARIABLEFILE is string, which if defined will + * be loaded when the executor is initialized. If this + * string is not defined then nothing will be loaded.. + * + * Example: + * + * #define EXEC_DEBUGVARIABLEFILE "/a/postgres/cimarron/.pg_debugvars" + # + * Note: since these variables are read at execution time, + * they can't affect the first query.. this hack should be + * replaced by something better sometime. -cim 11/2/89 + * ---------------- + */ +#undef EXEC_DEBUGVARIABLEFILE + +/* ---------------------------------------------------------------- + * #defines controlled by above definitions + * + * Note: most of these are "incomplete" because I didn't + * need the ones not defined. More should be added + * only as necessary -cim 10/26/89 + * ---------------------------------------------------------------- + */ +#define T_OR_F(b) (b ? "true" : "false") +#define NULL_OR_TUPLE(slot) (TupIsNull(slot) ? "null" : "a tuple") + + +/* #define EXEC_TUPLECOUNT - XXX take out for now for executor stubbing -- jolly*/ +/* ---------------- + * tuple count debugging defines + * ---------------- + */ +#ifdef EXEC_TUPLECOUNT +extern int NTupleProcessed; +extern int NTupleRetrieved; +extern int NTupleReplaced; +extern int NTupleAppended; +extern int NTupleDeleted; +extern int NIndexTupleProcessed; +extern int NIndexTupleInserted; + +#define IncrRetrieved() NTupleRetrieved++ +#define IncrAppended() NTupleAppended++ +#define IncrDeleted() NTupleDeleted++ +#define IncrReplaced() NTupleReplaced++ +#define IncrInserted() NTupleInserted++ +#define IncrProcessed() NTupleProcessed++ +#define IncrIndexProcessed() NIndexTupleProcessed++ +#define IncrIndexInserted() NIndexTupleInserted++ +#else +#define IncrRetrieved() +#define IncrAppended() +#define IncrDeleted() +#define IncrReplaced() +#define IncrInserted() +#define IncrProcessed() +#define IncrIndexProcessed() +#define IncrIndexInserted() +#endif /* EXEC_TUPLECOUNT */ + +/* ---------------- + * memory context debugging defines + * ---------------- + */ +#ifdef EXEC_CONTEXTDEBUG +#define CXT_printf(s) printf(s) +#define CXT1_printf(s, a) printf(s, a) +#else +#define CXT_printf(s) +#define CXT1_printf(s, a) +#endif /* EXEC_CONTEXTDEBUG */ + +/* ---------------- + * eutils debugging defines + * ---------------- + */ +#ifdef EXEC_UTILSDEBUG +#define EU_nodeDisplay(l) nodeDisplay(l, 0) +#define EU_printf(s) printf(s) +#define EU1_printf(s, a) printf(s, a) +#define EU4_printf(s, a, b, c, d) printf(s, a, b, c, d) +#else +#define EU_nodeDisplay(l) +#define EU_printf(s) +#define EU1_printf(s, a) +#define EU4_printf(s, a, b, c, d) +#endif /* EXEC_UTILSDEBUG */ + + +/* ---------------- + * nest loop debugging defines + * ---------------- + */ +#ifdef EXEC_NESTLOOPDEBUG +#define NL_nodeDisplay(l) nodeDisplay(l, 0) +#define NL_printf(s) printf(s) +#define NL1_printf(s, a) printf(s, a) +#define NL4_printf(s, a, b, c, d) printf(s, a, b, c, d) +#define ENL1_printf(message) printf("ExecNestLoop: %s\n", message) +#else +#define NL_nodeDisplay(l) +#define NL_printf(s) +#define NL1_printf(s, a) +#define NL4_printf(s, a, b, c, d) +#define ENL1_printf(message) +#endif /* EXEC_NESTLOOPDEBUG */ + +/* ---------------- + * proc node debugging defines + * ---------------- + */ +#ifdef EXEC_PROCDEBUG +#define PN_printf(s) printf(s) +#define PN1_printf(s, p) printf(s, p) +#else +#define PN_printf(s) +#define PN1_printf(s, p) +#endif /* EXEC_PROCDEBUG */ + +/* ---------------- + * exec eval / target list debugging defines + * ---------------- + */ +#ifdef EXEC_EVALDEBUG +#define EV_nodeDisplay(l) nodeDisplay(l, 0) +#define EV_printf(s) printf(s) +#define EV1_printf(s, a) printf(s, a) +#define EV5_printf(s, a, b, c, d, e) printf(s, a, b, c, d, e) +#else +#define EV_nodeDisplay(l) +#define EV_printf(s) +#define EV1_printf(s, a) +#define EV5_printf(s, a, b, c, d, e) +#endif /* EXEC_EVALDEBUG */ + +/* ---------------- + * scan debugging defines + * ---------------- + */ +#ifdef EXEC_SCANDEBUG +#define S_nodeDisplay(l) nodeDisplay(l, 0) +#define S_printf(s) printf(s) +#define S1_printf(s, p) printf(s, p) +#else +#define S_nodeDisplay(l) +#define S_printf(s) +#define S1_printf(s, p) +#endif /* EXEC_SCANDEBUG */ + +/* ---------------- + * sort node debugging defines + * ---------------- + */ +#ifdef EXEC_SORTDEBUG +#define SO_nodeDisplay(l) nodeDisplay(l, 0) +#define SO_printf(s) printf(s) +#define SO1_printf(s, p) printf(s, p) +#else +#define SO_nodeDisplay(l) +#define SO_printf(s) +#define SO1_printf(s, p) +#endif /* EXEC_SORTDEBUG */ + +/* ---------------- + * merge join debugging defines + * ---------------- + */ +#ifdef EXEC_MERGEJOINDEBUG +#define MJ_nodeDisplay(l) nodeDisplay(l, 0) +#define MJ_printf(s) printf(s) +#define MJ1_printf(s, p) printf(s, p) +#define MJ2_printf(s, p1, p2) printf(s, p1, p2) +#define MJ_debugtup(tuple, type) debugtup(tuple, type) +#define MJ_dump(context, state) ExecMergeTupleDump(econtext, state) +#define MJ_DEBUG_QUAL(clause, res) \ + MJ2_printf(" ExecQual(%s, econtext) returns %s\n", \ + CppAsString(clause), T_OR_F(res)); + +#define MJ_DEBUG_MERGE_COMPARE(qual, res) \ + MJ2_printf(" MergeCompare(mergeclauses, %s, ..) returns %s\n", \ + CppAsString(qual), T_OR_F(res)); + +#define MJ_DEBUG_PROC_NODE(slot) \ + MJ2_printf(" %s = ExecProcNode(innerPlan) returns %s\n", \ + CppAsString(slot), NULL_OR_TUPLE(slot)); +#else +#define MJ_nodeDisplay(l) +#define MJ_printf(s) +#define MJ1_printf(s, p) +#define MJ2_printf(s, p1, p2) +#define MJ_debugtup(tuple, type) +#define MJ_dump(context, state) +#define MJ_DEBUG_QUAL(clause, res) +#define MJ_DEBUG_MERGE_COMPARE(qual, res) +#define MJ_DEBUG_PROC_NODE(slot) +#endif /* EXEC_MERGEJOINDEBUG */ + +/* ---------------------------------------------------------------- + * DO NOT DEFINE THESE EVER OR YOU WILL BURN! + * ---------------------------------------------------------------- + */ +/* ---------------- + * DOESNOTWORK is currently placed around memory manager + * code that is known to cause problems. Code in between + * is likely not converted and probably won't work anyways. + * ---------------- + */ +#undef DOESNOTWORK + +/* ---------------- + * PERHAPSNEVER is placed around the "scan attribute" + * support code for the rule manager because for now we + * do things inefficiently. The correct solution to our + * problem is to add code to the parser/planner to save + * attribute information for the rule manager rather than + * have the executor have to grope through the entire plan + * for it so if we ever decide to make things better, + * we should probably delete the stuff in between PERHAPSNEVER.. + * ---------------- + */ +#undef PERHAPSNEVER + +/* ---------------- + * NOTYET is placed around any code not yet implemented + * in the executor. Only remove these when actually implementing + * said code. + * ---------------- + */ +#undef NOTYET + +extern long NDirectFileRead; +extern long NDirectFileWrite; + +#endif /* ExecDebugIncluded */ diff --git a/src/backend/executor/execdefs.h b/src/backend/executor/execdefs.h new file mode 100644 index 00000000000..5aec485c95c --- /dev/null +++ b/src/backend/executor/execdefs.h @@ -0,0 +1,54 @@ +/*------------------------------------------------------------------------- + * + * execdefs.h-- + * + * + * + * Copyright (c) 1994, Regents of the University of California + * + * $Id: execdefs.h,v 1.1.1.1 1996/07/09 06:21:25 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +#ifndef EXECDEFS_H +#define EXECDEFS_H + +/* ---------------- + * executor scan direction definitions + * ---------------- + */ +#define EXEC_FRWD 1 /* Scan forward */ +#define EXEC_BKWD -1 /* Scan backward */ + +/* ---------------- + * ExecutePlan() tuplecount definitions + * ---------------- + */ +#define ALL_TUPLES 0 /* return all tuples */ +#define ONE_TUPLE 1 /* return only one tuple */ + +/* ---------------- + * constants used by ExecMain + * ---------------- + */ +#define EXEC_RUN 3 +#define EXEC_FOR 4 +#define EXEC_BACK 5 +#define EXEC_RETONE 6 +#define EXEC_RESULT 7 + +/* ---------------- + * Merge Join states + * ---------------- + */ +#define EXEC_MJ_INITIALIZE 1 +#define EXEC_MJ_JOINMARK 2 +#define EXEC_MJ_JOINTEST 3 +#define EXEC_MJ_JOINTUPLES 4 +#define EXEC_MJ_NEXTOUTER 5 +#define EXEC_MJ_TESTOUTER 6 +#define EXEC_MJ_NEXTINNER 7 +#define EXEC_MJ_SKIPINNER 8 +#define EXEC_MJ_SKIPOUTER 9 + +#endif /* EXECDEFS_H */ diff --git a/src/backend/executor/execdesc.h b/src/backend/executor/execdesc.h new file mode 100644 index 00000000000..54752625f55 --- /dev/null +++ b/src/backend/executor/execdesc.h @@ -0,0 +1,38 @@ +/*------------------------------------------------------------------------- + * + * execdesc.h-- + * plan and query descriptor accessor macros used by the executor + * and related modules. + * + * + * Copyright (c) 1994, Regents of the University of California + * + * $Id: execdesc.h,v 1.1.1.1 1996/07/09 06:21:25 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +#ifndef EXECDESC_H +#define EXECDESC_H + +#include "nodes/parsenodes.h" +#include "nodes/plannodes.h" +#include "tcop/dest.h" + +/* ---------------- + * query descriptor: + * a QueryDesc encapsulates everything that the executor + * needs to execute the query + * --------------------- + */ +typedef struct QueryDesc { + CmdType operation; /* CMD_SELECT, CMD_UPDATE, etc. */ + Query *parsetree; + Plan *plantree; + CommandDest dest; /* the destination output of the execution */ +} QueryDesc; + +/* in pquery.c */ +extern QueryDesc *CreateQueryDesc(Query *parsetree, Plan *plantree, + CommandDest dest); + +#endif /* EXECDESC_H */ diff --git a/src/backend/executor/executor.h b/src/backend/executor/executor.h new file mode 100644 index 00000000000..65caf098f13 --- /dev/null +++ b/src/backend/executor/executor.h @@ -0,0 +1,229 @@ +/*------------------------------------------------------------------------- + * + * executor.h-- + * support for the POSTGRES executor module + * + * + * Copyright (c) 1994, Regents of the University of California + * + * $Id: executor.h,v 1.1.1.1 1996/07/09 06:21:25 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +#ifndef EXECUTOR_H +#define EXECUTOR_H + +/* ---------------------------------------------------------------- + * #includes + * ---------------------------------------------------------------- + */ +#include <stdio.h> +#include <string.h> + +#include "postgres.h" +#include "nodes/pg_list.h" + +/* ---------------- + * executor debugging definitions are kept in a separate file + * so people can customize what debugging they want to see and not + * have this information clobbered every time a new version of + * executor.h is checked in -cim 10/26/89 + * ---------------- + */ +#include "executor/execdebug.h" + +#include "access/heapam.h" +#include "access/htup.h" +#include "access/istrat.h" +#include "access/itup.h" +#include "access/skey.h" +#include "utils/tqual.h" +#include "catalog/catname.h" +#include "utils/syscache.h" +#include "executor/execdefs.h" +#include "executor/tuptable.h" + +#include "nodes/parsenodes.h" + +#include "storage/buf.h" +#include "miscadmin.h" +#include "fmgr.h" +#include "utils/elog.h" +#include "utils/mcxt.h" +#include "utils/memutils.h" +#include "utils/rel.h" + +#include "catalog/pg_index.h" +#include "catalog/pg_proc.h" +#include "catalog/pg_type.h" +#include "catalog/pg_aggregate.h" + +#include "access/printtup.h" +#include "nodes/primnodes.h" +#include "nodes/plannodes.h" +#include "nodes/execnodes.h" + +#include "tcop/dest.h" +#include "storage/smgr.h" + +#include "access/genam.h" +#include "executor/execdesc.h" + +/* + * prototypes from functions in execAmi.c + */ +extern void ExecOpenScanR(Oid relOid, int nkeys, ScanKey skeys, bool isindex, + ScanDirection dir, TimeQual timeRange, + Relation *returnRelation, Pointer *returnScanDesc); +extern Relation ExecOpenR(Oid relationOid, bool isindex); +extern Pointer ExecBeginScan(Relation relation, int nkeys, ScanKey skeys, + bool isindex, ScanDirection dir, TimeQual time_range); +extern void ExecCloseR(Plan *node); +extern void ExecReScan(Plan *node, ExprContext *exprCtxt, Plan *parent); +extern HeapScanDesc ExecReScanR(Relation relDesc, HeapScanDesc scanDesc, + ScanDirection direction, int nkeys, ScanKey skeys); +extern void ExecMarkPos(Plan *node); +extern void ExecRestrPos(Plan *node); +extern Relation ExecCreatR(TupleDesc tupType, Oid relationOid); + +/* + * prototypes from functions in execJunk.c + */ +extern JunkFilter *ExecInitJunkFilter(List *targetList); +extern bool ExecGetJunkAttribute(JunkFilter *junkfilter, TupleTableSlot *slot, + char *attrName, Datum *value, bool *isNull); +extern HeapTuple ExecRemoveJunk(JunkFilter *junkfilter, TupleTableSlot *slot); + + +/* + * prototypes from functions in execMain.c + */ +extern TupleDesc ExecutorStart(QueryDesc *queryDesc, EState *estate); +extern TupleTableSlot* ExecutorRun(QueryDesc *queryDesc, EState *estate, int feature, int count); +extern void ExecutorEnd(QueryDesc *queryDesc, EState *estate); + +/* + * prototypes from functions in execProcnode.c + */ +extern bool ExecInitNode(Plan *node, EState *estate, Plan *parent); +extern TupleTableSlot *ExecProcNode(Plan *node, Plan *parent); +extern int ExecCountSlotsNode(Plan *node); +extern void ExecEndNode(Plan *node, Plan *parent); + +/* + * prototypes from functions in execQual.c + */ +extern bool execConstByVal; +extern int execConstLen; + +extern Datum ExecExtractResult(TupleTableSlot *slot, AttrNumber attnum, + bool *isNull); +extern Datum ExecEvalVar(Var *variable, ExprContext *econtext, bool *isNull); +extern Datum ExecEvalParam(Param *expression, ExprContext *econtext, + bool *isNull); +extern char *GetAttributeByNum(TupleTableSlot *slot, AttrNumber attrno, + bool *isNull); +extern char *att_by_num(TupleTableSlot *slot, AttrNumber attrno, + bool *isNull); +/* stop here */ +extern char *GetAttributeByName(TupleTableSlot *slot, char *attname, + bool *isNull); +extern char *att_by_name(TupleTableSlot *slot, char *attname, bool *isNull); +extern void ExecEvalFuncArgs(FunctionCachePtr fcache, ExprContext *econtext, + List *argList, Datum argV[], bool *argIsDone); +extern Datum ExecMakeFunctionResult(Node *node, List *arguments, + ExprContext *econtext, bool *isNull, bool *isDone); +extern Datum ExecEvalOper(Expr *opClause, ExprContext *econtext, + bool *isNull); +extern Datum ExecEvalFunc(Expr *funcClause, ExprContext *econtext, + bool *isNull, bool *isDone); +extern Datum ExecEvalNot(Expr *notclause, ExprContext *econtext, bool *isNull); +extern Datum ExecEvalOr(Expr *orExpr, ExprContext *econtext, bool *isNull); +extern Datum ExecEvalAnd(Expr *andExpr, ExprContext *econtext, bool *isNull); +extern Datum ExecEvalExpr(Node *expression, ExprContext *econtext, bool *isNull, + bool *isDone); +extern bool ExecQualClause(Node *clause, ExprContext *econtext); +extern bool ExecQual(List *qual, ExprContext *econtext); +extern int ExecTargetListLength(List *targetlist); +extern TupleTableSlot *ExecProject(ProjectionInfo *projInfo, bool *isDone); + +/* + * prototypes from functions in execScan.c + */ +extern TupleTableSlot *ExecScan(Scan *node, TupleTableSlot* (*accessMtd)()); + +/* + * prototypes from functions in execTuples.c + */ +extern TupleTable ExecCreateTupleTable(int initialSize); +extern void ExecDestroyTupleTable(TupleTable table, bool shouldFree); +extern TupleTableSlot* ExecAllocTableSlot(TupleTable table); +extern TupleTableSlot* ExecStoreTuple(HeapTuple tuple, + TupleTableSlot *slot, + Buffer buffer, + bool shouldFree); +extern TupleTableSlot* ExecClearTuple(TupleTableSlot* slot); +extern bool ExecSlotPolicy(TupleTableSlot *slot); +extern bool ExecSetSlotPolicy(TupleTableSlot *slot, bool shouldFree); +extern TupleDesc ExecSetSlotDescriptor(TupleTableSlot *slot, + TupleDesc tupdesc); +extern void ExecSetSlotDescriptorIsNew(TupleTableSlot *slot, bool isNew); +extern TupleDesc ExecSetNewSlotDescriptor(TupleTableSlot *slot, + TupleDesc tupdesc); +extern Buffer ExecSetSlotBuffer(TupleTableSlot *slot, Buffer b); +extern void ExecIncrSlotBufferRefcnt(TupleTableSlot *slot); +extern bool TupIsNull(TupleTableSlot* slot); +extern bool ExecSlotDescriptorIsNew(TupleTableSlot *slot); +extern void ExecInitResultTupleSlot(EState *estate, CommonState *commonstate); +extern void ExecInitScanTupleSlot(EState *estate, + CommonScanState *commonscanstate); +extern void ExecInitMarkedTupleSlot(EState *estate, MergeJoinState *mergestate); +extern void ExecInitOuterTupleSlot(EState *estate, HashJoinState *hashstate); +extern void ExecInitHashTupleSlot(EState *estate, HashJoinState *hashstate); +extern TupleTableSlot *NodeGetResultTupleSlot(Plan *node); + +extern TupleDesc ExecGetTupType(Plan *node); +extern TupleDesc ExecTypeFromTL(List *targetList); + +/* + * prototypes from functions in execTuples.c + */ +extern void ResetTupleCount(); +extern void DisplayTupleCount(FILE *statfp); +extern void ExecAssignNodeBaseInfo(EState *estate, CommonState *basenode, + Plan *parent); +extern void ExecAssignExprContext(EState *estate, CommonState *commonstate); +extern void ExecAssignResultType(CommonState *commonstate, + TupleDesc tupDesc); +extern void ExecAssignResultTypeFromOuterPlan(Plan *node, + CommonState *commonstate); +extern void ExecAssignResultTypeFromTL(Plan *node, CommonState *commonstate); +extern TupleDesc ExecGetResultType(CommonState *commonstate); +extern void ExecFreeResultType(CommonState *commonstate); +extern void ExecAssignProjectionInfo(Plan *node, CommonState *commonstate); +extern void ExecFreeProjectionInfo(CommonState *commonstate); +extern TupleDesc ExecGetScanType(CommonScanState *csstate); +extern void ExecFreeScanType(CommonScanState *csstate); +extern void ExecAssignScanType(CommonScanState *csstate, + TupleDesc tupDesc); +extern void ExecAssignScanTypeFromOuterPlan(Plan *node, + CommonScanState *csstate); +extern AttributeTupleForm ExecGetTypeInfo(Relation relDesc); + +extern void ExecGetIndexKeyInfo(IndexTupleForm indexTuple, int *numAttsOutP, + AttrNumber **attsOutP, FuncIndexInfoPtr fInfoP); +extern void ExecOpenIndices(Oid resultRelationOid, + RelationInfo *resultRelationInfo); +extern void ExecCloseIndices(RelationInfo *resultRelationInfo); +extern IndexTuple ExecFormIndexTuple(HeapTuple heapTuple, + Relation heapRelation, Relation indexRelation, IndexInfo *indexInfo); +extern void ExecInsertIndexTuples(TupleTableSlot *slot, ItemPointer tupleid, + EState *estate); + + +/* ---------------------------------------------------------------- + * the end + * ---------------------------------------------------------------- + */ + +#endif /* EXECUTOR_H */ diff --git a/src/backend/executor/functions.c b/src/backend/executor/functions.c new file mode 100644 index 00000000000..2f6e29d8277 --- /dev/null +++ b/src/backend/executor/functions.c @@ -0,0 +1,388 @@ +/*------------------------------------------------------------------------- + * + * functions.c-- + * Routines to handle functions called from the executor + * Putting this stuff in fmgr makes the postmaster a mess.... + * + * Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * $Header: /cvsroot/pgsql/src/backend/executor/functions.c,v 1.1.1.1 1996/07/09 06:21:25 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "nodes/primnodes.h" +#include "nodes/relation.h" +#include "nodes/execnodes.h" +#include "nodes/plannodes.h" + +#include "catalog/pg_proc.h" +#include "parser/parse_query.h" +#include "tcop/pquery.h" +#include "tcop/tcopprot.h" +#include "nodes/params.h" +#include "fmgr.h" +#include "utils/fcache.h" +#include "utils/datum.h" +#include "utils/elog.h" +#include "utils/palloc.h" +#include "utils/syscache.h" +#include "catalog/pg_language.h" +#include "access/heapam.h" +#include "access/xact.h" +#include "executor/executor.h" +#include "executor/functions.h" + +#undef new + +typedef enum {F_EXEC_START, F_EXEC_RUN, F_EXEC_DONE} ExecStatus; + +typedef struct local_es { + QueryDesc *qd; + EState *estate; + struct local_es *next; + ExecStatus status; +} execution_state; + +#define LAST_POSTQUEL_COMMAND(es) ((es)->next == (execution_state *)NULL) + +/* non-export function prototypes */ +static TupleDesc postquel_start(execution_state *es); +static execution_state *init_execution_state(FunctionCachePtr fcache, + char *args[]); +static TupleTableSlot *postquel_getnext(execution_state *es); +static void postquel_end(execution_state *es); +static void postquel_sub_params(execution_state *es, int nargs, + char *args[], bool *nullV); +static Datum postquel_execute(execution_state *es, FunctionCachePtr fcache, + List *fTlist, char **args, bool *isNull); + + +Datum +ProjectAttribute(TupleDesc TD, + TargetEntry *tlist, + HeapTuple tup, + bool *isnullP) +{ + Datum val,valueP; + Var *attrVar = (Var *)tlist->expr; + AttrNumber attrno = attrVar->varattno; + + + val = PointerGetDatum(heap_getattr(tup, + InvalidBuffer, + attrno, + TD, + isnullP)); + if (*isnullP) + return (Datum) NULL; + + valueP = datumCopy(val, + TD->attrs[attrno-1]->atttypid, + TD->attrs[attrno-1]->attbyval, + (Size) TD->attrs[attrno-1]->attlen); + return valueP; +} + +static execution_state * +init_execution_state(FunctionCachePtr fcache, + char *args[]) +{ + execution_state *newes; + execution_state *nextes; + execution_state *preves; + QueryTreeList *queryTree_list; + int i; + List *planTree_list; + int nargs; + + nargs = fcache->nargs; + + newes = (execution_state *) palloc(sizeof(execution_state)); + nextes = newes; + preves = (execution_state *)NULL; + + + planTree_list = (List *) + pg_plan(fcache->src, fcache->argOidVect, nargs, &queryTree_list, None); + + for (i=0; i < queryTree_list->len; i++) { + EState *estate; + Query *queryTree = (Query*) (queryTree_list->qtrees[i]); + Plan *planTree = lfirst(planTree_list); + + if (!nextes) + nextes = (execution_state *) palloc(sizeof(execution_state)); + if (preves) + preves->next = nextes; + + nextes->next = NULL; + nextes->status = F_EXEC_START; + nextes->qd = CreateQueryDesc(queryTree, + planTree, + None); + estate = CreateExecutorState(); + + if (nargs > 0) { + int i; + ParamListInfo paramLI; + + paramLI = + (ParamListInfo)palloc((nargs+1)*sizeof(ParamListInfoData)); + + memset(paramLI, 0, nargs*sizeof(ParamListInfoData)); + + estate->es_param_list_info = paramLI; + + for (i=0; i<nargs; paramLI++, i++) { + paramLI->kind = PARAM_NUM; + paramLI->id = i+1; + paramLI->isnull = false; + paramLI->value = (Datum) NULL; + } + paramLI->kind = PARAM_INVALID; + } + else + estate->es_param_list_info = (ParamListInfo)NULL; + nextes->estate = estate; + preves = nextes; + nextes = (execution_state *)NULL; + + planTree_list = lnext(planTree_list); + } + + return newes; +} + +static TupleDesc +postquel_start(execution_state *es) +{ + return ExecutorStart(es->qd, es->estate); +} + +static TupleTableSlot * +postquel_getnext(execution_state *es) +{ + int feature; + + feature = (LAST_POSTQUEL_COMMAND(es)) ? EXEC_RETONE : EXEC_RUN; + + return ExecutorRun(es->qd, es->estate, feature, 0); +} + +static void +postquel_end(execution_state *es) +{ + ExecutorEnd(es->qd, es->estate); +} + +static void +postquel_sub_params(execution_state *es, + int nargs, + char *args[], + bool *nullV) +{ + ParamListInfo paramLI; + EState *estate; + + estate = es->estate; + paramLI = estate->es_param_list_info; + + while (paramLI->kind != PARAM_INVALID) { + if (paramLI->kind == PARAM_NUM) { + Assert(paramLI->id <= nargs); + paramLI->value = (Datum)args[(paramLI->id - 1)]; + paramLI->isnull = nullV[(paramLI->id - 1)]; + } + paramLI++; + } +} + +static TupleTableSlot * +copy_function_result(FunctionCachePtr fcache, + TupleTableSlot *resultSlot) +{ + TupleTableSlot *funcSlot; + TupleDesc resultTd; + HeapTuple newTuple; + HeapTuple oldTuple; + + Assert(! TupIsNull(resultSlot)); + oldTuple = resultSlot->val; + + funcSlot = (TupleTableSlot*)fcache->funcSlot; + + if (funcSlot == (TupleTableSlot*)NULL) + return resultSlot; + + resultTd = resultSlot->ttc_tupleDescriptor; + /* + * When the funcSlot is NULL we have to initialize the funcSlot's + * tuple descriptor. + */ + if (TupIsNull(funcSlot)) { + int i= 0; + TupleDesc funcTd = funcSlot->ttc_tupleDescriptor; + + while (i < oldTuple->t_natts) { + funcTd->attrs[i] = + (AttributeTupleForm)palloc(ATTRIBUTE_TUPLE_SIZE); + memmove(funcTd->attrs[i], + resultTd->attrs[i], + ATTRIBUTE_TUPLE_SIZE); + i++; + } + } + + newTuple = heap_copytuple(oldTuple); + + return ExecStoreTuple(newTuple,funcSlot,InvalidBuffer,true); +} + +static Datum +postquel_execute(execution_state *es, + FunctionCachePtr fcache, + List *fTlist, + char **args, + bool *isNull) +{ + TupleTableSlot *slot; + Datum value; + + if (es->status == F_EXEC_START) + { + (void) postquel_start(es); + es->status = F_EXEC_RUN; + } + + if (fcache->nargs > 0) + postquel_sub_params(es, fcache->nargs, args, fcache->nullVect); + + slot = postquel_getnext(es); + + if (TupIsNull(slot)) { + postquel_end(es); + es->status = F_EXEC_DONE; + *isNull = true; + /* + * If this isn't the last command for the function + * we have to increment the command + * counter so that subsequent commands can see changes made + * by previous ones. + */ + if (!LAST_POSTQUEL_COMMAND(es)) CommandCounterIncrement(); + return (Datum)NULL; + } + + if (LAST_POSTQUEL_COMMAND(es)) { + TupleTableSlot *resSlot; + + /* + * Copy the result. copy_function_result is smart enough + * to do nothing when no action is called for. This helps + * reduce the logic and code redundancy here. + */ + resSlot = copy_function_result(fcache, slot); + if (fTlist != NIL) { + HeapTuple tup; + TargetEntry *tle = lfirst(fTlist); + + tup = resSlot->val; + value = ProjectAttribute(resSlot->ttc_tupleDescriptor, + tle, + tup, + isNull); + }else { + value = (Datum)resSlot; + *isNull = false; + } + + /* + * If this is a single valued function we have to end the + * function execution now. + */ + if (fcache->oneResult) { + postquel_end(es); + es->status = F_EXEC_DONE; + } + + return value; + } + /* + * If this isn't the last command for the function, we don't + * return any results, but we have to increment the command + * counter so that subsequent commands can see changes made + * by previous ones. + */ + CommandCounterIncrement(); + return (Datum)NULL; +} + +Datum +postquel_function(Func *funcNode, char **args, bool *isNull, bool *isDone) +{ + execution_state *es; + Datum result; + FunctionCachePtr fcache = funcNode->func_fcache; + + es = (execution_state *) fcache->func_state; + if (es == NULL) + { + es = init_execution_state(fcache, args); + fcache->func_state = (char *) es; + } + + while (es && es->status == F_EXEC_DONE) + es = es->next; + + Assert(es); + /* + * Execute each command in the function one after another until we're + * executing the final command and get a result or we run out of + * commands. + */ + while (es != (execution_state *)NULL) + { + result = postquel_execute(es, + fcache, + funcNode->func_tlist, + args, + isNull); + if (es->status != F_EXEC_DONE) + break; + es = es->next; + } + + /* + * If we've gone through every command in this function, we are done. + */ + if (es == (execution_state *)NULL) + { + /* + * Reset the execution states to start over again + */ + es = (execution_state *)fcache->func_state; + while (es) + { + es->status = F_EXEC_START; + es = es->next; + } + /* + * Let caller know we're finished. + */ + *isDone = true; + return (fcache->oneResult) ? result : (Datum)NULL; + } + /* + * If we got a result from a command within the function it has + * to be the final command. All others shouldn't be returing + * anything. + */ + Assert ( LAST_POSTQUEL_COMMAND(es) ); + *isDone = false; + + return result; +} diff --git a/src/backend/executor/functions.h b/src/backend/executor/functions.h new file mode 100644 index 00000000000..1a1a88b36a1 --- /dev/null +++ b/src/backend/executor/functions.h @@ -0,0 +1,22 @@ +/*------------------------------------------------------------------------- + * + * functions.h-- + * + * + * + * Copyright (c) 1994, Regents of the University of California + * + * $Id: functions.h,v 1.1.1.1 1996/07/09 06:21:25 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +#ifndef FUNCTIONS_H +#define FUNCTIONS_H + +extern Datum ProjectAttribute(TupleDesc TD, TargetEntry *tlist, + HeapTuple tup, bool *isnullP); + +extern Datum postquel_function(Func *funcNode, char **args, + bool *isNull, bool *isDone); + +#endif /* FUNCTIONS_H */ diff --git a/src/backend/executor/hashjoin.h b/src/backend/executor/hashjoin.h new file mode 100644 index 00000000000..e7ae086fe16 --- /dev/null +++ b/src/backend/executor/hashjoin.h @@ -0,0 +1,82 @@ +/*------------------------------------------------------------------------- + * + * hashjoin.h-- + * internal structures for hash table and buckets + * + * + * Copyright (c) 1994, Regents of the University of California + * + * $Id: hashjoin.h,v 1.1.1.1 1996/07/09 06:21:25 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +#ifndef HASHJOIN_H +#define HASHJOIN_H + +#include "access/htup.h" +#include "storage/ipc.h" + +/* ----------------- + * have to use relative address as pointers in the hashtable + * because the hashtable may reallocate in difference processes + * ----------------- + */ +typedef int RelativeAddr; + +/* ------------------ + * the relative addresses are always relative to the head of the + * hashtable, the following macro converts them to absolute address. + * ------------------ + */ +#define ABSADDR(X) ((X) < 0 ? NULL: (char*)hashtable + X) +#define RELADDR(X) (RelativeAddr)((char*)(X) - (char*)hashtable) + +typedef char **charPP; +typedef int *intP; + +/* ---------------------------------------------------------------- + * hash-join hash table structures + * ---------------------------------------------------------------- + */ +typedef struct HashTableData { + int nbuckets; + int totalbuckets; + int bucketsize; + IpcMemoryId shmid; + RelativeAddr top; /* char* */ + RelativeAddr bottom; /* char* */ + RelativeAddr overflownext; /* char* */ + RelativeAddr batch; /* char* */ + RelativeAddr readbuf; /* char* */ + int nbatch; + RelativeAddr outerbatchNames; /* RelativeAddr* */ + RelativeAddr outerbatchPos; /* RelativeAddr* */ + RelativeAddr innerbatchNames; /* RelativeAddr* */ + RelativeAddr innerbatchPos; /* RelativeAddr* */ + RelativeAddr innerbatchSizes; /* int* */ + int curbatch; + int nprocess; + int pcount; +} HashTableData; /* real hash table follows here */ + +typedef HashTableData *HashJoinTable; + +typedef struct OverflowTupleData { + RelativeAddr tuple; /* HeapTuple */ + RelativeAddr next; /* struct OverflowTupleData * */ +} OverflowTupleData; /* real tuple follows here */ + +typedef OverflowTupleData *OverflowTuple; + +typedef struct HashBucketData { + RelativeAddr top; /* HeapTuple */ + RelativeAddr bottom; /* HeapTuple */ + RelativeAddr firstotuple; /* OverflowTuple */ + RelativeAddr lastotuple; /* OverflowTuple */ +} HashBucketData; /* real bucket follows here */ + +typedef HashBucketData *HashBucket; + +#define HASH_PERMISSION 0700 + +#endif /* HASHJOIN_H */ diff --git a/src/backend/executor/nodeAgg.c b/src/backend/executor/nodeAgg.c new file mode 100644 index 00000000000..ee187367c74 --- /dev/null +++ b/src/backend/executor/nodeAgg.c @@ -0,0 +1,558 @@ +/*------------------------------------------------------------------------- + * + * nodeAgg.c-- + * Routines to handle aggregate nodes. + * + * Copyright (c) 1994, Regents of the University of California + * + * + * NOTE + * The implementation of Agg node has been reworked to handle legal + * SQL aggregates. (Do not expect POSTQUEL semantics.) -- ay 2/95 + * + * IDENTIFICATION + * /usr/local/devel/pglite/cvs/src/backend/executor/nodeAgg.c,v 1.13 1995/08/01 20:19:07 jolly Exp + * + *------------------------------------------------------------------------- + */ +#include "access/heapam.h" +#include "catalog/pg_aggregate.h" +#include "catalog/catalog.h" +#include "executor/executor.h" +#include "executor/nodeAgg.h" +#include "storage/bufmgr.h" +#include "utils/palloc.h" +#include "parser/catalog_utils.h" + +/* + * AggFuncInfo - + * keeps the transition functions information around + */ +typedef struct AggFuncInfo { + Oid xfn1_oid; + Oid xfn2_oid; + Oid finalfn_oid; + func_ptr xfn1; + func_ptr xfn2; + func_ptr finalfn; + int xfn1_nargs; + int xfn2_nargs; + int finalfn_nargs; +} AggFuncInfo; + +static Datum aggGetAttr(TupleTableSlot *tuple, Aggreg *agg, bool *isNull); + + +/* --------------------------------------- + * + * ExecAgg - + * + * ExecAgg receives tuples from its outer subplan and aggregates over + * the appropriate attribute for each (unique) aggregate in the target + * list. (The number of tuples to aggregate over depends on whether a + * GROUP BY clause is present. It might be the number of tuples in a + * group or all the tuples that satisfy the qualifications.) The value of + * each aggregate is stored in the expression context for ExecProject to + * evaluate the result tuple. + * + * ExecAgg evaluates each aggregate in the following steps: (initcond1, + * initcond2 are the initial values and sfunc1, sfunc2, and finalfunc are + * the transition functions.) + * + * value1[i] = initcond1 + * value2[i] = initcond2 + * forall tuples do + * value1[i] = sfunc1(aggregate_attribute, value1[i]) + * value2[i] = sfunc2(value2[i]) + * value1[i] = finalfunc(value1[i], value2[i]) + * + * If the outer subplan is a Group node, ExecAgg returns as many tuples + * as there are groups. + * + * XXX handling of NULL doesn't work + * + * OLD COMMENTS + * + * XXX Aggregates should probably have another option: what to do + * with transfn2 if we hit a null value. "count" (transfn1 = null, + * transfn2 = increment) will want to have transfn2 called; "avg" + * (transfn1 = add, transfn2 = increment) will not. -pma 1/3/93 + * + * ------------------------------------------ + */ +TupleTableSlot * +ExecAgg(Agg *node) +{ + AggState *aggstate; + EState *estate; + Aggreg **aggregates; + Plan *outerPlan; + int i, nagg; + Datum *value1, *value2; + int *noInitValue; + AggFuncInfo *aggFuncInfo; + long nTuplesAgged = 0; + ExprContext *econtext; + ProjectionInfo *projInfo; + TupleTableSlot *resultSlot; + HeapTuple oneTuple; + char* nulls; + bool isDone; + bool isNull = FALSE, isNull1 = FALSE, isNull2 = FALSE; + + /* --------------------- + * get state info from node + * --------------------- + */ + aggstate = node->aggstate; + if (aggstate->agg_done) + return NULL; + + estate = node->plan.state; + econtext = aggstate->csstate.cstate.cs_ExprContext; + aggregates = node->aggs; + nagg = node->numAgg; + + value1 = node->aggstate->csstate.cstate.cs_ExprContext->ecxt_values; + nulls = node->aggstate->csstate.cstate.cs_ExprContext->ecxt_nulls; + + value2 = (Datum *)palloc(sizeof(Datum) * nagg); + memset(value2, 0, sizeof(Datum) * nagg); + + aggFuncInfo = (AggFuncInfo *)palloc(sizeof(AggFuncInfo) * nagg); + memset(aggFuncInfo, 0, sizeof(AggFuncInfo) * nagg); + + noInitValue = (int *)palloc(sizeof(int) * nagg); + memset(noInitValue, 0, sizeof(noInitValue) * nagg); + + outerPlan = outerPlan(node); + oneTuple = NULL; + + projInfo = aggstate->csstate.cstate.cs_ProjInfo; + + for(i = 0; i < nagg; i++) { + Aggreg *agg; + char *aggname; + HeapTuple aggTuple; + Form_pg_aggregate aggp; + Oid xfn1_oid, xfn2_oid, finalfn_oid; + func_ptr xfn1_ptr, xfn2_ptr, finalfn_ptr; + int xfn1_nargs, xfn2_nargs, finalfn_nargs; + + agg = aggregates[i]; + + /* --------------------- + * find transfer functions of all the aggregates and initialize + * their initial values + * --------------------- + */ + aggname = agg->aggname; + aggTuple = SearchSysCacheTuple(AGGNAME, + PointerGetDatum(aggname), + ObjectIdGetDatum(agg->basetype), + 0,0); + if (!HeapTupleIsValid(aggTuple)) + elog(WARN, "ExecAgg: cache lookup failed for aggregate \"%s\"(%s)", + aggname, + tname(get_id_type(agg->basetype))); + aggp = (Form_pg_aggregate) GETSTRUCT(aggTuple); + + xfn1_oid = aggp->aggtransfn1; + xfn2_oid = aggp->aggtransfn2; + finalfn_oid = aggp->aggfinalfn; + + if (OidIsValid(finalfn_oid)) { + fmgr_info(finalfn_oid, &finalfn_ptr, &finalfn_nargs); + aggFuncInfo[i].finalfn_oid = finalfn_oid; + aggFuncInfo[i].finalfn = finalfn_ptr; + aggFuncInfo[i].finalfn_nargs = finalfn_nargs; + } + + if (OidIsValid(xfn2_oid)) { + fmgr_info(xfn2_oid, &xfn2_ptr, &xfn2_nargs); + aggFuncInfo[i].xfn2_oid = xfn2_oid; + aggFuncInfo[i].xfn2 = xfn2_ptr; + aggFuncInfo[i].xfn2_nargs = xfn2_nargs; + value2[i] = (Datum)AggNameGetInitVal((char*)aggname, + aggp->aggbasetype, + 2, + &isNull2); + /* ------------------------------------------ + * If there is a second transition function, its initial + * value must exist -- as it does not depend on data values, + * we have no other way of determining an initial value. + * ------------------------------------------ + */ + if (isNull2) + elog(WARN, "ExecAgg: agginitval2 is null"); + } + + if (OidIsValid(xfn1_oid)) { + fmgr_info(xfn1_oid, &xfn1_ptr, &xfn1_nargs); + aggFuncInfo[i].xfn1_oid = xfn1_oid; + aggFuncInfo[i].xfn1 = xfn1_ptr; + aggFuncInfo[i].xfn1_nargs = xfn1_nargs; + value1[i] = (Datum)AggNameGetInitVal((char*)aggname, + aggp->aggbasetype, + 1, + &isNull1); + + /* ------------------------------------------ + * If the initial value for the first transition function + * doesn't exist in the pg_aggregate table then we let + * the first value returned from the outer procNode become + * the initial value. (This is useful for aggregates like + * max{} and min{}.) + * ------------------------------------------ + */ + if (isNull1) { + noInitValue[i] = 1; + nulls[i] = 1; + } + } + } + + /* ---------------- + * for each tuple from the the outer plan, apply all the aggregates + * ---------------- + */ + for (;;) { + HeapTuple outerTuple = NULL; + TupleTableSlot *outerslot; + + isNull = isNull1 = isNull2 = 0; + outerslot = ExecProcNode(outerPlan, (Plan*)node); + if (outerslot) outerTuple = outerslot->val; + if (!HeapTupleIsValid(outerTuple)) { + /* when the outerplan doesn't return a single tuple, + create a dummy heaptuple anyway + because we still need to return a valid aggregate value. + The value returned will be the initial values of the + transition functions */ + if (nTuplesAgged == 0) { + TupleDesc tupType; + Datum *tupValue; + char* null_array; + + tupType = aggstate->csstate.css_ScanTupleSlot->ttc_tupleDescriptor; + tupValue = projInfo->pi_tupValue; + + /* initially, set all the values to NULL */ + null_array = malloc(nagg); + for (i=0;i<nagg;i++) + null_array[i] = 'n'; + oneTuple = heap_formtuple(tupType, tupValue, null_array); + free(null_array); + } + break; + } + + for(i = 0; i < nagg; i++) { + AttrNumber attnum; + int2 attlen; + Datum newVal; + AggFuncInfo *aggfns = &aggFuncInfo[i]; + Datum args[2]; + + newVal = aggGetAttr(outerslot, + aggregates[i], + &isNull); + + if (isNull) + continue; /* ignore this tuple for this agg */ + + if (aggfns->xfn1) { + if (noInitValue[i]) { + /* + * value1 and value2 has not been initialized. This + * is the first non-NULL value. We use it as the + * initial value. + */ + /* but we can't just use it straight, we have + to make a copy of it since the tuple from which + it came will be freed on the next iteration + of the scan */ + attnum = ((Var*)aggregates[i]->target)->varattno; + attlen = outerslot->ttc_tupleDescriptor->attrs[attnum-1]->attlen; + if (attlen == -1) { + /* variable length */ + attlen = VARSIZE((struct varlena*) newVal); + } + value1[i] = (Datum)palloc(attlen); + if (outerslot->ttc_tupleDescriptor->attrs[attnum-1]->attbyval) + value1[i] = newVal; + else + memmove((char*) (value1[i]), (char*) (newVal), attlen); + /* value1[i] = newVal; */ + noInitValue[i] = 0; + nulls[i] = 0; + } else { + /* + * apply the transition functions. + */ + args[0] = value1[i]; + args[1] = newVal; + value1[i] = + (Datum)fmgr_c(aggfns->xfn1, aggfns->xfn1_oid, + aggfns->xfn1_nargs, (FmgrValues *)args, + &isNull1); + Assert(!isNull1); + } + } + + if (aggfns->xfn2) { + Datum xfn2_val = value2[i]; + + value2[i] = + (Datum)fmgr_c(aggfns->xfn2, aggfns->xfn2_oid, + aggfns->xfn2_nargs, + (FmgrValues *)&xfn2_val, &isNull2); + Assert(!isNull2); + } + } + + /* + * keep this for the projection (we only need one of these - + * all the tuples we aggregate over share the same group column) + */ + if (!oneTuple) { + oneTuple = heap_copytuple(outerslot->val); + } + + nTuplesAgged++; + } + + /* -------------- + * finalize the aggregate (if necessary), and get the resultant value + * -------------- + */ + for(i = 0; i < nagg; i++) { + char *args[2]; + AggFuncInfo *aggfns = &aggFuncInfo[i]; + + if (aggfns->finalfn && nTuplesAgged > 0) { + if (aggfns->finalfn_nargs > 1) { + args[0] = (char*)value1[i]; + args[1] = (char*)value2[i]; + } else if (aggfns->xfn1) { + args[0] = (char*)value1[i]; + } else if (aggfns->xfn2) { + args[0] = (char*)value2[i]; + } else + elog(WARN, "ExecAgg: no valid transition functions??"); + value1[i] = + (Datum)fmgr_c(aggfns->finalfn, aggfns->finalfn_oid, + aggfns->finalfn_nargs, (FmgrValues *) args, + &(nulls[i])); + } else if (aggfns->xfn1) { + /* + * value in the right place, ignore. (If you remove this + * case, fix the else part. -ay 2/95) + */ + } else if (aggfns->xfn2) { + value1[i] = value2[i]; + } else + elog(WARN, "ExecAgg: no valid transition functions??"); + } + + /* + * whether the aggregation is done depends on whether we are doing + * aggregation over groups or the entire table + */ + if (nodeTag(outerPlan)==T_Group) { + /* aggregation over groups */ + aggstate->agg_done = ((Group*)outerPlan)->grpstate->grp_done; + } else { + aggstate->agg_done = TRUE; + } + + /* ---------------- + * form a projection tuple, store it in the result tuple + * slot and return it. + * ---------------- + */ + ExecStoreTuple(oneTuple, + aggstate->csstate.css_ScanTupleSlot, + InvalidBuffer, + false); + econtext->ecxt_scantuple = aggstate->csstate.css_ScanTupleSlot; + resultSlot = ExecProject(projInfo, &isDone); + + if (oneTuple) + pfree(oneTuple); + + return resultSlot; +} + +/* ----------------- + * ExecInitAgg + * + * Creates the run-time information for the agg node produced by the + * planner and initializes its outer subtree + * ----------------- + */ +bool +ExecInitAgg(Agg *node, EState *estate, Plan *parent) +{ + AggState *aggstate; + Plan *outerPlan; + ExprContext *econtext; + + /* + * assign the node's execution state + */ + node->plan.state = estate; + + /* + * create state structure + */ + aggstate = makeNode(AggState); + node->aggstate = aggstate; + aggstate->agg_done = FALSE; + + /* + * assign node's base id and create expression context + */ + ExecAssignNodeBaseInfo(estate, &aggstate->csstate.cstate, + (Plan*) parent); + ExecAssignExprContext(estate, &aggstate->csstate.cstate); + +#define AGG_NSLOTS 2 + /* + * tuple table initialization + */ + ExecInitScanTupleSlot(estate, &aggstate->csstate); + ExecInitResultTupleSlot(estate, &aggstate->csstate.cstate); + + econtext = aggstate->csstate.cstate.cs_ExprContext; + econtext->ecxt_values = + (Datum *)palloc(sizeof(Datum) * node->numAgg); + memset(econtext->ecxt_values, 0, sizeof(Datum) * node->numAgg); + econtext->ecxt_nulls = (char *)palloc(node->numAgg); + memset(econtext->ecxt_nulls, 0, node->numAgg); + + /* + * initializes child nodes + */ + outerPlan = outerPlan(node); + ExecInitNode(outerPlan, estate, (Plan *)node); + + /* ---------------- + * initialize tuple type. + * ---------------- + */ + ExecAssignScanTypeFromOuterPlan((Plan *) node, &aggstate->csstate); + + /* + * Initialize tuple type for both result and scan. + * This node does no projection + */ + ExecAssignResultTypeFromTL((Plan*) node, &aggstate->csstate.cstate); + ExecAssignProjectionInfo((Plan*)node, &aggstate->csstate.cstate); + + return TRUE; +} + +int +ExecCountSlotsAgg(Agg *node) +{ + return ExecCountSlotsNode(outerPlan(node)) + + ExecCountSlotsNode(innerPlan(node)) + + AGG_NSLOTS; +} + +/* ------------------------ + * ExecEndAgg(node) + * + * ----------------------- + */ +void +ExecEndAgg(Agg *node) +{ + AggState *aggstate; + Plan *outerPlan; + + aggstate = node->aggstate; + + ExecFreeProjectionInfo(&aggstate->csstate.cstate); + + outerPlan = outerPlan(node); + ExecEndNode(outerPlan, (Plan*)node); + + /* clean up tuple table */ + ExecClearTuple(aggstate->csstate.css_ScanTupleSlot); +} + + +/***************************************************************************** + * Support Routines + *****************************************************************************/ + +/* + * aggGetAttr - + * get the attribute (specified in the Var node in agg) to aggregate + * over from the tuple + */ +static Datum +aggGetAttr(TupleTableSlot *slot, + Aggreg *agg, + bool *isNull) +{ + Datum result; + AttrNumber attnum; + HeapTuple heapTuple; + TupleDesc tuple_type; + Buffer buffer; + + /* ---------------- + * extract tuple information from the slot + * ---------------- + */ + heapTuple = slot->val; + tuple_type = slot->ttc_tupleDescriptor; + buffer = slot->ttc_buffer; + + attnum = ((Var*)agg->target)->varattno; + + /* + * If the attribute number is invalid, then we are supposed to + * return the entire tuple, we give back a whole slot so that + * callers know what the tuple looks like. + */ + if (attnum == InvalidAttrNumber) { + TupleTableSlot *tempSlot; + TupleDesc td; + HeapTuple tup; + + tempSlot = makeNode(TupleTableSlot); + tempSlot->ttc_shouldFree = false; + tempSlot->ttc_descIsNew = true; + tempSlot->ttc_tupleDescriptor = (TupleDesc)NULL, + tempSlot->ttc_buffer = InvalidBuffer; + tempSlot->ttc_whichplan = -1; + + tup = heap_copytuple(slot->val); + td = CreateTupleDescCopy(slot->ttc_tupleDescriptor); + + ExecSetSlotDescriptor(tempSlot, td); + + ExecStoreTuple(tup, tempSlot, InvalidBuffer, true); + return (Datum) tempSlot; + } + + result = (Datum) + heap_getattr(heapTuple, /* tuple containing attribute */ + buffer, /* buffer associated with tuple */ + attnum, /* attribute number of desired attribute */ + tuple_type, /* tuple descriptor of tuple */ + isNull); /* return: is attribute null? */ + + /* ---------------- + * return null if att is null + * ---------------- + */ + if (*isNull) + return (Datum) NULL; + + return result; +} diff --git a/src/backend/executor/nodeAgg.h b/src/backend/executor/nodeAgg.h new file mode 100644 index 00000000000..51c2b2b2270 --- /dev/null +++ b/src/backend/executor/nodeAgg.h @@ -0,0 +1,21 @@ +/*------------------------------------------------------------------------- + * + * nodeAgg.h-- + * + * + * + * Copyright (c) 1994, Regents of the University of California + * + * $Id: nodeAgg.h,v 1.1.1.1 1996/07/09 06:21:26 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +#ifndef NODEAGG_H +#define NODEAGG_H + +extern TupleTableSlot *ExecAgg(Agg *node); +extern bool ExecInitAgg(Agg *node, EState *estate, Plan *parent); +extern int ExecCountSlotsAgg(Agg *node); +extern void ExecEndAgg(Agg *node); + +#endif /* NODEAGG_H */ diff --git a/src/backend/executor/nodeAppend.c b/src/backend/executor/nodeAppend.c new file mode 100644 index 00000000000..0a6cd5d01bb --- /dev/null +++ b/src/backend/executor/nodeAppend.c @@ -0,0 +1,483 @@ +/*------------------------------------------------------------------------- + * + * nodeAppend.c-- + * routines to handle append nodes. + * + * Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * $Header: /cvsroot/pgsql/src/backend/executor/nodeAppend.c,v 1.1.1.1 1996/07/09 06:21:26 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +/* INTERFACE ROUTINES + * ExecInitAppend - initialize the append node + * ExecProcAppend - retrieve the next tuple from the node + * ExecEndAppend - shut down the append node + * + * NOTES + * Each append node contains a list of one or more subplans which + * must be iteratively processed (forwards or backwards). + * Tuples are retrieved by executing the 'whichplan'th subplan + * until the subplan stops returning tuples, at which point that + * plan is shut down and the next started up. + * + * Append nodes don't make use of their left and right + * subtrees, rather they maintain a list of subplans so + * a typical append node looks like this in the plan tree: + * + * ... + * / + * Append -------+------+------+--- nil + * / \ | | | + * nil nil ... ... ... + * subplans + * + * Append nodes are currently used to support inheritance + * queries, where several relations need to be scanned. + * For example, in our standard person/student/employee/student-emp + * example, where student and employee inherit from person + * and student-emp inherits from student and employee, the + * query: + * + * retrieve (e.name) from e in person* + * + * generates the plan: + * + * | + * Append -------+-------+--------+--------+ + * / \ | | | | + * nil nil Scan Scan Scan Scan + * | | | | + * person employee student student-emp + */ + +#include "executor/executor.h" +#include "executor/nodeAppend.h" +#include "executor/nodeIndexscan.h" +#include "utils/palloc.h" +#include "parser/parsetree.h" /* for rt_store() macro */ + +/* ---------------------------------------------------------------- + * exec-append-initialize-next + * + * Sets up the append node state (i.e. the append state node) + * for the "next" scan. + * + * Returns t iff there is a "next" scan to process. + * ---------------------------------------------------------------- + */ +bool +exec_append_initialize_next(Append *node) +{ + EState *estate; + AppendState *unionstate; + TupleTableSlot *result_slot; + List *rangeTable; + + int whichplan; + int nplans; + List *rtentries; + ResTarget *rtentry; + + Index unionrelid; + + /* ---------------- + * get information from the append node + * ---------------- + */ + estate = node->plan.state; + unionstate = node->unionstate; + result_slot = unionstate->cstate.cs_ResultTupleSlot; + rangeTable = estate->es_range_table; + + whichplan = unionstate->as_whichplan; + nplans = unionstate->as_nplans; + rtentries = node->unionrtentries; + + if (whichplan < 0) { + /* ---------------- + * if scanning in reverse, we start at + * the last scan in the list and then + * proceed back to the first.. in any case + * we inform ExecProcAppend that we are + * at the end of the line by returning FALSE + * ---------------- + */ + unionstate->as_whichplan = 0; + return FALSE; + + } else if (whichplan >= nplans) { + /* ---------------- + * as above, end the scan if we go beyond + * the last scan in our list.. + * ---------------- + */ + unionstate->as_whichplan = nplans - 1; + return FALSE; + + } else { + /* ---------------- + * initialize the scan + * (and update the range table appropriately) + * (doesn't this leave the range table hosed for anybody upstream + * of the Append node??? - jolly ) + * ---------------- + */ + if (node->unionrelid > 0) { + rtentry = nth(whichplan, rtentries); + if (rtentry == NULL) + elog(DEBUG, "exec_append_initialize_next: rtentry is nil"); + + unionrelid = node->unionrelid; + + rt_store(unionrelid, rangeTable, rtentry); + + if (unionstate->as_junkFilter_list) { + estate->es_junkFilter = + (JunkFilter*)nth(whichplan, + unionstate->as_junkFilter_list); + } + if (unionstate->as_result_relation_info_list) { + estate->es_result_relation_info = + (RelationInfo*) nth(whichplan, + unionstate->as_result_relation_info_list); + } + result_slot->ttc_whichplan = whichplan; + } + + return TRUE; + } +} + +/* ---------------------------------------------------------------- + * ExecInitAppend + * + * Begins all of the subscans of the append node, storing the + * scan structures in the 'initialized' vector of the append-state + * structure. + * + * (This is potentially wasteful, since the entire result of the + * append node may not be scanned, but this way all of the + * structures get allocated in the executor's top level memory + * block instead of that of the call to ExecProcAppend.) + * + * Returns the scan result of the first scan. + * ---------------------------------------------------------------- + */ +bool +ExecInitAppend(Append *node, EState *estate, Plan *parent) +{ + AppendState *unionstate; + int nplans; + List *resultList; + List *rtentries; + List *unionplans; + bool *initialized; + int i; + Plan *initNode; + List *junkList; + RelationInfo *es_rri = estate->es_result_relation_info; + + /* ---------------- + * assign execution state to node and get information + * for append state + * ---------------- + */ + node->plan.state = estate; + + unionplans = node->unionplans; + nplans = length(unionplans); + rtentries = node->unionrtentries; + + CXT1_printf("ExecInitAppend: context is %d\n", CurrentMemoryContext); + initialized = (bool *)palloc(nplans * sizeof(bool)); + + /* ---------------- + * create new AppendState for our append node + * ---------------- + */ + unionstate = makeNode(AppendState); + unionstate->as_whichplan = 0; + unionstate->as_nplans = nplans; + unionstate->as_initialized = initialized; + unionstate->as_rtentries = rtentries; + + node->unionstate = unionstate; + + /* ---------------- + * Miscellanious initialization + * + * + assign node's base_id + * + assign debugging hooks + * + * Append plans don't have expression contexts because they + * never call ExecQual or ExecTargetList. + * ---------------- + */ + ExecAssignNodeBaseInfo(estate, &unionstate->cstate, parent); + +#define APPEND_NSLOTS 1 + /* ---------------- + * append nodes still have Result slots, which hold pointers + * to tuples, so we have to initialize them.. + * ---------------- + */ + ExecInitResultTupleSlot(estate, &unionstate->cstate); + + /* + * If the inherits rtentry is the result relation, we have to make + * a result relation info list for all inheritors so we can update + * their indices and put the result tuples in the right place etc. + * + * e.g. replace p (age = p.age + 1) from p in person* + */ + if ((es_rri != (RelationInfo*)NULL) && + (node->unionrelid == es_rri->ri_RangeTableIndex)) + { + RelationInfo *rri; + List *rtentryP; + + foreach(rtentryP,rtentries) + { + Oid reloid; + RangeTblEntry *rtentry = lfirst(rtentryP); + + reloid = rtentry->relid; + rri = makeNode(RelationInfo); + rri->ri_RangeTableIndex = es_rri->ri_RangeTableIndex; + rri->ri_RelationDesc = heap_open(reloid); + rri->ri_NumIndices = 0; + rri->ri_IndexRelationDescs = NULL; /* index descs */ + rri->ri_IndexRelationInfo = NULL; /* index key info */ + + resultList = lcons(rri,resultList); + ExecOpenIndices(reloid, rri); + } + unionstate->as_result_relation_info_list = resultList; + } + /* ---------------- + * call ExecInitNode on each of the plans in our list + * and save the results into the array "initialized" + * ---------------- + */ + junkList = NIL; + + for(i = 0; i < nplans ; i++ ) { + JunkFilter *j; + List *targetList; + /* ---------------- + * NOTE: we first modify range table in + * exec_append_initialize_next() and + * then initialize the subnode, + * since it may use the range table. + * ---------------- + */ + unionstate->as_whichplan = i; + exec_append_initialize_next(node); + + initNode = (Plan *) nth(i, unionplans); + initialized[i] = ExecInitNode(initNode, estate, (Plan*) node); + + /* --------------- + * Each targetlist in the subplan may need its own junk filter + * + * This is true only when the reln being replaced/deleted is + * the one that we're looking at the subclasses of + * --------------- + */ + if ((es_rri != (RelationInfo*)NULL) && + (node->unionrelid == es_rri->ri_RangeTableIndex)) { + + targetList = initNode->targetlist; + j = (JunkFilter *) ExecInitJunkFilter(targetList); + junkList = lappend(junkList, j); + } + + } + unionstate->as_junkFilter_list = junkList; + if (junkList != NIL) + estate->es_junkFilter = (JunkFilter *)lfirst(junkList); + + /* ---------------- + * initialize the return type from the appropriate subplan. + * ---------------- + */ + initNode = (Plan *) nth(0, unionplans); + ExecAssignResultType(&unionstate->cstate, +/* ExecGetExecTupDesc(initNode), */ + ExecGetTupType(initNode)); + unionstate->cstate.cs_ProjInfo = NULL; + + /* ---------------- + * return the result from the first subplan's initialization + * ---------------- + */ + unionstate->as_whichplan = 0; + exec_append_initialize_next(node); +#if 0 + result = (List *) initialized[0]; +#endif + return TRUE; +} + +int +ExecCountSlotsAppend(Append *node) +{ + List *plan; + List *unionplans = node->unionplans; + int nSlots = 0; + + foreach (plan,unionplans) { + nSlots += ExecCountSlotsNode((Plan *)lfirst(plan)); + } + return nSlots + APPEND_NSLOTS; +} + +/* ---------------------------------------------------------------- + * ExecProcAppend + * + * Handles the iteration over the multiple scans. + * + * NOTE: Can't call this ExecAppend, that name is used in execMain.l + * ---------------------------------------------------------------- + */ +TupleTableSlot * +ExecProcAppend(Append *node) +{ + EState *estate; + AppendState *unionstate; + + int whichplan; + List *unionplans; + Plan *subnode; + TupleTableSlot *result; + TupleTableSlot *result_slot; + ScanDirection direction; + + /* ---------------- + * get information from the node + * ---------------- + */ + unionstate = node->unionstate; + estate = node->plan.state; + direction = estate->es_direction; + + unionplans = node->unionplans; + whichplan = unionstate->as_whichplan; + result_slot = unionstate->cstate.cs_ResultTupleSlot; + + /* ---------------- + * figure out which subplan we are currently processing + * ---------------- + */ + subnode = (Plan *) nth(whichplan, unionplans); + + if (subnode == NULL) + elog(DEBUG, "ExecProcAppend: subnode is NULL"); + + /* ---------------- + * get a tuple from the subplan + * ---------------- + */ + result = ExecProcNode(subnode, (Plan*)node); + + if (! TupIsNull(result)) { + /* ---------------- + * if the subplan gave us something then place a copy of + * whatever we get into our result slot and return it, else.. + * ---------------- + */ + return ExecStoreTuple(result->val, + result_slot, result->ttc_buffer, false); + + } else { + /* ---------------- + * .. go on to the "next" subplan in the appropriate + * direction and try processing again (recursively) + * ---------------- + */ + whichplan = unionstate->as_whichplan; + + if (ScanDirectionIsForward(direction)) + { + unionstate->as_whichplan = whichplan + 1; + } + else + { + unionstate->as_whichplan = whichplan - 1; + } + + /* ---------------- + * return something from next node or an empty slot + * all of our subplans have been exhausted. + * ---------------- + */ + if (exec_append_initialize_next(node)) { + ExecSetSlotDescriptorIsNew(result_slot, true); + return + ExecProcAppend(node); + } else + return ExecClearTuple(result_slot); + } +} + +/* ---------------------------------------------------------------- + * ExecEndAppend + * + * Shuts down the subscans of the append node. + * + * Returns nothing of interest. + * ---------------------------------------------------------------- + */ +void +ExecEndAppend(Append *node) +{ + AppendState *unionstate; + int nplans; + List *unionplans; + bool *initialized; + int i; + List *resultRelationInfoList; + RelationInfo *resultRelationInfo; + + /* ---------------- + * get information from the node + * ---------------- + */ + unionstate = node->unionstate; + unionplans = node->unionplans; + nplans = unionstate->as_nplans; + initialized = unionstate->as_initialized; + + /* ---------------- + * shut down each of the subscans + * ---------------- + */ + for(i = 0; i < nplans; i++) { + if (initialized[i]==TRUE) { + ExecEndNode( (Plan *) nth(i, unionplans), (Plan*)node ); + } + } + + /* ---------------- + * close out the different result relations + * ---------------- + */ + resultRelationInfoList = unionstate->as_result_relation_info_list; + while (resultRelationInfoList != NIL) { + Relation resultRelationDesc; + + resultRelationInfo = (RelationInfo*) lfirst(resultRelationInfoList); + resultRelationDesc = resultRelationInfo->ri_RelationDesc; + heap_close(resultRelationDesc); + pfree(resultRelationInfo); + resultRelationInfoList = lnext(resultRelationInfoList); + } + if (unionstate->as_result_relation_info_list) + pfree(unionstate->as_result_relation_info_list); + + /* XXX should free unionstate->as_rtentries and unionstate->as_junkfilter_list here */ +} + diff --git a/src/backend/executor/nodeAppend.h b/src/backend/executor/nodeAppend.h new file mode 100644 index 00000000000..fd2cdbbe81e --- /dev/null +++ b/src/backend/executor/nodeAppend.h @@ -0,0 +1,22 @@ +/*------------------------------------------------------------------------- + * + * nodeAppend.h-- + * + * + * + * Copyright (c) 1994, Regents of the University of California + * + * $Id: nodeAppend.h,v 1.1.1.1 1996/07/09 06:21:26 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +#ifndef NODEAPPEND_H +#define NODEAPPEND_H + +extern bool exec_append_initialize_next(Append *node); +extern bool ExecInitAppend(Append *node, EState *estate, Plan *parent); +extern int ExecCountSlotsAppend(Append *node); +extern TupleTableSlot *ExecProcAppend(Append *node); +extern void ExecEndAppend(Append *node); + +#endif /* NODEAPPEND_H */ diff --git a/src/backend/executor/nodeGroup.c b/src/backend/executor/nodeGroup.c new file mode 100644 index 00000000000..e9b0847b5f9 --- /dev/null +++ b/src/backend/executor/nodeGroup.c @@ -0,0 +1,407 @@ +/*------------------------------------------------------------------------- + * + * nodeGroup.c-- + * Routines to handle group nodes (used for queries with GROUP BY clause). + * + * Copyright (c) 1994, Regents of the University of California + * + * + * DESCRIPTION + * The Group node is designed for handling queries with a GROUP BY clause. + * It's outer plan must be a sort node. It assumes that the tuples it gets + * back from the outer plan is sorted in the order specified by the group + * columns. (ie. tuples from the same group are consecutive) + * + * IDENTIFICATION + * $Header: /cvsroot/pgsql/src/backend/executor/nodeGroup.c,v 1.1.1.1 1996/07/09 06:21:26 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +#include "access/heapam.h" +#include "catalog/catalog.h" +#include "executor/executor.h" +#include "executor/nodeGroup.h" + +static TupleTableSlot *ExecGroupEveryTuple(Group *node); +static TupleTableSlot *ExecGroupOneTuple(Group *node); +static bool sameGroup(TupleTableSlot *oldslot, TupleTableSlot *newslot, + int numCols, AttrNumber *grpColIdx, TupleDesc tupdesc); + +/* --------------------------------------- + * ExecGroup - + * + * There are two modes in which tuples are returned by ExecGroup. If + * tuplePerGroup is TRUE, every tuple from the same group will be + * returned, followed by a NULL at the end of each group. This is + * useful for Agg node which needs to aggregate over tuples of the same + * group. (eg. SELECT salary, count{*} FROM emp GROUP BY salary) + * + * If tuplePerGroup is FALSE, only one tuple per group is returned. The + * tuple returned contains only the group columns. NULL is returned only + * at the end when no more groups is present. This is useful when + * the query does not involve aggregates. (eg. SELECT salary FROM emp + * GROUP BY salary) + * ------------------------------------------ + */ +TupleTableSlot * +ExecGroup(Group *node) +{ + if (node->tuplePerGroup) + return ExecGroupEveryTuple(node); + else + return ExecGroupOneTuple(node); +} + +/* + * ExecGroupEveryTuple - + * return every tuple with a NULL between each group + */ +static TupleTableSlot * +ExecGroupEveryTuple(Group *node) +{ + GroupState *grpstate; + EState *estate; + ExprContext *econtext; + + HeapTuple outerTuple = NULL; + TupleTableSlot *outerslot, *lastslot; + ProjectionInfo *projInfo; + TupleTableSlot *resultSlot; + + bool isDone; + + /* --------------------- + * get state info from node + * --------------------- + */ + grpstate = node->grpstate; + if (grpstate->grp_done) + return NULL; + + estate = node->plan.state; + + econtext = grpstate->csstate.cstate.cs_ExprContext; + + if (grpstate->grp_useLastTuple) { + /* + * we haven't returned last tuple yet because it is not of the + * same group + */ + grpstate->grp_useLastTuple = FALSE; + + ExecStoreTuple(grpstate->grp_lastSlot->val, + grpstate->csstate.css_ScanTupleSlot, + grpstate->grp_lastSlot->ttc_buffer, + false); + } else { + outerslot = ExecProcNode(outerPlan(node), (Plan*)node); + if (outerslot) + outerTuple = outerslot->val; + if (!HeapTupleIsValid(outerTuple)) { + grpstate->grp_done = TRUE; + return NULL; + } + + /* ---------------- + * Compare with last tuple and see if this tuple is of + * the same group. + * ---------------- + */ + lastslot = grpstate->csstate.css_ScanTupleSlot; + + if (lastslot->val != NULL && + (!sameGroup(lastslot, outerslot, + node->numCols, node->grpColIdx, + ExecGetScanType(&grpstate->csstate)))) { +/* ExecGetResultType(&grpstate->csstate.cstate)))) {*/ + + grpstate->grp_useLastTuple = TRUE; + + /* save it for next time */ + grpstate->grp_lastSlot = outerslot; + + /* + * signifies the end of the group + */ + return NULL; + } + + ExecStoreTuple(outerTuple, + grpstate->csstate.css_ScanTupleSlot, + outerslot->ttc_buffer, + false); + } + + /* ---------------- + * form a projection tuple, store it in the result tuple + * slot and return it. + * ---------------- + */ + projInfo = grpstate->csstate.cstate.cs_ProjInfo; + + econtext->ecxt_scantuple = grpstate->csstate.css_ScanTupleSlot; + resultSlot = ExecProject(projInfo, &isDone); + + return resultSlot; +} + +/* + * ExecGroupOneTuple - + * returns one tuple per group, a NULL at the end when there are no more + * tuples. + */ +static TupleTableSlot * +ExecGroupOneTuple(Group *node) +{ + GroupState *grpstate; + EState *estate; + ExprContext *econtext; + + HeapTuple outerTuple = NULL; + TupleTableSlot *outerslot, *lastslot; + ProjectionInfo *projInfo; + TupleTableSlot *resultSlot; + + bool isDone; + + /* --------------------- + * get state info from node + * --------------------- + */ + grpstate = node->grpstate; + if (grpstate->grp_done) + return NULL; + + estate = node->plan.state; + + econtext = node->grpstate->csstate.cstate.cs_ExprContext; + + if (grpstate->grp_useLastTuple) { + grpstate->grp_useLastTuple = FALSE; + ExecStoreTuple(grpstate->grp_lastSlot->val, + grpstate->csstate.css_ScanTupleSlot, + grpstate->grp_lastSlot->ttc_buffer, + false); + } else { + outerslot = ExecProcNode(outerPlan(node), (Plan*)node); + if (outerslot) outerTuple = outerslot->val; + if (!HeapTupleIsValid(outerTuple)) { + grpstate->grp_done = TRUE; + return NULL; + } + ExecStoreTuple(outerTuple, + grpstate->csstate.css_ScanTupleSlot, + outerslot->ttc_buffer, + false); + } + lastslot = grpstate->csstate.css_ScanTupleSlot; + + /* + * find all tuples that belong to a group + */ + for(;;) { + outerslot = ExecProcNode(outerPlan(node), (Plan*)node); + outerTuple = (outerslot) ? outerslot->val : NULL; + if (!HeapTupleIsValid(outerTuple)) { + /* + * we have at least one tuple (lastslot) if we reach here + */ + grpstate->grp_done = TRUE; + + /* return lastslot */ + break; + } + + /* ---------------- + * Compare with last tuple and see if this tuple is of + * the same group. + * ---------------- + */ + if ((!sameGroup(lastslot, outerslot, + node->numCols, node->grpColIdx, + ExecGetScanType(&grpstate->csstate)))) { +/* ExecGetResultType(&grpstate->csstate.cstate)))) {*/ + + grpstate->grp_useLastTuple = TRUE; + + /* save it for next time */ + grpstate->grp_lastSlot = outerslot; + + /* return lastslot */ + break; + } + + ExecStoreTuple(outerTuple, + grpstate->csstate.css_ScanTupleSlot, + outerslot->ttc_buffer, + false); + + lastslot = grpstate->csstate.css_ScanTupleSlot; + } + + ExecStoreTuple(lastslot->val, + grpstate->csstate.css_ScanTupleSlot, + lastslot->ttc_buffer, + false); + + /* ---------------- + * form a projection tuple, store it in the result tuple + * slot and return it. + * ---------------- + */ + projInfo = grpstate->csstate.cstate.cs_ProjInfo; + + econtext->ecxt_scantuple = lastslot; + resultSlot = ExecProject(projInfo, &isDone); + + return resultSlot; +} + +/* ----------------- + * ExecInitGroup + * + * Creates the run-time information for the group node produced by the + * planner and initializes its outer subtree + * ----------------- + */ +bool +ExecInitGroup(Group *node, EState *estate, Plan *parent) +{ + GroupState *grpstate; + Plan *outerPlan; + + /* + * assign the node's execution state + */ + node->plan.state = estate; + + /* + * create state structure + */ + grpstate = makeNode(GroupState); + node->grpstate = grpstate; + grpstate->grp_useLastTuple = FALSE; + grpstate->grp_done = FALSE; + + /* + * assign node's base id and create expression context + */ + ExecAssignNodeBaseInfo(estate, &grpstate->csstate.cstate, + (Plan*) parent); + ExecAssignExprContext(estate, &grpstate->csstate.cstate); + +#define GROUP_NSLOTS 2 + /* + * tuple table initialization + */ + ExecInitScanTupleSlot(estate, &grpstate->csstate); + ExecInitResultTupleSlot(estate, &grpstate->csstate.cstate); + + /* + * initializes child nodes + */ + outerPlan = outerPlan(node); + ExecInitNode(outerPlan, estate, (Plan *)node); + + /* ---------------- + * initialize tuple type. + * ---------------- + */ + ExecAssignScanTypeFromOuterPlan((Plan *) node, &grpstate->csstate); + + /* + * Initialize tuple type for both result and scan. + * This node does no projection + */ + ExecAssignResultTypeFromTL((Plan*) node, &grpstate->csstate.cstate); + ExecAssignProjectionInfo((Plan*)node, &grpstate->csstate.cstate); + + return TRUE; +} + +int +ExecCountSlotsGroup(Group *node) +{ + return ExecCountSlotsNode(outerPlan(node)) + GROUP_NSLOTS; +} + +/* ------------------------ + * ExecEndGroup(node) + * + * ----------------------- + */ +void +ExecEndGroup(Group *node) +{ + GroupState *grpstate; + Plan *outerPlan; + + grpstate = node->grpstate; + + ExecFreeProjectionInfo(&grpstate->csstate.cstate); + + outerPlan = outerPlan(node); + ExecEndNode(outerPlan, (Plan*)node); + + /* clean up tuple table */ + ExecClearTuple(grpstate->csstate.css_ScanTupleSlot); +} + +/***************************************************************************** + * + *****************************************************************************/ + +/* + * code swiped from nodeUnique.c + */ +static bool +sameGroup(TupleTableSlot *oldslot, + TupleTableSlot *newslot, + int numCols, + AttrNumber *grpColIdx, + TupleDesc tupdesc) +{ + bool isNull1,isNull2; + char *attr1, *attr2; + char *val1, *val2; + int i; + AttrNumber att; + Oid typoutput; + + for(i = 0; i < numCols; i++) { + att = grpColIdx[i]; + typoutput = typtoout((Oid)tupdesc->attrs[att-1]->atttypid); + + attr1 = heap_getattr(oldslot->val, + InvalidBuffer, + att, + tupdesc, + &isNull1); + + attr2 = heap_getattr(newslot->val, + InvalidBuffer, + att, + tupdesc, + &isNull2); + + if (isNull1 == isNull2) { + if (isNull1) /* both are null, they are equal */ + continue; + + val1 = fmgr(typoutput, attr1, + gettypelem(tupdesc->attrs[att-1]->atttypid)); + val2 = fmgr(typoutput, attr2, + gettypelem(tupdesc->attrs[att-1]->atttypid)); + + /* now, val1 and val2 are ascii representations so we can + use strcmp for comparison */ + if (strcmp(val1,val2) != 0) + return FALSE; + } else { + /* one is null and the other isn't, they aren't equal */ + return FALSE; + } + } + + return TRUE; +} diff --git a/src/backend/executor/nodeGroup.h b/src/backend/executor/nodeGroup.h new file mode 100644 index 00000000000..067028ea8e1 --- /dev/null +++ b/src/backend/executor/nodeGroup.h @@ -0,0 +1,21 @@ +/*------------------------------------------------------------------------- + * + * nodeGroup.h-- + * prototypes for nodeGroup.c + * + * + * Copyright (c) 1994, Regents of the University of California + * + * $Id: nodeGroup.h,v 1.1.1.1 1996/07/09 06:21:26 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +#ifndef NODEGROUP_H +#define NODEGROUP_H + +extern TupleTableSlot *ExecGroup(Group *node); +extern bool ExecInitGroup(Group *node, EState *estate, Plan *parent); +extern int ExecCountSlotsGroup(Group *node); +extern void ExecEndGroup(Group *node); + +#endif /* NODEGROUP_H */ diff --git a/src/backend/executor/nodeHash.c b/src/backend/executor/nodeHash.c new file mode 100644 index 00000000000..55a5e1f0276 --- /dev/null +++ b/src/backend/executor/nodeHash.c @@ -0,0 +1,828 @@ +/*------------------------------------------------------------------------- + * + * nodeHash.c-- + * Routines to hash relations for hashjoin + * + * Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * $Header: /cvsroot/pgsql/src/backend/executor/nodeHash.c,v 1.1.1.1 1996/07/09 06:21:26 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +/* + * INTERFACE ROUTINES + * ExecHash - generate an in-memory hash table of the relation + * ExecInitHash - initialize node and subnodes.. + * ExecEndHash - shutdown node and subnodes + * + */ + +#include <stdio.h> /* for sprintf() */ +#include <math.h> +#include <sys/file.h> +#include "storage/fd.h" /* for SEEK_ */ +#include "storage/ipc.h" +#include "storage/bufmgr.h" /* for BLCKSZ */ +#include "executor/executor.h" +#include "executor/nodeHash.h" +#include "executor/nodeHashjoin.h" +#include "utils/palloc.h" + +extern int NBuffers; +static int HashTBSize; + +static void mk_hj_temp(char *tempname); +static int hashFunc(char *key, int len); + +/* ---------------------------------------------------------------- + * ExecHash + * + * build hash table for hashjoin, all do partitioning if more + * than one batches are required. + * ---------------------------------------------------------------- + */ +TupleTableSlot * +ExecHash(Hash *node) +{ + EState *estate; + HashState *hashstate; + Plan *outerNode; + Var *hashkey; + HashJoinTable hashtable; + TupleTableSlot *slot; + ExprContext *econtext; + + int nbatch; + File *batches; + RelativeAddr *batchPos; + int *batchSizes; + int i; + RelativeAddr *innerbatchNames; + + /* ---------------- + * get state info from node + * ---------------- + */ + + hashstate = node->hashstate; + estate = node->plan.state; + outerNode = outerPlan(node); + + hashtable = node->hashtable; + if (hashtable == NULL) + elog(WARN, "ExecHash: hash table is NULL."); + + nbatch = hashtable->nbatch; + + if (nbatch > 0) { /* if needs hash partition */ + innerbatchNames = (RelativeAddr *) ABSADDR(hashtable->innerbatchNames); + + /* -------------- + * allocate space for the file descriptors of batch files + * then open the batch files in the current processes. + * -------------- + */ + batches = (File*)palloc(nbatch * sizeof(File)); + for (i=0; i<nbatch; i++) { + batches[i] = FileNameOpenFile(ABSADDR(innerbatchNames[i]), + O_CREAT | O_RDWR, 0600); + } + hashstate->hashBatches = batches; + batchPos = (RelativeAddr*) ABSADDR(hashtable->innerbatchPos); + batchSizes = (int*) ABSADDR(hashtable->innerbatchSizes); + } + + /* ---------------- + * set expression context + * ---------------- + */ + hashkey = node->hashkey; + econtext = hashstate->cstate.cs_ExprContext; + + /* ---------------- + * get tuple and insert into the hash table + * ---------------- + */ + for (;;) { + slot = ExecProcNode(outerNode, (Plan*)node); + if (TupIsNull(slot)) + break; + + econtext->ecxt_innertuple = slot; + ExecHashTableInsert(hashtable, econtext, hashkey, + hashstate->hashBatches); + + ExecClearTuple(slot); + } + + /* + * end of build phase, flush all the last pages of the batches. + */ + for (i=0; i<nbatch; i++) { + if (FileSeek(batches[i], 0L, SEEK_END) < 0) + perror("FileSeek"); + if (FileWrite(batches[i],ABSADDR(hashtable->batch)+i*BLCKSZ,BLCKSZ) < 0) + perror("FileWrite"); + NDirectFileWrite++; + } + + /* --------------------- + * Return the slot so that we have the tuple descriptor + * when we need to save/restore them. -Jeff 11 July 1991 + * --------------------- + */ + return slot; +} + +/* ---------------------------------------------------------------- + * ExecInitHash + * + * Init routine for Hash node + * ---------------------------------------------------------------- + */ +bool +ExecInitHash(Hash *node, EState *estate, Plan *parent) +{ + HashState *hashstate; + Plan *outerPlan; + + SO1_printf("ExecInitHash: %s\n", + "initializing hash node"); + + /* ---------------- + * assign the node's execution state + * ---------------- + */ + node->plan.state = estate; + + /* ---------------- + * create state structure + * ---------------- + */ + hashstate = makeNode(HashState); + node->hashstate = hashstate; + hashstate->hashBatches = NULL; + + /* ---------------- + * Miscellanious initialization + * + * + assign node's base_id + * + assign debugging hooks and + * + create expression context for node + * ---------------- + */ + ExecAssignNodeBaseInfo(estate, &hashstate->cstate, parent); + ExecAssignExprContext(estate, &hashstate->cstate); + +#define HASH_NSLOTS 1 + /* ---------------- + * initialize our result slot + * ---------------- + */ + ExecInitResultTupleSlot(estate, &hashstate->cstate); + + /* ---------------- + * initializes child nodes + * ---------------- + */ + outerPlan = outerPlan(node); + ExecInitNode(outerPlan, estate, (Plan *)node); + + /* ---------------- + * initialize tuple type. no need to initialize projection + * info because this node doesn't do projections + * ---------------- + */ + ExecAssignResultTypeFromOuterPlan((Plan *) node, &hashstate->cstate); + hashstate->cstate.cs_ProjInfo = NULL; + + return TRUE; +} + +int +ExecCountSlotsHash(Hash *node) +{ + return ExecCountSlotsNode(outerPlan(node)) + + ExecCountSlotsNode(innerPlan(node)) + + HASH_NSLOTS; +} + +/* --------------------------------------------------------------- + * ExecEndHash + * + * clean up routine for Hash node + * ---------------------------------------------------------------- + */ +void +ExecEndHash(Hash *node) +{ + HashState *hashstate; + Plan *outerPlan; + File *batches; + + /* ---------------- + * get info from the hash state + * ---------------- + */ + hashstate = node->hashstate; + batches = hashstate->hashBatches; + if (batches != NULL) + pfree(batches); + + /* ---------------- + * free projection info. no need to free result type info + * because that came from the outer plan... + * ---------------- + */ + ExecFreeProjectionInfo(&hashstate->cstate); + + /* ---------------- + * shut down the subplan + * ---------------- + */ + outerPlan = outerPlan(node); + ExecEndNode(outerPlan, (Plan*)node); +} + +RelativeAddr +hashTableAlloc(int size, HashJoinTable hashtable) +{ + RelativeAddr p; + p = hashtable->top; + hashtable->top += size; + return p; +} + +/* ---------------------------------------------------------------- + * ExecHashTableCreate + * + * create a hashtable in shared memory for hashjoin. + * ---------------------------------------------------------------- + */ +#define NTUP_PER_BUCKET 10 +#define FUDGE_FAC 1.5 + +HashJoinTable +ExecHashTableCreate(Hash *node) +{ + Plan *outerNode; + int nbatch; + int ntuples; + int tupsize; + IpcMemoryId shmid; + HashJoinTable hashtable; + HashBucket bucket; + int nbuckets; + int totalbuckets; + int bucketsize; + int i; + RelativeAddr *outerbatchNames; + RelativeAddr *outerbatchPos; + RelativeAddr *innerbatchNames; + RelativeAddr *innerbatchPos; + int *innerbatchSizes; + RelativeAddr tempname; + + nbatch = -1; + HashTBSize = NBuffers/2; + while (nbatch < 0) { + /* + * determine number of batches for the hashjoin + */ + HashTBSize *= 2; + nbatch = ExecHashPartition(node); + } + /* ---------------- + * get information about the size of the relation + * ---------------- + */ + outerNode = outerPlan(node); + ntuples = outerNode->plan_size; + if (ntuples <= 0) + ntuples = 1000; /* XXX just a hack */ + tupsize = outerNode->plan_width + sizeof(HeapTupleData); + + /* + * totalbuckets is the total number of hash buckets needed for + * the entire relation + */ + totalbuckets = ceil((double)ntuples/NTUP_PER_BUCKET); + bucketsize = LONGALIGN (NTUP_PER_BUCKET * tupsize + sizeof(*bucket)); + + /* + * nbuckets is the number of hash buckets for the first pass + * of hybrid hashjoin + */ + nbuckets = (HashTBSize - nbatch) * BLCKSZ / (bucketsize * FUDGE_FAC); + if (totalbuckets < nbuckets) + totalbuckets = nbuckets; + if (nbatch == 0) + nbuckets = totalbuckets; +#ifdef HJDEBUG + printf("nbatch = %d, totalbuckets = %d, nbuckets = %d\n", nbatch, totalbuckets, nbuckets); +#endif + + /* ---------------- + * in non-parallel machines, we don't need to put the hash table + * in the shared memory. We just palloc it. + * ---------------- + */ + hashtable = (HashJoinTable)palloc((HashTBSize+1)*BLCKSZ); + shmid = 0; + + if (hashtable == NULL) { + elog(WARN, "not enough memory for hashjoin."); + } + /* ---------------- + * initialize the hash table header + * ---------------- + */ + hashtable->nbuckets = nbuckets; + hashtable->totalbuckets = totalbuckets; + hashtable->bucketsize = bucketsize; + hashtable->shmid = shmid; + hashtable->top = sizeof(HashTableData); + hashtable->bottom = HashTBSize * BLCKSZ; + /* + * hashtable->readbuf has to be long aligned!!! + */ + hashtable->readbuf = hashtable->bottom; + hashtable->nbatch = nbatch; + hashtable->curbatch = 0; + hashtable->pcount = hashtable->nprocess = 0; + if (nbatch > 0) { + /* --------------- + * allocate and initialize the outer batches + * --------------- + */ + outerbatchNames = (RelativeAddr*)ABSADDR( + hashTableAlloc(nbatch * sizeof(RelativeAddr), hashtable)); + outerbatchPos = (RelativeAddr*)ABSADDR( + hashTableAlloc(nbatch * sizeof(RelativeAddr), hashtable)); + for (i=0; i<nbatch; i++) { + tempname = hashTableAlloc(12, hashtable); + mk_hj_temp(ABSADDR(tempname)); + outerbatchNames[i] = tempname; + outerbatchPos[i] = -1; + } + hashtable->outerbatchNames = RELADDR(outerbatchNames); + hashtable->outerbatchPos = RELADDR(outerbatchPos); + /* --------------- + * allocate and initialize the inner batches + * --------------- + */ + innerbatchNames = (RelativeAddr*)ABSADDR( + hashTableAlloc(nbatch * sizeof(RelativeAddr), hashtable)); + innerbatchPos = (RelativeAddr*)ABSADDR( + hashTableAlloc(nbatch * sizeof(RelativeAddr), hashtable)); + innerbatchSizes = (int*)ABSADDR( + hashTableAlloc(nbatch * sizeof(int), hashtable)); + for (i=0; i<nbatch; i++) { + tempname = hashTableAlloc(12, hashtable); + mk_hj_temp(ABSADDR(tempname)); + innerbatchNames[i] = tempname; + innerbatchPos[i] = -1; + innerbatchSizes[i] = 0; + } + hashtable->innerbatchNames = RELADDR(innerbatchNames); + hashtable->innerbatchPos = RELADDR(innerbatchPos); + hashtable->innerbatchSizes = RELADDR(innerbatchSizes); + } + else { + hashtable->outerbatchNames = (RelativeAddr)NULL; + hashtable->outerbatchPos = (RelativeAddr)NULL; + hashtable->innerbatchNames = (RelativeAddr)NULL; + hashtable->innerbatchPos = (RelativeAddr)NULL; + hashtable->innerbatchSizes = (RelativeAddr)NULL; + } + + hashtable->batch = (RelativeAddr)LONGALIGN(hashtable->top + + bucketsize * nbuckets); + hashtable->overflownext=hashtable->batch + nbatch * BLCKSZ; + /* ---------------- + * initialize each hash bucket + * ---------------- + */ + bucket = (HashBucket)ABSADDR(hashtable->top); + for (i=0; i<nbuckets; i++) { + bucket->top = RELADDR((char*)bucket + sizeof(*bucket)); + bucket->bottom = bucket->top; + bucket->firstotuple = bucket->lastotuple = -1; + bucket = (HashBucket)LONGALIGN(((char*)bucket + bucketsize)); + } + return(hashtable); +} + +/* ---------------------------------------------------------------- + * ExecHashTableInsert + * + * insert a tuple into the hash table depending on the hash value + * it may just go to a tmp file for other batches + * ---------------------------------------------------------------- + */ +void +ExecHashTableInsert(HashJoinTable hashtable, + ExprContext *econtext, + Var *hashkey, + File *batches) +{ + TupleTableSlot *slot; + HeapTuple heapTuple; + HashBucket bucket; + int bucketno; + int nbatch; + int batchno; + char *buffer; + RelativeAddr *batchPos; + int *batchSizes; + char *pos; + + nbatch = hashtable->nbatch; + batchPos = (RelativeAddr*)ABSADDR(hashtable->innerbatchPos); + batchSizes = (int*)ABSADDR(hashtable->innerbatchSizes); + + slot = econtext->ecxt_innertuple; + heapTuple = slot->val; + +#ifdef HJDEBUG + printf("Inserting "); +#endif + + bucketno = ExecHashGetBucket(hashtable, econtext, hashkey); + + /* ---------------- + * decide whether to put the tuple in the hash table or a tmp file + * ---------------- + */ + if (bucketno < hashtable->nbuckets) { + /* --------------- + * put the tuple in hash table + * --------------- + */ + bucket = (HashBucket) + (ABSADDR(hashtable->top) + bucketno * hashtable->bucketsize); + if ((char*)LONGALIGN(ABSADDR(bucket->bottom)) + -(char*)bucket+heapTuple->t_len > hashtable->bucketsize) + ExecHashOverflowInsert(hashtable, bucket, heapTuple); + else { + memmove((char*)LONGALIGN(ABSADDR(bucket->bottom)), + heapTuple, + heapTuple->t_len); + bucket->bottom = + ((RelativeAddr)LONGALIGN(bucket->bottom) + heapTuple->t_len); + } + } + else { + /* ----------------- + * put the tuple into a tmp file for other batches + * ----------------- + */ + batchno = (float)(bucketno - hashtable->nbuckets)/ + (float)(hashtable->totalbuckets - hashtable->nbuckets) + * nbatch; + buffer = ABSADDR(hashtable->batch) + batchno * BLCKSZ; + batchSizes[batchno]++; + pos= (char *) + ExecHashJoinSaveTuple(heapTuple, + buffer, + batches[batchno], + (char*)ABSADDR(batchPos[batchno])); + batchPos[batchno] = RELADDR(pos); + } +} + +/* ---------------------------------------------------------------- + * ExecHashTableDestroy + * + * destroy a hash table + * ---------------------------------------------------------------- + */ +void +ExecHashTableDestroy(HashJoinTable hashtable) +{ + pfree(hashtable); +} + +/* ---------------------------------------------------------------- + * ExecHashGetBucket + * + * Get the hash value for a tuple + * ---------------------------------------------------------------- + */ +int +ExecHashGetBucket(HashJoinTable hashtable, + ExprContext *econtext, + Var *hashkey) +{ + int bucketno; + Datum keyval; + bool isNull; + + + /* ---------------- + * Get the join attribute value of the tuple + * ---------------- + */ + keyval = ExecEvalVar(hashkey, econtext, &isNull); + + /* ------------------ + * compute the hash function + * ------------------ + */ + if (execConstByVal) + bucketno = + hashFunc((char *) &keyval, execConstLen) % hashtable->totalbuckets; + else + bucketno = + hashFunc((char *) keyval, execConstLen) % hashtable->totalbuckets; +#ifdef HJDEBUG + if (bucketno >= hashtable->nbuckets) + printf("hash(%d) = %d SAVED\n", keyval, bucketno); + else + printf("hash(%d) = %d\n", keyval, bucketno); +#endif + + return(bucketno); +} + +/* ---------------------------------------------------------------- + * ExecHashOverflowInsert + * + * insert into the overflow area of a hash bucket + * ---------------------------------------------------------------- + */ +void +ExecHashOverflowInsert(HashJoinTable hashtable, + HashBucket bucket, + HeapTuple heapTuple) +{ + OverflowTuple otuple; + RelativeAddr newend; + OverflowTuple firstotuple; + OverflowTuple lastotuple; + + firstotuple = (OverflowTuple)ABSADDR(bucket->firstotuple); + lastotuple = (OverflowTuple)ABSADDR(bucket->lastotuple); + /* ---------------- + * see if we run out of overflow space + * ---------------- + */ + newend = (RelativeAddr)LONGALIGN(hashtable->overflownext + sizeof(*otuple) + + heapTuple->t_len); + if (newend > hashtable->bottom) { + elog(DEBUG, "hash table out of memory. expanding."); + /* ------------------ + * XXX this is a temporary hack + * eventually, recursive hash partitioning will be + * implemented + * ------------------ + */ + hashtable->readbuf = hashtable->bottom = 2 * hashtable->bottom; + hashtable = + (HashJoinTable)repalloc(hashtable, hashtable->bottom+BLCKSZ); + if (hashtable == NULL) { + perror("repalloc"); + elog(WARN, "can't expand hashtable."); + } + } + + /* ---------------- + * establish the overflow chain + * ---------------- + */ + otuple = (OverflowTuple)ABSADDR(hashtable->overflownext); + hashtable->overflownext = newend; + if (firstotuple == NULL) + bucket->firstotuple = bucket->lastotuple = RELADDR(otuple); + else { + lastotuple->next = RELADDR(otuple); + bucket->lastotuple = RELADDR(otuple); + } + + /* ---------------- + * copy the tuple into the overflow area + * ---------------- + */ + otuple->next = -1; + otuple->tuple = RELADDR(LONGALIGN(((char*)otuple + sizeof(*otuple)))); + memmove(ABSADDR(otuple->tuple), + heapTuple, + heapTuple->t_len); +} + +/* ---------------------------------------------------------------- + * ExecScanHashBucket + * + * scan a hash bucket of matches + * ---------------------------------------------------------------- + */ +HeapTuple +ExecScanHashBucket(HashJoinState *hjstate, + HashBucket bucket, + HeapTuple curtuple, + List *hjclauses, + ExprContext *econtext) +{ + HeapTuple heapTuple; + bool qualResult; + OverflowTuple otuple = NULL; + OverflowTuple curotuple; + TupleTableSlot *inntuple; + OverflowTuple firstotuple; + OverflowTuple lastotuple; + HashJoinTable hashtable; + + hashtable = hjstate->hj_HashTable; + firstotuple = (OverflowTuple)ABSADDR(bucket->firstotuple); + lastotuple = (OverflowTuple)ABSADDR(bucket->lastotuple); + + /* ---------------- + * search the hash bucket + * ---------------- + */ + if (curtuple == NULL || curtuple < (HeapTuple)ABSADDR(bucket->bottom)) { + if (curtuple == NULL) + heapTuple = (HeapTuple) + LONGALIGN(ABSADDR(bucket->top)); + else + heapTuple = (HeapTuple) + LONGALIGN(((char*)curtuple+curtuple->t_len)); + + while (heapTuple < (HeapTuple)ABSADDR(bucket->bottom)) { + + inntuple = ExecStoreTuple(heapTuple, /* tuple to store */ + hjstate->hj_HashTupleSlot, /* slot */ + InvalidBuffer,/* tuple has no buffer */ + false); /* do not pfree this tuple */ + + econtext->ecxt_innertuple = inntuple; + qualResult = ExecQual((List*)hjclauses, econtext); + + if (qualResult) + return heapTuple; + + heapTuple = (HeapTuple) + LONGALIGN(((char*)heapTuple+heapTuple->t_len)); + } + + if (firstotuple == NULL) + return NULL; + otuple = firstotuple; + } + + /* ---------------- + * search the overflow area of the hash bucket + * ---------------- + */ + if (otuple == NULL) { + curotuple = hjstate->hj_CurOTuple; + otuple = (OverflowTuple)ABSADDR(curotuple->next); + } + + while (otuple != NULL) { + heapTuple = (HeapTuple)ABSADDR(otuple->tuple); + + inntuple = ExecStoreTuple(heapTuple, /* tuple to store */ + hjstate->hj_HashTupleSlot, /* slot */ + InvalidBuffer, /* SP?? this tuple has no buffer */ + false); /* do not pfree this tuple */ + + econtext->ecxt_innertuple = inntuple; + qualResult = ExecQual((List*)hjclauses, econtext); + + if (qualResult) { + hjstate->hj_CurOTuple = otuple; + return heapTuple; + } + + otuple = (OverflowTuple)ABSADDR(otuple->next); + } + + /* ---------------- + * no match + * ---------------- + */ + return NULL; +} + +/* ---------------------------------------------------------------- + * hashFunc + * + * the hash function, copied from Margo + * ---------------------------------------------------------------- + */ +static int +hashFunc(char *key, int len) +{ + register unsigned int h; + register int l; + register unsigned char *k; + + /* + * If this is a variable length type, then 'k' points + * to a "struct varlena" and len == -1. + * NOTE: + * VARSIZE returns the "real" data length plus the sizeof the + * "vl_len" attribute of varlena (the length information). + * 'k' points to the beginning of the varlena struct, so + * we have to use "VARDATA" to find the beginning of the "real" + * data. + */ + if (len == -1) { + l = VARSIZE(key) - VARHDRSZ; + k = (unsigned char*) VARDATA(key); + } else { + l = len; + k = (unsigned char *) key; + } + + h = 0; + + /* + * Convert string to integer + */ + while (l--) h = h * PRIME1 ^ (*k++); + h %= PRIME2; + + return (h); +} + +/* ---------------------------------------------------------------- + * ExecHashPartition + * + * determine the number of batches needed for a hashjoin + * ---------------------------------------------------------------- + */ +int +ExecHashPartition(Hash *node) +{ + Plan *outerNode; + int b; + int pages; + int ntuples; + int tupsize; + + /* + * get size information for plan node + */ + outerNode = outerPlan(node); + ntuples = outerNode->plan_size; + if (ntuples == 0) ntuples = 1000; + tupsize = outerNode->plan_width + sizeof(HeapTupleData); + pages = ceil((double)ntuples * tupsize * FUDGE_FAC / BLCKSZ); + + /* + * if amount of buffer space below hashjoin threshold, + * return negative + */ + if (ceil(sqrt((double)pages)) > HashTBSize) + return -1; + if (pages <= HashTBSize) + b = 0; /* fit in memory, no partitioning */ + else + b = ceil((double)(pages - HashTBSize)/(double)(HashTBSize - 1)); + + return b; +} + +/* ---------------------------------------------------------------- + * ExecHashTableReset + * + * reset hash table header for new batch + * ---------------------------------------------------------------- + */ +void +ExecHashTableReset(HashJoinTable hashtable, int ntuples) +{ + int i; + HashBucket bucket; + + hashtable->nbuckets = hashtable->totalbuckets + = ceil((double)ntuples/NTUP_PER_BUCKET); + + hashtable->overflownext = hashtable->top + hashtable->bucketsize * + hashtable->nbuckets; + + bucket = (HashBucket)ABSADDR(hashtable->top); + for (i=0; i<hashtable->nbuckets; i++) { + bucket->top = RELADDR((char*)bucket + sizeof(*bucket)); + bucket->bottom = bucket->top; + bucket->firstotuple = bucket->lastotuple = -1; + bucket = (HashBucket)((char*)bucket + hashtable->bucketsize); + } + hashtable->pcount = hashtable->nprocess; +} + +static int hjtmpcnt = 0; + +static void +mk_hj_temp(char *tempname) +{ + sprintf(tempname, "HJ%d.%d", getpid(), hjtmpcnt); + hjtmpcnt = (hjtmpcnt + 1) % 1000; +} + + + diff --git a/src/backend/executor/nodeHash.h b/src/backend/executor/nodeHash.h new file mode 100644 index 00000000000..cec479dbb01 --- /dev/null +++ b/src/backend/executor/nodeHash.h @@ -0,0 +1,35 @@ +/*------------------------------------------------------------------------- + * + * nodeHash.h-- + * + * + * + * Copyright (c) 1994, Regents of the University of California + * + * $Id: nodeHash.h,v 1.1.1.1 1996/07/09 06:21:26 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +#ifndef NODEHASH_H +#define NODEHASH_H + +extern TupleTableSlot *ExecHash(Hash *node); +extern bool ExecInitHash(Hash *node, EState *estate, Plan *parent); +extern int ExecCountSlotsHash(Hash *node); +extern void ExecEndHash(Hash *node); +extern RelativeAddr hashTableAlloc(int size, HashJoinTable hashtable); +extern HashJoinTable ExecHashTableCreate(Hash *node); +extern void ExecHashTableInsert(HashJoinTable hashtable, ExprContext *econtext, + Var *hashkey, File *batches); +extern void ExecHashTableDestroy(HashJoinTable hashtable); +extern int ExecHashGetBucket(HashJoinTable hashtable, ExprContext *econtext, + Var *hashkey); +extern void ExecHashOverflowInsert(HashJoinTable hashtable, HashBucket bucket, + HeapTuple heapTuple); +extern HeapTuple ExecScanHashBucket(HashJoinState *hjstate, HashBucket bucket, + HeapTuple curtuple, List *hjclauses, + ExprContext *econtext); +extern int ExecHashPartition(Hash *node); +extern void ExecHashTableReset(HashJoinTable hashtable, int ntuples); + +#endif /* NODEHASH_H */ diff --git a/src/backend/executor/nodeHashjoin.c b/src/backend/executor/nodeHashjoin.c new file mode 100644 index 00000000000..7ed4c141b95 --- /dev/null +++ b/src/backend/executor/nodeHashjoin.c @@ -0,0 +1,792 @@ +/*------------------------------------------------------------------------- + * + * nodeHashjoin.c-- + * Routines to handle hash join nodes + * + * Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * $Header: /cvsroot/pgsql/src/backend/executor/nodeHashjoin.c,v 1.1.1.1 1996/07/09 06:21:26 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +#include <sys/file.h> + +#include "storage/bufmgr.h" /* for BLCKSZ */ +#include "storage/fd.h" /* for SEEK_ */ +#include "executor/executor.h" +#include "executor/nodeHash.h" +#include "executor/nodeHashjoin.h" + +#include "optimizer/clauses.h" /* for get_leftop */ + + +#include "utils/palloc.h" + +static TupleTableSlot * +ExecHashJoinOuterGetTuple(Plan *node, Plan* parent, HashJoinState *hjstate); + +static TupleTableSlot * +ExecHashJoinGetSavedTuple(HashJoinState *hjstate, char *buffer, + File file, TupleTableSlot *tupleSlot, int *block, char **position); + +/* ---------------------------------------------------------------- + * ExecHashJoin + * + * This function implements the Hybrid Hashjoin algorithm. + * recursive partitioning remains to be added. + * Note: the relation we build hash table on is the inner + * the other one is outer. + * ---------------------------------------------------------------- + */ +TupleTableSlot * /* return: a tuple or NULL */ +ExecHashJoin(HashJoin *node) +{ + HashJoinState *hjstate; + EState *estate; + Plan *outerNode; + Hash *hashNode; + List *hjclauses; + Expr *clause; + List *qual; + ScanDirection dir; + TupleTableSlot *inntuple; + Var *outerVar; + ExprContext *econtext; + + HashJoinTable hashtable; + int bucketno; + HashBucket bucket; + HeapTuple curtuple; + + bool qualResult; + + TupleTableSlot *outerTupleSlot; + TupleTableSlot *innerTupleSlot; + int nbatch; + int curbatch; + File *outerbatches; + RelativeAddr *outerbatchNames; + RelativeAddr *outerbatchPos; + Var *innerhashkey; + int batch; + int batchno; + char *buffer; + int i; + bool hashPhaseDone; + char *pos; + + /* ---------------- + * get information from HashJoin node + * ---------------- + */ + hjstate = node->hashjoinstate; + hjclauses = node->hashclauses; + clause = lfirst(hjclauses); + estate = node->join.state; + qual = node->join.qual; + hashNode = (Hash *)innerPlan(node); + outerNode = outerPlan(node); + hashPhaseDone = node->hashdone; + + dir = estate->es_direction; + + /* ----------------- + * get information from HashJoin state + * ----------------- + */ + hashtable = hjstate->hj_HashTable; + bucket = hjstate->hj_CurBucket; + curtuple = hjstate->hj_CurTuple; + + /* -------------------- + * initialize expression context + * -------------------- + */ + econtext = hjstate->jstate.cs_ExprContext; + + if (hjstate->jstate.cs_TupFromTlist) { + TupleTableSlot *result; + bool isDone; + + result = ExecProject(hjstate->jstate.cs_ProjInfo, &isDone); + if (!isDone) + return result; + } + /* ---------------- + * if this is the first call, build the hash table for inner relation + * ---------------- + */ + if (!hashPhaseDone) { /* if the hash phase not completed */ + hashtable = node->hashjointable; + if (hashtable == NULL) { /* if the hash table has not been created */ + /* ---------------- + * create the hash table + * ---------------- + */ + hashtable = ExecHashTableCreate(hashNode); + hjstate->hj_HashTable = hashtable; + innerhashkey = hashNode->hashkey; + hjstate->hj_InnerHashKey = innerhashkey; + + /* ---------------- + * execute the Hash node, to build the hash table + * ---------------- + */ + hashNode->hashtable = hashtable; + innerTupleSlot = ExecProcNode((Plan *)hashNode, (Plan*) node); + } + bucket = NULL; + curtuple = NULL; + curbatch = 0; + node->hashdone = true; + } + nbatch = hashtable->nbatch; + outerbatches = hjstate->hj_OuterBatches; + if (nbatch > 0 && outerbatches == NULL) { /* if needs hash partition */ + /* ----------------- + * allocate space for file descriptors of outer batch files + * then open the batch files in the current process + * ----------------- + */ + innerhashkey = hashNode->hashkey; + hjstate->hj_InnerHashKey = innerhashkey; + outerbatchNames = (RelativeAddr*) + ABSADDR(hashtable->outerbatchNames); + outerbatches = (File*) + palloc(nbatch * sizeof(File)); + for (i=0; i<nbatch; i++) { + outerbatches[i] = FileNameOpenFile( + ABSADDR(outerbatchNames[i]), + O_CREAT | O_RDWR, 0600); + } + hjstate->hj_OuterBatches = outerbatches; + + /* ------------------ + * get the inner batch file descriptors from the + * hash node + * ------------------ + */ + hjstate->hj_InnerBatches = + hashNode->hashstate->hashBatches; + } + outerbatchPos = (RelativeAddr*)ABSADDR(hashtable->outerbatchPos); + curbatch = hashtable->curbatch; + outerbatchNames = (RelativeAddr*)ABSADDR(hashtable->outerbatchNames); + + /* ---------------- + * Now get an outer tuple and probe into the hash table for matches + * ---------------- + */ + outerTupleSlot = hjstate->jstate.cs_OuterTupleSlot; + outerVar = get_leftop(clause); + + bucketno = -1; /* if bucketno remains -1, means use old outer tuple */ + if (TupIsNull(outerTupleSlot)) { + /* + * if the current outer tuple is nil, get a new one + */ + outerTupleSlot = (TupleTableSlot*) + ExecHashJoinOuterGetTuple(outerNode, (Plan*)node, hjstate); + + while (curbatch <= nbatch && TupIsNull(outerTupleSlot)) { + /* + * if the current batch runs out, switch to new batch + */ + curbatch = ExecHashJoinNewBatch(hjstate); + if (curbatch > nbatch) { + /* + * when the last batch runs out, clean up + */ + ExecHashTableDestroy(hashtable); + hjstate->hj_HashTable = NULL; + return NULL; + } + else + outerTupleSlot = (TupleTableSlot*) + ExecHashJoinOuterGetTuple(outerNode, (Plan*)node, hjstate); + } + /* + * now we get an outer tuple, find the corresponding bucket for + * this tuple from the hash table + */ + econtext->ecxt_outertuple = outerTupleSlot; + +#ifdef HJDEBUG + printf("Probing "); +#endif + bucketno = ExecHashGetBucket(hashtable, econtext, outerVar); + bucket=(HashBucket)(ABSADDR(hashtable->top) + + bucketno * hashtable->bucketsize); + } + + for (;;) { + /* ---------------- + * Now we've got an outer tuple and the corresponding hash bucket, + * but this tuple may not belong to the current batch. + * ---------------- + */ + if (curbatch == 0 && bucketno != -1) /* if this is the first pass */ + batch = ExecHashJoinGetBatch(bucketno, hashtable, nbatch); + else + batch = 0; + if (batch > 0) { + /* + * if the current outer tuple does not belong to + * the current batch, save to the tmp file for + * the corresponding batch. + */ + buffer = ABSADDR(hashtable->batch) + (batch - 1) * BLCKSZ; + batchno = batch - 1; + pos = ExecHashJoinSaveTuple(outerTupleSlot->val, + buffer, + outerbatches[batchno], + ABSADDR(outerbatchPos[batchno])); + + outerbatchPos[batchno] = RELADDR(pos); + } + else if (bucket != NULL) { + do { + /* + * scan the hash bucket for matches + */ + curtuple = ExecScanHashBucket(hjstate, + bucket, + curtuple, + hjclauses, + econtext); + + if (curtuple != NULL) { + /* + * we've got a match, but still need to test qpqual + */ + inntuple = ExecStoreTuple(curtuple, + hjstate->hj_HashTupleSlot, + InvalidBuffer, + false); /* don't pfree this tuple */ + + econtext->ecxt_innertuple = inntuple; + + /* ---------------- + * test to see if we pass the qualification + * ---------------- + */ + qualResult = ExecQual((List*)qual, econtext); + + /* ---------------- + * if we pass the qual, then save state for next call and + * have ExecProject form the projection, store it + * in the tuple table, and return the slot. + * ---------------- + */ + if (qualResult) { + ProjectionInfo *projInfo; + TupleTableSlot *result; + bool isDone; + + hjstate->hj_CurBucket = bucket; + hjstate->hj_CurTuple = curtuple; + hashtable->curbatch = curbatch; + hjstate->jstate.cs_OuterTupleSlot = outerTupleSlot; + + projInfo = hjstate->jstate.cs_ProjInfo; + result = ExecProject(projInfo, &isDone); + hjstate->jstate.cs_TupFromTlist = !isDone; + return result; + } + } + } + while (curtuple != NULL); + } + + /* ---------------- + * Now the current outer tuple has run out of matches, + * so we free it and get a new outer tuple. + * ---------------- + */ + outerTupleSlot = (TupleTableSlot*) + ExecHashJoinOuterGetTuple(outerNode, (Plan*) node, hjstate); + + while (curbatch <= nbatch && TupIsNull(outerTupleSlot)) { + /* + * if the current batch runs out, switch to new batch + */ + curbatch = ExecHashJoinNewBatch(hjstate); + if (curbatch > nbatch) { + /* + * when the last batch runs out, clean up + */ + ExecHashTableDestroy(hashtable); + hjstate->hj_HashTable = NULL; + return NULL; + } + else + outerTupleSlot = (TupleTableSlot*) + ExecHashJoinOuterGetTuple(outerNode, (Plan*)node, hjstate); + } + + /* ---------------- + * Now get the corresponding hash bucket for the new + * outer tuple. + * ---------------- + */ + econtext->ecxt_outertuple = outerTupleSlot; +#ifdef HJDEBUG + printf("Probing "); +#endif + bucketno = ExecHashGetBucket(hashtable, econtext, outerVar); + bucket=(HashBucket)(ABSADDR(hashtable->top) + + bucketno * hashtable->bucketsize); + curtuple = NULL; + } +} + +/* ---------------------------------------------------------------- + * ExecInitHashJoin + * + * Init routine for HashJoin node. + * ---------------------------------------------------------------- + */ +bool /* return: initialization status */ +ExecInitHashJoin(HashJoin *node, EState *estate, Plan *parent) +{ + HashJoinState *hjstate; + Plan *outerNode; + Hash *hashNode; + + /* ---------------- + * assign the node's execution state + * ---------------- + */ + node->join.state = estate; + + /* ---------------- + * create state structure + * ---------------- + */ + hjstate = makeNode(HashJoinState); + + node->hashjoinstate = hjstate; + + /* ---------------- + * Miscellanious initialization + * + * + assign node's base_id + * + assign debugging hooks and + * + create expression context for node + * ---------------- + */ + ExecAssignNodeBaseInfo(estate, &hjstate->jstate, parent); + ExecAssignExprContext(estate, &hjstate->jstate); + +#define HASHJOIN_NSLOTS 2 + /* ---------------- + * tuple table initialization + * ---------------- + */ + ExecInitResultTupleSlot(estate, &hjstate->jstate); + ExecInitOuterTupleSlot(estate, hjstate); + + /* ---------------- + * initializes child nodes + * ---------------- + */ + outerNode = outerPlan((Plan *)node); + hashNode = (Hash*)innerPlan((Plan *)node); + + ExecInitNode(outerNode, estate, (Plan *) node); + ExecInitNode((Plan*)hashNode, estate, (Plan *) node); + + /* ---------------- + * now for some voodoo. our temporary tuple slot + * is actually the result tuple slot of the Hash node + * (which is our inner plan). we do this because Hash + * nodes don't return tuples via ExecProcNode() -- instead + * the hash join node uses ExecScanHashBucket() to get + * at the contents of the hash table. -cim 6/9/91 + * ---------------- + */ + { + HashState *hashstate = hashNode->hashstate; + TupleTableSlot *slot = + hashstate->cstate.cs_ResultTupleSlot; + hjstate->hj_HashTupleSlot = slot; + } + hjstate->hj_OuterTupleSlot->ttc_tupleDescriptor = + ExecGetTupType(outerNode); + +/* + hjstate->hj_OuterTupleSlot->ttc_execTupDescriptor = + ExecGetExecTupDesc(outerNode); +*/ + + /* ---------------- + * initialize tuple type and projection info + * ---------------- + */ + ExecAssignResultTypeFromTL((Plan*) node, &hjstate->jstate); + ExecAssignProjectionInfo((Plan*) node, &hjstate->jstate); + + /* ---------------- + * XXX comment me + * ---------------- + */ + + node->hashdone = false; + + hjstate->hj_HashTable = (HashJoinTable)NULL; + hjstate->hj_HashTableShmId = (IpcMemoryId)0; + hjstate->hj_CurBucket = (HashBucket )NULL; + hjstate->hj_CurTuple = (HeapTuple )NULL; + hjstate->hj_CurOTuple = (OverflowTuple )NULL; + hjstate->hj_InnerHashKey = (Var*)NULL; + hjstate->hj_OuterBatches = (File*)NULL; + hjstate->hj_InnerBatches = (File*)NULL; + hjstate->hj_OuterReadPos = (char*)NULL; + hjstate->hj_OuterReadBlk = (int)0; + + hjstate->jstate.cs_OuterTupleSlot = (TupleTableSlot*) NULL; + hjstate->jstate.cs_TupFromTlist = (bool) false; + + return TRUE; +} + +int +ExecCountSlotsHashJoin(HashJoin *node) +{ + return ExecCountSlotsNode(outerPlan(node)) + + ExecCountSlotsNode(innerPlan(node)) + + HASHJOIN_NSLOTS; +} + +/* ---------------------------------------------------------------- + * ExecEndHashJoin + * + * clean up routine for HashJoin node + * ---------------------------------------------------------------- + */ +void +ExecEndHashJoin(HashJoin *node) +{ + HashJoinState *hjstate; + + /* ---------------- + * get info from the HashJoin state + * ---------------- + */ + hjstate = node->hashjoinstate; + + /* ---------------- + * free hash table in case we end plan before all tuples are retrieved + * --------------- + */ + if (hjstate->hj_HashTable) { + ExecHashTableDestroy(hjstate->hj_HashTable); + hjstate->hj_HashTable = NULL; + } + + /* ---------------- + * Free the projection info and the scan attribute info + * + * Note: we don't ExecFreeResultType(hjstate) + * because the rule manager depends on the tupType + * returned by ExecMain(). So for now, this + * is freed at end-transaction time. -cim 6/2/91 + * ---------------- + */ + ExecFreeProjectionInfo(&hjstate->jstate); + + /* ---------------- + * clean up subtrees + * ---------------- + */ + ExecEndNode(outerPlan((Plan *) node), (Plan*)node); + ExecEndNode(innerPlan((Plan *) node), (Plan*)node); + + /* ---------------- + * clean out the tuple table + * ---------------- + */ + ExecClearTuple(hjstate->jstate.cs_ResultTupleSlot); + ExecClearTuple(hjstate->hj_OuterTupleSlot); + ExecClearTuple(hjstate->hj_HashTupleSlot); + +} + +/* ---------------------------------------------------------------- + * ExecHashJoinOuterGetTuple + * + * get the next outer tuple for hashjoin: either by + * executing a plan node as in the first pass, or from + * the tmp files for the hashjoin batches. + * ---------------------------------------------------------------- + */ + +static TupleTableSlot * +ExecHashJoinOuterGetTuple(Plan *node, Plan* parent, HashJoinState *hjstate) +{ + TupleTableSlot *slot; + HashJoinTable hashtable; + int curbatch; + File *outerbatches; + char *outerreadPos; + int batchno; + char *outerreadBuf; + int outerreadBlk; + + hashtable = hjstate->hj_HashTable; + curbatch = hashtable->curbatch; + + if (curbatch == 0) { /* if it is the first pass */ + slot = ExecProcNode(node, parent); + return slot; + } + + /* + * otherwise, read from the tmp files + */ + outerbatches = hjstate->hj_OuterBatches; + outerreadPos = hjstate->hj_OuterReadPos; + outerreadBlk = hjstate->hj_OuterReadBlk; + outerreadBuf = ABSADDR(hashtable->readbuf); + batchno = curbatch - 1; + + slot = ExecHashJoinGetSavedTuple(hjstate, + outerreadBuf, + outerbatches[batchno], + hjstate->hj_OuterTupleSlot, + &outerreadBlk, + &outerreadPos); + + hjstate->hj_OuterReadPos = outerreadPos; + hjstate->hj_OuterReadBlk = outerreadBlk; + + return slot; +} + +/* ---------------------------------------------------------------- + * ExecHashJoinGetSavedTuple + * + * read the next tuple from a tmp file using a certain buffer + * ---------------------------------------------------------------- + */ + +static TupleTableSlot * +ExecHashJoinGetSavedTuple(HashJoinState *hjstate, + char *buffer, + File file, + TupleTableSlot *tupleSlot, + int *block, /* return parameter */ + char **position) /* return parameter */ +{ + char *bufstart; + char *bufend; + int cc; + HeapTuple heapTuple; + HashJoinTable hashtable; + + hashtable = hjstate->hj_HashTable; + bufend = buffer + *(long*)buffer; + bufstart = (char*)(buffer + sizeof(long)); + if ((*position == NULL) || (*position >= bufend)) { + if (*position == NULL) + (*block) = 0; + else + (*block)++; + FileSeek(file, *block * BLCKSZ, SEEK_SET); + cc = FileRead(file, buffer, BLCKSZ); + NDirectFileRead++; + if (cc < 0) + perror("FileRead"); + if (cc == 0) /* end of file */ + return NULL; + else + (*position) = bufstart; + } + heapTuple = (HeapTuple) (*position); + (*position) = (char*)LONGALIGN(*position + heapTuple->t_len); + + return ExecStoreTuple(heapTuple,tupleSlot,InvalidBuffer,false); +} + +/* ---------------------------------------------------------------- + * ExecHashJoinNewBatch + * + * switch to a new hashjoin batch + * ---------------------------------------------------------------- + */ +int +ExecHashJoinNewBatch(HashJoinState *hjstate) +{ + File *innerBatches; + File *outerBatches; + int *innerBatchSizes; + Var *innerhashkey; + HashJoinTable hashtable; + int nbatch; + char *readPos; + int readBlk; + char *readBuf; + TupleTableSlot *slot; + ExprContext *econtext; + int i; + int cc; + int newbatch; + + hashtable = hjstate->hj_HashTable; + outerBatches = hjstate->hj_OuterBatches; + innerBatches = hjstate->hj_InnerBatches; + nbatch = hashtable->nbatch; + newbatch = hashtable->curbatch + 1; + + /* ------------------ + * this is the last process, so it will do the cleanup and + * batch-switching. + * ------------------ + */ + if (newbatch == 1) { + /* + * if it is end of the first pass, flush all the last pages for + * the batches. + */ + outerBatches = hjstate->hj_OuterBatches; + for (i=0; i<nbatch; i++) { + cc = FileSeek(outerBatches[i], 0L, SEEK_END); + if (cc < 0) + perror("FileSeek"); + cc = FileWrite(outerBatches[i], + ABSADDR(hashtable->batch) + i * BLCKSZ, BLCKSZ); + NDirectFileWrite++; + if (cc < 0) + perror("FileWrite"); + } + } + if (newbatch > 1) { + /* + * remove the previous outer batch + */ + FileUnlink(outerBatches[newbatch - 2]); + } + /* + * rebuild the hash table for the new inner batch + */ + innerBatchSizes = (int*)ABSADDR(hashtable->innerbatchSizes); + /* -------------- + * skip over empty inner batches + * -------------- + */ + while (newbatch <= nbatch && innerBatchSizes[newbatch - 1] == 0) { + FileUnlink(outerBatches[newbatch-1]); + FileUnlink(innerBatches[newbatch-1]); + newbatch++; + } + if (newbatch > nbatch) { + hashtable->pcount = hashtable->nprocess; + + return newbatch; + } + ExecHashTableReset(hashtable, innerBatchSizes[newbatch - 1]); + + + econtext = hjstate->jstate.cs_ExprContext; + innerhashkey = hjstate->hj_InnerHashKey; + readPos = NULL; + readBlk = 0; + readBuf = ABSADDR(hashtable->readbuf); + + while ((slot = ExecHashJoinGetSavedTuple(hjstate, + readBuf, + innerBatches[newbatch-1], + hjstate->hj_HashTupleSlot, + &readBlk, + &readPos)) + && ! TupIsNull(slot)) { + econtext->ecxt_innertuple = slot; + ExecHashTableInsert(hashtable, econtext, innerhashkey,NULL); + /* possible bug - glass */ + } + + + /* ----------------- + * only the last process comes to this branch + * now all the processes have finished the build phase + * ---------------- + */ + + /* + * after we build the hash table, the inner batch is no longer needed + */ + FileUnlink(innerBatches[newbatch - 1]); + hjstate->hj_OuterReadPos = NULL; + hashtable->pcount = hashtable->nprocess; + + hashtable->curbatch = newbatch; + return newbatch; +} + +/* ---------------------------------------------------------------- + * ExecHashJoinGetBatch + * + * determine the batch number for a bucketno + * +----------------+-------+-------+ ... +-------+ + * 0 nbuckets totalbuckets + * batch 0 1 2 ... + * ---------------------------------------------------------------- + */ +int +ExecHashJoinGetBatch(int bucketno, HashJoinTable hashtable, int nbatch) +{ + int b; + if (bucketno < hashtable->nbuckets || nbatch == 0) + return 0; + + b = (float)(bucketno - hashtable->nbuckets) / + (float)(hashtable->totalbuckets - hashtable->nbuckets) * + nbatch; + return b+1; +} + +/* ---------------------------------------------------------------- + * ExecHashJoinSaveTuple + * + * save a tuple to a tmp file using a buffer. + * the first few bytes in a page is an offset to the end + * of the page. + * ---------------------------------------------------------------- + */ + +char * +ExecHashJoinSaveTuple(HeapTuple heapTuple, + char *buffer, + File file, + char *position) +{ + long *pageend; + char *pagestart; + char *pagebound; + int cc; + + pageend = (long*)buffer; + pagestart = (char*)(buffer + sizeof(long)); + pagebound = buffer + BLCKSZ; + if (position == NULL) + position = pagestart; + + if (position + heapTuple->t_len >= pagebound) { + cc = FileSeek(file, 0L, SEEK_END); + if (cc < 0) + perror("FileSeek"); + cc = FileWrite(file, buffer, BLCKSZ); + NDirectFileWrite++; + if (cc < 0) + perror("FileWrite"); + position = pagestart; + *pageend = 0; + } + memmove(position, heapTuple, heapTuple->t_len); + position = (char*)LONGALIGN(position + heapTuple->t_len); + *pageend = position - buffer; + + return position; +} diff --git a/src/backend/executor/nodeHashjoin.h b/src/backend/executor/nodeHashjoin.h new file mode 100644 index 00000000000..b8c12942b3b --- /dev/null +++ b/src/backend/executor/nodeHashjoin.h @@ -0,0 +1,33 @@ +/*------------------------------------------------------------------------- + * + * nodeHashjoin.h-- + * + * + * + * Copyright (c) 1994, Regents of the University of California + * + * $Id: nodeHashjoin.h,v 1.1.1.1 1996/07/09 06:21:26 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +#ifndef NODEHASHJOIN_H +#define NODEHASHJOIN_H + +extern TupleTableSlot *ExecHashJoin(HashJoin *node); + +extern bool ExecInitHashJoin(HashJoin *node, EState *estate, Plan *parent); + +extern int ExecCountSlotsHashJoin(HashJoin *node); + +extern void ExecEndHashJoin(HashJoin *node); + +extern int ExecHashJoinNewBatch(HashJoinState *hjstate); + +extern char *ExecHashJoinSaveTuple(HeapTuple heapTuple, char *buffer, + File file, char *position); + +extern int ExecHashJoinGetBatch(int bucketno, HashJoinTable hashtable, + int nbatch); + + +#endif /* NODEHASHJOIN_H */ diff --git a/src/backend/executor/nodeIndexscan.c b/src/backend/executor/nodeIndexscan.c new file mode 100644 index 00000000000..758fabdefe5 --- /dev/null +++ b/src/backend/executor/nodeIndexscan.c @@ -0,0 +1,902 @@ +/*------------------------------------------------------------------------- + * + * nodeIndexscan.c-- + * Routines to support indexes and indexed scans of relations + * + * Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * $Header: /cvsroot/pgsql/src/backend/executor/nodeIndexscan.c,v 1.1.1.1 1996/07/09 06:21:26 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +/* + * INTERFACE ROUTINES + * ExecInsertIndexTuples inserts tuples into indices on result relation + * + * ExecIndexScan scans a relation using indices + * ExecIndexNext using index to retrieve next tuple + * ExecInitIndexScan creates and initializes state info. + * ExecIndexReScan rescans the indexed relation. + * ExecEndIndexScan releases all storage. + * ExecIndexMarkPos marks scan position. + * ExecIndexRestrPos restores scan position. + * + * NOTES + * the code supporting ExecInsertIndexTuples should be + * collected and merged with the genam stuff. + * + */ +#include "executor/executor.h" +#include "executor/nodeIndexscan.h" + +#include "optimizer/clauses.h" /* for get_op, get_leftop, get_rightop */ +#include "parser/parsetree.h" /* for rt_fetch() */ + +#include "access/skey.h" +#include "utils/palloc.h" +#include "catalog/index.h" +#include "storage/bufmgr.h" +#include "storage/lmgr.h" +#include "nodes/nodeFuncs.h" + +/* ---------------- + * Misc stuff to move to executor.h soon -cim 6/5/90 + * ---------------- + */ +#define NO_OP 0 +#define LEFT_OP 1 +#define RIGHT_OP 2 + +static TupleTableSlot *IndexNext(IndexScan *node); + +/* ---------------------------------------------------------------- + * IndexNext + * + * Retrieve a tuple from the IndexScan node's currentRelation + * using the indices in the IndexScanState information. + * + * note: the old code mentions 'Primary indices'. to my knowledge + * we only support a single secondary index. -cim 9/11/89 + * + * old comments: + * retrieve a tuple from relation using the indices given. + * The indices are used in the order they appear in 'indices'. + * The indices may be primary or secondary indices: + * * primary index -- scan the relation 'relID' using keys supplied. + * * secondary index -- scan the index relation to get the 'tid' for + * a tuple in the relation 'relID'. + * If the current index(pointed by 'indexPtr') fails to return a + * tuple, the next index in the indices is used. + * + * bug fix so that it should retrieve on a null scan key. + * ---------------------------------------------------------------- + */ +static TupleTableSlot * +IndexNext(IndexScan *node) +{ + EState *estate; + CommonScanState *scanstate; + IndexScanState *indexstate; + ScanDirection direction; + int indexPtr; + IndexScanDescPtr scanDescs; + IndexScanDesc scandesc; + Relation heapRelation; + RetrieveIndexResult result; + ItemPointer iptr; + HeapTuple tuple; + TupleTableSlot *slot; + Buffer buffer = InvalidBuffer; + + /* ---------------- + * extract necessary information from index scan node + * ---------------- + */ + estate = node->scan.plan.state; + direction = estate->es_direction; + scanstate = node->scan.scanstate; + indexstate = node->indxstate; + indexPtr = indexstate->iss_IndexPtr; + scanDescs = indexstate->iss_ScanDescs; + scandesc = scanDescs[ indexPtr ]; + heapRelation = scanstate->css_currentRelation; + + slot = scanstate->css_ScanTupleSlot; + + /* ---------------- + * ok, now that we have what we need, fetch an index tuple. + * ---------------- + */ + + for(;;) { + result = index_getnext(scandesc, direction); + /* ---------------- + * if scanning this index succeeded then return the + * appropriate heap tuple.. else return NULL. + * ---------------- + */ + if (result) { + iptr = &result->heap_iptr; + tuple = heap_fetch(heapRelation, + NowTimeQual, + iptr, + &buffer); + /* be tidy */ + pfree(result); + + if (tuple == NULL) { + /* ---------------- + * we found a deleted tuple, so keep on scanning.. + * ---------------- + */ + if (BufferIsValid(buffer)) + ReleaseBuffer(buffer); + continue; + } + + /* ---------------- + * store the scanned tuple in the scan tuple slot of + * the scan state. Eventually we will only do this and not + * return a tuple. Note: we pass 'false' because tuples + * returned by amgetnext are pointers onto disk pages and + * were not created with palloc() and so should not be pfree()'d. + * ---------------- + */ + ExecStoreTuple(tuple, /* tuple to store */ + slot, /* slot to store in */ + buffer, /* buffer associated with tuple */ + false); /* don't pfree */ + + return slot; + } + + /* ---------------- + * if we get here it means the index scan failed so we + * are at the end of the scan.. + * ---------------- + */ + return ExecClearTuple(slot); + } +} + +/* ---------------------------------------------------------------- + * ExecIndexScan(node) + * + * old comments: + * Scans the relation using primary or secondary indices and returns + * the next qualifying tuple in the direction specified. + * It calls ExecScan() and passes it the access methods which returns + * the next tuple using the indices. + * + * Conditions: + * -- the "cursor" maintained by the AMI is positioned at the tuple + * returned previously. + * + * Initial States: + * -- the relation indicated is opened for scanning so that the + * "cursor" is positioned before the first qualifying tuple. + * -- all index realtions are opened for scanning. + * -- indexPtr points to the first index. + * -- state variable ruleFlag = nil. + * ---------------------------------------------------------------- + */ +TupleTableSlot * +ExecIndexScan(IndexScan *node) +{ + TupleTableSlot *returnTuple; + + /* ---------------- + * use IndexNext as access method + * ---------------- + */ + returnTuple = ExecScan(&node->scan, IndexNext); + return returnTuple; +} + +/* ---------------------------------------------------------------- + * ExecIndexReScan(node) + * + * Recalculates the value of the scan keys whose value depends on + * information known at runtime and rescans the indexed relation. + * Updating the scan key was formerly done separately in + * ExecUpdateIndexScanKeys. Integrating it into ReScan + * makes rescans of indices and + * relations/general streams more uniform. + * + * ---------------------------------------------------------------- + */ +void +ExecIndexReScan(IndexScan *node, ExprContext *exprCtxt, Plan* parent) +{ + EState *estate; + IndexScanState *indexstate; + ScanDirection direction; + IndexScanDescPtr scanDescs; + ScanKey *scanKeys; + IndexScanDesc sdesc; + ScanKey skey; + int numIndices; + int i; + + Pointer *runtimeKeyInfo; + int indexPtr; + int *numScanKeys; + List *indxqual; + List *qual; + int n_keys; + ScanKey scan_keys; + int *run_keys; + int j; + Expr *clause; + Node *scanexpr; + Datum scanvalue; + bool isNull; + bool isDone; + + indexstate = node->indxstate; + estate = node->scan.plan.state; + direction = estate->es_direction; + indexstate = node->indxstate; + numIndices = indexstate->iss_NumIndices; + scanDescs = indexstate->iss_ScanDescs; + scanKeys = indexstate->iss_ScanKeys; + + runtimeKeyInfo = (Pointer *) indexstate->iss_RuntimeKeyInfo; + + if (runtimeKeyInfo != NULL) { + /* + * get the index qualifications and + * recalculate the appropriate values + */ + indexPtr = indexstate->iss_IndexPtr; + indxqual = node->indxqual; + qual = nth(indexPtr, indxqual); + numScanKeys = indexstate->iss_NumScanKeys; + n_keys = numScanKeys[indexPtr]; + run_keys = (int *) runtimeKeyInfo[indexPtr]; + scan_keys = (ScanKey) scanKeys[indexPtr]; + + for (j=0; j < n_keys; j++) { + /* + * If we have a run-time key, then extract the run-time + * expression and evaluate it with respect to the current + * outer tuple. We then stick the result into the scan + * key. + */ + if (run_keys[j] != NO_OP) { + clause = nth(j, qual); + scanexpr = (run_keys[j] == RIGHT_OP) ? + (Node*) get_rightop(clause) : (Node*) get_leftop(clause) ; + /* pass in isDone but ignore it. We don't iterate in quals */ + scanvalue = (Datum) + ExecEvalExpr(scanexpr, exprCtxt, &isNull, &isDone); + scan_keys[j].sk_argument = scanvalue; + } + } + } + + /* + * rescans all indices + * + * note: AMrescan assumes only one scan key. This may have + * to change if we ever decide to support multiple keys. + */ + for (i = 0; i < numIndices; i++) { + sdesc = scanDescs[ i ]; + skey = scanKeys[ i ]; + index_rescan(sdesc, direction, skey); + } + + /* ---------------- + * perhaps return something meaningful + * ---------------- + */ + return; +} + +/* ---------------------------------------------------------------- + * ExecEndIndexScan + * + * old comments + * Releases any storage allocated through C routines. + * Returns nothing. + * ---------------------------------------------------------------- + */ +void +ExecEndIndexScan(IndexScan *node) +{ + CommonScanState *scanstate; + IndexScanState *indexstate; + ScanKey *scanKeys; + int numIndices; + int i; + + scanstate = node->scan.scanstate; + indexstate = node->indxstate; + + /* ---------------- + * extract information from the node + * ---------------- + */ + numIndices = indexstate->iss_NumIndices; + scanKeys = indexstate->iss_ScanKeys; + + /* ---------------- + * Free the projection info and the scan attribute info + * + * Note: we don't ExecFreeResultType(scanstate) + * because the rule manager depends on the tupType + * returned by ExecMain(). So for now, this + * is freed at end-transaction time. -cim 6/2/91 + * ---------------- + */ + ExecFreeProjectionInfo(&scanstate->cstate); + + /* ---------------- + * close the heap and index relations + * ---------------- + */ + ExecCloseR((Plan *) node); + + /* ---------------- + * free the scan keys used in scanning the indices + * ---------------- + */ + for (i=0; i<numIndices; i++) { + if (scanKeys[i]!=NULL) + pfree(scanKeys[i]); + + } + + /* ---------------- + * clear out tuple table slots + * ---------------- + */ + ExecClearTuple(scanstate->cstate.cs_ResultTupleSlot); + ExecClearTuple(scanstate->css_ScanTupleSlot); +/* ExecClearTuple(scanstate->css_RawTupleSlot); */ +} + +/* ---------------------------------------------------------------- + * ExecIndexMarkPos + * + * old comments + * Marks scan position by marking the current index. + * Returns nothing. + * ---------------------------------------------------------------- + */ +void +ExecIndexMarkPos(IndexScan *node) +{ + IndexScanState *indexstate; + IndexScanDescPtr indexScanDescs; + IndexScanDesc scanDesc; + int indexPtr; + + indexstate = node->indxstate; + indexPtr = indexstate->iss_IndexPtr; + indexScanDescs = indexstate->iss_ScanDescs; + scanDesc = indexScanDescs[ indexPtr ]; + + /* ---------------- + * XXX access methods don't return marked positions so + * ---------------- + */ + IndexScanMarkPosition( scanDesc ); + return; +} + +/* ---------------------------------------------------------------- + * ExecIndexRestrPos + * + * old comments + * Restores scan position by restoring the current index. + * Returns nothing. + * + * XXX Assumes previously marked scan position belongs to current index + * ---------------------------------------------------------------- + */ +void +ExecIndexRestrPos(IndexScan *node) +{ + IndexScanState *indexstate; + IndexScanDescPtr indexScanDescs; + IndexScanDesc scanDesc; + int indexPtr; + + indexstate = node->indxstate; + indexPtr = indexstate->iss_IndexPtr; + indexScanDescs = indexstate->iss_ScanDescs; + scanDesc = indexScanDescs[ indexPtr ]; + + IndexScanRestorePosition( scanDesc ); +} + +/* ---------------------------------------------------------------- + * ExecInitIndexScan + * + * Initializes the index scan's state information, creates + * scan keys, and opens the base and index relations. + * + * Note: index scans have 2 sets of state information because + * we have to keep track of the base relation and the + * index relations. + * + * old comments + * Creates the run-time state information for the node and + * sets the relation id to contain relevant decriptors. + * + * Parameters: + * node: IndexNode node produced by the planner. + * estate: the execution state initialized in InitPlan. + * ---------------------------------------------------------------- + */ +bool +ExecInitIndexScan(IndexScan *node, EState *estate, Plan *parent) +{ + IndexScanState *indexstate; + CommonScanState *scanstate; + List *indxqual; + List *indxid; + int i; + int numIndices; + int indexPtr; + ScanKey *scanKeys; + int *numScanKeys; + RelationPtr relationDescs; + IndexScanDescPtr scanDescs; + Pointer *runtimeKeyInfo; + bool have_runtime_keys; + List *rangeTable; + RangeTblEntry *rtentry; + Index relid; + Oid reloid; + TimeQual timeQual; + + Relation currentRelation; + HeapScanDesc currentScanDesc; + ScanDirection direction; + int baseid; + + /* ---------------- + * assign execution state to node + * ---------------- + */ + node->scan.plan.state = estate; + + /* -------------------------------- + * Part 1) initialize scan state + * + * create new CommonScanState for node + * -------------------------------- + */ + scanstate = makeNode(CommonScanState); +/* + scanstate->ss_ProcOuterFlag = false; + scanstate->ss_OldRelId = 0; +*/ + + node->scan.scanstate = scanstate; + + /* ---------------- + * assign node's base_id .. we don't use AssignNodeBaseid() because + * the increment is done later on after we assign the index scan's + * scanstate. see below. + * ---------------- + */ + baseid = estate->es_BaseId; +/* scanstate->csstate.cstate.bnode.base_id = baseid; */ + scanstate->cstate.cs_base_id = baseid; + + /* ---------------- + * create expression context for node + * ---------------- + */ + ExecAssignExprContext(estate, &scanstate->cstate); + +#define INDEXSCAN_NSLOTS 3 + /* ---------------- + * tuple table initialization + * ---------------- + */ + ExecInitResultTupleSlot(estate, &scanstate->cstate); + ExecInitScanTupleSlot(estate, scanstate); +/* ExecInitRawTupleSlot(estate, scanstate); */ + + /* ---------------- + * initialize projection info. result type comes from scan desc + * below.. + * ---------------- + */ + ExecAssignProjectionInfo((Plan *) node, &scanstate->cstate); + + /* -------------------------------- + * Part 2) initialize index scan state + * + * create new IndexScanState for node + * -------------------------------- + */ + indexstate = makeNode(IndexScanState); + indexstate->iss_NumIndices = 0; + indexstate->iss_IndexPtr = 0; + indexstate->iss_ScanKeys = NULL; + indexstate->iss_NumScanKeys = NULL; + indexstate->iss_RuntimeKeyInfo = NULL; + indexstate->iss_RelationDescs = NULL; + indexstate->iss_ScanDescs = NULL; + + node->indxstate = indexstate; + + /* ---------------- + * assign base id to index scan state also + * ---------------- + */ + indexstate->cstate.cs_base_id = baseid; + baseid++; + estate->es_BaseId = baseid; + + /* ---------------- + * get the index node information + * ---------------- + */ + indxid = node->indxid; + indxqual = node->indxqual; + numIndices = length(indxid); + indexPtr = 0; + + CXT1_printf("ExecInitIndexScan: context is %d\n", CurrentMemoryContext); + + /* ---------------- + * scanKeys is used to keep track of the ScanKey's. This is needed + * because a single scan may use several indices and each index has + * its own ScanKey. + * ---------------- + */ + numScanKeys = (int *) palloc(numIndices * sizeof(int)); + scanKeys = (ScanKey *) palloc(numIndices * sizeof(ScanKey)); + relationDescs = (RelationPtr) palloc(numIndices * sizeof(Relation)); + scanDescs = (IndexScanDescPtr) palloc(numIndices * sizeof(IndexScanDesc)); + + /* ---------------- + * initialize runtime key info. + * ---------------- + */ + have_runtime_keys = false; + runtimeKeyInfo = (Pointer *) + palloc(numIndices * sizeof(Pointer)); + + /* ---------------- + * build the index scan keys from the index qualification + * ---------------- + */ + for (i=0; i < numIndices; i++) { + int j; + List *qual; + int n_keys; + ScanKey scan_keys; + int *run_keys; + + qual = nth(i, indxqual); + n_keys = length(qual); + scan_keys = (n_keys <= 0) ? NULL : + (ScanKey)palloc(n_keys * sizeof(ScanKeyData)); + + CXT1_printf("ExecInitIndexScan: context is %d\n", + CurrentMemoryContext); + + if (n_keys > 0) { + run_keys = (int *) palloc(n_keys * sizeof(int)); + } + + /* ---------------- + * for each opclause in the given qual, + * convert each qual's opclause into a single scan key + * ---------------- + */ + for (j=0; j < n_keys; j++) { + Expr *clause; /* one part of index qual */ + Oper *op; /* operator used in scan.. */ + Node *leftop; /* expr on lhs of operator */ + Node *rightop; /* expr on rhs ... */ + + int scanvar; /* which var identifies varattno */ + AttrNumber varattno; /* att number used in scan */ + Oid opid; /* operator id used in scan */ + Datum scanvalue; /* value used in scan (if const) */ + + /* ---------------- + * extract clause information from the qualification + * ---------------- + */ + clause = nth(j, qual); + + op = (Oper*)clause->oper; + if (!IsA(op,Oper)) + elog(WARN, "ExecInitIndexScan: op not an Oper!"); + + opid = op->opid; + + /* ---------------- + * Here we figure out the contents of the index qual. + * The usual case is (op var const) or (op const var) + * which means we form a scan key for the attribute + * listed in the var node and use the value of the const. + * + * If we don't have a const node, then it means that + * one of the var nodes refers to the "scan" tuple and + * is used to determine which attribute to scan, and the + * other expression is used to calculate the value used in + * scanning the index. + * + * This means our index scan's scan key is a function of + * information obtained during the execution of the plan + * in which case we need to recalculate the index scan key + * at run time. + * + * Hence, we set have_runtime_keys to true and then set + * the appropriate flag in run_keys to LEFT_OP or RIGHT_OP. + * The corresponding scan keys are recomputed at run time. + * ---------------- + */ + + scanvar = NO_OP; + + /* ---------------- + * determine information in leftop + * ---------------- + */ + leftop = (Node*) get_leftop(clause); + + if (IsA(leftop,Var) && var_is_rel((Var*)leftop)) { + /* ---------------- + * if the leftop is a "rel-var", then it means + * that it is a var node which tells us which + * attribute to use for our scan key. + * ---------------- + */ + varattno = ((Var*) leftop)->varattno; + scanvar = LEFT_OP; + } else if (IsA(leftop,Const)) { + /* ---------------- + * if the leftop is a const node then it means + * it identifies the value to place in our scan key. + * ---------------- + */ + run_keys[ j ] = NO_OP; + scanvalue = ((Const*) leftop)->constvalue; + } else if (leftop != NULL && + is_funcclause(leftop) && + var_is_rel(lfirst(((Expr*)leftop)->args))) { + /* ---------------- + * if the leftop is a func node then it means + * it identifies the value to place in our scan key. + * Since functional indices have only one attribute + * the attno must always be set to 1. + * ---------------- + */ + varattno = 1; + scanvar = LEFT_OP; + + } else { + /* ---------------- + * otherwise, the leftop contains information usable + * at runtime to figure out the value to place in our + * scan key. + * ---------------- + */ + have_runtime_keys = true; + run_keys[ j ] = LEFT_OP; + scanvalue = Int32GetDatum((int32) true); + } + + /* ---------------- + * now determine information in rightop + * ---------------- + */ + rightop = (Node*) get_rightop(clause); + + if (IsA(rightop,Var) && var_is_rel((Var*)rightop)) { + /* ---------------- + * here we make sure only one op identifies the + * scan-attribute... + * ---------------- + */ + if (scanvar == LEFT_OP) + elog(WARN, "ExecInitIndexScan: %s", + "both left and right op's are rel-vars"); + + /* ---------------- + * if the rightop is a "rel-var", then it means + * that it is a var node which tells us which + * attribute to use for our scan key. + * ---------------- + */ + varattno = ((Var*) rightop)->varattno; + scanvar = RIGHT_OP; + + } else if (IsA(rightop,Const)) { + /* ---------------- + * if the leftop is a const node then it means + * it identifies the value to place in our scan key. + * ---------------- + */ + run_keys[ j ] = NO_OP; + scanvalue = ((Const*) rightop)->constvalue; + + } else if (rightop!=NULL && + is_funcclause(rightop) && + var_is_rel(lfirst(((Expr*)rightop)->args))) { + /* ---------------- + * if the rightop is a func node then it means + * it identifies the value to place in our scan key. + * Since functional indices have only one attribute + * the attno must always be set to 1. + * ---------------- + */ + if (scanvar == LEFT_OP) + elog(WARN, "ExecInitIndexScan: %s", + "both left and right ops are rel-vars"); + + varattno = 1; + scanvar = RIGHT_OP; + + } else { + /* ---------------- + * otherwise, the leftop contains information usable + * at runtime to figure out the value to place in our + * scan key. + * ---------------- + */ + have_runtime_keys = true; + run_keys[ j ] = RIGHT_OP; + scanvalue = Int32GetDatum((int32) true); + } + + /* ---------------- + * now check that at least one op tells us the scan + * attribute... + * ---------------- + */ + if (scanvar == NO_OP) + elog(WARN, "ExecInitIndexScan: %s", + "neither leftop nor rightop refer to scan relation"); + + /* ---------------- + * initialize the scan key's fields appropriately + * ---------------- + */ + ScanKeyEntryInitialize(&scan_keys[j], + 0, + varattno, /* attribute number to scan */ + (RegProcedure) opid, /* reg proc to use */ + (Datum) scanvalue); /* constant */ + } + + /* ---------------- + * store the key information into our array. + * ---------------- + */ + numScanKeys[ i ] = n_keys; + scanKeys[ i ] = scan_keys; + runtimeKeyInfo[ i ] = (Pointer) run_keys; + } + + indexstate->iss_NumIndices = numIndices; + indexstate->iss_IndexPtr = indexPtr; + indexstate->iss_ScanKeys = scanKeys; + indexstate->iss_NumScanKeys = numScanKeys; + + /* ---------------- + * If all of our keys have the form (op var const) , then we have no + * runtime keys so we store NULL in the runtime key info. + * Otherwise runtime key info contains an array of pointers + * (one for each index) to arrays of flags (one for each key) + * which indicate that the qual needs to be evaluated at runtime. + * -cim 10/24/89 + * ---------------- + */ + if (have_runtime_keys) + { + indexstate->iss_RuntimeKeyInfo = (Pointer) runtimeKeyInfo; + } + else { + indexstate->iss_RuntimeKeyInfo = NULL; + for (i=0; i < numIndices; i++) { + List *qual; + int n_keys; + qual = nth(i, indxqual); + n_keys = length(qual); + if (n_keys > 0) + pfree(runtimeKeyInfo[i]); + } + pfree(runtimeKeyInfo); + } + + /* ---------------- + * get the range table and direction information + * from the execution state (these are needed to + * open the relations). + * ---------------- + */ + rangeTable = estate->es_range_table; + direction = estate->es_direction; + + /* ---------------- + * open the base relation + * ---------------- + */ + relid = node->scan.scanrelid; + rtentry = rt_fetch(relid, rangeTable); + reloid = rtentry->relid; + timeQual = rtentry->timeQual; + + ExecOpenScanR(reloid, /* relation */ + 0, /* nkeys */ + (ScanKey) NULL, /* scan key */ + 0, /* is index */ + direction, /* scan direction */ + timeQual, /* time qual */ + ¤tRelation, /* return: rel desc */ + (Pointer *) ¤tScanDesc); /* return: scan desc */ + + scanstate->css_currentRelation = currentRelation; + scanstate->css_currentScanDesc = currentScanDesc; + + + /* ---------------- + * get the scan type from the relation descriptor. + * ---------------- + */ + ExecAssignScanType(scanstate, RelationGetTupleDescriptor(currentRelation)); + ExecAssignResultTypeFromTL((Plan *) node, &scanstate->cstate); + + /* ---------------- + * index scans don't have subtrees.. + * ---------------- + */ +/* scanstate->ss_ProcOuterFlag = false; */ + + /* ---------------- + * open the index relations and initialize + * relation and scan descriptors. + * ---------------- + */ + for (i=0; i < numIndices; i++) { + Oid indexOid; + + indexOid = (Oid)nth(i, indxid); + + if (indexOid != 0) { + ExecOpenScanR(indexOid, /* relation */ + numScanKeys[ i ], /* nkeys */ + scanKeys[ i ], /* scan key */ + true, /* is index */ + direction, /* scan direction */ + timeQual, /* time qual */ + &(relationDescs[ i ]), /* return: rel desc */ + (Pointer *) &(scanDescs[ i ])); + /* return: scan desc */ + } + } + + indexstate->iss_RelationDescs = relationDescs; + indexstate->iss_ScanDescs = scanDescs; + + indexstate->cstate.cs_TupFromTlist = false; + + /* ---------------- + * all done. + * ---------------- + */ + return TRUE; +} + +int +ExecCountSlotsIndexScan(IndexScan *node) +{ + return ExecCountSlotsNode(outerPlan((Plan *)node)) + + ExecCountSlotsNode(innerPlan((Plan *)node)) + + INDEXSCAN_NSLOTS; +} diff --git a/src/backend/executor/nodeIndexscan.h b/src/backend/executor/nodeIndexscan.h new file mode 100644 index 00000000000..27bbff0a293 --- /dev/null +++ b/src/backend/executor/nodeIndexscan.h @@ -0,0 +1,32 @@ +/*------------------------------------------------------------------------- + * + * nodeIndexscan.h-- + * + * + * + * Copyright (c) 1994, Regents of the University of California + * + * $Id: nodeIndexscan.h,v 1.1.1.1 1996/07/09 06:21:26 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +#ifndef NODEINDEXSCAN_H +#define NODEINDEXSCAN_H + +extern TupleTableSlot *ExecIndexScan(IndexScan *node); + +extern void ExecIndexReScan(IndexScan *node, ExprContext *exprCtxt, Plan *parent); + +extern void ExecEndIndexScan(IndexScan *node); + +extern void ExecIndexMarkPos(IndexScan *node); + +extern void ExecIndexRestrPos(IndexScan *node); + +extern void ExecUpdateIndexScanKeys(IndexScan *node, ExprContext *econtext); + +extern bool ExecInitIndexScan(IndexScan *node, EState *estate, Plan *parent); + +extern int ExecCountSlotsIndexScan(IndexScan *node); + +#endif /* NODEINDEXSCAN_H */ diff --git a/src/backend/executor/nodeMaterial.c b/src/backend/executor/nodeMaterial.c new file mode 100644 index 00000000000..88fc93d2d4e --- /dev/null +++ b/src/backend/executor/nodeMaterial.c @@ -0,0 +1,392 @@ +/*------------------------------------------------------------------------- + * + * nodeMaterial.c-- + * Routines to handle materialization nodes. + * + * Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * $Header: /cvsroot/pgsql/src/backend/executor/nodeMaterial.c,v 1.1.1.1 1996/07/09 06:21:26 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +/* + * INTERFACE ROUTINES + * ExecMaterial - generate a temporary relation + * ExecInitMaterial - initialize node and subnodes.. + * ExecEndMaterial - shutdown node and subnodes + * + */ + +#include "executor/executor.h" +#include "executor/nodeMaterial.h" +#include "catalog/catalog.h" +#include "optimizer/internal.h" /* for _TEMP_RELATION_ID_ */ + +/* ---------------------------------------------------------------- + * ExecMaterial + * + * The first time this is called, ExecMaterial retrieves tuples + * this node's outer subplan and inserts them into a temporary + * relation. After this is done, a flag is set indicating that + * the subplan has been materialized. Once the relation is + * materialized, the first tuple is then returned. Successive + * calls to ExecMaterial return successive tuples from the temp + * relation. + * + * Initial State: + * + * ExecMaterial assumes the temporary relation has been + * created and openend by ExecInitMaterial during the prior + * InitPlan() phase. + * + * ---------------------------------------------------------------- + */ +TupleTableSlot * /* result tuple from subplan */ +ExecMaterial(Material *node) +{ + EState *estate; + MaterialState *matstate; + Plan *outerNode; + ScanDirection dir; + Relation tempRelation; + Relation currentRelation; + HeapScanDesc currentScanDesc; + HeapTuple heapTuple; + TupleTableSlot *slot; + Buffer buffer; + + /* ---------------- + * get state info from node + * ---------------- + */ + matstate = node->matstate; + estate = node->plan.state; + dir = estate->es_direction; + + /* ---------------- + * the first time we call this, we retrieve all tuples + * from the subplan into a temporary relation and then + * we sort the relation. Subsequent calls return tuples + * from the temporary relation. + * ---------------- + */ + + if (matstate->mat_Flag == false) { + /* ---------------- + * set all relations to be scanned in the forward direction + * while creating the temporary relation. + * ---------------- + */ + estate->es_direction = EXEC_FRWD; + + /* ---------------- + * if we couldn't create the temp or current relations then + * we print a warning and return NULL. + * ---------------- + */ + tempRelation = matstate->mat_TempRelation; + if (tempRelation == NULL) { + elog(DEBUG, "ExecMaterial: temp relation is NULL! aborting..."); + return NULL; + } + + currentRelation = matstate->csstate.css_currentRelation; + if (currentRelation == NULL) { + elog(DEBUG, "ExecMaterial: current relation is NULL! aborting..."); + return NULL; + } + + /* ---------------- + * retrieve tuples from the subplan and + * insert them in the temporary relation + * ---------------- + */ + outerNode = outerPlan((Plan *) node); + for (;;) { + slot = ExecProcNode(outerNode, (Plan*) node); + + heapTuple = slot->val; + if (heapTuple == NULL) + break; + + heap_insert(tempRelation, /* relation desc */ + heapTuple); /* heap tuple to insert */ + + ExecClearTuple( slot); + } + currentRelation = tempRelation; + + /* ---------------- + * restore to user specified direction + * ---------------- + */ + estate->es_direction = dir; + + /* ---------------- + * now initialize the scan descriptor to scan the + * sorted relation and update the sortstate information + * ---------------- + */ + currentScanDesc = heap_beginscan(currentRelation, /* relation */ + ScanDirectionIsBackward(dir), + /* bkwd flag */ + NowTimeQual, /* time qual */ + 0, /* num scan keys */ + NULL); /* scan keys */ + matstate->csstate.css_currentRelation = currentRelation; + matstate->csstate.css_currentScanDesc = currentScanDesc; + + ExecAssignScanType(&matstate->csstate, + RelationGetTupleDescriptor(currentRelation)); + + /* ---------------- + * finally set the sorted flag to true + * ---------------- + */ + matstate->mat_Flag = true; + } + + /* ---------------- + * at this point we know we have a sorted relation so + * we preform a simple scan on it with amgetnext().. + * ---------------- + */ + currentScanDesc = matstate->csstate.css_currentScanDesc; + + heapTuple = heap_getnext(currentScanDesc, /* scan desc */ + ScanDirectionIsBackward(dir), + /* bkwd flag */ + &buffer); /* return: buffer */ + + /* ---------------- + * put the tuple into the scan tuple slot and return the slot. + * Note: since the tuple is really a pointer to a page, we don't want + * to call pfree() on it.. + * ---------------- + */ + slot = (TupleTableSlot *)matstate->csstate.css_ScanTupleSlot; + + return ExecStoreTuple(heapTuple, /* tuple to store */ + slot, /* slot to store in */ + buffer, /* buffer for this tuple */ + false); /* don't pfree this pointer */ + +} + +/* ---------------------------------------------------------------- + * ExecInitMaterial + * ---------------------------------------------------------------- + */ +bool /* initialization status */ +ExecInitMaterial(Material *node, EState *estate, Plan *parent) +{ + MaterialState *matstate; + Plan *outerPlan; + TupleDesc tupType; + Relation tempDesc; + int len; + + /* ---------------- + * assign the node's execution state + * ---------------- + */ + node->plan.state = estate; + + /* ---------------- + * create state structure + * ---------------- + */ + matstate = makeNode(MaterialState); + matstate->mat_Flag = false; + matstate->mat_TempRelation = NULL; + node->matstate = matstate; + + /* ---------------- + * Miscellanious initialization + * + * + assign node's base_id + * + assign debugging hooks and + * + assign result tuple slot + * + * Materialization nodes don't need ExprContexts because + * they never call ExecQual or ExecTargetList. + * ---------------- + */ + ExecAssignNodeBaseInfo(estate, &matstate->csstate.cstate, parent); + +#define MATERIAL_NSLOTS 1 + /* ---------------- + * tuple table initialization + * ---------------- + */ + ExecInitScanTupleSlot(estate, &matstate->csstate); + + /* ---------------- + * initializes child nodes + * ---------------- + */ + outerPlan = outerPlan((Plan *) node); + ExecInitNode(outerPlan, estate, (Plan *) node); + + /* ---------------- + * initialize matstate information + * ---------------- + */ + matstate->mat_Flag = false; + + /* ---------------- + * initialize tuple type. no need to initialize projection + * info because this node doesn't do projections. + * ---------------- + */ + ExecAssignScanTypeFromOuterPlan((Plan *) node, &matstate->csstate); + matstate->csstate.cstate.cs_ProjInfo = NULL; + + /* ---------------- + * get type information needed for ExecCreatR + * ---------------- + */ + tupType = ExecGetScanType(&matstate->csstate); + + /* ---------------- + * ExecCreatR wants it's second argument to be an object id of + * a relation in the range table or a _TEMP_RELATION_ID + * indicating that the relation is not in the range table. + * + * In the second case ExecCreatR creates a temp relation. + * (currently this is the only case we support -cim 10/16/89) + * ---------------- + */ + /* ---------------- + * create the temporary relation + * ---------------- + */ +/* len = ExecTargetListLength(node->plan.targetlist); */ + tempDesc = ExecCreatR(tupType, _TEMP_RELATION_ID_); + + /* ---------------- + * save the relation descriptor in the sortstate + * ---------------- + */ + matstate->mat_TempRelation = tempDesc; + matstate->csstate.css_currentRelation = tempDesc; + + /* ---------------- + * return relation oid of temporary relation in a list + * (someday -- for now we return LispTrue... cim 10/12/89) + * ---------------- + */ + return TRUE; +} + +int +ExecCountSlotsMaterial(Material *node) +{ + return ExecCountSlotsNode(outerPlan((Plan *)node)) + + ExecCountSlotsNode(innerPlan((Plan *)node)) + + MATERIAL_NSLOTS; +} + +/* ---------------------------------------------------------------- + * ExecEndMaterial + * + * old comments + * destroys the temporary relation. + * ---------------------------------------------------------------- + */ +void +ExecEndMaterial(Material *node) +{ + MaterialState *matstate; + Relation tempRelation; + Plan *outerPlan; + + /* ---------------- + * get info from the material state + * ---------------- + */ + matstate = node->matstate; + tempRelation = matstate->mat_TempRelation; + + heap_destroyr(tempRelation); + + /* ---------------- + * close the temp relation and shut down the scan. + * ---------------- + */ + ExecCloseR((Plan *) node); + + /* ---------------- + * shut down the subplan + * ---------------- + */ + outerPlan = outerPlan((Plan *) node); + ExecEndNode(outerPlan, (Plan*) node); + + /* ---------------- + * clean out the tuple table + * ---------------- + */ + ExecClearTuple(matstate->csstate.css_ScanTupleSlot); +} + +#if 0 /* not used */ +/* ---------------------------------------------------------------- + * ExecMaterialMarkPos + * ---------------------------------------------------------------- + */ +List /* nothing of interest */ +ExecMaterialMarkPos(Material node) +{ + MaterialState matstate; + HeapScanDesc sdesc; + + /* ---------------- + * if we haven't materialized yet, just return NIL. + * ---------------- + */ + matstate = get_matstate(node); + if (get_mat_Flag(matstate) == false) + return NIL; + + /* ---------------- + * XXX access methods don't return positions yet so + * for now we return NIL. It's possible that + * they will never return positions for all I know -cim 10/16/89 + * ---------------- + */ + sdesc = get_css_currentScanDesc((CommonScanState)matstate); + heap_markpos(sdesc); + + return NIL; +} + +/* ---------------------------------------------------------------- + * ExecMaterialRestrPos + * ---------------------------------------------------------------- + */ +void +ExecMaterialRestrPos(Material node) +{ + MaterialState matstate; + HeapScanDesc sdesc; + + /* ---------------- + * if we haven't materialized yet, just return. + * ---------------- + */ + matstate = get_matstate(node); + if (get_mat_Flag(matstate) == false) + return; + + /* ---------------- + * restore the scan to the previously marked position + * ---------------- + */ + sdesc = get_css_currentScanDesc((CommonScanState)matstate); + heap_restrpos(sdesc); +} +#endif + diff --git a/src/backend/executor/nodeMaterial.h b/src/backend/executor/nodeMaterial.h new file mode 100644 index 00000000000..d85b025b7bf --- /dev/null +++ b/src/backend/executor/nodeMaterial.h @@ -0,0 +1,23 @@ +/*------------------------------------------------------------------------- + * + * nodeMaterial.h-- + * + * + * + * Copyright (c) 1994, Regents of the University of California + * + * $Id: nodeMaterial.h,v 1.1.1.1 1996/07/09 06:21:27 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +#ifndef NODEMATERIAL_H +#define NODEMATERIAL_H + +extern TupleTableSlot *ExecMaterial(Material *node); +extern bool ExecInitMaterial(Material *node, EState *estate, Plan *parent); +extern int ExecCountSlotsMaterial(Material *node); +extern void ExecEndMaterial(Material *node); +extern List ExecMaterialMarkPos(Material *node); +extern void ExecMaterialRestrPos(Material *node); + +#endif /* NODEMATERIAL_H */ diff --git a/src/backend/executor/nodeMergejoin.c b/src/backend/executor/nodeMergejoin.c new file mode 100644 index 00000000000..54a3aa28c4e --- /dev/null +++ b/src/backend/executor/nodeMergejoin.c @@ -0,0 +1,1194 @@ +/*------------------------------------------------------------------------- + * + * nodeMergejoin.c-- + * routines supporting merge joins + * + * Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * $Header: /cvsroot/pgsql/src/backend/executor/nodeMergejoin.c,v 1.1.1.1 1996/07/09 06:21:27 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +/* + * INTERFACE ROUTINES + * ExecMergeJoin mergejoin outer and inner relations. + * ExecInitMergeJoin creates and initializes run time states + * ExecEndMergeJoin cleand up the node. + * + * NOTES + * Essential operation of the merge join algorithm is as follows: + * (** indicates the tuples satisify the merge clause). + * + * Join { - + * get initial outer and inner tuples INITIALIZE + * Skip Inner SKIPINNER + * mark inner position JOINMARK + * do forever { - + * while (outer ** inner) { JOINTEST + * join tuples JOINTUPLES + * advance inner position NEXTINNER + * } - + * advance outer position NEXTOUTER + * if (outer ** mark) { TESTOUTER + * restore inner position to mark TESTOUTER + * continue - + * } else { - + * Skip Outer SKIPOUTER + * mark inner position JOINMARK + * } - + * } - + * } - + * + * Skip Outer { SKIPOUTER + * if (inner ** outer) Join Tuples JOINTUPLES + * while (outer < inner) SKIPOUTER + * advance outer SKIPOUTER + * if (outer > inner) SKIPOUTER + * Skip Inner SKIPINNER + * } - + * + * Skip Inner { SKIPINNER + * if (inner ** outer) Join Tuples JOINTUPLES + * while (outer > inner) SKIPINNER + * advance inner SKIPINNER + * if (outer < inner) SKIPINNER + * Skip Outer SKIPOUTER + * } - + * + * Currently, the merge join operation is coded in the fashion + * of a state machine. At each state, we do something and then + * proceed to another state. This state is stored in the node's + * execution state information and is preserved across calls to + * ExecMergeJoin. -cim 10/31/89 + * + * Warning: This code is known to fail for inequality operations + * and is being redesigned. Specifically, = and > work + * but the logic is not correct for <. Since mergejoins + * are no better then nestloops for inequalitys, the planner + * should not plan them anyways. Alternatively, the + * planner could just exchange the inner/outer relations + * if it ever sees a <... -cim 7/1/90 + * + * Update: The executor tuple table has long since alleviated the + * problem described above -cim 4/23/91 + * + */ +#include "executor/executor.h" +#include "executor/nodeMergejoin.h" +#include "utils/lsyscache.h" + +/* ---------------------------------------------------------------- + * MarkInnerTuple and RestoreInnerTuple macros + * + * when we "mark" a tuple, we place a pointer to it + * in the marked tuple slot. now there are two pointers + * to this tuple and we don't want it to be freed until + * next time we mark a tuple, so we move the policy to + * the marked tuple slot and set the inner tuple slot policy + * to false. + * + * But, when we restore the inner tuple, the marked tuple + * retains the policy. Basically once a tuple is marked, it + * should only be freed when we mark another tuple. -cim 9/27/90 + * + * Note: now that we store buffers in the tuple table, + * we have to also increment buffer reference counts + * correctly whenever we propagate an additional pointer + * to a buffer item. Later, when ExecStoreTuple() is + * called again on this slot, the refcnt is decremented + * when the old tuple is replaced. + * ---------------------------------------------------------------- + */ +#define MarkInnerTuple(innerTupleSlot, mergestate) \ +{ \ + bool shouldFree; \ + shouldFree = ExecSetSlotPolicy(innerTupleSlot, false); \ + ExecStoreTuple(innerTupleSlot->val, \ + mergestate->mj_MarkedTupleSlot, \ + innerTupleSlot->ttc_buffer, \ + shouldFree); \ + ExecIncrSlotBufferRefcnt(innerTupleSlot); \ +} + +#define RestoreInnerTuple(innerTupleSlot, markedTupleSlot) \ + ExecStoreTuple(markedTupleSlot->val, \ + innerTupleSlot, \ + markedTupleSlot->ttc_buffer, \ + false); \ + ExecIncrSlotBufferRefcnt(innerTupleSlot) + +/* ---------------------------------------------------------------- + * MJFormOSortopI + * + * This takes the mergeclause which is a qualification of the + * form ((= expr expr) (= expr expr) ...) and forms a new + * qualification like ((> expr expr) (> expr expr) ...) which + * is used by ExecMergeJoin() in order to determine if we should + * skip tuples. + * + * old comments + * The 'qual' must be of the form: + * {(= outerkey1 innerkey1)(= outerkey2 innerkey2) ...} + * The "sortOp outerkey innerkey" is formed by substituting the "=" + * by "sortOp". + * ---------------------------------------------------------------- + */ +static List * +MJFormOSortopI(List *qualList, Oid sortOp) +{ + List *qualCopy; + List *qualcdr; + Expr *qual; + Oper *op; + + /* ---------------- + * qualList is a list: ((op .. ..) ...) + * first we make a copy of it. copyObject() makes a deep copy + * so let's use it instead of the old fashoned lispCopy()... + * ---------------- + */ + qualCopy = (List*) copyObject((Node*) qualList); + + foreach (qualcdr, qualCopy) { + /* ---------------- + * first get the current (op .. ..) list + * ---------------- + */ + qual = lfirst(qualcdr); + + /* ---------------- + * now get at the op + * ---------------- + */ + op = (Oper*)qual->oper; + if (!IsA(op,Oper)) { + elog(DEBUG, "MJFormOSortopI: op not an Oper!"); + return NIL; + } + + /* ---------------- + * change it's opid and since Op nodes now carry around a + * cached pointer to the associated op function, we have + * to make sure we invalidate this. Otherwise you get bizarre + * behavior when someone runs a mergejoin with _exec_repeat_ > 1 + * -cim 4/23/91 + * ---------------- + */ + op->opid = sortOp; + op->op_fcache = NULL; + } + + return qualCopy; +} + +/* ---------------------------------------------------------------- + * MJFormISortopO + * + * This does the same thing as MJFormOSortopI() except that + * it also reverses the expressions in the qualifications. + * For example: ((= expr1 expr2)) produces ((> expr2 expr1)) + * + * old comments + * The 'qual' must be of the form: + * {(= outerkey1 innerkey1) (= outerkey2 innerkey2) ...} + * The 'sortOp innerkey1 outerkey" is formed by substituting the "=" + * by "sortOp" and reversing the positions of the keys. + * ---------------------------------------------------------------- + */ +List * +MJFormISortopO(List *qualList, Oid sortOp) +{ + List *ISortopO; + List *qualcdr; + + /* ---------------- + * first generate OSortopI, a list of the form + * ((op outer inner) (op outer inner) ... ) + * ---------------- + */ + ISortopO = MJFormOSortopI(qualList, sortOp); + + /* ---------------- + * now swap the cadr and caddr of each qual to form ISortopO, + * ((op inner outer) (op inner outer) ... ) + * ---------------- + */ + foreach (qualcdr, ISortopO) { + Expr *qual; + List *inner; + List *outer; + qual = lfirst(qualcdr); + + inner = lfirst(qual->args); + outer = lfirst(lnext(qual->args)); + lfirst(qual->args) = outer; + lfirst(lnext(qual->args)) = inner; + } + + return ISortopO; +} + +/* ---------------------------------------------------------------- + * MergeCompare + * + * Compare the keys according to 'compareQual' which is of the + * form: {(key1a > key2a)(key1b > key2b) ...}. + * + * (actually, it could also be the form (key1a < key2a)..) + * + * This is different from calling ExecQual because ExecQual returns + * true only if ALL the comparisions clauses are satisfied. + * However, there is an order of significance among the keys with + * the first keys being most significant. Therefore, the clauses + * are evaluated in order and the 'compareQual' is satisfied + * if (key1i > key2i) is true and (key1j = key2j) for 0 < j < i. + * ---------------------------------------------------------------- + */ +bool +MergeCompare(List *eqQual, List *compareQual, ExprContext *econtext) +{ + List *clause; + List *eqclause; + Datum const_value; + bool isNull; + bool isDone; + + /* ---------------- + * if we have no compare qualification, return nil + * ---------------- + */ + if (compareQual == NIL) + return false; + + /* ---------------- + * for each pair of clauses, test them until + * our compare conditions are satisified + * ---------------- + */ + eqclause = eqQual; + foreach (clause, compareQual) { + /* ---------------- + * first test if our compare clause is satisified. + * if so then return true. ignore isDone, don't iterate in + * quals. + * ---------------- + */ + const_value = (Datum) + ExecEvalExpr((Node*) lfirst(clause), econtext, &isNull, &isDone); + + if (DatumGetInt32(const_value) != 0) + return true; + + /* ---------------- + * ok, the compare clause failed so we test if the keys + * are equal... if key1 != key2, we return false. + * otherwise key1 = key2 so we move on to the next pair of keys. + * + * ignore isDone, don't iterate in quals. + * ---------------- + */ + const_value = ExecEvalExpr((Node*) lfirst(eqclause), + econtext, + &isNull, + &isDone); + + if (DatumGetInt32(const_value) == 0) + return false; + eqclause = lnext(eqclause); + } + + /* ---------------- + * if we get here then it means none of our key greater-than + * conditions were satisified so we return false. + * ---------------- + */ + return false; +} + +/* ---------------------------------------------------------------- + * ExecMergeTupleDump + * + * This function is called through the MJ_dump() macro + * when EXEC_MERGEJOINDEBUG is defined + * ---------------------------------------------------------------- + */ +void +ExecMergeTupleDumpInner(ExprContext *econtext) +{ + TupleTableSlot *innerSlot; + + printf("==== inner tuple ====\n"); + innerSlot = econtext->ecxt_innertuple; + if (TupIsNull(innerSlot)) + printf("(nil)\n"); + else + debugtup(innerSlot->val, + innerSlot->ttc_tupleDescriptor); +} + +void +ExecMergeTupleDumpOuter(ExprContext *econtext) +{ + TupleTableSlot *outerSlot; + + printf("==== outer tuple ====\n"); + outerSlot = econtext->ecxt_outertuple; + if (TupIsNull(outerSlot)) + printf("(nil)\n"); + else + debugtup(outerSlot->val, + outerSlot->ttc_tupleDescriptor); +} + +void +ExecMergeTupleDumpMarked(ExprContext *econtext, + MergeJoinState *mergestate) +{ + TupleTableSlot *markedSlot; + + printf("==== marked tuple ====\n"); + markedSlot = mergestate->mj_MarkedTupleSlot; + + if (TupIsNull(markedSlot)) + printf("(nil)\n"); + else + debugtup(markedSlot->val, + markedSlot->ttc_tupleDescriptor); +} + +void +ExecMergeTupleDump(ExprContext *econtext, MergeJoinState *mergestate) +{ + printf("******** ExecMergeTupleDump ********\n"); + + ExecMergeTupleDumpInner(econtext); + ExecMergeTupleDumpOuter(econtext); + ExecMergeTupleDumpMarked(econtext, mergestate); + + printf("******** \n"); +} + +/* ---------------------------------------------------------------- + * ExecMergeJoin + * + * old comments + * Details of the merge-join routines: + * + * (1) ">" and "<" operators + * + * Merge-join is done by joining the inner and outer tuples satisfying + * the join clauses of the form ((= outerKey innerKey) ...). + * The join clauses is provided by the query planner and may contain + * more than one (= outerKey innerKey) clauses (for composite key). + * + * However, the query executor needs to know whether an outer + * tuple is "greater/smaller" than an inner tuple so that it can + * "synchronize" the two relations. For e.g., consider the following + * relations: + * + * outer: (0 ^1 1 2 5 5 5 6 6 7) current tuple: 1 + * inner: (1 ^3 5 5 5 5 6) current tuple: 3 + * + * To continue the merge-join, the executor needs to scan both inner + * and outer relations till the matching tuples 5. It needs to know + * that currently inner tuple 3 is "greater" than outer tuple 1 and + * therefore it should scan the outer relation first to find a + * matching tuple and so on. + * + * Therefore, when initializing the merge-join node, the executor + * creates the "greater/smaller" clause by substituting the "=" + * operator in the join clauses with the sort operator used to + * sort the outer and inner relation forming (outerKey sortOp innerKey). + * The sort operator is "<" if the relations are in ascending order + * otherwise, it is ">" if the relations are in descending order. + * The opposite "smaller/greater" clause is formed by reversing the + * outer and inner keys forming (innerKey sortOp outerKey). + * + * (2) repositioning inner "cursor" + * + * Consider the above relations and suppose that the executor has + * just joined the first outer "5" with the last inner "5". The + * next step is of course to join the second outer "5" with all + * the inner "5's". This requires repositioning the inner "cursor" + * to point at the first inner "5". This is done by "marking" the + * first inner 5 and restore the "cursor" to it before joining + * with the second outer 5. The access method interface provides + * routines to mark and restore to a tuple. + * ---------------------------------------------------------------- + */ +TupleTableSlot * +ExecMergeJoin(MergeJoin *node) +{ + EState *estate; + MergeJoinState *mergestate; + ScanDirection direction; + List *innerSkipQual; + List *outerSkipQual; + List *mergeclauses; + List *qual; + bool qualResult; + bool compareResult; + + Plan *innerPlan; + TupleTableSlot *innerTupleSlot; + + Plan *outerPlan; + TupleTableSlot *outerTupleSlot; + + TupleTableSlot *markedTupleSlot; + + ExprContext *econtext; + + /* ---------------- + * get information from node + * ---------------- + */ + mergestate = node->mergestate; + estate = node->join.state; + direction = estate->es_direction; + innerPlan = innerPlan((Plan *)node); + outerPlan = outerPlan((Plan *)node); + econtext = mergestate->jstate.cs_ExprContext; + mergeclauses = node->mergeclauses; + qual = node->join.qual; + + if (ScanDirectionIsForward(direction)) { + outerSkipQual = mergestate->mj_OSortopI; + innerSkipQual = mergestate->mj_ISortopO; + } else { + outerSkipQual = mergestate->mj_ISortopO; + innerSkipQual = mergestate->mj_OSortopI; + } + + /* ---------------- + * ok, everything is setup.. let's go to work + * ---------------- + */ + if (mergestate->jstate.cs_TupFromTlist) { + TupleTableSlot *result; + ProjectionInfo *projInfo; + bool isDone; + + projInfo = mergestate->jstate.cs_ProjInfo; + result = ExecProject(projInfo, &isDone); + if (!isDone) + return result; + } + for (;;) { + /* ---------------- + * get the current state of the join and do things accordingly. + * Note: The join states are highlighted with 32-* comments for + * improved readability. + * ---------------- + */ + MJ_dump(econtext, mergestate); + + switch (mergestate->mj_JoinState) { + /* ******************************** + * EXEC_MJ_INITIALIZE means that this is the first time + * ExecMergeJoin() has been called and so we have to + * initialize the inner, outer and marked tuples as well + * as various stuff in the expression context. + * ******************************** + */ + case EXEC_MJ_INITIALIZE: + MJ_printf("ExecMergeJoin: EXEC_MJ_INITIALIZE\n"); + /* ---------------- + * Note: at this point, if either of our inner or outer + * tuples are nil, then the join ends immediately because + * we know one of the subplans is empty. + * ---------------- + */ + innerTupleSlot = ExecProcNode(innerPlan, (Plan*)node); + if (TupIsNull(innerTupleSlot)) { + MJ_printf("ExecMergeJoin: **** inner tuple is nil ****\n"); + return NULL; + } + + outerTupleSlot = ExecProcNode(outerPlan, (Plan*)node); + if (TupIsNull(outerTupleSlot)) { + MJ_printf("ExecMergeJoin: **** outer tuple is nil ****\n"); + return NULL; + } + + /* ---------------- + * store the inner and outer tuple in the merge state + * ---------------- + */ + econtext->ecxt_innertuple = innerTupleSlot; + econtext->ecxt_outertuple = outerTupleSlot; + + /* ---------------- + * set the marked tuple to nil + * and initialize its tuple descriptor atttributes. + * -jeff 10 july 1991 + * ---------------- + */ + ExecClearTuple(mergestate->mj_MarkedTupleSlot); + mergestate->mj_MarkedTupleSlot->ttc_tupleDescriptor = + innerTupleSlot->ttc_tupleDescriptor; +/* + mergestate->mj_MarkedTupleSlot->ttc_execTupDescriptor = + innerTupleSlot->ttc_execTupDescriptor; +*/ + /* ---------------- + * initialize merge join state to skip inner tuples. + * ---------------- + */ + mergestate->mj_JoinState = EXEC_MJ_SKIPINNER; + break; + + /* ******************************** + * EXEC_MJ_JOINMARK means we have just found a new + * outer tuple and a possible matching inner tuple. + * This is the case after the INITIALIZE, SKIPOUTER + * or SKIPINNER states. + * ******************************** + */ + case EXEC_MJ_JOINMARK: + MJ_printf("ExecMergeJoin: EXEC_MJ_JOINMARK\n"); + ExecMarkPos(innerPlan); + + innerTupleSlot = econtext->ecxt_innertuple; + MarkInnerTuple(innerTupleSlot, mergestate); + + mergestate->mj_JoinState = EXEC_MJ_JOINTEST; + break; + + /* ******************************** + * EXEC_MJ_JOINTEST means we have two tuples which + * might satisify the merge clause, so we test them. + * + * If they do satisify, then we join them and move + * on to the next inner tuple (EXEC_MJ_JOINTUPLES). + * + * If they do not satisify then advance to next outer tuple. + * ******************************** + */ + case EXEC_MJ_JOINTEST: + MJ_printf("ExecMergeJoin: EXEC_MJ_JOINTEST\n"); + + qualResult = ExecQual((List*)mergeclauses, econtext); + MJ_DEBUG_QUAL(mergeclauses, qualResult); + + if (qualResult) + { + mergestate->mj_JoinState = EXEC_MJ_JOINTUPLES; + } + else + { + mergestate->mj_JoinState = EXEC_MJ_NEXTOUTER; + } + break; + + /* ******************************** + * EXEC_MJ_JOINTUPLES means we have two tuples which + * satisified the merge clause so we join them and then + * proceed to get the next inner tuple (EXEC_NEXT_INNER). + * ******************************** + */ + case EXEC_MJ_JOINTUPLES: + MJ_printf("ExecMergeJoin: EXEC_MJ_JOINTUPLES\n"); + mergestate->mj_JoinState = EXEC_MJ_NEXTINNER; + + qualResult = ExecQual((List*)qual, econtext); + MJ_DEBUG_QUAL(qual, qualResult); + + if (qualResult) { + /* ---------------- + * qualification succeeded. now form the desired + * projection tuple and return the slot containing it. + * ---------------- + */ + ProjectionInfo *projInfo; + TupleTableSlot *result; + bool isDone; + + MJ_printf("ExecMergeJoin: **** returning tuple ****\n"); + + projInfo = mergestate->jstate.cs_ProjInfo; + + result = ExecProject(projInfo, &isDone); + mergestate->jstate.cs_TupFromTlist = !isDone; + return result; + } + break; + + /* ******************************** + * EXEC_MJ_NEXTINNER means advance the inner scan + * to the next tuple. If the tuple is not nil, we then + * proceed to test it against the join qualification. + * ******************************** + */ + case EXEC_MJ_NEXTINNER: + MJ_printf("ExecMergeJoin: EXEC_MJ_NEXTINNER\n"); + + /* ---------------- + * now we get the next inner tuple, if any + * ---------------- + */ + innerTupleSlot = ExecProcNode(innerPlan, (Plan*)node); + MJ_DEBUG_PROC_NODE(innerTupleSlot); + econtext->ecxt_innertuple = innerTupleSlot; + + if (TupIsNull(innerTupleSlot)) + { + mergestate->mj_JoinState = EXEC_MJ_NEXTOUTER; + } + else + { + mergestate->mj_JoinState = EXEC_MJ_JOINTEST; + } + break; + + /* ******************************** + * EXEC_MJ_NEXTOUTER means + * + * outer inner + * outer tuple - 5 5 - marked tuple + * 5 5 + * 6 6 - inner tuple + * 7 7 + * + * we know we just bumped into + * the first inner tuple > current outer tuple + * so get a new outer tuple and then proceed to test + * it against the marked tuple (EXEC_MJ_TESTOUTER) + * ******************************** + */ + case EXEC_MJ_NEXTOUTER: + MJ_printf("ExecMergeJoin: EXEC_MJ_NEXTOUTER\n"); + + outerTupleSlot = ExecProcNode(outerPlan, (Plan*)node); + MJ_DEBUG_PROC_NODE(outerTupleSlot); + econtext->ecxt_outertuple = outerTupleSlot; + + /* ---------------- + * if the outer tuple is null then we know + * we are done with the join + * ---------------- + */ + if (TupIsNull(outerTupleSlot)) { + MJ_printf("ExecMergeJoin: **** outer tuple is nil ****\n"); + return NULL; + } + + mergestate->mj_JoinState = EXEC_MJ_TESTOUTER; + break; + + /* ******************************** + * EXEC_MJ_TESTOUTER + * If the new outer tuple and the marked tuple satisify + * the merge clause then we know we have duplicates in + * the outer scan so we have to restore the inner scan + * to the marked tuple and proceed to join the new outer + * tuples with the inner tuples (EXEC_MJ_JOINTEST) + * + * This is the case when + * + * outer inner + * 4 5 - marked tuple + * outer tuple - 5 5 + * new outer tuple - 5 5 + * 6 8 - inner tuple + * 7 12 + * + * new outer tuple = marked tuple + * + * If the outer tuple fails the test, then we know we have + * to proceed to skip outer tuples until outer >= inner + * (EXEC_MJ_SKIPOUTER). + * + * This is the case when + * + * outer inner + * 5 5 - marked tuple + * outer tuple - 5 5 + * new outer tuple - 6 8 - inner tuple + * 7 12 + * + * new outer tuple > marked tuple + * + * ******************************** + */ + case EXEC_MJ_TESTOUTER: + MJ_printf("ExecMergeJoin: EXEC_MJ_TESTOUTER\n"); + + /* ---------------- + * here we compare the outer tuple with the marked inner tuple + * by using the marked tuple in place of the inner tuple. + * ---------------- + */ + innerTupleSlot = econtext->ecxt_innertuple; + markedTupleSlot = mergestate->mj_MarkedTupleSlot; + econtext->ecxt_innertuple = markedTupleSlot; + + qualResult = ExecQual((List*)mergeclauses, econtext); + MJ_DEBUG_QUAL(mergeclauses, qualResult); + + if (qualResult) { + /* ---------------- + * the merge clause matched so now we juggle the slots + * back the way they were and proceed to JOINTEST. + * ---------------- + */ + econtext->ecxt_innertuple = innerTupleSlot; + + RestoreInnerTuple(innerTupleSlot, markedTupleSlot); + + ExecRestrPos(innerPlan); + mergestate->mj_JoinState = EXEC_MJ_JOINTEST; + + } else { + /* ---------------- + * if the inner tuple was nil and the new outer + * tuple didn't match the marked outer tuple then + * we may have the case: + * + * outer inner + * 4 4 - marked tuple + * new outer - 5 4 + * 6 nil - inner tuple + * 7 + * + * which means that all subsequent outer tuples will be + * larger than our inner tuples. + * ---------------- + */ + if (TupIsNull(innerTupleSlot)) { + MJ_printf("ExecMergeJoin: **** wierd case 1 ****\n"); + return NULL; + } + + /* ---------------- + * restore the inner tuple and continue on to + * skip outer tuples. + * ---------------- + */ + econtext->ecxt_innertuple = innerTupleSlot; + mergestate->mj_JoinState = EXEC_MJ_SKIPOUTER; + } + break; + + /* ******************************** + * EXEC_MJ_SKIPOUTER means skip over tuples in the outer plan + * until we find an outer tuple > current inner tuple. + * + * For example: + * + * outer inner + * 5 5 + * 5 5 + * outer tuple - 6 8 - inner tuple + * 7 12 + * 8 14 + * + * we have to advance the outer scan + * until we find the outer 8. + * + * ******************************** + */ + case EXEC_MJ_SKIPOUTER: + MJ_printf("ExecMergeJoin: EXEC_MJ_SKIPOUTER\n"); + /* ---------------- + * before we advance, make sure the current tuples + * do not satisify the mergeclauses. If they do, then + * we update the marked tuple and go join them. + * ---------------- + */ + qualResult = ExecQual((List*)mergeclauses, econtext); + MJ_DEBUG_QUAL(mergeclauses, qualResult); + + if (qualResult) { + ExecMarkPos(innerPlan); + innerTupleSlot = econtext->ecxt_innertuple; + + MarkInnerTuple(innerTupleSlot, mergestate); + + mergestate->mj_JoinState = EXEC_MJ_JOINTUPLES; + break; + } + + /* ---------------- + * ok, now test the skip qualification + * ---------------- + */ + compareResult = MergeCompare(mergeclauses, + outerSkipQual, + econtext); + + MJ_DEBUG_MERGE_COMPARE(outerSkipQual, compareResult); + + /* ---------------- + * compareResult is true as long as we should + * continue skipping tuples. + * ---------------- + */ + if (compareResult) { + + outerTupleSlot = ExecProcNode(outerPlan, (Plan*)node); + MJ_DEBUG_PROC_NODE(outerTupleSlot); + econtext->ecxt_outertuple = outerTupleSlot; + + /* ---------------- + * if the outer tuple is null then we know + * we are done with the join + * ---------------- + */ + if (TupIsNull(outerTupleSlot)) { + MJ_printf("ExecMergeJoin: **** outerTuple is nil ****\n"); + return NULL; + } + /* ---------------- + * otherwise test the new tuple against the skip qual. + * (we remain in the EXEC_MJ_SKIPOUTER state) + * ---------------- + */ + break; + } + + /* ---------------- + * now check the inner skip qual to see if we + * should now skip inner tuples... if we fail the + * inner skip qual, then we know we have a new pair + * of matching tuples. + * ---------------- + */ + compareResult = MergeCompare(mergeclauses, + innerSkipQual, + econtext); + + MJ_DEBUG_MERGE_COMPARE(innerSkipQual, compareResult); + + if (compareResult) + { + mergestate->mj_JoinState = EXEC_MJ_SKIPINNER; + } + else + { + mergestate->mj_JoinState = EXEC_MJ_JOINMARK; + } + break; + + /* ******************************** + * EXEC_MJ_SKIPINNER means skip over tuples in the inner plan + * until we find an inner tuple > current outer tuple. + * + * For example: + * + * outer inner + * 5 5 + * 5 5 + * outer tuple - 12 8 - inner tuple + * 14 10 + * 17 12 + * + * we have to advance the inner scan + * until we find the inner 12. + * + * ******************************** + */ + case EXEC_MJ_SKIPINNER: + MJ_printf("ExecMergeJoin: EXEC_MJ_SKIPINNER\n"); + /* ---------------- + * before we advance, make sure the current tuples + * do not satisify the mergeclauses. If they do, then + * we update the marked tuple and go join them. + * ---------------- + */ + qualResult = ExecQual((List*)mergeclauses, econtext); + MJ_DEBUG_QUAL(mergeclauses, qualResult); + + if (qualResult) { + ExecMarkPos(innerPlan); + innerTupleSlot = econtext->ecxt_innertuple; + + MarkInnerTuple(innerTupleSlot, mergestate); + + mergestate->mj_JoinState = EXEC_MJ_JOINTUPLES; + break; + } + + /* ---------------- + * ok, now test the skip qualification + * ---------------- + */ + compareResult = MergeCompare(mergeclauses, + innerSkipQual, + econtext); + + MJ_DEBUG_MERGE_COMPARE(innerSkipQual, compareResult); + + /* ---------------- + * compareResult is true as long as we should + * continue skipping tuples. + * ---------------- + */ + if (compareResult) { + /* ---------------- + * now try and get a new inner tuple + * ---------------- + */ + innerTupleSlot = ExecProcNode(innerPlan, (Plan*)node); + MJ_DEBUG_PROC_NODE(innerTupleSlot); + econtext->ecxt_innertuple = innerTupleSlot; + + /* ---------------- + * if the inner tuple is null then we know + * we have to restore the inner scan + * and advance to the next outer tuple + * ---------------- + */ + if (TupIsNull(innerTupleSlot)) { + /* ---------------- + * this is an interesting case.. all our + * inner tuples are smaller then our outer + * tuples so we never found an inner tuple + * to mark. + * + * outer inner + * outer tuple - 5 4 + * 5 4 + * 6 nil - inner tuple + * 7 + * + * This means the join should end. + * ---------------- + */ + MJ_printf("ExecMergeJoin: **** wierd case 2 ****\n"); + return NULL; + } + + /* ---------------- + * otherwise test the new tuple against the skip qual. + * (we remain in the EXEC_MJ_SKIPINNER state) + * ---------------- + */ + break; + } + + /* ---------------- + * compare finally failed and we have stopped skipping + * inner tuples so now check the outer skip qual + * to see if we should now skip outer tuples... + * ---------------- + */ + compareResult = MergeCompare(mergeclauses, + outerSkipQual, + econtext); + + MJ_DEBUG_MERGE_COMPARE(outerSkipQual, compareResult); + + if (compareResult) + { + mergestate->mj_JoinState = EXEC_MJ_SKIPOUTER; + } + else + { + mergestate->mj_JoinState = EXEC_MJ_JOINMARK; + } + + break; + + /* ******************************** + * if we get here it means our code is fucked up and + * so we just end the join prematurely. + * ******************************** + */ + default: + elog(NOTICE, "ExecMergeJoin: invalid join state. aborting"); + return NULL; + } + } +} + +/* ---------------------------------------------------------------- + * ExecInitMergeJoin + * + * old comments + * Creates the run-time state information for the node and + * sets the relation id to contain relevant decriptors. + * ---------------------------------------------------------------- + */ +bool +ExecInitMergeJoin(MergeJoin *node, EState *estate, Plan *parent) +{ + MergeJoinState *mergestate; + List *joinclauses; + RegProcedure rightsortop; + RegProcedure leftsortop; + RegProcedure sortop; + + List *OSortopI; + List *ISortopO; + + MJ1_printf("ExecInitMergeJoin: %s\n", + "initializing node"); + + /* ---------------- + * assign the node's execution state and + * get the range table and direction from it + * ---------------- + */ + node->join.state = estate; + + /* ---------------- + * create new merge state for node + * ---------------- + */ + mergestate = makeNode(MergeJoinState); + mergestate->mj_OSortopI = NIL; + mergestate->mj_ISortopO = NIL; + mergestate->mj_JoinState = 0; + mergestate->mj_MarkedTupleSlot = NULL; + node->mergestate = mergestate; + + /* ---------------- + * Miscellanious initialization + * + * + assign node's base_id + * + assign debugging hooks and + * + create expression context for node + * ---------------- + */ + ExecAssignNodeBaseInfo(estate, &mergestate->jstate, parent); + ExecAssignExprContext(estate, &mergestate->jstate); + +#define MERGEJOIN_NSLOTS 2 + /* ---------------- + * tuple table initialization + * ---------------- + */ + ExecInitResultTupleSlot(estate, &mergestate->jstate); + ExecInitMarkedTupleSlot(estate, mergestate); + + /* ---------------- + * get merge sort operators. + * + * XXX for now we assume all quals in the joinclauses were + * sorted with the same operator in both the inner and + * outer relations. -cim 11/2/89 + * ---------------- + */ + joinclauses = node->mergeclauses; + + rightsortop = get_opcode(node->mergerightorder[0]); + leftsortop = get_opcode(node->mergeleftorder[0]); + + if (leftsortop != rightsortop) + elog(NOTICE, "ExecInitMergeJoin: %s", + "left and right sortop's are unequal!"); + + sortop = rightsortop; + + /* ---------------- + * form merge skip qualifications + * + * XXX MJform routines need to be extended + * to take a list of sortops.. -cim 11/2/89 + * ---------------- + */ + OSortopI = MJFormOSortopI(joinclauses, sortop); + ISortopO = MJFormISortopO(joinclauses, sortop); + mergestate->mj_OSortopI = OSortopI; + mergestate->mj_ISortopO = ISortopO; + + MJ_printf("\nExecInitMergeJoin: OSortopI is "); + MJ_nodeDisplay(OSortopI); + MJ_printf("\nExecInitMergeJoin: ISortopO is "); + MJ_nodeDisplay(ISortopO); + MJ_printf("\n"); + + /* ---------------- + * initialize join state + * ---------------- + */ + mergestate->mj_JoinState = EXEC_MJ_INITIALIZE; + + /* ---------------- + * initialize subplans + * ---------------- + */ + ExecInitNode(outerPlan((Plan *) node), estate, (Plan *) node); + ExecInitNode(innerPlan((Plan *) node), estate, (Plan *) node); + + /* ---------------- + * initialize tuple type and projection info + * ---------------- + */ + ExecAssignResultTypeFromTL((Plan *) node, &mergestate->jstate); + ExecAssignProjectionInfo((Plan *) node, &mergestate->jstate); + + mergestate->jstate.cs_TupFromTlist = false; + /* ---------------- + * initialization successful + * ---------------- + */ + MJ1_printf("ExecInitMergeJoin: %s\n", + "node initialized"); + + return TRUE; +} + +int +ExecCountSlotsMergeJoin(MergeJoin *node) +{ + return ExecCountSlotsNode(outerPlan((Plan *)node)) + + ExecCountSlotsNode(innerPlan((Plan *)node)) + + MERGEJOIN_NSLOTS; +} + +/* ---------------------------------------------------------------- + * ExecEndMergeJoin + * + * old comments + * frees storage allocated through C routines. + * ---------------------------------------------------------------- + */ +void +ExecEndMergeJoin(MergeJoin *node) +{ + MergeJoinState *mergestate; + + MJ1_printf("ExecEndMergeJoin: %s\n", + "ending node processing"); + + /* ---------------- + * get state information from the node + * ---------------- + */ + mergestate = node->mergestate; + + /* ---------------- + * Free the projection info and the scan attribute info + * + * Note: we don't ExecFreeResultType(mergestate) + * because the rule manager depends on the tupType + * returned by ExecMain(). So for now, this + * is freed at end-transaction time. -cim 6/2/91 + * ---------------- + */ + ExecFreeProjectionInfo(&mergestate->jstate); + + /* ---------------- + * shut down the subplans + * ---------------- + */ + ExecEndNode((Plan*) innerPlan((Plan *) node), (Plan*)node); + ExecEndNode((Plan*) outerPlan((Plan *) node), (Plan*)node); + + /* ---------------- + * clean out the tuple table so that we don't try and + * pfree the marked tuples.. see HACK ALERT at the top of + * this file. + * ---------------- + */ + ExecClearTuple(mergestate->jstate.cs_ResultTupleSlot); + ExecClearTuple(mergestate->mj_MarkedTupleSlot); + + MJ1_printf("ExecEndMergeJoin: %s\n", + "node processing ended"); +} + diff --git a/src/backend/executor/nodeMergejoin.h b/src/backend/executor/nodeMergejoin.h new file mode 100644 index 00000000000..ebdca08e32f --- /dev/null +++ b/src/backend/executor/nodeMergejoin.h @@ -0,0 +1,40 @@ +/*------------------------------------------------------------------------- + * + * nodeMergejoin.h-- + * + * + * + * Copyright (c) 1994, Regents of the University of California + * + * $Id: nodeMergejoin.h,v 1.1.1.1 1996/07/09 06:21:27 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +#ifndef NODEMERGEJOIN_H +#define NODEMERGEJOIN_H + +#if 0 /* aren't these static? */ +extern List MJFormOSortopI(List qualList, Oid sortOp); +extern List MJFormISortopO(List qualList, Oid sortOp); +#endif +extern bool MergeCompare(List *eqQual, List *compareQual, ExprContext *econtext); + +extern void ExecMergeTupleDumpInner(ExprContext *econtext); + +extern void ExecMergeTupleDumpOuter(ExprContext *econtext); + +extern void ExecMergeTupleDumpMarked(ExprContext *econtext, + MergeJoinState *mergestate); + +extern void ExecMergeTupleDump(ExprContext *econtext, + MergeJoinState *mergestate); + +extern TupleTableSlot *ExecMergeJoin(MergeJoin *node); + +extern bool ExecInitMergeJoin(MergeJoin *node, EState *estate, Plan *parent); + +extern int ExecCountSlotsMergeJoin(MergeJoin *node); + +extern void ExecEndMergeJoin(MergeJoin *node); + +#endif /* NODEMERGEJOIN_H; */ diff --git a/src/backend/executor/nodeNestloop.c b/src/backend/executor/nodeNestloop.c new file mode 100644 index 00000000000..1ef30ce431f --- /dev/null +++ b/src/backend/executor/nodeNestloop.c @@ -0,0 +1,370 @@ +/*------------------------------------------------------------------------- + * + * nodeNestloop.c-- + * routines to support nest-loop joins + * + * Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * $Header: /cvsroot/pgsql/src/backend/executor/nodeNestloop.c,v 1.1.1.1 1996/07/09 06:21:27 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +/* + * INTERFACE ROUTINES + * ExecNestLoop - process a nestloop join of two plans + * ExecInitNestLoop - initialize the join + * ExecEndNestLoop - shut down the join + */ +#include "executor/executor.h" +#include "executor/nodeNestloop.h" +#include "executor/nodeIndexscan.h" + +/* ---------------------------------------------------------------- + * ExecNestLoop(node) + * + * old comments + * Returns the tuple joined from inner and outer tuples which + * satisfies the qualification clause. + * + * It scans the inner relation to join with current outer tuple. + * + * If none is found, next tuple form the outer relation is retrieved + * and the inner relation is scanned from the beginning again to join + * with the outer tuple. + * + * Nil is returned if all the remaining outer tuples are tried and + * all fail to join with the inner tuples. + * + * Nil is also returned if there is no tuple from inner realtion. + * + * Conditions: + * -- outerTuple contains current tuple from outer relation and + * the right son(inner realtion) maintains "cursor" at the tuple + * returned previously. + * This is achieved by maintaining a scan position on the outer + * relation. + * + * Initial States: + * -- the outer child and the inner child + * are prepared to return the first tuple. + * ---------------------------------------------------------------- + */ +TupleTableSlot * +ExecNestLoop(NestLoop *node, Plan* parent) +{ + NestLoopState *nlstate; + Plan *innerPlan; + Plan *outerPlan; + bool needNewOuterTuple; + + TupleTableSlot *outerTupleSlot; + TupleTableSlot *innerTupleSlot; + + List *qual; + bool qualResult; + ExprContext *econtext; + + /* ---------------- + * get information from the node + * ---------------- + */ + ENL1_printf("getting info from node"); + + nlstate = node->nlstate; + qual = node->join.qual; + outerPlan = outerPlan(&node->join); + innerPlan = innerPlan(&node->join); + + /* ---------------- + * initialize expression context + * ---------------- + */ + econtext = nlstate->jstate.cs_ExprContext; + + /* ---------------- * get the current outer tuple + * ---------------- + */ + outerTupleSlot = nlstate->jstate.cs_OuterTupleSlot; + econtext->ecxt_outertuple = outerTupleSlot; + + /* ---------------- + * Ok, everything is setup for the join so now loop until + * we return a qualifying join tuple.. + * ---------------- + */ + + if (nlstate->jstate.cs_TupFromTlist) { + TupleTableSlot *result; + bool isDone; + + result = ExecProject(nlstate->jstate.cs_ProjInfo, &isDone); + if (!isDone) + return result; + } + + ENL1_printf("entering main loop"); + for(;;) { + /* ---------------- + * The essential idea now is to get the next inner tuple + * and join it with the current outer tuple. + * ---------------- + */ + needNewOuterTuple = false; + + /* ---------------- + * If outer tuple is not null then that means + * we are in the middle of a scan and we should + * restore our previously saved scan position. + * ---------------- + */ + if (! TupIsNull(outerTupleSlot)) { + ENL1_printf("have outer tuple, restoring outer plan"); + ExecRestrPos(outerPlan); + } else { + ENL1_printf("outer tuple is nil, need new outer tuple"); + needNewOuterTuple = true; + } + + /* ---------------- + * if we have an outerTuple, try to get the next inner tuple. + * ---------------- + */ + if (!needNewOuterTuple) { + ENL1_printf("getting new inner tuple"); + + innerTupleSlot = ExecProcNode(innerPlan, (Plan*)node); + econtext->ecxt_innertuple = innerTupleSlot; + + if (TupIsNull(innerTupleSlot)) { + ENL1_printf("no inner tuple, need new outer tuple"); + needNewOuterTuple = true; + } + } + + /* ---------------- + * loop until we have a new outer tuple and a new + * inner tuple. + * ---------------- + */ + while (needNewOuterTuple) { + /* ---------------- + * now try to get the next outer tuple + * ---------------- + */ + ENL1_printf("getting new outer tuple"); + outerTupleSlot = ExecProcNode(outerPlan, (Plan*)node); + econtext->ecxt_outertuple = outerTupleSlot; + + /* ---------------- + * if there are no more outer tuples, then the join + * is complete.. + * ---------------- + */ + if (TupIsNull(outerTupleSlot)) { + ENL1_printf("no outer tuple, ending join"); + return NULL; + } + + /* ---------------- + * we have a new outer tuple so we mark our position + * in the outer scan and save the outer tuple in the + * NestLoop state + * ---------------- + */ + ENL1_printf("saving new outer tuple information"); + ExecMarkPos(outerPlan); + nlstate->jstate.cs_OuterTupleSlot = outerTupleSlot; + + /* ---------------- + * now rescan the inner plan and get a new inner tuple + * ---------------- + */ + + ENL1_printf("rescanning inner plan"); + /* + * The scan key of the inner plan might depend on the current + * outer tuple (e.g. in index scans), that's why we pass our + * expr context. + */ + ExecReScan(innerPlan, econtext, parent); + + ENL1_printf("getting new inner tuple"); + + innerTupleSlot = ExecProcNode(innerPlan, (Plan*)node); + econtext->ecxt_innertuple = innerTupleSlot; + + if (TupIsNull(innerTupleSlot)) { + ENL1_printf("couldn't get inner tuple - need new outer tuple"); + } else { + ENL1_printf("got inner and outer tuples"); + needNewOuterTuple = false; + } + } /* while (needNewOuterTuple) */ + + /* ---------------- + * at this point we have a new pair of inner and outer + * tuples so we test the inner and outer tuples to see + * if they satisify the node's qualification. + * ---------------- + */ + ENL1_printf("testing qualification"); + qualResult = ExecQual((List*)qual, econtext); + + if (qualResult) { + /* ---------------- + * qualification was satisified so we project and + * return the slot containing the result tuple + * using ExecProject(). + * ---------------- + */ + ProjectionInfo *projInfo; + TupleTableSlot *result; + bool isDone; + + ENL1_printf("qualification succeeded, projecting tuple"); + + projInfo = nlstate->jstate.cs_ProjInfo; + result = ExecProject(projInfo, &isDone); + nlstate->jstate.cs_TupFromTlist = !isDone; + return result; + } + + /* ---------------- + * qualification failed so we have to try again.. + * ---------------- + */ + ENL1_printf("qualification failed, looping"); + } +} + +/* ---------------------------------------------------------------- + * ExecInitNestLoop + * + * Creates the run-time state information for the nestloop node + * produced by the planner and initailizes inner and outer relations + * (child nodes). + * ---------------------------------------------------------------- + */ +bool +ExecInitNestLoop(NestLoop *node, EState *estate, Plan *parent) +{ + NestLoopState *nlstate; + + NL1_printf("ExecInitNestLoop: %s\n", + "initializing node"); + + /* ---------------- + * assign execution state to node + * ---------------- + */ + node->join.state = estate; + + /* ---------------- + * create new nest loop state + * ---------------- + */ + nlstate = makeNode(NestLoopState); + nlstate->nl_PortalFlag = false; + node->nlstate = nlstate; + + /* ---------------- + * Miscellanious initialization + * + * + assign node's base_id + * + assign debugging hooks and + * + create expression context for node + * ---------------- + */ + ExecAssignNodeBaseInfo(estate, &nlstate->jstate, parent); + ExecAssignExprContext(estate, &nlstate->jstate); + +#define NESTLOOP_NSLOTS 1 + /* ---------------- + * tuple table initialization + * ---------------- + */ + ExecInitResultTupleSlot(estate, &nlstate->jstate); + + /* ---------------- + * now initialize children + * ---------------- + */ + ExecInitNode(outerPlan((Plan*)node), estate, (Plan*)node); + ExecInitNode(innerPlan((Plan*)node), estate, (Plan*)node); + + /* ---------------- + * initialize tuple type and projection info + * ---------------- + */ + ExecAssignResultTypeFromTL((Plan *) node, &nlstate->jstate); + ExecAssignProjectionInfo((Plan *) node, &nlstate->jstate); + + /* ---------------- + * finally, wipe the current outer tuple clean. + * ---------------- + */ + nlstate->jstate.cs_OuterTupleSlot = NULL; + nlstate->jstate.cs_TupFromTlist = false; + + NL1_printf("ExecInitNestLoop: %s\n", + "node initialized"); + return TRUE; +} + +int +ExecCountSlotsNestLoop(NestLoop *node) +{ + return ExecCountSlotsNode(outerPlan(node)) + + ExecCountSlotsNode(innerPlan(node)) + + NESTLOOP_NSLOTS; +} + +/* ---------------------------------------------------------------- + * ExecEndNestLoop + * + * closes down scans and frees allocated storage + * ---------------------------------------------------------------- + */ +void +ExecEndNestLoop(NestLoop *node) +{ + NestLoopState *nlstate; + + NL1_printf("ExecEndNestLoop: %s\n", + "ending node processing"); + + /* ---------------- + * get info from the node + * ---------------- + */ + nlstate = node->nlstate; + + /* ---------------- + * Free the projection info + * + * Note: we don't ExecFreeResultType(nlstate) + * because the rule manager depends on the tupType + * returned by ExecMain(). So for now, this + * is freed at end-transaction time. -cim 6/2/91 + * ---------------- + */ + ExecFreeProjectionInfo(&nlstate->jstate); + + /* ---------------- + * close down subplans + * ---------------- + */ + ExecEndNode(outerPlan((Plan *) node), (Plan*)node); + ExecEndNode(innerPlan((Plan *) node), (Plan*)node); + + /* ---------------- + * clean out the tuple table + * ---------------- + */ + ExecClearTuple(nlstate->jstate.cs_ResultTupleSlot); + + NL1_printf("ExecEndNestLoop: %s\n", + "node processing ended"); +} diff --git a/src/backend/executor/nodeNestloop.h b/src/backend/executor/nodeNestloop.h new file mode 100644 index 00000000000..c227c90a735 --- /dev/null +++ b/src/backend/executor/nodeNestloop.h @@ -0,0 +1,21 @@ +/*------------------------------------------------------------------------- + * + * nodeNestloop.h-- + * + * + * + * Copyright (c) 1994, Regents of the University of California + * + * $Id: nodeNestloop.h,v 1.1.1.1 1996/07/09 06:21:27 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +#ifndef NODENESTLOOP_H +#define NODENESTLOOP_H + +extern TupleTableSlot *ExecNestLoop(NestLoop *node, Plan *parent); +extern bool ExecInitNestLoop(NestLoop *node, EState *estate, Plan *parent); +extern int ExecCountSlotsNestLoop(NestLoop *node); +extern void ExecEndNestLoop(NestLoop *node); + +#endif /* NODENESTLOOP_H */ diff --git a/src/backend/executor/nodeResult.c b/src/backend/executor/nodeResult.c new file mode 100644 index 00000000000..793119244db --- /dev/null +++ b/src/backend/executor/nodeResult.c @@ -0,0 +1,288 @@ +/*------------------------------------------------------------------------- + * + * nodeResult.c-- + * support for constant nodes needing special code. + * + * Copyright (c) 1994, Regents of the University of California + * + * + * DESCRIPTION + * + * Example: in constant queries where no relations are scanned, + * the planner generates result nodes. Examples of such queries are: + * + * retrieve (x = 1) + * and + * append emp (name = "mike", salary = 15000) + * + * Result nodes are also used to optimise queries + * with tautological qualifications like: + * + * retrieve (emp.all) where 2 > 1 + * + * In this case, the plan generated is + * + * Result (with 2 > 1 qual) + * / + * SeqScan (emp.all) + * + * IDENTIFICATION + * $Header: /cvsroot/pgsql/src/backend/executor/nodeResult.c,v 1.1.1.1 1996/07/09 06:21:27 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ + +#include "executor/executor.h" +#include "executor/nodeResult.h" + +/* ---------------------------------------------------------------- + * ExecResult(node) + * + * returns the tuples from the outer plan which satisify the + * qualification clause. Since result nodes with right + * subtrees are never planned, we ignore the right subtree + * entirely (for now).. -cim 10/7/89 + * + * The qualification containing only constant clauses are + * checked first before any processing is done. It always returns + * 'nil' if the constant qualification is not satisfied. + * ---------------------------------------------------------------- + */ +TupleTableSlot * +ExecResult(Result *node) +{ + ResultState *resstate; + TupleTableSlot *outerTupleSlot; + TupleTableSlot *resultSlot; + Plan *outerPlan; + ExprContext *econtext; + Node *qual; + bool qualResult; + bool isDone; + ProjectionInfo *projInfo; + + /* ---------------- + * initialize the result node's state + * ---------------- + */ + resstate = node->resstate; + + /* ---------------- + * get the expression context + * ---------------- + */ + econtext = resstate->cstate.cs_ExprContext; + + /* ---------------- + * check tautological qualifications like (2 > 1) + * ---------------- + */ + qual = node->resconstantqual; + if (qual != NULL) { + qualResult = ExecQual((List*)qual, econtext); + /* ---------------- + * if we failed the constant qual, then there + * is no need to continue processing because regardless of + * what happens, the constant qual will be false.. + * ---------------- + */ + if (qualResult == false) + return NULL; + + /* ---------------- + * our constant qualification succeeded so now we + * throw away the qual because we know it will always + * succeed. + * ---------------- + */ + node->resconstantqual = NULL; + } + + if (resstate->cstate.cs_TupFromTlist) { + ProjectionInfo *projInfo; + + projInfo = resstate->cstate.cs_ProjInfo; + resultSlot = ExecProject(projInfo, &isDone); + if (!isDone) + return resultSlot; + } + + /* ---------------- + * retrieve a tuple that satisfy the qual from the outer plan until + * there are no more. + * + * if rs_done is 1 then it means that we were asked to return + * a constant tuple and we alread did the last time ExecResult() + * was called, so now we are through. + * ---------------- + */ + outerPlan = outerPlan(node); + + while (!resstate->rs_done) { + + /* ---------------- + * get next outer tuple if necessary. + * ---------------- + */ + if (outerPlan != NULL) { + outerTupleSlot = ExecProcNode(outerPlan, (Plan*)node); + + if (TupIsNull(outerTupleSlot)) + return NULL; + + resstate->cstate.cs_OuterTupleSlot = outerTupleSlot; + } else { + + /* ---------------- + * if we don't have an outer plan, then it's probably + * the case that we are doing a retrieve or an append + * with a constant target list, so we should only return + * the constant tuple once or never if we fail the qual. + * ---------------- + */ + resstate->rs_done = 1; + } + + /* ---------------- + * get the information to place into the expr context + * ---------------- + */ + resstate = node->resstate; + + outerTupleSlot = resstate->cstate.cs_OuterTupleSlot; + + /* ---------------- + * fill in the information in the expression context + * XXX gross hack. use outer tuple as scan tuple + * ---------------- + */ + econtext->ecxt_outertuple = outerTupleSlot; + econtext->ecxt_scantuple = outerTupleSlot; + + /* ---------------- + * form the result tuple and pass it back using ExecProject() + * ---------------- + */ + projInfo = resstate->cstate.cs_ProjInfo; + resultSlot = ExecProject(projInfo, &isDone); + resstate->cstate.cs_TupFromTlist = !isDone; + return resultSlot; + } + + return NULL; +} + +/* ---------------------------------------------------------------- + * ExecInitResult + * + * Creates the run-time state information for the result node + * produced by the planner and initailizes outer relations + * (child nodes). + * ---------------------------------------------------------------- + */ +bool +ExecInitResult(Result *node, EState *estate, Plan *parent) +{ + ResultState *resstate; + + /* ---------------- + * assign execution state to node + * ---------------- + */ + node->plan.state = estate; + + /* ---------------- + * create new ResultState for node + * ---------------- + */ + resstate = makeNode(ResultState); + resstate->rs_done = 0; + node->resstate = resstate; + + /* ---------------- + * Miscellanious initialization + * + * + assign node's base_id + * + assign debugging hooks and + * + create expression context for node + * ---------------- + */ + ExecAssignNodeBaseInfo(estate, &resstate->cstate, parent); + ExecAssignExprContext(estate, &resstate->cstate); + +#define RESULT_NSLOTS 1 + /* ---------------- + * tuple table initialization + * ---------------- + */ + ExecInitResultTupleSlot(estate, &resstate->cstate); + + /* ---------------- + * then initialize children + * ---------------- + */ + ExecInitNode(outerPlan(node), estate, (Plan*)node); + + /* + * we don't use inner plan + */ + Assert(innerPlan(node)==NULL); + + /* ---------------- + * initialize tuple type and projection info + * ---------------- + */ + ExecAssignResultTypeFromTL((Plan*)node, &resstate->cstate); + ExecAssignProjectionInfo((Plan*)node, &resstate->cstate); + + /* ---------------- + * set "are we done yet" to false + * ---------------- + */ + resstate->rs_done = 0; + + return TRUE; +} + +int +ExecCountSlotsResult(Result *node) +{ + return ExecCountSlotsNode(outerPlan(node)) + RESULT_NSLOTS; +} + +/* ---------------------------------------------------------------- + * ExecEndResult + * + * fees up storage allocated through C routines + * ---------------------------------------------------------------- + */ +void +ExecEndResult(Result *node) +{ + ResultState *resstate; + + resstate = node->resstate; + + /* ---------------- + * Free the projection info + * + * Note: we don't ExecFreeResultType(resstate) + * because the rule manager depends on the tupType + * returned by ExecMain(). So for now, this + * is freed at end-transaction time. -cim 6/2/91 + * ---------------- + */ + ExecFreeProjectionInfo(&resstate->cstate); + + /* ---------------- + * shut down subplans + * ---------------- + */ + ExecEndNode(outerPlan(node), (Plan*)node); + + /* ---------------- + * clean out the tuple table + * ---------------- + */ + ExecClearTuple(resstate->cstate.cs_ResultTupleSlot); +} diff --git a/src/backend/executor/nodeResult.h b/src/backend/executor/nodeResult.h new file mode 100644 index 00000000000..a2ab286c089 --- /dev/null +++ b/src/backend/executor/nodeResult.h @@ -0,0 +1,21 @@ +/*------------------------------------------------------------------------- + * + * nodeResult.h-- + * + * + * + * Copyright (c) 1994, Regents of the University of California + * + * $Id: nodeResult.h,v 1.1.1.1 1996/07/09 06:21:27 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +#ifndef NODERESULT_H +#define NODERESULT_H + +extern TupleTableSlot *ExecResult(Result *node); +extern bool ExecInitResult(Result *node, EState *estate, Plan *parent); +extern int ExecCountSlotsResult(Result *node); +extern void ExecEndResult(Result *node); + +#endif /* NODERESULT_H */ diff --git a/src/backend/executor/nodeSeqscan.c b/src/backend/executor/nodeSeqscan.c new file mode 100644 index 00000000000..a0e434c4bb0 --- /dev/null +++ b/src/backend/executor/nodeSeqscan.c @@ -0,0 +1,449 @@ +/*------------------------------------------------------------------------- + * + * nodeSeqscan.c-- + * Support routines for sequential scans of relations. + * + * Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * $Header: /cvsroot/pgsql/src/backend/executor/nodeSeqscan.c,v 1.1.1.1 1996/07/09 06:21:27 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +/* + * INTERFACE ROUTINES + * ExecSeqScan sequentially scans a relation. + * ExecSeqNext retrieve next tuple in sequential order. + * ExecInitSeqScan creates and initializes a seqscan node. + * ExecEndSeqScan releases any storage allocated. + * ExecSeqReScan rescans the relation + * ExecMarkPos marks scan position + * ExecRestrPos restores scan position + * + */ +#include "executor/executor.h" +#include "executor/nodeSeqscan.h" +#include "parser/parsetree.h" + +/* ---------------------------------------------------------------- + * Scan Support + * ---------------------------------------------------------------- + */ +/* ---------------------------------------------------------------- + * SeqNext + * + * This is a workhorse for ExecSeqScan + * ---------------------------------------------------------------- + */ +TupleTableSlot * +SeqNext(SeqScan *node) +{ + HeapTuple tuple; + HeapScanDesc scandesc; + CommonScanState *scanstate; + EState *estate; + ScanDirection direction; + TupleTableSlot *slot; + Buffer buffer; + + /* ---------------- + * get information from the estate and scan state + * ---------------- + */ + estate = node->plan.state; + scanstate = node->scanstate; + scandesc = scanstate->css_currentScanDesc; + direction = estate->es_direction; + + /* ---------------- + * get the next tuple from the access methods + * ---------------- + */ + tuple = heap_getnext(scandesc, /* scan desc */ + ScanDirectionIsBackward(direction), /*backward flag*/ + &buffer); /* return: buffer */ + + /* ---------------- + * save the tuple and the buffer returned to us by the access methods + * in our scan tuple slot and return the slot. Note: we pass 'false' + * because tuples returned by heap_getnext() are pointers onto + * disk pages and were not created with palloc() and so should not + * be pfree()'d. + * ---------------- + */ + slot = scanstate->css_ScanTupleSlot; + + slot = ExecStoreTuple(tuple, /* tuple to store */ + slot, /* slot to store in */ + buffer, /* buffer associated with this tuple */ + false); /* don't pfree this pointer */ + + /* ---------------- + * XXX -- mao says: The sequential scan for heap relations will + * automatically unpin the buffer this tuple is on when we cross + * a page boundary. The clearslot code also does this. We bump + * the pin count on the page here, since we actually have two + * pointers to it -- one in the scan desc and one in the tuple + * table slot. --mar 20 91 + * ---------------- + */ + ExecIncrSlotBufferRefcnt(slot); + + return slot; +} + +/* ---------------------------------------------------------------- + * ExecSeqScan(node) + * + * Scans the relation sequentially and returns the next qualifying + * tuple. + * It calls the ExecScan() routine and passes it the access method + * which retrieve tuples sequentially. + * + */ + +TupleTableSlot * +ExecSeqScan(SeqScan *node) +{ + TupleTableSlot *slot; + Plan *outerPlan; + +S_printf("ExecSeqScan: scanning node: "); S_nodeDisplay(node); + + /* ---------------- + * if there is an outer subplan, get a tuple from it + * else, scan the relation + * ---------------- + */ + outerPlan = outerPlan((Plan *) node); + if (outerPlan) { + slot = ExecProcNode(outerPlan, (Plan*) node); + } else { + slot = ExecScan(node, SeqNext); + } + +S1_printf("ExecSeqScan: returned tuple slot: %d\n", slot); + + return slot; +} + +/* ---------------------------------------------------------------- + * InitScanRelation + * + * This does the initialization for scan relations and + * subplans of scans. + * ---------------------------------------------------------------- + */ +Oid +InitScanRelation(SeqScan *node, EState *estate, + CommonScanState *scanstate, Plan *outerPlan) +{ + Index relid; + List *rangeTable; + RangeTblEntry *rtentry; + Oid reloid; + TimeQual timeQual; + ScanDirection direction; + Relation currentRelation; + HeapScanDesc currentScanDesc; + RelationInfo *resultRelationInfo; + + if (outerPlan == NULL) { + /* ---------------- + * if the outer node is nil then we are doing a simple + * sequential scan of a relation... + * + * get the relation object id from the relid'th entry + * in the range table, open that relation and initialize + * the scan state... + * ---------------- + */ + relid = node->scanrelid; + rangeTable = estate->es_range_table; + rtentry = rt_fetch(relid, rangeTable); + reloid = rtentry->relid; + timeQual = rtentry->timeQual; + direction = estate->es_direction; + resultRelationInfo = estate->es_result_relation_info; + + ExecOpenScanR(reloid, /* relation */ + 0, /* nkeys */ + NULL, /* scan key */ + 0, /* is index */ + direction, /* scan direction */ + timeQual, /* time qual */ + ¤tRelation, /* return: rel desc */ + (Pointer *) ¤tScanDesc); /* return: scan desc */ + + scanstate->css_currentRelation = currentRelation; + scanstate->css_currentScanDesc = currentScanDesc; + + ExecAssignScanType(scanstate, + RelationGetTupleDescriptor(currentRelation)); + } else { + /* ---------------- + * otherwise we are scanning tuples from the + * outer subplan so we initialize the outer plan + * and nullify + * ---------------- + */ + ExecInitNode(outerPlan, estate, (Plan*)node); + + node->scanrelid = 0; + scanstate->css_currentRelation = NULL; + scanstate->css_currentScanDesc = NULL; + ExecAssignScanType(scanstate, NULL); + reloid = InvalidOid; + } + + /* ---------------- + * return the relation + * ---------------- + */ + return reloid; +} + + +/* ---------------------------------------------------------------- + * ExecInitSeqScan + * + * old comments + * Creates the run-time state information for the seqscan node + * and sets the relation id to contain relevant descriptors. + * + * If there is a outer subtree (sort), the outer subtree + * is initialized and the relation id is set to the descriptors + * returned by the subtree. + * ---------------------------------------------------------------- + */ +bool +ExecInitSeqScan(SeqScan *node, EState *estate, Plan *parent) +{ + CommonScanState *scanstate; + Plan *outerPlan; + Oid reloid; + HeapScanDesc scandesc; + + /* ---------------- + * assign the node's execution state + * ---------------- + */ + node->plan.state = estate; + + /* ---------------- + * create new CommonScanState for node + * ---------------- + */ + scanstate = makeNode(CommonScanState); + node->scanstate = scanstate; + + /* ---------------- + * Miscellanious initialization + * + * + assign node's base_id + * + create expression context for node + * ---------------- + */ + ExecAssignNodeBaseInfo(estate, &scanstate->cstate, parent); + ExecAssignExprContext(estate, &scanstate->cstate); + +#define SEQSCAN_NSLOTS 3 + /* ---------------- + * tuple table initialization + * ---------------- + */ + ExecInitResultTupleSlot(estate, &scanstate->cstate); + ExecInitScanTupleSlot(estate, scanstate); + + /* ---------------- + * initialize scan relation or outer subplan + * ---------------- + */ + outerPlan = outerPlan((Plan *)node); + + reloid = InitScanRelation(node, estate, scanstate, outerPlan); + + scandesc = scanstate->css_currentScanDesc; + scanstate->cstate.cs_TupFromTlist = false; + + /* ---------------- + * initialize tuple type + * ---------------- + */ + ExecAssignResultTypeFromTL((Plan*)node, &scanstate->cstate); + ExecAssignProjectionInfo((Plan*)node, &scanstate->cstate); + + return TRUE; +} + +int +ExecCountSlotsSeqScan(SeqScan *node) +{ + return ExecCountSlotsNode(outerPlan(node)) + + ExecCountSlotsNode(innerPlan(node)) + + SEQSCAN_NSLOTS; +} + +/* ---------------------------------------------------------------- + * ExecEndSeqScan + * + * frees any storage allocated through C routines. + *| ...and also closes relations and/or shuts down outer subplan + *| -cim 8/14/89 + * ---------------------------------------------------------------- + */ +void +ExecEndSeqScan(SeqScan *node) +{ + CommonScanState *scanstate; + Plan *outerPlan; + + /* ---------------- + * get information from node + * ---------------- + */ + scanstate = node->scanstate; + + /* ---------------- + * Free the projection info and the scan attribute info + * + * Note: we don't ExecFreeResultType(scanstate) + * because the rule manager depends on the tupType + * returned by ExecMain(). So for now, this + * is freed at end-transaction time. -cim 6/2/91 + * ---------------- + */ + ExecFreeProjectionInfo(&scanstate->cstate); + + /* ---------------- + * close scan relation + * ---------------- + */ + ExecCloseR((Plan*) node); + + /* ---------------- + * clean up outer subtree (does nothing if there is no outerPlan) + * ---------------- + */ + outerPlan = outerPlan((Plan *)node); + ExecEndNode(outerPlan, (Plan*)node); + + /* ---------------- + * clean out the tuple table + * ---------------- + */ + ExecClearTuple(scanstate->cstate.cs_ResultTupleSlot); + ExecClearTuple(scanstate->css_ScanTupleSlot); +} + +/* ---------------------------------------------------------------- + * Join Support + * ---------------------------------------------------------------- + */ +/* ---------------------------------------------------------------- + * ExecSeqReScan + * + * Rescans the relation. + * ---------------------------------------------------------------- + */ +void +ExecSeqReScan(SeqScan *node, ExprContext *exprCtxt, Plan* parent) +{ + CommonScanState *scanstate; + EState *estate; + Plan *outerPlan; + Relation rdesc; + HeapScanDesc sdesc; + ScanDirection direction; + + scanstate = node->scanstate; + estate = node->plan.state; + + outerPlan = outerPlan((Plan*)node); + if (outerPlan) { + /* we are scanning a subplan */ + outerPlan = outerPlan((Plan *)node); + ExecReScan(outerPlan, exprCtxt, parent); + } else { + /* otherwise, we are scanning a relation */ + rdesc = scanstate->css_currentRelation; + sdesc = scanstate->css_currentScanDesc; + direction = estate->es_direction; + sdesc = ExecReScanR(rdesc, sdesc, direction, 0, NULL); + scanstate->css_currentScanDesc = sdesc; + } +} + +/* ---------------------------------------------------------------- + * ExecSeqMarkPos(node) + * + * Marks scan position. + * ---------------------------------------------------------------- + */ +void +ExecSeqMarkPos(SeqScan *node) +{ + CommonScanState *scanstate; + Plan *outerPlan; + HeapScanDesc sdesc; + + scanstate = node->scanstate; + + /* ---------------- + * if we are scanning a subplan then propagate + * the ExecMarkPos() request to the subplan + * ---------------- + */ + outerPlan = outerPlan((Plan*)node); + if (outerPlan) { + ExecMarkPos(outerPlan); + return; + } + + /* ---------------- + * otherwise we are scanning a relation so mark the + * position using the access methods.. + * + * ---------------- + */ + sdesc = scanstate->css_currentScanDesc; + heap_markpos(sdesc); + + return; +} + +/* ---------------------------------------------------------------- + * ExecSeqRestrPos + * + * Restores scan position. + * ---------------------------------------------------------------- + */ +void +ExecSeqRestrPos(SeqScan *node) +{ + CommonScanState *scanstate; + Plan *outerPlan; + HeapScanDesc sdesc; + + scanstate = node->scanstate; + + /* ---------------- + * if we are scanning a subplan then propagate + * the ExecRestrPos() request to the subplan + * ---------------- + */ + outerPlan = outerPlan((Plan*)node); + if (outerPlan) { + ExecRestrPos(outerPlan); + return; + } + + /* ---------------- + * otherwise we are scanning a relation so restore the + * position using the access methods.. + * ---------------- + */ + sdesc = scanstate->css_currentScanDesc; + heap_restrpos(sdesc); +} diff --git a/src/backend/executor/nodeSeqscan.h b/src/backend/executor/nodeSeqscan.h new file mode 100644 index 00000000000..cce029d40b7 --- /dev/null +++ b/src/backend/executor/nodeSeqscan.h @@ -0,0 +1,27 @@ +/*------------------------------------------------------------------------- + * + * nodeSeqscan.h-- + * + * + * + * Copyright (c) 1994, Regents of the University of California + * + * $Id: nodeSeqscan.h,v 1.1.1.1 1996/07/09 06:21:27 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +#ifndef NODESEQSCAN_H +#define NODESEQSCAN_H + +extern TupleTableSlot *SeqNext(SeqScan *node); +extern TupleTableSlot *ExecSeqScan(SeqScan *node); +extern Oid InitScanRelation(SeqScan *node, EState *estate, + CommonScanState *scanstate, Plan *outerPlan); +extern bool ExecInitSeqScan(SeqScan *node, EState *estate, Plan *parent); +extern int ExecCountSlotsSeqScan(SeqScan *node); +extern void ExecEndSeqScan(SeqScan *node); +extern void ExecSeqReScan(SeqScan *node, ExprContext *exprCtxt, Plan* parent); +extern void ExecSeqMarkPos(SeqScan *node); +extern void ExecSeqRestrPos(SeqScan *node); + +#endif /* NODESEQSCAN_H */ diff --git a/src/backend/executor/nodeSort.c b/src/backend/executor/nodeSort.c new file mode 100644 index 00000000000..9a7f4f9456f --- /dev/null +++ b/src/backend/executor/nodeSort.c @@ -0,0 +1,523 @@ +/*------------------------------------------------------------------------- + * + * nodeSort.c-- + * Routines to handle sorting of relations into temporaries. + * + * Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * $Header: /cvsroot/pgsql/src/backend/executor/nodeSort.c,v 1.1.1.1 1996/07/09 06:21:27 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +#include "executor/executor.h" +#include "executor/nodeSort.h" +#include "utils/palloc.h" +#include "utils/psort.h" +#include "catalog/catalog.h" +#include "storage/bufmgr.h" +#include "optimizer/internal.h" /* for _TEMP_RELATION_ID_ */ + +/* ---------------------------------------------------------------- + * FormSortKeys(node) + * + * Forms the structure containing information used to sort the relation. + * + * Returns an array of ScanKeyData. + * ---------------------------------------------------------------- + */ +static ScanKey +FormSortKeys(Sort *sortnode) +{ + ScanKey sortkeys; + List *targetList; + List *tl; + int keycount; + Resdom *resdom; + AttrNumber resno; + Index reskey; + Oid reskeyop; + + /* ---------------- + * get information from the node + * ---------------- + */ + targetList = sortnode->plan.targetlist; + keycount = sortnode->keycount; + + /* ---------------- + * first allocate space for scan keys + * ---------------- + */ + if (keycount <= 0) + elog(WARN, "FormSortKeys: keycount <= 0"); + sortkeys = (ScanKey) palloc(keycount * sizeof(ScanKeyData)); + + /* ---------------- + * form each scan key from the resdom info in the target list + * ---------------- + */ + foreach(tl, targetList) { + TargetEntry *target = (TargetEntry *)lfirst(tl); + resdom = target->resdom; + resno = resdom->resno; + reskey = resdom->reskey; + reskeyop = resdom->reskeyop; + + if (reskey > 0) { + ScanKeyEntryInitialize(&sortkeys[reskey-1], + 0, + resno, + (RegProcedure) DatumGetInt32(reskeyop), + (Datum) 0); + } + } + + return sortkeys; +} + +/* ---------------------------------------------------------------- + * ExecSort + * + * old comments + * Retrieves tuples fron the outer subtree and insert them into a + * temporary relation. The temporary relation is then sorted and + * the sorted relation is stored in the relation whose ID is indicated + * in the 'tempid' field of this node. + * Assumes that heap access method is used. + * + * Conditions: + * -- none. + * + * Initial States: + * -- the outer child is prepared to return the first tuple. + * ---------------------------------------------------------------- + */ +TupleTableSlot * +ExecSort(Sort *node) +{ + EState *estate; + SortState *sortstate; + Plan *outerNode; + ScanDirection dir; + int keycount; + ScanKey sortkeys; + Relation tempRelation; + Relation currentRelation; + HeapScanDesc currentScanDesc; + HeapTuple heapTuple; + TupleTableSlot *slot; + Buffer buffer; + int tupCount = 0; + + /* ---------------- + * get state info from node + * ---------------- + */ + SO1_printf("ExecSort: %s\n", + "entering routine"); + + sortstate = node->sortstate; + estate = node->plan.state; + dir = estate->es_direction; + + /* ---------------- + * the first time we call this, we retrieve all tuples + * from the subplan into a temporary relation and then + * we sort the relation. Subsequent calls return tuples + * from the temporary relation. + * ---------------- + */ + + if (sortstate->sort_Flag == false) { + SO1_printf("ExecSort: %s\n", + "sortstate == false -> sorting subplan"); + /* ---------------- + * set all relations to be scanned in the forward direction + * while creating the temporary relation. + * ---------------- + */ + estate->es_direction = EXEC_FRWD; + + /* ---------------- + * if we couldn't create the temp or current relations then + * we print a warning and return NULL. + * ---------------- + */ + tempRelation = sortstate->sort_TempRelation; + if (tempRelation == NULL) { + elog(DEBUG, "ExecSort: temp relation is NULL! aborting..."); + return NULL; + } + + currentRelation = sortstate->csstate.css_currentRelation; + if (currentRelation == NULL) { + elog(DEBUG, "ExecSort: current relation is NULL! aborting..."); + return NULL; + } + + /* ---------------- + * retrieve tuples from the subplan and + * insert them in the temporary relation + * ---------------- + */ + outerNode = outerPlan((Plan *) node); + SO1_printf("ExecSort: %s\n", + "inserting tuples into tempRelation"); + + for (;;) { + slot = ExecProcNode(outerNode, (Plan*)node); + + if (TupIsNull(slot)) + break; + + tupCount++; + + heapTuple = slot->val; + + heap_insert(tempRelation, /* relation desc */ + heapTuple); /* heap tuple to insert */ + + ExecClearTuple(slot); + } + + /* ---------------- + * now sort the tuples in our temporary relation + * into a new sorted relation using psort() + * + * psort() seems to require that the relations + * are created and opened in advance. + * -cim 1/25/90 + * ---------------- + */ + keycount = node->keycount; + sortkeys = (ScanKey)sortstate->sort_Keys; + SO1_printf("ExecSort: %s\n", + "calling psort"); + + /* + * If no tuples were fetched from the proc node return NULL now + * psort dumps it if 0 tuples are in the relation and I don't want + * to try to debug *that* routine!! + */ + if (tupCount == 0) + return NULL; + + psort(tempRelation, /* old relation */ + currentRelation, /* new relation */ + keycount, /* number keys */ + sortkeys); /* keys */ + + if (currentRelation == NULL) { + elog(DEBUG, "ExecSort: sorted relation is NULL! aborting..."); + return NULL; + } + + /* ---------------- + * restore to user specified direction + * ---------------- + */ + estate->es_direction = dir; + + /* ---------------- + * now initialize the scan descriptor to scan the + * sorted relation and update the sortstate information + * ---------------- + */ + currentScanDesc = heap_beginscan(currentRelation, /* relation */ + ScanDirectionIsBackward(dir), + /* bkwd flag */ + NowTimeQual, /* time qual */ + 0, /* num scan keys */ + NULL); /* scan keys */ + + sortstate->csstate.css_currentRelation = currentRelation; + sortstate->csstate.css_currentScanDesc = currentScanDesc; + + /* ---------------- + * make sure the tuple descriptor is up to date + * ---------------- + */ + slot = sortstate->csstate.css_ScanTupleSlot; + + slot->ttc_tupleDescriptor = + RelationGetTupleDescriptor(currentRelation); + + /* ---------------- + * finally set the sorted flag to true + * ---------------- + */ + sortstate->sort_Flag = true; + } + else { + slot = sortstate->csstate.css_ScanTupleSlot; + } + + SO1_printf("ExecSort: %s\n", + "retrieveing tuple from sorted relation"); + + /* ---------------- + * at this point we know we have a sorted relation so + * we preform a simple scan on it with amgetnext().. + * ---------------- + */ + currentScanDesc = sortstate->csstate.css_currentScanDesc; + + heapTuple = heap_getnext(currentScanDesc, /* scan desc */ + ScanDirectionIsBackward(dir), + /* bkwd flag */ + &buffer); /* return: buffer */ + + /* 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. + - 01/09/93 */ + + if (buffer != InvalidBuffer) + IncrBufferRefCount(buffer); + + return ExecStoreTuple(heapTuple, /* tuple to store */ + slot, /* slot to store in */ + buffer, /* this tuple's buffer */ + false); /* don't free stuff from amgetnext */ +} + +/* ---------------------------------------------------------------- + * ExecInitSort + * + * old comments + * Creates the run-time state information for the sort node + * produced by the planner and initailizes its outer subtree. + * ---------------------------------------------------------------- + */ +bool +ExecInitSort(Sort *node, EState *estate, Plan *parent) +{ + SortState *sortstate; + Plan *outerPlan; + ScanKey sortkeys; + TupleDesc tupType; + Oid tempOid; + Oid sortOid; + Relation tempDesc; + Relation sortedDesc; + + SO1_printf("ExecInitSort: %s\n", + "initializing sort node"); + + /* ---------------- + * assign the node's execution state + * ---------------- + */ + node->plan.state = estate; + + /* ---------------- + * create state structure + * ---------------- + */ + sortstate = makeNode(SortState); + sortstate->sort_Flag = 0; + sortstate->sort_Keys = NULL; + sortstate->sort_TempRelation = NULL; + + node->sortstate = sortstate; + + /* ---------------- + * Miscellanious initialization + * + * + assign node's base_id + * + assign debugging hooks + * + * Sort nodes don't initialize their ExprContexts because + * they never call ExecQual or ExecTargetList. + * ---------------- + */ + ExecAssignNodeBaseInfo(estate, &sortstate->csstate.cstate, parent); + +#define SORT_NSLOTS 1 + /* ---------------- + * tuple table initialization + * + * sort nodes only return scan tuples from their sorted + * relation. + * ---------------- + */ + ExecInitScanTupleSlot(estate, &sortstate->csstate); + ExecInitResultTupleSlot(estate, &sortstate->csstate.cstate); + + /* ---------------- + * initializes child nodes + * ---------------- + */ + outerPlan = outerPlan((Plan *) node); + ExecInitNode(outerPlan, estate, (Plan *) node); + + /* ---------------- + * initialize sortstate information + * ---------------- + */ + sortkeys = FormSortKeys(node); + sortstate->sort_Keys = sortkeys; + sortstate->sort_Flag = false; + + /* ---------------- + * initialize tuple type. no need to initialize projection + * info because this node doesn't do projections. + * ---------------- + */ + ExecAssignScanTypeFromOuterPlan((Plan *) node, &sortstate->csstate); + sortstate->csstate.cstate.cs_ProjInfo = NULL; + + /* ---------------- + * get type information needed for ExecCreatR + * ---------------- + */ + tupType = ExecGetScanType(&sortstate->csstate); + + /* ---------------- + * ExecCreatR wants its second argument to be an object id of + * a relation in the range table or _TEMP_RELATION_ID_ + * indicating that the relation is not in the range table. + * + * In the second case ExecCreatR creates a temp relation. + * (currently this is the only case we support -cim 10/16/89) + * ---------------- + */ + tempOid = node->tempid; + sortOid = _TEMP_RELATION_ID_; + + /* ---------------- + * create the temporary relations + * ---------------- + */ +/* len = ExecTargetListLength(node->plan.targetlist); */ + tempDesc = ExecCreatR(tupType, tempOid); + sortedDesc = ExecCreatR(tupType, sortOid); + + /* ---------------- + * save the relation descriptor in the sortstate + * ---------------- + */ + sortstate->sort_TempRelation = tempDesc; + sortstate->csstate.css_currentRelation = sortedDesc; + SO1_printf("ExecInitSort: %s\n", + "sort node initialized"); + + /* ---------------- + * return relation oid of temporary sort relation in a list + * (someday -- for now we return LispTrue... cim 10/12/89) + * ---------------- + */ + return TRUE; +} + +int +ExecCountSlotsSort(Sort *node) +{ + return ExecCountSlotsNode(outerPlan((Plan *)node)) + + ExecCountSlotsNode(innerPlan((Plan *)node)) + + SORT_NSLOTS; +} + +/* ---------------------------------------------------------------- + * ExecEndSort(node) + * + * old comments + * destroys the temporary relation. + * ---------------------------------------------------------------- + */ +void +ExecEndSort(Sort *node) +{ + SortState *sortstate; + Relation tempRelation; + Relation sortedRelation; + Plan *outerPlan; + + /* ---------------- + * get info from the sort state + * ---------------- + */ + SO1_printf("ExecEndSort: %s\n", + "shutting down sort node"); + + sortstate = node->sortstate; + tempRelation = sortstate->sort_TempRelation; + sortedRelation = sortstate->csstate.css_currentRelation; + + heap_destroyr(tempRelation); + heap_destroyr(sortedRelation); + + + /* ---------------- + * close the sorted relation and shut down the scan. + * ---------------- + */ + ExecCloseR((Plan *) node); + + /* ---------------- + * shut down the subplan + * ---------------- + */ + outerPlan = outerPlan((Plan *) node); + ExecEndNode(outerPlan, (Plan*)node); + + /* ---------------- + * clean out the tuple table + * ---------------- + */ + ExecClearTuple(sortstate->csstate.css_ScanTupleSlot); + + SO1_printf("ExecEndSort: %s\n", + "sort node shutdown"); +} + +/* ---------------------------------------------------------------- + * ExecSortMarkPos + * ---------------------------------------------------------------- + */ +void +ExecSortMarkPos(Sort *node) +{ + SortState *sortstate; + HeapScanDesc sdesc; + + /* ---------------- + * if we haven't sorted yet, just return + * ---------------- + */ + sortstate = node->sortstate; + if (sortstate->sort_Flag == false) + return; + + sdesc = sortstate->csstate.css_currentScanDesc; + heap_markpos(sdesc); + return; +} + +/* ---------------------------------------------------------------- + * ExecSortRestrPos + * ---------------------------------------------------------------- + */ +void +ExecSortRestrPos(Sort *node) +{ + SortState *sortstate; + HeapScanDesc sdesc; + + /* ---------------- + * if we haven't sorted yet, just return. + * ---------------- + */ + sortstate = node->sortstate; + if (sortstate->sort_Flag == false) + return; + + /* ---------------- + * restore the scan to the previously marked position + * ---------------- + */ + sdesc = sortstate->csstate.css_currentScanDesc; + heap_restrpos(sdesc); +} diff --git a/src/backend/executor/nodeSort.h b/src/backend/executor/nodeSort.h new file mode 100644 index 00000000000..504b8a1f19e --- /dev/null +++ b/src/backend/executor/nodeSort.h @@ -0,0 +1,23 @@ +/*------------------------------------------------------------------------- + * + * nodeSort.h-- + * + * + * + * Copyright (c) 1994, Regents of the University of California + * + * $Id: nodeSort.h,v 1.1.1.1 1996/07/09 06:21:27 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +#ifndef NODESORT_H +#define NODESORT_H + +extern TupleTableSlot *ExecSort(Sort *node); +extern bool ExecInitSort(Sort *node, EState *estate, Plan *parent); +extern int ExecCountSlotsSort(Sort *node); +extern void ExecEndSort(Sort *node); +extern void ExecSortMarkPos(Sort *node); +extern void ExecSortRestrPos(Sort *node); + +#endif /* NODESORT_H */ diff --git a/src/backend/executor/nodeTee.c b/src/backend/executor/nodeTee.c new file mode 100644 index 00000000000..5be700b9ae7 --- /dev/null +++ b/src/backend/executor/nodeTee.c @@ -0,0 +1,503 @@ +/*------------------------------------------------------------------------- + * + * nodeTee.c-- + * + * + * 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 + * + * IDENTIFICATION + * $Header: /cvsroot/pgsql/src/backend/executor/Attic/nodeTee.c,v 1.1.1.1 1996/07/09 06:21:27 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ + +#include <sys/file.h> +#include "utils/palloc.h" +#include "utils/relcache.h" +#include "storage/bufmgr.h" /* for IncrBufferRefCount */ +#include "optimizer/internal.h" +#include "executor/executor.h" +#include "executor/nodeTee.h" +#include "catalog/catalog.h" +#include "tcop/pquery.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; + + /* 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); + +/* bufferRel = ExecCreatR(len, tupType, _TEMP_RELATION_ID_); */ + + /* 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(teeState->tee_bufferRelname, +/*FIX */ NULL, + 'n', + DEFAULT_SMGR, + tupType)); + } + else { + sprintf(teeState->tee_bufferRelname, + "ttemp_%d", /* 'ttemp' for 'tee' temporary*/ + newoid()); +/* bufferRel = ExecCreatR(len, tupType, _TEMP_RELATION_ID); */ + bufferRel = heap_open(heap_create(teeState->tee_bufferRelname, + NULL, /*XXX */ + 'n', + DEFAULT_SMGR, + tupType)); + } + + 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 +* ---------------------------------------------------------------- +*/ +void +initTeeScanDescs(Tee* node) +{ + TeeState *teeState; + Relation bufferRel; + ScanDirection dir; + 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 */ + + if (teeState->tee_leftScanDesc == NULL) + { + teeState->tee_leftScanDesc = heap_beginscan(bufferRel, + ScanDirectionIsBackward(dir), + NowTimeQual, /* time qual */ + 0, /* num scan keys */ + NULL /* scan keys */ + ); + } + if (teeState->tee_rightScanDesc == NULL) + { + teeState->tee_rightScanDesc = heap_beginscan(bufferRel, + ScanDirectionIsBackward(dir), + NowTimeQual, /* time qual */ + 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; + Buffer buffer; + + 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(WARN,"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)) + { + heapTuple = slot->val; + + /* insert into temporary relation */ + heap_insert(bufferRel, 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), + /* &buffer */ + (Buffer*)NULL); + } + + /* 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), + &buffer); + + /* 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 (buffer != InvalidBuffer) + IncrBufferRefCount(buffer); + + slot = teeState->cstate.cs_ResultTupleSlot; + slot->ttc_tupleDescriptor = RelationGetTupleDescriptor(bufferRel); + + result = ExecStoreTuple(heapTuple,/* tuple to store */ + slot, /* slot to store in */ + buffer,/* 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; +} + +/* ---------------------------------------------------------------- + * ExecTeeReScan(node) + * + * Rescans the relation. + * ---------------------------------------------------------------- + */ +void +ExecTeeReScan(Tee *node, ExprContext *exprCtxt, Plan *parent) +{ + + EState *estate; + TeeState *teeState; + ScanDirection dir; + + estate = ((Plan*)node)->state; + teeState = node->teestate; + + dir = estate->es_direction; + + /* XXX doesn't handle backwards direction yet */ + + if (parent == node->leftParent) { + if (teeState->tee_leftScanDesc) + { + heap_rescan(teeState->tee_leftScanDesc, + ScanDirectionIsBackward(dir), + NULL); + teeState->tee_leftPlace = 0; + } + } + else + { + if (teeState->tee_rightScanDesc) + { + heap_rescan(teeState->tee_leftScanDesc, + ScanDirectionIsBackward(dir), + NULL); + teeState->tee_rightPlace = 0; + } + } +} + + +/* --------------------------------------------------------------------- + * 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_destroyr(bufferRel); + teeState->tee_bufferRel = NULL; + if (teeState->tee_mcxt) { + orig = CurrentMemoryContext; + MemoryContextSwitchTo(teeState->tee_mcxt); + } + + 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; + } + } + } + +} + diff --git a/src/backend/executor/nodeTee.h b/src/backend/executor/nodeTee.h new file mode 100644 index 00000000000..aa50efdead4 --- /dev/null +++ b/src/backend/executor/nodeTee.h @@ -0,0 +1,22 @@ +/*------------------------------------------------------------------------- + * + * nodeTee.h-- + * support functions for a Tee executor node + * + * Copyright (c) 1994, Regents of the University of California + * + * $Id: nodeTee.h,v 1.1.1.1 1996/07/09 06:21:27 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ + +#ifndef NODETEE_H +#define NODETEE_H + +extern TupleTableSlot* ExecTee(Tee* node, Plan* parent); +extern bool ExecInitTee(Tee* node, EState* estate, Plan* parent); +extern void ExecTeeReScan(Tee *node, ExprContext *exprCtxt, Plan *parent); +extern void ExecEndTee(Tee* node, Plan* parent); +extern int ExecCountSlotsTee(Tee* node); + +#endif /* NODETEE_H */ diff --git a/src/backend/executor/nodeUnique.c b/src/backend/executor/nodeUnique.c new file mode 100644 index 00000000000..8be0bd8497c --- /dev/null +++ b/src/backend/executor/nodeUnique.c @@ -0,0 +1,316 @@ +/*------------------------------------------------------------------------- + * + * nodeUnique.c-- + * Routines to handle unique'ing of queries where appropriate + * + * Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * $Header: /cvsroot/pgsql/src/backend/executor/nodeUnique.c,v 1.1.1.1 1996/07/09 06:21:27 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +/* + * INTERFACE ROUTINES + * ExecUnique - generate a unique'd temporary relation + * ExecInitUnique - initialize node and subnodes.. + * ExecEndUnique - shutdown node and subnodes + * + * NOTES + * Assumes tuples returned from subplan arrive in + * sorted order. + * + */ +#include "executor/executor.h" +#include "executor/nodeUnique.h" +#include "optimizer/clauses.h" +#include "access/printtup.h" /* for typtoout() */ +#include "utils/builtins.h" /* for namecpy()*/ + +/* ---------------------------------------------------------------- + * ExecIdenticalTuples + * + * This is a hack function used by ExecUnique to see if + * two tuples are identical. This should be provided + * by the heap tuple code but isn't. The real problem + * is that we assume we can byte compare tuples to determine + * if they are "equal". In fact, if we have user defined + * types there may be problems because it's possible that + * an ADT may have multiple representations with the + * same ADT value. -cim + * ---------------------------------------------------------------- + */ +static bool /* true if tuples are identical, false otherwise */ +ExecIdenticalTuples(TupleTableSlot *t1, TupleTableSlot *t2) +{ + HeapTuple h1; + HeapTuple h2; + char *d1; + char *d2; + int len; + + h1 = t1->val; + h2 = t2->val; + + /* ---------------- + * if tuples aren't the same length then they are + * obviously different (one may have null attributes). + * ---------------- + */ + if (h1->t_len != h2->t_len) + return false; + + /* ---------------- + * if the tuples have different header offsets then + * they are different. This will prevent us from returning + * true when comparing tuples of one attribute where one of + * two we're looking at is null (t_len - t_hoff == 0). + * THE t_len FIELDS CAN BE THE SAME IN THIS CASE!! + * ---------------- + */ + if (h1->t_hoff != h2->t_hoff) + return false; + + /* ---------------- + * ok, now get the pointers to the data and the + * size of the attribute portion of the tuple. + * ---------------- + */ + d1 = (char *) GETSTRUCT(h1); + d2 = (char *) GETSTRUCT(h2); + len = (int) h1->t_len - (int) h1->t_hoff; + + /* ---------------- + * byte compare the data areas and return the result. + * ---------------- + */ + if (memcmp(d1, d2, len) != 0) + return false; + + return true; +} + +/* ---------------------------------------------------------------- + * ExecUnique + * + * This is a very simple node which filters out duplicate + * tuples from a stream of sorted tuples from a subplan. + * + * XXX see comments below regarding freeing tuples. + * ---------------------------------------------------------------- + */ +TupleTableSlot * /* return: a tuple or NULL */ +ExecUnique(Unique *node) +{ + UniqueState *uniquestate; + TupleTableSlot *resultTupleSlot; + TupleTableSlot *slot; + Plan *outerPlan; + char *uniqueAttr; + AttrNumber uniqueAttrNum; + TupleDesc tupDesc; + Oid typoutput; + + /* ---------------- + * get information from the node + * ---------------- + */ + uniquestate = node->uniquestate; + outerPlan = outerPlan((Plan *) node); + resultTupleSlot = uniquestate->cs_ResultTupleSlot; + uniqueAttr = node->uniqueAttr; + uniqueAttrNum = node->uniqueAttrNum; + + if (uniqueAttr) { + tupDesc = ExecGetResultType(uniquestate); + typoutput = typtoout((Oid)tupDesc->attrs[uniqueAttrNum]->atttypid); + } + + /* ---------------- + * now loop, returning only non-duplicate tuples. + * We assume that the tuples arrive in sorted order + * so we can detect duplicates easily. + * ---------------- + */ + for (;;) { + /* ---------------- + * fetch a tuple from the outer subplan + * ---------------- + */ + slot = ExecProcNode(outerPlan, (Plan*)node); + if (TupIsNull(slot)) + return NULL; + + /* ---------------- + * we use the result tuple slot to hold our saved tuples. + * if we haven't a saved tuple to compare our new tuple with, + * then we exit the loop. This new tuple as the saved tuple + * the next time we get here. + * ---------------- + */ + if (TupIsNull(resultTupleSlot)) + break; + + /* ---------------- + * now test if the new tuple and the previous + * tuple match. If so then we loop back and fetch + * another new tuple from the subplan. + * ---------------- + */ + + if (uniqueAttr) { + /* to check equality, we check to see if the typoutput + of the attributes are equal */ + bool isNull1,isNull2; + char *attr1, *attr2; + char *val1, *val2; + + attr1 = heap_getattr(slot->val, InvalidBuffer, + uniqueAttrNum, tupDesc,&isNull1); + attr2 = heap_getattr(resultTupleSlot->val, InvalidBuffer, + uniqueAttrNum, tupDesc,&isNull2); + + if (isNull1 == isNull2) { + if (isNull1) /* both are null, they are equal */ + continue; + val1 = fmgr(typoutput, attr1, gettypelem(tupDesc->attrs[uniqueAttrNum]->atttypid)); + val2 = fmgr(typoutput, attr2, gettypelem(tupDesc->attrs[uniqueAttrNum]->atttypid)); + /* now, val1 and val2 are ascii representations so we can + use strcmp for comparison */ + if (strcmp(val1,val2) == 0) /* they are equal */ + continue; + else + break; + } + else /* one is null and the other isn't, they aren't equal */ + break; + + } + else { + if (! ExecIdenticalTuples(slot, resultTupleSlot)) + break; + } + + } + + /* ---------------- + * we have a new tuple different from the previous saved tuple + * so we save it in the saved tuple slot. We copy the tuple + * so we don't increment the buffer ref count. + * ---------------- + */ + ExecStoreTuple(heap_copytuple(slot->val), + resultTupleSlot, + InvalidBuffer, + true); + + return resultTupleSlot; +} + +/* ---------------------------------------------------------------- + * ExecInitUnique + * + * This initializes the unique node state structures and + * the node's subplan. + * ---------------------------------------------------------------- + */ +bool /* return: initialization status */ +ExecInitUnique(Unique *node, EState *estate, Plan *parent) +{ + UniqueState *uniquestate; + Plan *outerPlan; + char *uniqueAttr; + + /* ---------------- + * assign execution state to node + * ---------------- + */ + node->plan.state = estate; + + /* ---------------- + * create new UniqueState for node + * ---------------- + */ + uniquestate = makeNode(UniqueState); + node->uniquestate = uniquestate; + uniqueAttr = node->uniqueAttr; + + /* ---------------- + * Miscellanious initialization + * + * + assign node's base_id + * + assign debugging hooks and + * + * Unique nodes have no ExprContext initialization because + * they never call ExecQual or ExecTargetList. + * ---------------- + */ + ExecAssignNodeBaseInfo(estate, uniquestate, parent); + +#define UNIQUE_NSLOTS 1 + /* ------------ + * Tuple table initialization + * ------------ + */ + ExecInitResultTupleSlot(estate, uniquestate); + + /* ---------------- + * then initialize outer plan + * ---------------- + */ + outerPlan = outerPlan((Plan *) node); + ExecInitNode(outerPlan, estate, (Plan *) node); + + /* ---------------- + * unique nodes do no projections, so initialize + * projection info for this node appropriately + * ---------------- + */ + ExecAssignResultTypeFromOuterPlan((Plan *)node,uniquestate); + uniquestate->cs_ProjInfo = NULL; + + if (uniqueAttr) { + TupleDesc tupDesc; + int i = 0; + + tupDesc = ExecGetResultType(uniquestate); + /* the parser should have ensured that uniqueAttr is a legal attribute name*/ + while ( strcmp((tupDesc->attrs[i]->attname).data, uniqueAttr) != 0) + i++; + node->uniqueAttrNum = i+1; /* attribute numbers start from 1 */ + } + else + node->uniqueAttrNum = InvalidAttrNumber; + + /* ---------------- + * all done. + * ---------------- + */ + return TRUE; +} + +int +ExecCountSlotsUnique(Unique *node) +{ + return ExecCountSlotsNode(outerPlan(node)) + + ExecCountSlotsNode(innerPlan(node)) + + UNIQUE_NSLOTS; +} + +/* ---------------------------------------------------------------- + * ExecEndUnique + * + * This shuts down the subplan and frees resources allocated + * to this node. + * ---------------------------------------------------------------- + */ +void +ExecEndUnique(Unique *node) +{ + UniqueState *uniquestate; + + uniquestate = node->uniquestate; + ExecEndNode(outerPlan((Plan *) node), (Plan*)node); + ExecClearTuple(uniquestate->cs_ResultTupleSlot); +} diff --git a/src/backend/executor/nodeUnique.h b/src/backend/executor/nodeUnique.h new file mode 100644 index 00000000000..a8dfc9bd6b9 --- /dev/null +++ b/src/backend/executor/nodeUnique.h @@ -0,0 +1,21 @@ +/*------------------------------------------------------------------------- + * + * nodeUnique.h-- + * + * + * + * Copyright (c) 1994, Regents of the University of California + * + * $Id: nodeUnique.h,v 1.1.1.1 1996/07/09 06:21:28 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +#ifndef NODEUNIQUE_H +#define NODEUNIQUE_H + +extern TupleTableSlot *ExecUnique(Unique *node); +extern bool ExecInitUnique(Unique *node, EState *estate, Plan *parent); +extern int ExecCountSlotsUnique(Unique *node); +extern void ExecEndUnique(Unique *node); + +#endif /* NODEUNIQUE_H */ diff --git a/src/backend/executor/tuptable.h b/src/backend/executor/tuptable.h new file mode 100644 index 00000000000..33f7de33589 --- /dev/null +++ b/src/backend/executor/tuptable.h @@ -0,0 +1,72 @@ +/*------------------------------------------------------------------------- + * + * tuptable.h-- + * tuple table support stuff + * + * + * Copyright (c) 1994, Regents of the University of California + * + * $Id: tuptable.h,v 1.1.1.1 1996/07/09 06:21:28 scrappy Exp $ + * + * NOTES + * The tuple table interface is getting pretty ugly. + * It should be redesigned soon. + * + *------------------------------------------------------------------------- + */ +#ifndef TUPTABLE_H +#define TUPTABLE_H + +/* ---------------- + * Note: the executor tuple table is managed and manipulated by special + * code and macros in executor/execTuples.c and tupTable.h + * + * TupleTableSlot information + * + * shouldFree boolean - should we call pfree() on tuple + * descIsNew boolean - true when tupleDescriptor changes + * tupleDescriptor type information kept regarding the tuple data + * buffer the buffer for tuples pointing to disk pages + * + * The executor stores pointers to tuples in a ``tuple table'' + * which is composed of TupleTableSlot's. Some of the tuples + * are pointers to buffer pages and others are pointers to + * palloc'ed memory and the shouldFree variable tells us when + * we may call pfree() on a tuple. -cim 9/23/90 + * + * In the implementation of nested-dot queries such as + * "retrieve (EMP.hobbies.all)", a single scan may return tuples + * of many types, so now we return pointers to tuple descriptors + * along with tuples returned via the tuple table. -cim 1/18/90 + * ---------------- + */ +typedef struct TupleTableSlot { + NodeTag type; + HeapTuple val; + bool ttc_shouldFree; + bool ttc_descIsNew; + TupleDesc ttc_tupleDescriptor; + Buffer ttc_buffer; + int ttc_whichplan; +} TupleTableSlot; + +/* ---------------- + * tuple table data structure + * ---------------- + */ +typedef struct TupleTableData { + int size; /* size of the table */ + int next; /* next available slot number */ + TupleTableSlot *array; /* array of TupleTableSlot's */ +} TupleTableData; + +typedef TupleTableData *TupleTable; + +/* + tuple table macros are all excised from the system now + see executor.h for decls of functions defined in execTuples.c + + - jolly +*/ + +#endif /* TUPTABLE_H */ |