2010-10-14 16:56:39 -04:00
|
|
|
/*-------------------------------------------------------------------------
|
|
|
|
|
*
|
|
|
|
|
* nodeMergeAppend.c
|
|
|
|
|
* routines to handle MergeAppend nodes.
|
|
|
|
|
*
|
2025-01-01 11:21:55 -05:00
|
|
|
* Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
|
2010-10-14 16:56:39 -04:00
|
|
|
* Portions Copyright (c) 1994, Regents of the University of California
|
|
|
|
|
*
|
|
|
|
|
*
|
|
|
|
|
* IDENTIFICATION
|
|
|
|
|
* src/backend/executor/nodeMergeAppend.c
|
|
|
|
|
*
|
|
|
|
|
*-------------------------------------------------------------------------
|
|
|
|
|
*/
|
|
|
|
|
/* INTERFACE ROUTINES
|
|
|
|
|
* ExecInitMergeAppend - initialize the MergeAppend node
|
|
|
|
|
* ExecMergeAppend - retrieve the next tuple from the node
|
|
|
|
|
* ExecEndMergeAppend - shut down the MergeAppend node
|
|
|
|
|
* ExecReScanMergeAppend - rescan the MergeAppend node
|
|
|
|
|
*
|
|
|
|
|
* NOTES
|
|
|
|
|
* A MergeAppend node contains a list of one or more subplans.
|
|
|
|
|
* These are each expected to deliver tuples that are sorted according
|
|
|
|
|
* to a common sort key. The MergeAppend node merges these streams
|
|
|
|
|
* to produce output sorted the same way.
|
|
|
|
|
*
|
|
|
|
|
* MergeAppend nodes don't make use of their left and right
|
|
|
|
|
* subtrees, rather they maintain a list of subplans so
|
|
|
|
|
* a typical MergeAppend node looks like this in the plan tree:
|
|
|
|
|
*
|
|
|
|
|
* ...
|
|
|
|
|
* /
|
|
|
|
|
* MergeAppend---+------+------+--- nil
|
|
|
|
|
* / \ | | |
|
|
|
|
|
* nil nil ... ... ...
|
|
|
|
|
* subplans
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
#include "postgres.h"
|
|
|
|
|
|
2024-03-04 06:00:11 -05:00
|
|
|
#include "executor/executor.h"
|
2018-07-19 06:49:43 -04:00
|
|
|
#include "executor/execPartition.h"
|
2010-10-14 16:56:39 -04:00
|
|
|
#include "executor/nodeMergeAppend.h"
|
2012-11-29 11:13:08 -05:00
|
|
|
#include "lib/binaryheap.h"
|
2017-07-25 20:37:17 -04:00
|
|
|
#include "miscadmin.h"
|
2012-11-29 11:13:08 -05:00
|
|
|
|
2010-10-14 16:56:39 -04:00
|
|
|
/*
|
2012-11-29 11:13:08 -05:00
|
|
|
* We have one slot for each item in the heap array. We use SlotNumber
|
|
|
|
|
* to store slot indexes. This doesn't actually provide any formal
|
|
|
|
|
* type-safety, but it makes the code more self-documenting.
|
2010-10-14 16:56:39 -04:00
|
|
|
*/
|
2012-11-29 11:13:08 -05:00
|
|
|
typedef int32 SlotNumber;
|
2010-10-14 16:56:39 -04:00
|
|
|
|
2017-07-17 03:33:49 -04:00
|
|
|
static TupleTableSlot *ExecMergeAppend(PlanState *pstate);
|
2012-11-29 11:13:08 -05:00
|
|
|
static int heap_compare_slots(Datum a, Datum b, void *arg);
|
2010-10-14 16:56:39 -04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
/* ----------------------------------------------------------------
|
|
|
|
|
* ExecInitMergeAppend
|
|
|
|
|
*
|
|
|
|
|
* Begin all of the subscans of the MergeAppend node.
|
|
|
|
|
* ----------------------------------------------------------------
|
|
|
|
|
*/
|
|
|
|
|
MergeAppendState *
|
|
|
|
|
ExecInitMergeAppend(MergeAppend *node, EState *estate, int eflags)
|
|
|
|
|
{
|
|
|
|
|
MergeAppendState *mergestate = makeNode(MergeAppendState);
|
|
|
|
|
PlanState **mergeplanstates;
|
2024-12-19 17:07:14 -05:00
|
|
|
const TupleTableSlotOps *mergeops;
|
2018-07-19 06:49:43 -04:00
|
|
|
Bitmapset *validsubplans;
|
2010-10-14 16:56:39 -04:00
|
|
|
int nplans;
|
2018-07-19 06:49:43 -04:00
|
|
|
int i,
|
|
|
|
|
j;
|
2010-10-14 16:56:39 -04:00
|
|
|
|
|
|
|
|
/* check for unsupported flags */
|
|
|
|
|
Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* create new MergeAppendState for our node
|
|
|
|
|
*/
|
|
|
|
|
mergestate->ps.plan = (Plan *) node;
|
|
|
|
|
mergestate->ps.state = estate;
|
2017-07-17 03:33:49 -04:00
|
|
|
mergestate->ps.ExecProcNode = ExecMergeAppend;
|
2018-07-19 06:49:43 -04:00
|
|
|
|
|
|
|
|
/* If run-time partition pruning is enabled, then set that up now */
|
2025-01-29 21:57:32 -05:00
|
|
|
if (node->part_prune_index >= 0)
|
2018-07-19 06:49:43 -04:00
|
|
|
{
|
|
|
|
|
PartitionPruneState *prunestate;
|
|
|
|
|
|
2022-04-05 05:46:48 -04:00
|
|
|
/*
|
|
|
|
|
* Set up pruning data structure. This also initializes the set of
|
|
|
|
|
* subplans to initialize (validsubplans) by taking into account the
|
|
|
|
|
* result of performing initial pruning if any.
|
|
|
|
|
*/
|
Perform runtime initial pruning outside ExecInitNode()
This commit builds on the prior change that moved PartitionPruneInfos
out of individual plan nodes into a list in PlannedStmt, making it
possible to initialize PartitionPruneStates without traversing the
plan tree and perform runtime initial pruning before ExecInitNode()
initializes the plan trees. These tasks are now handled in a new
routine, ExecDoInitialPruning(), which is called by InitPlan()
before calling ExecInitNode() on various plan trees.
ExecDoInitialPruning() performs the initial pruning and saves the
result -- a Bitmapset of indexes for surviving child subnodes -- in
es_part_prune_results, a list in EState.
PartitionPruneStates created for initial pruning are stored in
es_part_prune_states, another list in EState, for later use during
exec pruning. Both lists are parallel to es_part_prune_infos, which
holds the PartitionPruneInfos from PlannedStmt, enabling shared
indexing.
PartitionPruneStates initialized in ExecDoInitialPruning() now
include only the PartitionPruneContexts for initial pruning steps.
Exec pruning contexts are initialized later in
ExecInitPartitionExecPruning() when the parent plan node is
initialized, as the exec pruning step expressions depend on the parent
node's PlanState.
The existing function PartitionPruneFixSubPlanMap() has been
repurposed for this initialization to avoid duplicating a similar
loop structure for finding PartitionedRelPruningData to initialize
exec pruning contexts for. It has been renamed to
InitExecPruningContexts() to reflect its new primary responsibility.
The original logic to "fix subplan maps" remains intact but is now
encapsulated within the renamed function.
This commit removes two obsolete Asserts in partkey_datum_from_expr().
The ExprContext used for pruning expression evaluation is now
independent of the parent PlanState, making these Asserts unnecessary.
By centralizing pruning logic and decoupling it from the plan
initialization step (ExecInitNode()), this change sets the stage for
future patches that will use the result of initial pruning to
save the overhead of redundant processing for pruned partitions.
Reviewed-by: Robert Haas <robertmhaas@gmail.com>
Reviewed-by: Tomas Vondra <tomas@vondra.me>
Discussion: https://postgr.es/m/CA+HiwqFGkMSge6TgC9KQzde0ohpAycLQuV7ooitEEpbKB0O_mg@mail.gmail.com
2025-01-31 01:47:15 -05:00
|
|
|
prunestate = ExecInitPartitionExecPruning(&mergestate->ps,
|
|
|
|
|
list_length(node->mergeplans),
|
|
|
|
|
node->part_prune_index,
|
|
|
|
|
node->apprelids,
|
|
|
|
|
&validsubplans);
|
2018-07-19 06:49:43 -04:00
|
|
|
mergestate->ms_prune_state = prunestate;
|
2022-04-05 05:46:48 -04:00
|
|
|
nplans = bms_num_members(validsubplans);
|
2018-07-19 06:49:43 -04:00
|
|
|
|
|
|
|
|
/*
|
2019-12-11 17:05:30 -05:00
|
|
|
* When no run-time pruning is required and there's at least one
|
2022-04-11 04:49:41 -04:00
|
|
|
* subplan, we can fill ms_valid_subplans immediately, preventing
|
2019-12-11 17:05:30 -05:00
|
|
|
* later calls to ExecFindMatchingSubPlans.
|
2018-07-19 06:49:43 -04:00
|
|
|
*/
|
2019-12-11 17:05:30 -05:00
|
|
|
if (!prunestate->do_exec_prune && nplans > 0)
|
2018-07-19 06:49:43 -04:00
|
|
|
mergestate->ms_valid_subplans = bms_add_range(NULL, 0, nplans - 1);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
nplans = list_length(node->mergeplans);
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* When run-time partition pruning is not enabled we can just mark all
|
|
|
|
|
* subplans as valid; they must also all be initialized.
|
|
|
|
|
*/
|
2018-07-30 17:03:19 -04:00
|
|
|
Assert(nplans > 0);
|
2018-07-19 06:49:43 -04:00
|
|
|
mergestate->ms_valid_subplans = validsubplans =
|
|
|
|
|
bms_add_range(NULL, 0, nplans - 1);
|
|
|
|
|
mergestate->ms_prune_state = NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
mergeplanstates = (PlanState **) palloc(nplans * sizeof(PlanState *));
|
2010-10-14 16:56:39 -04:00
|
|
|
mergestate->mergeplans = mergeplanstates;
|
|
|
|
|
mergestate->ms_nplans = nplans;
|
|
|
|
|
|
|
|
|
|
mergestate->ms_slots = (TupleTableSlot **) palloc0(sizeof(TupleTableSlot *) * nplans);
|
2024-04-11 04:18:05 -04:00
|
|
|
mergestate->ms_heap = binaryheap_allocate(nplans, heap_compare_slots,
|
2012-11-29 11:13:08 -05:00
|
|
|
mergestate);
|
2010-10-14 16:56:39 -04:00
|
|
|
|
|
|
|
|
/*
|
2018-07-19 06:49:43 -04:00
|
|
|
* call ExecInitNode on each of the valid plans to be executed and save
|
|
|
|
|
* the results into the mergeplanstates array.
|
2010-10-14 16:56:39 -04:00
|
|
|
*/
|
2019-07-22 03:03:12 -04:00
|
|
|
j = 0;
|
|
|
|
|
i = -1;
|
|
|
|
|
while ((i = bms_next_member(validsubplans, i)) >= 0)
|
2010-10-14 16:56:39 -04:00
|
|
|
{
|
2019-07-22 03:03:12 -04:00
|
|
|
Plan *initNode = (Plan *) list_nth(node->mergeplans, i);
|
2010-10-14 16:56:39 -04:00
|
|
|
|
2019-07-22 03:03:12 -04:00
|
|
|
mergeplanstates[j++] = ExecInitNode(initNode, estate, eflags);
|
2010-10-14 16:56:39 -04:00
|
|
|
}
|
|
|
|
|
|
2024-12-19 17:07:14 -05:00
|
|
|
/*
|
|
|
|
|
* Initialize MergeAppend's result tuple type and slot. If the child
|
|
|
|
|
* plans all produce the same fixed slot type, we can use that slot type;
|
|
|
|
|
* otherwise make a virtual slot. (Note that the result slot itself is
|
|
|
|
|
* used only to return a null tuple at end of execution; real tuples are
|
|
|
|
|
* returned to the caller in the children's own result slots. What we are
|
|
|
|
|
* doing here is allowing the parent plan node to optimize if the
|
|
|
|
|
* MergeAppend will return only one kind of slot.)
|
|
|
|
|
*/
|
|
|
|
|
mergeops = ExecGetCommonSlotOps(mergeplanstates, j);
|
|
|
|
|
if (mergeops != NULL)
|
|
|
|
|
{
|
|
|
|
|
ExecInitResultTupleSlotTL(&mergestate->ps, mergeops);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
ExecInitResultTupleSlotTL(&mergestate->ps, &TTSOpsVirtual);
|
|
|
|
|
/* show that the output slot type is not fixed */
|
|
|
|
|
mergestate->ps.resultopsset = true;
|
|
|
|
|
mergestate->ps.resultopsfixed = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Miscellaneous initialization
|
|
|
|
|
*/
|
2010-10-14 16:56:39 -04:00
|
|
|
mergestate->ps.ps_ProjInfo = NULL;
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* initialize sort-key information
|
|
|
|
|
*/
|
|
|
|
|
mergestate->ms_nkeys = node->numCols;
|
2011-12-07 00:18:38 -05:00
|
|
|
mergestate->ms_sortkeys = palloc0(sizeof(SortSupportData) * node->numCols);
|
2010-10-14 16:56:39 -04:00
|
|
|
|
|
|
|
|
for (i = 0; i < node->numCols; i++)
|
|
|
|
|
{
|
2011-12-07 00:18:38 -05:00
|
|
|
SortSupport sortKey = mergestate->ms_sortkeys + i;
|
2011-03-26 18:28:40 -04:00
|
|
|
|
2011-12-07 00:18:38 -05:00
|
|
|
sortKey->ssup_cxt = CurrentMemoryContext;
|
|
|
|
|
sortKey->ssup_collation = node->collations[i];
|
|
|
|
|
sortKey->ssup_nulls_first = node->nullsFirst[i];
|
|
|
|
|
sortKey->ssup_attno = node->sortColIdx[i];
|
|
|
|
|
|
2015-01-19 15:20:31 -05:00
|
|
|
/*
|
|
|
|
|
* It isn't feasible to perform abbreviated key conversion, since
|
|
|
|
|
* tuples are pulled into mergestate's binary heap as needed. It
|
|
|
|
|
* would likely be counter-productive to convert tuples into an
|
|
|
|
|
* abbreviated representation as they're pulled up, so opt out of that
|
|
|
|
|
* additional optimization entirely.
|
|
|
|
|
*/
|
|
|
|
|
sortKey->abbreviate = false;
|
|
|
|
|
|
2011-12-07 00:18:38 -05:00
|
|
|
PrepareSortSupportFromOrderingOp(node->sortOperators[i], sortKey);
|
2010-10-14 16:56:39 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* initialize to show we have not run the subplans yet
|
|
|
|
|
*/
|
|
|
|
|
mergestate->ms_initialized = false;
|
|
|
|
|
|
|
|
|
|
return mergestate;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* ----------------------------------------------------------------
|
|
|
|
|
* ExecMergeAppend
|
|
|
|
|
*
|
|
|
|
|
* Handles iteration over multiple subplans.
|
|
|
|
|
* ----------------------------------------------------------------
|
|
|
|
|
*/
|
2017-07-17 03:33:49 -04:00
|
|
|
static TupleTableSlot *
|
|
|
|
|
ExecMergeAppend(PlanState *pstate)
|
2010-10-14 16:56:39 -04:00
|
|
|
{
|
2017-07-17 03:33:49 -04:00
|
|
|
MergeAppendState *node = castNode(MergeAppendState, pstate);
|
2010-10-14 16:56:39 -04:00
|
|
|
TupleTableSlot *result;
|
|
|
|
|
SlotNumber i;
|
|
|
|
|
|
2017-07-25 20:37:17 -04:00
|
|
|
CHECK_FOR_INTERRUPTS();
|
|
|
|
|
|
2010-10-14 16:56:39 -04:00
|
|
|
if (!node->ms_initialized)
|
|
|
|
|
{
|
2018-07-19 06:49:43 -04:00
|
|
|
/* Nothing to do if all subplans were pruned */
|
2019-12-11 17:05:30 -05:00
|
|
|
if (node->ms_nplans == 0)
|
2018-07-19 06:49:43 -04:00
|
|
|
return ExecClearTuple(node->ps.ps_ResultTupleSlot);
|
|
|
|
|
|
2010-10-14 16:56:39 -04:00
|
|
|
/*
|
2018-07-19 06:49:43 -04:00
|
|
|
* If we've yet to determine the valid subplans then do so now. If
|
|
|
|
|
* run-time pruning is disabled then the valid subplans will always be
|
|
|
|
|
* set to all subplans.
|
2010-10-14 16:56:39 -04:00
|
|
|
*/
|
2018-07-19 06:49:43 -04:00
|
|
|
if (node->ms_valid_subplans == NULL)
|
|
|
|
|
node->ms_valid_subplans =
|
Track unpruned relids to avoid processing pruned relations
This commit introduces changes to track unpruned relations explicitly,
making it possible for top-level plan nodes, such as ModifyTable and
LockRows, to avoid processing partitions pruned during initial
pruning. Scan-level nodes, such as Append and MergeAppend, already
avoid the unnecessary processing by accessing partition pruning
results directly via part_prune_index. In contrast, top-level nodes
cannot access pruning results directly and need to determine which
partitions remain unpruned.
To address this, this commit introduces a new bitmapset field,
es_unpruned_relids, which the executor uses to track the set of
unpruned relations. This field is referenced during plan
initialization to skip initializing certain nodes for pruned
partitions. It is initialized with PlannedStmt.unprunableRelids,
a new field that the planner populates with RT indexes of relations
that cannot be pruned during runtime pruning. These include relations
not subject to partition pruning and those required for execution
regardless of pruning.
PlannedStmt.unprunableRelids is computed during set_plan_refs() by
removing the RT indexes of runtime-prunable relations, identified
from PartitionPruneInfos, from the full set of relation RT indexes.
ExecDoInitialPruning() then updates es_unpruned_relids by adding
partitions that survive initial pruning.
To support this, PartitionedRelPruneInfo and PartitionedRelPruningData
now include a leafpart_rti_map[] array that maps partition indexes to
their corresponding RT indexes. The former is used in set_plan_refs()
when constructing unprunableRelids, while the latter is used in
ExecDoInitialPruning() to convert partition indexes returned by
get_matching_partitions() into RT indexes, which are then added to
es_unpruned_relids.
These changes make it possible for ModifyTable and LockRows nodes to
process only relations that remain unpruned after initial pruning.
ExecInitModifyTable() trims lists, such as resultRelations,
withCheckOptionLists, returningLists, and updateColnosLists, to
consider only unpruned partitions. It also creates ResultRelInfo
structs only for these partitions. Similarly, child RowMarks for
pruned relations are skipped.
By avoiding unnecessary initialization of structures for pruned
partitions, these changes improve the performance of updates and
deletes on partitioned tables during initial runtime pruning.
Due to ExecInitModifyTable() changes as described above, EXPLAIN on a
plan for UPDATE and DELETE that uses runtime initial pruning no longer
lists partitions pruned during initial pruning.
Reviewed-by: Robert Haas <robertmhaas@gmail.com> (earlier versions)
Reviewed-by: Tomas Vondra <tomas@vondra.me>
Discussion: https://postgr.es/m/CA+HiwqFGkMSge6TgC9KQzde0ohpAycLQuV7ooitEEpbKB0O_mg@mail.gmail.com
2025-02-07 03:15:09 -05:00
|
|
|
ExecFindMatchingSubPlans(node->ms_prune_state, false, NULL);
|
2018-07-19 06:49:43 -04:00
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* First time through: pull the first tuple from each valid subplan,
|
|
|
|
|
* and set up the heap.
|
|
|
|
|
*/
|
|
|
|
|
i = -1;
|
|
|
|
|
while ((i = bms_next_member(node->ms_valid_subplans, i)) >= 0)
|
2010-10-14 16:56:39 -04:00
|
|
|
{
|
|
|
|
|
node->ms_slots[i] = ExecProcNode(node->mergeplans[i]);
|
|
|
|
|
if (!TupIsNull(node->ms_slots[i]))
|
2012-11-29 11:13:08 -05:00
|
|
|
binaryheap_add_unordered(node->ms_heap, Int32GetDatum(i));
|
2010-10-14 16:56:39 -04:00
|
|
|
}
|
2012-11-29 11:13:08 -05:00
|
|
|
binaryheap_build(node->ms_heap);
|
2010-10-14 16:56:39 -04:00
|
|
|
node->ms_initialized = true;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
/*
|
|
|
|
|
* Otherwise, pull the next tuple from whichever subplan we returned
|
2012-11-29 11:13:08 -05:00
|
|
|
* from last time, and reinsert the subplan index into the heap,
|
|
|
|
|
* because it might now compare differently against the existing
|
|
|
|
|
* elements of the heap. (We could perhaps simplify the logic a bit
|
|
|
|
|
* by doing this before returning from the prior call, but it's better
|
|
|
|
|
* to not pull tuples until necessary.)
|
2010-10-14 16:56:39 -04:00
|
|
|
*/
|
2012-11-29 11:13:08 -05:00
|
|
|
i = DatumGetInt32(binaryheap_first(node->ms_heap));
|
2010-10-14 16:56:39 -04:00
|
|
|
node->ms_slots[i] = ExecProcNode(node->mergeplans[i]);
|
|
|
|
|
if (!TupIsNull(node->ms_slots[i]))
|
2012-11-29 11:13:08 -05:00
|
|
|
binaryheap_replace_first(node->ms_heap, Int32GetDatum(i));
|
|
|
|
|
else
|
|
|
|
|
(void) binaryheap_remove_first(node->ms_heap);
|
2010-10-14 16:56:39 -04:00
|
|
|
}
|
|
|
|
|
|
2012-11-29 11:13:08 -05:00
|
|
|
if (binaryheap_empty(node->ms_heap))
|
2010-10-14 16:56:39 -04:00
|
|
|
{
|
|
|
|
|
/* All the subplans are exhausted, and so is the heap */
|
|
|
|
|
result = ExecClearTuple(node->ps.ps_ResultTupleSlot);
|
|
|
|
|
}
|
2012-11-29 11:13:08 -05:00
|
|
|
else
|
2010-10-14 16:56:39 -04:00
|
|
|
{
|
2012-11-29 11:13:08 -05:00
|
|
|
i = DatumGetInt32(binaryheap_first(node->ms_heap));
|
|
|
|
|
result = node->ms_slots[i];
|
2010-10-14 16:56:39 -04:00
|
|
|
}
|
|
|
|
|
|
2012-11-29 11:13:08 -05:00
|
|
|
return result;
|
2010-10-14 16:56:39 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Compare the tuples in the two given slots.
|
|
|
|
|
*/
|
|
|
|
|
static int32
|
2012-11-29 11:13:08 -05:00
|
|
|
heap_compare_slots(Datum a, Datum b, void *arg)
|
2010-10-14 16:56:39 -04:00
|
|
|
{
|
2012-11-29 11:13:08 -05:00
|
|
|
MergeAppendState *node = (MergeAppendState *) arg;
|
|
|
|
|
SlotNumber slot1 = DatumGetInt32(a);
|
|
|
|
|
SlotNumber slot2 = DatumGetInt32(b);
|
|
|
|
|
|
2010-10-14 16:56:39 -04:00
|
|
|
TupleTableSlot *s1 = node->ms_slots[slot1];
|
|
|
|
|
TupleTableSlot *s2 = node->ms_slots[slot2];
|
|
|
|
|
int nkey;
|
|
|
|
|
|
|
|
|
|
Assert(!TupIsNull(s1));
|
|
|
|
|
Assert(!TupIsNull(s2));
|
|
|
|
|
|
|
|
|
|
for (nkey = 0; nkey < node->ms_nkeys; nkey++)
|
|
|
|
|
{
|
2011-12-07 00:18:38 -05:00
|
|
|
SortSupport sortKey = node->ms_sortkeys + nkey;
|
|
|
|
|
AttrNumber attno = sortKey->ssup_attno;
|
2010-10-14 16:56:39 -04:00
|
|
|
Datum datum1,
|
|
|
|
|
datum2;
|
|
|
|
|
bool isNull1,
|
|
|
|
|
isNull2;
|
2011-12-07 00:18:38 -05:00
|
|
|
int compare;
|
2010-10-14 16:56:39 -04:00
|
|
|
|
|
|
|
|
datum1 = slot_getattr(s1, attno, &isNull1);
|
|
|
|
|
datum2 = slot_getattr(s2, attno, &isNull2);
|
|
|
|
|
|
2011-12-07 00:18:38 -05:00
|
|
|
compare = ApplySortComparator(datum1, isNull1,
|
|
|
|
|
datum2, isNull2,
|
|
|
|
|
sortKey);
|
|
|
|
|
if (compare != 0)
|
Allow btree comparison functions to return INT_MIN.
Historically we forbade datatype-specific comparison functions from
returning INT_MIN, so that it would be safe to invert the sort order
just by negating the comparison result. However, this was never
really safe for comparison functions that directly return the result
of memcmp(), strcmp(), etc, as POSIX doesn't place any such restriction
on those library functions. Buildfarm results show that at least on
recent Linux on s390x, memcmp() actually does return INT_MIN sometimes,
causing sort failures.
The agreed-on answer is to remove this restriction and fix relevant
call sites to not make such an assumption; code such as "res = -res"
should be replaced by "INVERT_COMPARE_RESULT(res)". The same is needed
in a few places that just directly negated the result of memcmp or
strcmp.
To help find places having this problem, I've also added a compile option
to nbtcompare.c that causes some of the commonly used comparators to
return INT_MIN/INT_MAX instead of their usual -1/+1. It'd likely be
a good idea to have at least one buildfarm member running with
"-DSTRESS_SORT_INT_MIN". That's far from a complete test of course,
but it should help to prevent fresh introductions of such bugs.
This is a longstanding portability hazard, so back-patch to all supported
branches.
Discussion: https://postgr.es/m/20180928185215.ffoq2xrq5d3pafna@alap3.anarazel.de
2018-10-05 16:01:29 -04:00
|
|
|
{
|
|
|
|
|
INVERT_COMPARE_RESULT(compare);
|
|
|
|
|
return compare;
|
|
|
|
|
}
|
2010-10-14 16:56:39 -04:00
|
|
|
}
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* ----------------------------------------------------------------
|
|
|
|
|
* ExecEndMergeAppend
|
|
|
|
|
*
|
|
|
|
|
* Shuts down the subscans of the MergeAppend node.
|
|
|
|
|
*
|
|
|
|
|
* Returns nothing of interest.
|
|
|
|
|
* ----------------------------------------------------------------
|
|
|
|
|
*/
|
|
|
|
|
void
|
|
|
|
|
ExecEndMergeAppend(MergeAppendState *node)
|
|
|
|
|
{
|
|
|
|
|
PlanState **mergeplans;
|
|
|
|
|
int nplans;
|
|
|
|
|
int i;
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* get information from the node
|
|
|
|
|
*/
|
|
|
|
|
mergeplans = node->mergeplans;
|
|
|
|
|
nplans = node->ms_nplans;
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* shut down each of the subscans
|
|
|
|
|
*/
|
|
|
|
|
for (i = 0; i < nplans; i++)
|
|
|
|
|
ExecEndNode(mergeplans[i]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
ExecReScanMergeAppend(MergeAppendState *node)
|
|
|
|
|
{
|
|
|
|
|
int i;
|
|
|
|
|
|
2018-07-19 06:49:43 -04:00
|
|
|
/*
|
|
|
|
|
* If any PARAM_EXEC Params used in pruning expressions have changed, then
|
|
|
|
|
* we'd better unset the valid subplans so that they are reselected for
|
|
|
|
|
* the new parameter values.
|
|
|
|
|
*/
|
|
|
|
|
if (node->ms_prune_state &&
|
|
|
|
|
bms_overlap(node->ps.chgParam,
|
|
|
|
|
node->ms_prune_state->execparamids))
|
|
|
|
|
{
|
|
|
|
|
bms_free(node->ms_valid_subplans);
|
|
|
|
|
node->ms_valid_subplans = NULL;
|
|
|
|
|
}
|
|
|
|
|
|
2010-10-14 16:56:39 -04:00
|
|
|
for (i = 0; i < node->ms_nplans; i++)
|
|
|
|
|
{
|
|
|
|
|
PlanState *subnode = node->mergeplans[i];
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* ExecReScan doesn't know about my subplans, so I have to do
|
|
|
|
|
* changed-parameter signaling myself.
|
|
|
|
|
*/
|
|
|
|
|
if (node->ps.chgParam != NULL)
|
|
|
|
|
UpdateChangedParamSet(subnode, node->ps.chgParam);
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* If chgParam of subnode is not null then plan will be re-scanned by
|
|
|
|
|
* first ExecProcNode.
|
|
|
|
|
*/
|
|
|
|
|
if (subnode->chgParam == NULL)
|
|
|
|
|
ExecReScan(subnode);
|
|
|
|
|
}
|
2013-08-30 19:15:21 -04:00
|
|
|
binaryheap_reset(node->ms_heap);
|
2010-10-14 16:56:39 -04:00
|
|
|
node->ms_initialized = false;
|
|
|
|
|
}
|