| /* |
| * 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); |
| } |
| } |