blob: 7b4eadfa85cd3365bd44e9b441945fd11e76bdd8 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*-------------------------------------------------------------------------
*
* nodeResult.c
* support for constant nodes needing special code.
*
* DESCRIPTION
*
* Result nodes are used in queries where no relations are scanned.
* Examples of such queries are:
*
* select 1 * 2
*
* insert into emp values ('mike', 15000)
*
* (Remember that in an INSERT or UPDATE, we need a plan tree that
* generates the new rows.)
*
* Result nodes are also used to optimise queries with constant
* qualifications (ie, quals that do not depend on the scanned data),
* such as:
*
* select * from emp where 2 > 1
*
* In this case, the plan generated is
*
* Result (with 2 > 1 qual)
* /
* SeqScan (emp.*)
*
* At runtime, the Result node evaluates the constant qual once,
* which is shown by EXPLAIN as a One-Time Filter. If it's
* false, we can return an empty result set without running the
* controlled plan at all. If it's true, we run the controlled
* plan normally and pass back the results.
*
*
* Portions Copyright (c) 2005-2008, Greenplum inc.
* Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/executor/nodeResult.c,v 1.34.2.4 2007/02/16 03:49:10 tgl Exp $
*
*-------------------------------------------------------------------------
*/
#include "postgres.h"
#include "executor/executor.h"
#include "executor/nodeResult.h"
#include "utils/memutils.h"
#include "catalog/pg_type.h"
#include "utils/lsyscache.h"
#include "cdb/cdbhash.h"
#include "cdb/cdbvars.h"
#include "cdb/memquota.h"
#include "executor/spi.h"
static TupleTableSlot *NextInputSlot(ResultState *node);
static bool TupleMatchesHashFilter(Result *resultNode, TupleTableSlot *resultSlot);
/**
* Returns the next valid input tuple from the left subtree
*/
static TupleTableSlot *NextInputSlot(ResultState *node)
{
Assert(outerPlanState(node));
TupleTableSlot *inputSlot = NULL;
while (!inputSlot)
{
PlanState *outerPlan = outerPlanState(node);
TupleTableSlot *candidateInputSlot = ExecProcNode(outerPlan);
if (TupIsNull(candidateInputSlot))
{
/**
* No more input tuples.
*/
break;
}
ExprContext *econtext = node->ps.ps_ExprContext;
/*
* Reset per-tuple memory context to free any expression evaluation
* storage allocated in the previous tuple cycle. Note this can't happen
* until we're done projecting out tuples from a scan tuple.
*/
ResetExprContext(econtext);
node->ps.ps_OuterTupleSlot = candidateInputSlot;
econtext->ecxt_outertuple = candidateInputSlot;
econtext->ecxt_scantuple = candidateInputSlot;
/**
* Extract out qual in case result node is also performing filtering.
*/
List *qual = node->ps.qual;
bool passesFilter = !qual || ExecQual(qual, econtext, false);
if (passesFilter)
{
inputSlot = candidateInputSlot;
}
}
return inputSlot;
}
/* ----------------------------------------------------------------
* ExecResult(node)
*
* returns the tuples from the outer plan which satisfy 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(ResultState *node)
{
ExprContext *econtext = node->ps.ps_ExprContext;
/*
* check constant qualifications like (2 > 1), if not already done
*/
if (node->rs_checkqual)
{
bool qualResult = ExecQual((List *) node->resconstantqual,
econtext,
false);
node->rs_checkqual = false;
if (!qualResult)
{
return NULL;
}
}
TupleTableSlot *outputSlot = NULL;
while (!outputSlot)
{
TupleTableSlot *candidateOutputSlot = NULL;
/*
* Check to see if we're still projecting out tuples from a previous scan
* tuple (because there is a function-returning-set in the projection
* expressions). If so, try to project another one.
*/
if (node->isSRF && node->lastSRFCond == ExprMultipleResult)
{
ExprDoneCond isDone;
candidateOutputSlot = ExecProject(node->ps.ps_ProjInfo, &isDone);
Assert(isDone != ExprSingleResult);
node->lastSRFCond = isDone;
}
/**
* If we did not find an input slot yet, we need to return from the outer plan node.
*/
if (TupIsNull(candidateOutputSlot) && outerPlanState(node))
{
TupleTableSlot *inputSlot = NextInputSlot(node);
if (TupIsNull(inputSlot))
{
/**
* Did not find an input tuple. No point going further.
*/
break;
}
/*
* Reset per-tuple memory context to free any expression evaluation
* storage allocated in the previous tuple cycle. Note this can't happen
* until we're done projecting out tuples from a scan tuple.
*/
ResetExprContext(econtext);
node->ps.ps_OuterTupleSlot = inputSlot;
econtext->ecxt_outertuple = inputSlot;
econtext->ecxt_scantuple = inputSlot;
ExprDoneCond isDone;
/*
* form the result tuple using ExecProject(), and return it --- unless
* the projection produces an empty set, in which case we must loop
* back to see if there are more outerPlan tuples.
*/
candidateOutputSlot = ExecProject(node->ps.ps_ProjInfo, &isDone);
if (isDone != ExprSingleResult)
{
node->isSRF = true;
node->lastSRFCond = isDone;
}
}
else if (TupIsNull(candidateOutputSlot) && !outerPlanState(node) && !(node->inputFullyConsumed))
{
ExprDoneCond isDone;
/*
* form the result tuple using ExecProject(), and return it --- unless
* the projection produces an empty set, in which case we must loop
* back to see if there are more outerPlan tuples.
*/
candidateOutputSlot = ExecProject(node->ps.ps_ProjInfo, &isDone);
node->inputFullyConsumed = true;
if (isDone != ExprSingleResult)
{
node->isSRF = true;
node->lastSRFCond = isDone;
}
}
if (!TupIsNull(candidateOutputSlot))
{
Result *result = (Result *)node->ps.plan;
if (TupleMatchesHashFilter(result, candidateOutputSlot))
{
outputSlot = candidateOutputSlot;
}
}
/*
* Under these conditions, we don't expect to find any more tuples.
*/
if (TupIsNull(candidateOutputSlot)
&& (!node->isSRF
|| (node->isSRF && node->inputFullyConsumed)
)
)
{
break;
}
}
return outputSlot;
}
/**
* Returns true if tuple matches hash filter.
*/
static bool TupleMatchesHashFilter(Result *resultNode, TupleTableSlot *resultSlot)
{
bool res = true;
Assert(resultNode);
Assert(!TupIsNull(resultSlot));
if (resultNode->hashFilter)
{
Assert(resultNode->hashFilter);
ListCell *cell = NULL;
Assert(list_length(resultNode->hashList) <= resultSlot->tts_tupleDescriptor->natts);
CdbHash *hash = makeCdbHash(GetQEGangNum(), HASH_FNV_1);
cdbhashinit(hash);
foreach(cell, resultNode->hashList)
{
/**
* Note that a table may be randomly distributed. The hashList will be empty.
*/
Datum hAttr;
bool isnull;
Oid att_type;
int attnum = lfirst_int(cell);
Assert(attnum > 0);
hAttr = slot_getattr(resultSlot, attnum, &isnull);
if (!isnull)
{
att_type = resultSlot->tts_tupleDescriptor->attrs[attnum - 1]->atttypid;
if (get_typtype(att_type) == 'd')
att_type = getBaseType(att_type);
/* CdbHash treats all array-types as ANYARRAYOID, it doesn't know how to hash
* the individual types (why is this ?) */
switch (att_type)
{
case INT2ARRAYOID:
case INT4ARRAYOID:
case INT8ARRAYOID:
case FLOAT4ARRAYOID:
case FLOAT8ARRAYOID:
case REGTYPEARRAYOID:
att_type = ANYARRAYOID;
/* fall through */
default:
break;
}
cdbhash(hash, hAttr, att_type);
}
else
cdbhashnull(hash);
}
int targetSeg = cdbhashreduce(hash);
pfree(hash);
res = (targetSeg == GetQEIndex());
}
return res;
}
/* ----------------------------------------------------------------
* ExecResultMarkPos
* ----------------------------------------------------------------
*/
void
ExecResultMarkPos(ResultState *node)
{
PlanState *outerPlan = outerPlanState(node);
if (outerPlan != NULL)
ExecMarkPos(outerPlan);
else
elog(DEBUG2, "Result nodes do not support mark/restore");
}
/* ----------------------------------------------------------------
* ExecResultRestrPos
* ----------------------------------------------------------------
*/
void
ExecResultRestrPos(ResultState *node)
{
PlanState *outerPlan = outerPlanState(node);
if (outerPlan != NULL)
ExecRestrPos(outerPlan);
else
insist_log(false, "Result nodes do not support mark/restore");
}
/* ----------------------------------------------------------------
* ExecInitResult
*
* Creates the run-time state information for the result node
* produced by the planner and initializes outer relations
* (child nodes).
* ----------------------------------------------------------------
*/
ResultState *
ExecInitResult(Result *node, EState *estate, int eflags)
{
ResultState *resstate;
/* check for unsupported flags */
Assert(!(eflags & (EXEC_FLAG_MARK | EXEC_FLAG_BACKWARD)) ||
outerPlan(node) != NULL);
/*
* create state structure
*/
resstate = makeNode(ResultState);
resstate->ps.plan = (Plan *) node;
resstate->ps.state = estate;
resstate->inputFullyConsumed = false;
resstate->rs_checkqual = (node->resconstantqual == NULL) ? false : true;
/*
* Miscellaneous initialization
*
* create expression context for node
*/
ExecAssignExprContext(estate, &resstate->ps);
resstate->isSRF = false;
#define RESULT_NSLOTS 1
/*
* tuple table initialization
*/
ExecInitResultTupleSlot(estate, &resstate->ps);
/*
* initialize child expressions
*/
resstate->ps.targetlist = (List *)
ExecInitExpr((Expr *) node->plan.targetlist,
(PlanState *) resstate);
resstate->ps.qual = (List *)
ExecInitExpr((Expr *) node->plan.qual,
(PlanState *) resstate);
resstate->resconstantqual = ExecInitExpr((Expr *) node->resconstantqual,
(PlanState *) resstate);
/*
* initialize child nodes
*/
outerPlanState(resstate) = ExecInitNode(outerPlan(node), estate, eflags);
/*
* we don't use inner plan
*/
Assert(innerPlan(node) == NULL);
/*
* initialize tuple type and projection info
*/
ExecAssignResultTypeFromTL(&resstate->ps);
ExecAssignProjectionInfo(&resstate->ps, NULL);
if (gp_resqueue_memory_policy != RESQUEUE_MEMORY_POLICY_NONE
&& IsResultMemoryIntesive(node))
{
SPI_ReserveMemory(((Plan *)node)->operatorMemKB * 1024L);
}
initGpmonPktForResult((Plan *)node, &resstate->ps.gpmon_pkt, estate);
return resstate;
}
int
ExecCountSlotsResult(Result *node)
{
return ExecCountSlotsNode(outerPlan(node)) + RESULT_NSLOTS;
}
/* ----------------------------------------------------------------
* ExecEndResult
*
* frees up storage allocated through C routines
* ----------------------------------------------------------------
*/
void
ExecEndResult(ResultState *node)
{
/*
* Free the exprcontext
*/
ExecFreeExprContext(&node->ps);
/*
* clean out the tuple table
*/
ExecClearTuple(node->ps.ps_ResultTupleSlot);
/*
* shut down subplans
*/
ExecEndNode(outerPlanState(node));
EndPlanStateGpmonPkt(&node->ps);
}
void
ExecReScanResult(ResultState *node, ExprContext *exprCtxt)
{
node->inputFullyConsumed = false;
node->isSRF = false;
node->rs_checkqual = (node->resconstantqual == NULL) ? false : true;
/*
* If chgParam of subnode is not null then plan will be re-scanned by
* first ExecProcNode. However, if caller is passing us an exprCtxt
* then forcibly rescan the subnode now, so that we can pass the
* exprCtxt down to the subnode (needed for gated indexscan).
*/
if (node->ps.lefttree &&
(node->ps.lefttree->chgParam == NULL || exprCtxt != NULL))
ExecReScan(node->ps.lefttree, exprCtxt);
}
void
initGpmonPktForResult(Plan *planNode, gpmon_packet_t *gpmon_pkt, EState *estate)
{
Assert(planNode != NULL && gpmon_pkt != NULL && IsA(planNode, Result));
{
Assert(GPMON_RESULT_TOTAL <= (int)GPMON_QEXEC_M_COUNT);
InitPlanNodeGpmonPkt(planNode, gpmon_pkt, estate, PMNT_Result, (int64)0, NULL);
}
}