Quelle partprune.c
Sprache: C
/*-------------------------------------------------------------------------
*
* partprune . c
* Support for partition pruning during query planning and execution
*
* This module implements partition pruning using the information contained in
* a table ' s partition descriptor , query clauses , and run - time parameters .
*
* During planning , clauses that can be matched to the table ' s partition key
* are turned into a set of " pruning steps " , which are then executed to
* identify a set of partitions ( as indexes in the RelOptInfo - > part_rels
* array ) that satisfy the constraints in the step . Partitions not in the set
* are said to have been pruned .
*
* A base pruning step may involve expressions whose values are only known
* during execution , such as Params , in which case pruning cannot occur
* entirely during planning . In that case , such steps are included alongside
* the plan , so that they can be used by the executor for further pruning .
*
* There are two kinds of pruning steps . A " base " pruning step represents
* tests on partition key column ( s ) , typically comparisons to expressions .
* A " combine " pruning step represents a Boolean connector ( AND / OR ) , and
* combines the outputs of some previous steps using the appropriate
* combination method .
*
* See gen_partprune_steps_internal ( ) for more details on step generation .
*
* Portions Copyright ( c ) 1996 - 2025 , PostgreSQL Global Development Group
* Portions Copyright ( c ) 1994 , Regents of the University of California
*
* IDENTIFICATION
* src / backend / partitioning / partprune . c
*
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
*/
#include "postgres.h"
#include "access/hash.h"
#include "access/nbtree.h"
#include "catalog/pg_operator.h"
#include "catalog/pg_opfamily.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_type.h"
#include "executor/executor.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/appendinfo.h"
#include "optimizer/cost.h"
#include "optimizer/optimizer.h"
#include "optimizer/pathnode.h"
#include "optimizer/placeholder.h"
#include "parser/parsetree.h"
#include "partitioning/partbounds.h"
#include "partitioning/partprune.h"
#include "utils/array.h"
#include "utils/lsyscache.h"
/*
* Information about a clause matched with a partition key .
*/
typedef struct PartClauseInfo
{
int keyno; /* Partition key number (0 to partnatts - 1) */
Oid opno; /* operator used to compare partkey to expr */
bool op_is_ne; /* is clause's original operator <> ? */
Expr *expr; /* expr the partition key is compared to */
Oid cmpfn; /* Oid of function to compare 'expr' to the
* partition key */
int op_strategy; /* btree strategy identifying the operator */
} PartClauseInfo;
/*
* PartClauseMatchStatus
* Describes the result of match_clause_to_partition_key ( )
*/
typedef enum PartClauseMatchStatus
{
PARTCLAUSE_NOMATCH,
PARTCLAUSE_MATCH_CLAUSE,
PARTCLAUSE_MATCH_NULLNESS,
PARTCLAUSE_MATCH_STEPS,
PARTCLAUSE_MATCH_CONTRADICT,
PARTCLAUSE_UNSUPPORTED,
} PartClauseMatchStatus;
/*
* PartClauseTarget
* Identifies which qual clauses we can use for generating pruning steps
*/
typedef enum PartClauseTarget
{
PARTTARGET_PLANNER, /* want to prune during planning */
PARTTARGET_INITIAL, /* want to prune during executor startup */
PARTTARGET_EXEC, /* want to prune during each plan node scan */
} PartClauseTarget;
/*
* GeneratePruningStepsContext
* Information about the current state of generation of " pruning steps "
* for a given set of clauses
*
* gen_partprune_steps ( ) initializes and returns an instance of this struct .
*
* Note that has_mutable_op , has_mutable_arg , and has_exec_param are set if
* we found any potentially - useful - for - pruning clause having those properties ,
* whether or not we actually used the clause in the steps list . This
* definition allows us to skip the PARTTARGET_EXEC pass in some cases .
*/
typedef struct GeneratePruningStepsContext
{
/* Copies of input arguments for gen_partprune_steps: */
RelOptInfo *rel; /* the partitioned relation */
PartClauseTarget target; /* use-case we're generating steps for */
/* Result data: */
List *steps; /* list of PartitionPruneSteps */
bool has_mutable_op; /* clauses include any stable operators */
bool has_mutable_arg; /* clauses include any mutable comparison
* values, *other than* exec params */
bool has_exec_param; /* clauses include any PARAM_EXEC params */
bool contradictory; /* clauses were proven self-contradictory */
/* Working state: */
int next_step_id;
} GeneratePruningStepsContext;
/* The result of performing one PartitionPruneStep */
typedef struct PruneStepResult
{
/*
* The offsets of bounds ( in a table ' s boundinfo ) whose partition is
* selected by the pruning step .
*/
Bitmapset *bound_offsets;
bool scan_default; /* Scan the default partition? */
bool scan_null; /* Scan the partition for NULL values? */
} PruneStepResult;
static List *add_part_relids(List *allpartrelids, Bitmapset *partrelids);
static List *make_partitionedrel_pruneinfo(PlannerInfo *root,
RelOptInfo *parentrel,
List *prunequal,
Bitmapset *partrelids,
int *relid_subplan_map,
Bitmapset **matchedsubplans);
static void gen_partprune_steps(RelOptInfo *rel, List *clauses,
PartClauseTarget target,
GeneratePruningStepsContext *context);
static List *gen_partprune_steps_internal(GeneratePruningStepsContext *context,
List *clauses);
static PartitionPruneStep *gen_prune_step_op(GeneratePruningStepsContext *context,
StrategyNumber opstrategy, bool op_is_ne,
List *exprs, List *cmpfns, Bitmapset *nullkeys);
static PartitionPruneStep *gen_prune_step_combine(GeneratePruningStepsContext *context,
List *source_stepids,
PartitionPruneCombineOp combineOp);
static List *gen_prune_steps_from_opexps(GeneratePruningStepsContext *context,
List **keyclauses, Bitmapset *nullkeys);
static PartClauseMatchStatus match_clause_to_partition_key(GeneratePruningStepsContext *context,
Expr *clause, Expr *partkey, int partkeyidx,
bool *clause_is_not_null,
PartClauseInfo **pc, List **clause_steps);
static List *get_steps_using_prefix(GeneratePruningStepsContext *context,
StrategyNumber step_opstrategy,
bool step_op_is_ne,
Expr *step_lastexpr,
Oid step_lastcmpfn,
Bitmapset *step_nullkeys,
List *prefix);
static List *get_steps_using_prefix_recurse(GeneratePruningStepsContext *context,
StrategyNumber step_opstrategy,
bool step_op_is_ne,
Expr *step_lastexpr,
Oid step_lastcmpfn,
Bitmapset *step_nullkeys,
List *prefix,
ListCell *start,
List *step_exprs,
List *step_cmpfns);
static PruneStepResult *get_matching_hash_bounds(PartitionPruneContext *context,
StrategyNumber opstrategy, Datum *values, int nvalues,
FmgrInfo *partsupfunc, Bitmapset *nullkeys);
static PruneStepResult *get_matching_list_bounds(PartitionPruneContext *context,
StrategyNumber opstrategy, Datum value, int nvalues,
FmgrInfo *partsupfunc, Bitmapset *nullkeys);
static PruneStepResult *get_matching_range_bounds(PartitionPruneContext *context,
StrategyNumber opstrategy, Datum *values, int nvalues,
FmgrInfo *partsupfunc, Bitmapset *nullkeys);
static Bitmapset *pull_exec_paramids(Expr *expr);
static bool pull_exec_paramids_walker(Node *node, Bitmapset **context);
static Bitmapset *get_partkey_exec_paramids(List *steps);
static PruneStepResult *perform_pruning_base_step(PartitionPruneContext *context,
PartitionPruneStepOp *opstep);
static PruneStepResult *perform_pruning_combine_step(PartitionPruneContext *context,
PartitionPruneStepCombine *cstep,
PruneStepResult **step_results);
static PartClauseMatchStatus match_boolean_partition_clause(Oid partopfamily,
Expr *clause,
Expr *partkey,
Expr **outconst,
bool *notclause);
static void partkey_datum_from_expr(PartitionPruneContext *context,
Expr *expr, int stateidx,
Datum *value, bool *isnull);
/*
* make_partition_pruneinfo
* Checks if the given set of quals can be used to build pruning steps
* that the executor can use to prune away unneeded partitions . If
* suitable quals are found then a PartitionPruneInfo is built and tagged
* onto the PlannerInfo ' s partPruneInfos list .
*
* The return value is the 0 - based index of the item added to the
* partPruneInfos list or - 1 if nothing was added .
*
* ' parentrel ' is the RelOptInfo for an appendrel , and ' subpaths ' is the list
* of scan paths for its child rels .
* ' prunequal ' is a list of potential pruning quals ( i . e . , restriction
* clauses that are applicable to the appendrel ) .
*/
int
make_partition_pruneinfo(PlannerInfo *root, RelOptInfo *parentrel,
List *subpaths,
List *prunequal)
{
PartitionPruneInfo *pruneinfo;
Bitmapset *allmatchedsubplans = NULL;
List *allpartrelids;
List *prunerelinfos;
int *relid_subplan_map;
ListCell *lc;
int i;
/*
* Scan the subpaths to see which ones are scans of partition child
* relations , and identify their parent partitioned rels . ( Note : we must
* restrict the parent partitioned rels to be parentrel or children of
* parentrel , otherwise we couldn ' t translate prunequal to match . )
*
* Also construct a temporary array to map from partition - child - relation
* relid to the index in ' subpaths ' of the scan plan for that partition .
* ( Use of " subplan " rather than " subpath " is a bit of a misnomer , but
* we ' ll let it stand . ) For convenience , we use 1 - based indexes here , so
* that zero can represent an un - filled array entry .
*/
allpartrelids = NIL;
relid_subplan_map = palloc0(sizeof (int ) * root->simple_rel_array_size);
i = 1 ;
foreach(lc, subpaths)
{
Path *path = (Path *) lfirst(lc);
RelOptInfo *pathrel = path->parent;
/* We don't consider partitioned joins here */
if (pathrel->reloptkind == RELOPT_OTHER_MEMBER_REL)
{
RelOptInfo *prel = pathrel;
Bitmapset *partrelids = NULL;
/*
* Traverse up to the pathrel ' s topmost partitioned parent ,
* collecting parent relids as we go ; but stop if we reach
* parentrel . ( Normally , a pathrel ' s topmost partitioned parent
* is either parentrel or a UNION ALL appendrel child of
* parentrel . But when handling partitionwise joins of
* multi - level partitioning trees , we can see an append path whose
* parentrel is an intermediate partitioned table . )
*/
do
{
AppendRelInfo *appinfo;
Assert(prel->relid < root->simple_rel_array_size);
appinfo = root->append_rel_array[prel->relid];
prel = find_base_rel(root, appinfo->parent_relid);
if (!IS_PARTITIONED_REL(prel))
break ; /* reached a non-partitioned parent */
/* accept this level as an interesting parent */
partrelids = bms_add_member(partrelids, prel->relid);
if (prel == parentrel)
break ; /* don't traverse above parentrel */
} while (prel->reloptkind == RELOPT_OTHER_MEMBER_REL);
if (partrelids)
{
/*
* Found some relevant parent partitions , which may or may not
* overlap with partition trees we already found . Add new
* information to the allpartrelids list .
*/
allpartrelids = add_part_relids(allpartrelids, partrelids);
/* Also record the subplan in relid_subplan_map[] */
/* No duplicates please */
Assert(relid_subplan_map[pathrel->relid] == 0 );
relid_subplan_map[pathrel->relid] = i;
}
}
i++;
}
/*
* We now build a PartitionedRelPruneInfo for each topmost partitioned rel
* ( omitting any that turn out not to have useful pruning quals ) .
*/
prunerelinfos = NIL;
foreach(lc, allpartrelids)
{
Bitmapset *partrelids = (Bitmapset *) lfirst(lc);
List *pinfolist;
Bitmapset *matchedsubplans = NULL;
pinfolist = make_partitionedrel_pruneinfo(root, parentrel,
prunequal,
partrelids,
relid_subplan_map,
&matchedsubplans);
/* When pruning is possible, record the matched subplans */
if (pinfolist != NIL)
{
prunerelinfos = lappend(prunerelinfos, pinfolist);
allmatchedsubplans = bms_join(matchedsubplans,
allmatchedsubplans);
}
}
pfree(relid_subplan_map);
/*
* If none of the partition hierarchies had any useful run - time pruning
* quals , then we can just not bother with run - time pruning .
*/
if (prunerelinfos == NIL)
return -1 ;
/* Else build the result data structure */
pruneinfo = makeNode(PartitionPruneInfo);
pruneinfo->relids = bms_copy(parentrel->relids);
pruneinfo->prune_infos = prunerelinfos;
/*
* Some subplans may not belong to any of the identified partitioned rels .
* This can happen for UNION ALL queries which include a non - partitioned
* table , or when some of the hierarchies aren ' t run - time prunable . Build
* a bitmapset of the indexes of all such subplans , so that the executor
* can identify which subplans should never be pruned .
*/
if (bms_num_members(allmatchedsubplans) < list_length(subpaths))
{
Bitmapset *other_subplans;
/* Create the complement of allmatchedsubplans */
other_subplans = bms_add_range(NULL, 0 , list_length(subpaths) - 1 );
other_subplans = bms_del_members(other_subplans, allmatchedsubplans);
pruneinfo->other_subplans = other_subplans;
}
else
pruneinfo->other_subplans = NULL;
root->partPruneInfos = lappend(root->partPruneInfos, pruneinfo);
return list_length(root->partPruneInfos) - 1 ;
}
/*
* add_part_relids
* Add new info to a list of Bitmapsets of partitioned relids .
*
* Within ' allpartrelids ' , there is one Bitmapset for each topmost parent
* partitioned rel . Each Bitmapset contains the RT indexes of the topmost
* parent as well as its relevant non - leaf child partitions . Since ( by
* construction of the rangetable list ) parent partitions must have lower
* RT indexes than their children , we can distinguish the topmost parent
* as being the lowest set bit in the Bitmapset .
*
* ' partrelids ' contains the RT indexes of a parent partitioned rel , and
* possibly some non - leaf children , that are newly identified as parents of
* some subpath rel passed to make_partition_pruneinfo ( ) . These are added
* to an appropriate member of ' allpartrelids ' .
*
* Note that the list contains only RT indexes of partitioned tables that
* are parents of some scan - level relation appearing in the ' subpaths ' that
* make_partition_pruneinfo ( ) is dealing with . Also , " topmost " parents are
* not allowed to be higher than the ' parentrel ' associated with the append
* path . In this way , we avoid expending cycles on partitioned rels that
* can ' t contribute useful pruning information for the problem at hand .
* ( It is possible for ' parentrel ' to be a child partitioned table , and it
* is also possible for scan - level relations to be child partitioned tables
* rather than leaf partitions . Hence we must construct this relation set
* with reference to the particular append path we ' re dealing with , rather
* than looking at the full partitioning structure represented in the
* RelOptInfos . )
*/
static List *
add_part_relids(List *allpartrelids, Bitmapset *partrelids)
{
Index targetpart;
ListCell *lc;
/* We can easily get the lowest set bit this way: */
targetpart = bms_next_member(partrelids, -1 );
Assert(targetpart > 0 );
/* Look for a matching topmost parent */
foreach(lc, allpartrelids)
{
Bitmapset *currpartrelids = (Bitmapset *) lfirst(lc);
Index currtarget = bms_next_member(currpartrelids, -1 );
if (targetpart == currtarget)
{
/* Found a match, so add any new RT indexes to this hierarchy */
currpartrelids = bms_add_members(currpartrelids, partrelids);
lfirst(lc) = currpartrelids;
return allpartrelids;
}
}
/* No match, so add the new partition hierarchy to the list */
return lappend(allpartrelids, partrelids);
}
/*
* make_partitionedrel_pruneinfo
* Build a List of PartitionedRelPruneInfos , one for each interesting
* partitioned rel in a partitioning hierarchy . These can be used in the
* executor to allow additional partition pruning to take place .
*
* parentrel : rel associated with the appendpath being considered
* prunequal : potential pruning quals , represented for parentrel
* partrelids : Set of RT indexes identifying relevant partitioned tables
* within a single partitioning hierarchy
* relid_subplan_map [ ] : maps child relation relids to subplan indexes
* matchedsubplans : on success , receives the set of subplan indexes which
* were matched to this partition hierarchy
*
* If we cannot find any useful run - time pruning steps , return NIL .
* However , on success , each rel identified in partrelids will have
* an element in the result list , even if some of them are useless .
*/
static List *
make_partitionedrel_pruneinfo(PlannerInfo *root, RelOptInfo *parentrel,
List *prunequal,
Bitmapset *partrelids,
int *relid_subplan_map,
Bitmapset **matchedsubplans)
{
RelOptInfo *targetpart = NULL;
List *pinfolist = NIL;
bool doruntimeprune = false ;
int *relid_subpart_map;
Bitmapset *subplansfound = NULL;
ListCell *lc;
int rti;
int i;
/*
* Examine each partitioned rel , constructing a temporary array to map
* from planner relids to index of the partitioned rel , and building a
* PartitionedRelPruneInfo for each partitioned rel .
*
* In this phase we discover whether runtime pruning is needed at all ; if
* not , we can avoid doing further work .
*/
relid_subpart_map = palloc0(sizeof (int ) * root->simple_rel_array_size);
i = 1 ;
rti = -1 ;
while ((rti = bms_next_member(partrelids, rti)) > 0 )
{
RelOptInfo *subpart = find_base_rel(root, rti);
PartitionedRelPruneInfo *pinfo;
List *partprunequal;
List *initial_pruning_steps;
List *exec_pruning_steps;
Bitmapset *execparamids;
GeneratePruningStepsContext context;
/*
* Fill the mapping array .
*
* relid_subpart_map maps relid of a non - leaf partition to the index
* in the returned PartitionedRelPruneInfo list of the info for that
* partition . We use 1 - based indexes here , so that zero can represent
* an un - filled array entry .
*/
Assert(rti < root->simple_rel_array_size);
relid_subpart_map[rti] = i++;
/*
* Translate pruning qual , if necessary , for this partition .
*
* The first item in the list is the target partitioned relation .
*/
if (!targetpart)
{
targetpart = subpart;
/*
* The prunequal is presented to us as a qual for ' parentrel ' .
* Frequently this rel is the same as targetpart , so we can skip
* an adjust_appendrel_attrs step . But it might not be , and then
* we have to translate . We update the prunequal parameter here ,
* because in later iterations of the loop for child partitions ,
* we want to translate from parent to child variables .
*/
if (!bms_equal(parentrel->relids, subpart->relids))
{
int nappinfos;
AppendRelInfo **appinfos = find_appinfos_by_relids(root,
subpart->relids,
&nappinfos);
prunequal = (List *) adjust_appendrel_attrs(root, (Node *)
prunequal,
nappinfos,
appinfos);
pfree(appinfos);
}
partprunequal = prunequal;
}
else
{
/*
* For sub - partitioned tables the columns may not be in the same
* order as the parent , so we must translate the prunequal to make
* it compatible with this relation .
*/
partprunequal = (List *)
adjust_appendrel_attrs_multilevel(root,
(Node *) prunequal,
subpart,
targetpart);
}
/*
* Convert pruning qual to pruning steps . We may need to do this
* twice , once to obtain executor startup pruning steps , and once for
* executor per - scan pruning steps . This first pass creates startup
* pruning steps and detects whether there ' s any possibly - useful quals
* that would require per - scan pruning .
*/
gen_partprune_steps(subpart, partprunequal, PARTTARGET_INITIAL,
&context);
if (context.contradictory)
{
/*
* This shouldn ' t happen as the planner should have detected this
* earlier . However , we do use additional quals from parameterized
* paths here . These do only compare Params to the partition key ,
* so this shouldn ' t cause the discovery of any new qual
* contradictions that were not previously discovered as the Param
* values are unknown during planning . Anyway , we ' d better do
* something sane here , so let ' s just disable run - time pruning .
*/
return NIL;
}
/*
* If no mutable operators or expressions appear in usable pruning
* clauses , then there ' s no point in running startup pruning , because
* plan - time pruning should have pruned everything prunable .
*/
if (context.has_mutable_op || context.has_mutable_arg)
initial_pruning_steps = context.steps;
else
initial_pruning_steps = NIL;
/*
* If no exec Params appear in potentially - usable pruning clauses ,
* then there ' s no point in even thinking about per - scan pruning .
*/
if (context.has_exec_param)
{
/* ... OK, we'd better think about it */
gen_partprune_steps(subpart, partprunequal, PARTTARGET_EXEC,
&context);
if (context.contradictory)
{
/* As above, skip run-time pruning if anything fishy happens */
return NIL;
}
exec_pruning_steps = context.steps;
/*
* Detect which exec Params actually got used ; the fact that some
* were in available clauses doesn ' t mean we actually used them .
* Skip per - scan pruning if there are none .
*/
execparamids = get_partkey_exec_paramids(exec_pruning_steps);
if (bms_is_empty(execparamids))
exec_pruning_steps = NIL;
}
else
{
/* No exec Params anywhere, so forget about scan-time pruning */
exec_pruning_steps = NIL;
execparamids = NULL;
}
if (initial_pruning_steps || exec_pruning_steps)
doruntimeprune = true ;
/* Begin constructing the PartitionedRelPruneInfo for this rel */
pinfo = makeNode(PartitionedRelPruneInfo);
pinfo->rtindex = rti;
pinfo->initial_pruning_steps = initial_pruning_steps;
pinfo->exec_pruning_steps = exec_pruning_steps;
pinfo->execparamids = execparamids;
/* Remaining fields will be filled in the next loop */
pinfolist = lappend(pinfolist, pinfo);
}
if (!doruntimeprune)
{
/* No run-time pruning required. */
pfree(relid_subpart_map);
return NIL;
}
/*
* Run - time pruning will be required , so initialize other information .
* That includes two maps - - one needed to convert partition indexes of
* leaf partitions to the indexes of their subplans in the subplan list ,
* another needed to convert partition indexes of sub - partitioned
* partitions to the indexes of their PartitionedRelPruneInfo in the
* PartitionedRelPruneInfo list .
*/
foreach(lc, pinfolist)
{
PartitionedRelPruneInfo *pinfo = lfirst(lc);
RelOptInfo *subpart = find_base_rel(root, pinfo->rtindex);
Bitmapset *present_parts;
int nparts = subpart->nparts;
int *subplan_map;
int *subpart_map;
Oid *relid_map;
int *leafpart_rti_map;
/*
* Construct the subplan and subpart maps for this partitioning level .
* Here we convert to zero - based indexes , with - 1 for empty entries .
* Also construct a Bitmapset of all partitions that are present ( that
* is , not pruned already ) .
*/
subplan_map = (int *) palloc(nparts * sizeof (int ));
memset(subplan_map, -1 , nparts * sizeof (int ));
subpart_map = (int *) palloc(nparts * sizeof (int ));
memset(subpart_map, -1 , nparts * sizeof (int ));
relid_map = (Oid *) palloc0(nparts * sizeof (Oid));
leafpart_rti_map = (int *) palloc0(nparts * sizeof (int ));
present_parts = NULL;
i = -1 ;
while ((i = bms_next_member(subpart->live_parts, i)) >= 0 )
{
RelOptInfo *partrel = subpart->part_rels[i];
int subplanidx;
int subpartidx;
Assert(partrel != NULL);
subplan_map[i] = subplanidx = relid_subplan_map[partrel->relid] - 1 ;
subpart_map[i] = subpartidx = relid_subpart_map[partrel->relid] - 1 ;
relid_map[i] = planner_rt_fetch(partrel->relid, root)->relid;
/*
* Track the RT indexes of " leaf " partitions so they can be
* included in the PlannerGlobal . prunableRelids set , indicating
* relations that may be pruned during executor startup .
*
* Only leaf partitions with a valid subplan that are prunable
* using initial pruning are added to prunableRelids . So
* partitions without a subplan due to constraint exclusion will
* remain in PlannedStmt . unprunableRelids .
*/
if (subplanidx >= 0 )
{
present_parts = bms_add_member(present_parts, i);
/*
* Non - leaf partitions may appear here when they use an
* unflattened Append or MergeAppend . These should not be
* included in prunableRelids .
*/
if (partrel->nparts == -1 )
leafpart_rti_map[i] = (int ) partrel->relid;
/* Record finding this subplan */
subplansfound = bms_add_member(subplansfound, subplanidx);
}
else if (subpartidx >= 0 )
present_parts = bms_add_member(present_parts, i);
}
/*
* Ensure there were no stray PartitionedRelPruneInfo generated for
* partitioned tables that we have no sub - paths or
* sub - PartitionedRelPruneInfo for .
*/
Assert(!bms_is_empty(present_parts));
/* Record the maps and other information. */
pinfo->present_parts = present_parts;
pinfo->nparts = nparts;
pinfo->subplan_map = subplan_map;
pinfo->subpart_map = subpart_map;
pinfo->relid_map = relid_map;
pinfo->leafpart_rti_map = leafpart_rti_map;
}
pfree(relid_subpart_map);
*matchedsubplans = subplansfound;
return pinfolist;
}
/*
* gen_partprune_steps
* Process ' clauses ' ( typically a rel ' s baserestrictinfo list of clauses )
* and create a list of " partition pruning steps " .
*
* ' target ' tells whether to generate pruning steps for planning ( use
* immutable clauses only ) , or for executor startup ( use any allowable
* clause except ones containing PARAM_EXEC Params ) , or for executor
* per - scan pruning ( use any allowable clause ) .
*
* ' context ' is an output argument that receives the steps list as well as
* some subsidiary flags ; see the GeneratePruningStepsContext typedef .
*/
static void
gen_partprune_steps(RelOptInfo *rel, List *clauses, PartClauseTarget target,
GeneratePruningStepsContext *context)
{
/* Initialize all output values to zero/false/NULL */
memset(context, 0 , sizeof (GeneratePruningStepsContext));
context->rel = rel;
context->target = target;
/*
* If this partitioned table is in turn a partition , and it shares any
* partition keys with its parent , then it ' s possible that the hierarchy
* allows the parent a narrower range of values than some of its
* partitions ( particularly the default one ) . This is normally not
* useful , but it can be to prune the default partition .
*/
if (partition_bound_has_default(rel->boundinfo) && rel->partition_qual)
{
/* Make a copy to avoid modifying the passed-in List */
clauses = list_concat_copy(clauses, rel->partition_qual);
}
/* Down into the rabbit-hole. */
(void ) gen_partprune_steps_internal(context, clauses);
}
/*
* prune_append_rel_partitions
* Process rel ' s baserestrictinfo and make use of quals which can be
* evaluated during query planning in order to determine the minimum set
* of partitions which must be scanned to satisfy these quals . Returns
* the matching partitions in the form of a Bitmapset containing the
* partitions ' indexes in the rel ' s part_rels array .
*
* Callers must ensure that ' rel ' is a partitioned table .
*/
Bitmapset *
prune_append_rel_partitions(RelOptInfo *rel)
{
List *clauses = rel->baserestrictinfo;
List *pruning_steps;
GeneratePruningStepsContext gcontext;
PartitionPruneContext context;
Assert(rel->part_scheme != NULL);
/* If there are no partitions, return the empty set */
if (rel->nparts == 0 )
return NULL;
/*
* If pruning is disabled or if there are no clauses to prune with , return
* all partitions .
*/
if (!enable_partition_pruning || clauses == NIL)
return bms_add_range(NULL, 0 , rel->nparts - 1 );
/*
* Process clauses to extract pruning steps that are usable at plan time .
* If the clauses are found to be contradictory , we can return the empty
* set .
*/
gen_partprune_steps(rel, clauses, PARTTARGET_PLANNER,
&gcontext);
if (gcontext.contradictory)
return NULL;
pruning_steps = gcontext.steps;
/* If there's nothing usable, return all partitions */
if (pruning_steps == NIL)
return bms_add_range(NULL, 0 , rel->nparts - 1 );
/* Set up PartitionPruneContext */
context.strategy = rel->part_scheme->strategy;
context.partnatts = rel->part_scheme->partnatts;
context.nparts = rel->nparts;
context.boundinfo = rel->boundinfo;
context.partcollation = rel->part_scheme->partcollation;
context.partsupfunc = rel->part_scheme->partsupfunc;
context.stepcmpfuncs = (FmgrInfo *) palloc0(sizeof (FmgrInfo) *
context.partnatts *
list_length(pruning_steps));
context.ppccontext = CurrentMemoryContext;
/* These are not valid when being called from the planner */
context.planstate = NULL;
context.exprcontext = NULL;
context.exprstates = NULL;
/* Actual pruning happens here. */
return get_matching_partitions(&context, pruning_steps);
}
/*
* get_matching_partitions
* Determine partitions that survive partition pruning
*
* Note : context - > exprcontext must be valid when the pruning_steps were
* generated with a target other than PARTTARGET_PLANNER .
*
* Returns a Bitmapset of the RelOptInfo - > part_rels indexes of the surviving
* partitions .
*/
Bitmapset *
get_matching_partitions(PartitionPruneContext *context, List *pruning_steps)
{
Bitmapset *result;
int num_steps = list_length(pruning_steps),
i;
PruneStepResult **results,
*final_result;
ListCell *lc;
bool scan_default;
/* If there are no pruning steps then all partitions match. */
if (num_steps == 0 )
{
Assert(context->nparts > 0 );
return bms_add_range(NULL, 0 , context->nparts - 1 );
}
/*
* Allocate space for individual pruning steps to store its result . Each
* slot will hold a PruneStepResult after performing a given pruning step .
* Later steps may use the result of one or more earlier steps . The
* result of applying all pruning steps is the value contained in the slot
* of the last pruning step .
*/
results = (PruneStepResult **)
palloc0(num_steps * sizeof (PruneStepResult *));
foreach(lc, pruning_steps)
{
PartitionPruneStep *step = lfirst(lc);
switch (nodeTag(step))
{
case T_PartitionPruneStepOp:
results[step->step_id] =
perform_pruning_base_step(context,
(PartitionPruneStepOp *) step);
break ;
case T_PartitionPruneStepCombine:
results[step->step_id] =
perform_pruning_combine_step(context,
(PartitionPruneStepCombine *) step,
results);
break ;
default :
elog(ERROR, "invalid pruning step type: %d" ,
(int ) nodeTag(step));
}
}
/*
* At this point we know the offsets of all the datums whose corresponding
* partitions need to be in the result , including special null - accepting
* and default partitions . Collect the actual partition indexes now .
*/
final_result = results[num_steps - 1 ];
Assert(final_result != NULL);
i = -1 ;
result = NULL;
scan_default = final_result->scan_default;
while ((i = bms_next_member(final_result->bound_offsets, i)) >= 0 )
{
int partindex;
Assert(i < context->boundinfo->nindexes);
partindex = context->boundinfo->indexes[i];
if (partindex < 0 )
{
/*
* In range partitioning cases , if a partition index is - 1 it
* means that the bound at the offset is the upper bound for a
* range not covered by any partition ( other than a possible
* default partition ) . In hash partitioning , the same means no
* partition has been defined for the corresponding remainder
* value .
*
* In either case , the value is still part of the queried range of
* values , so mark to scan the default partition if one exists .
*/
scan_default |= partition_bound_has_default(context->boundinfo);
continue ;
}
result = bms_add_member(result, partindex);
}
/* Add the null and/or default partition if needed and present. */
if (final_result->scan_null)
{
Assert(context->strategy == PARTITION_STRATEGY_LIST);
Assert(partition_bound_accepts_nulls(context->boundinfo));
result = bms_add_member(result, context->boundinfo->null_index);
}
if (scan_default)
{
Assert(context->strategy == PARTITION_STRATEGY_LIST ||
context->strategy == PARTITION_STRATEGY_RANGE);
Assert(partition_bound_has_default(context->boundinfo));
result = bms_add_member(result, context->boundinfo->default_index);
}
return result;
}
/*
* gen_partprune_steps_internal
* Processes ' clauses ' to generate a List of partition pruning steps . We
* return NIL when no steps were generated .
*
* These partition pruning steps come in 2 forms ; operator steps and combine
* steps .
*
* Operator steps ( PartitionPruneStepOp ) contain details of clauses that we
* determined that we can use for partition pruning . These contain details of
* the expression which is being compared to the partition key and the
* comparison function .
*
* Combine steps ( PartitionPruneStepCombine ) instruct the partition pruning
* code how it should produce a single set of partitions from multiple input
* operator and other combine steps . A PARTPRUNE_COMBINE_INTERSECT type
* combine step will merge its input steps to produce a result which only
* contains the partitions which are present in all of the input operator
* steps . A PARTPRUNE_COMBINE_UNION combine step will produce a result that
* has all of the partitions from each of the input operator steps .
*
* For BoolExpr clauses , each argument is processed recursively . Steps
* generated from processing an OR BoolExpr will be combined using
* PARTPRUNE_COMBINE_UNION . AND BoolExprs get combined using
* PARTPRUNE_COMBINE_INTERSECT .
*
* Otherwise , the list of clauses we receive we assume to be mutually ANDed .
* We generate all of the pruning steps we can based on these clauses and then
* at the end , if we have more than 1 step , we combine each step with a
* PARTPRUNE_COMBINE_INTERSECT combine step . Single steps are returned as - is .
*
* If we find clauses that are mutually contradictory , or contradictory with
* the partitioning constraint , or a pseudoconstant clause that contains
* false , we set context - > contradictory to true and return NIL ( that is , no
* pruning steps ) . Caller should consider all partitions as pruned in that
* case .
*/
static List *
gen_partprune_steps_internal(GeneratePruningStepsContext *context,
List *clauses)
{
PartitionScheme part_scheme = context->rel->part_scheme;
List *keyclauses[PARTITION_MAX_KEYS];
Bitmapset *nullkeys = NULL,
*notnullkeys = NULL;
bool generate_opsteps = false ;
List *result = NIL;
ListCell *lc;
/*
* If this partitioned relation has a default partition and is itself a
* partition ( as evidenced by partition_qual being not NIL ) , we first
* check if the clauses contradict the partition constraint . If they do ,
* there ' s no need to generate any steps as it ' d already be proven that no
* partitions need to be scanned .
*
* This is a measure of last resort only to be used because the default
* partition cannot be pruned using the steps generated from clauses that
* contradict the parent ' s partition constraint ; regular pruning , which is
* cheaper , is sufficient when no default partition exists .
*/
if (partition_bound_has_default(context->rel->boundinfo) &&
predicate_refuted_by(context->rel->partition_qual, clauses, false ))
{
context->contradictory = true ;
return NIL;
}
memset(keyclauses, 0 , sizeof (keyclauses));
foreach(lc, clauses)
{
Expr *clause = (Expr *) lfirst(lc);
int i;
/* Look through RestrictInfo, if any */
if (IsA(clause, RestrictInfo))
clause = ((RestrictInfo *) clause)->clause;
/* Constant-false-or-null is contradictory */
if (IsA(clause, Const ) &&
(((Const *) clause)->constisnull ||
!DatumGetBool(((Const *) clause)->constvalue)))
{
context->contradictory = true ;
return NIL;
}
/* Get the BoolExpr's out of the way. */
if (IsA(clause, BoolExpr))
{
/*
* Generate steps for arguments .
*
* While steps generated for the arguments themselves will be
* added to context - > steps during recursion and will be evaluated
* independently , collect their step IDs to be stored in the
* combine step we ' ll be creating .
*/
if (is_orclause(clause))
{
List *arg_stepids = NIL;
bool all_args_contradictory = true ;
ListCell *lc1;
/*
* We can share the outer context area with the recursive
* call , but contradictory had better not be true yet .
*/
Assert(!context->contradictory);
/*
* Get pruning step for each arg . If we get contradictory for
* all args , it means the OR expression is false as a whole .
*/
foreach(lc1, ((BoolExpr *) clause)->args)
{
Expr *arg = lfirst(lc1);
bool arg_contradictory;
List *argsteps;
argsteps = gen_partprune_steps_internal(context,
list_make1(arg));
arg_contradictory = context->contradictory;
/* Keep context->contradictory clear till we're done */
context->contradictory = false ;
if (arg_contradictory)
{
/* Just ignore self-contradictory arguments. */
continue ;
}
else
all_args_contradictory = false ;
if (argsteps != NIL)
{
/*
* gen_partprune_steps_internal ( ) always adds a single
* combine step when it generates multiple steps , so
* here we can just pay attention to the last one in
* the list . If it just generated one , then the last
* one in the list is still the one we want .
*/
PartitionPruneStep *last = llast(argsteps);
arg_stepids = lappend_int(arg_stepids, last->step_id);
}
else
{
PartitionPruneStep *orstep;
/*
* The arg didn ' t contain a clause matching this
* partition key . We cannot prune using such an arg .
* To indicate that to the pruning code , we must
* construct a dummy PartitionPruneStepCombine whose
* source_stepids is set to an empty List .
*/
orstep = gen_prune_step_combine(context, NIL,
PARTPRUNE_COMBINE_UNION);
arg_stepids = lappend_int(arg_stepids, orstep->step_id);
}
}
/* If all the OR arms are contradictory, we can stop */
if (all_args_contradictory)
{
context->contradictory = true ;
return NIL;
}
if (arg_stepids != NIL)
{
PartitionPruneStep *step;
step = gen_prune_step_combine(context, arg_stepids,
PARTPRUNE_COMBINE_UNION);
result = lappend(result, step);
}
continue ;
}
else if (is_andclause(clause))
{
List *args = ((BoolExpr *) clause)->args;
List *argsteps;
/*
* args may itself contain clauses of arbitrary type , so just
* recurse and later combine the component partitions sets
* using a combine step .
*/
argsteps = gen_partprune_steps_internal(context, args);
/* If any AND arm is contradictory, we can stop immediately */
if (context->contradictory)
return NIL;
/*
* gen_partprune_steps_internal ( ) always adds a single combine
* step when it generates multiple steps , so here we can just
* pay attention to the last one in the list . If it just
* generated one , then the last one in the list is still the
* one we want .
*/
if (argsteps != NIL)
result = lappend(result, llast(argsteps));
continue ;
}
/*
* Fall - through for a NOT clause , which if it ' s a Boolean clause ,
* will be handled in match_clause_to_partition_key ( ) . We
* currently don ' t perform any pruning for more complex NOT
* clauses .
*/
}
/*
* See if we can match this clause to any of the partition keys .
*/
for (i = 0 ; i < part_scheme->partnatts; i++)
{
Expr *partkey = linitial(context->rel->partexprs[i]);
bool clause_is_not_null = false ;
PartClauseInfo *pc = NULL;
List *clause_steps = NIL;
switch (match_clause_to_partition_key(context,
clause, partkey, i,
&clause_is_not_null,
&pc, &clause_steps))
{
case PARTCLAUSE_MATCH_CLAUSE:
Assert(pc != NULL);
/*
* Since we only allow strict operators , check for any
* contradicting IS NULL .
*/
if (bms_is_member(i, nullkeys))
{
context->contradictory = true ;
return NIL;
}
generate_opsteps = true ;
keyclauses[i] = lappend(keyclauses[i], pc);
break ;
case PARTCLAUSE_MATCH_NULLNESS:
if (!clause_is_not_null)
{
/*
* check for conflicting IS NOT NULL as well as
* contradicting strict clauses
*/
if (bms_is_member(i, notnullkeys) ||
keyclauses[i] != NIL)
{
context->contradictory = true ;
return NIL;
}
nullkeys = bms_add_member(nullkeys, i);
}
else
{
/* check for conflicting IS NULL */
if (bms_is_member(i, nullkeys))
{
context->contradictory = true ;
return NIL;
}
notnullkeys = bms_add_member(notnullkeys, i);
}
break ;
case PARTCLAUSE_MATCH_STEPS:
Assert(clause_steps != NIL);
result = list_concat(result, clause_steps);
break ;
case PARTCLAUSE_MATCH_CONTRADICT:
/* We've nothing more to do if a contradiction was found. */
context->contradictory = true ;
return NIL;
case PARTCLAUSE_NOMATCH:
/*
* Clause didn ' t match this key , but it might match the
* next one .
*/
continue ;
case PARTCLAUSE_UNSUPPORTED:
/* This clause cannot be used for pruning. */
break ;
}
/* done; go check the next clause. */
break ;
}
}
/*-----------
* Now generate some ( more ) pruning steps . We have three strategies :
*
* 1 ) Generate pruning steps based on IS NULL clauses :
* a ) For list partitioning , null partition keys can only be found in
* the designated null - accepting partition , so if there are IS NULL
* clauses containing partition keys we should generate a pruning
* step that gets rid of all partitions but that one . We can
* disregard any OpExpr we may have found .
* b ) For range partitioning , only the default partition can contain
* NULL values , so the same rationale applies .
* c ) For hash partitioning , we only apply this strategy if we have
* IS NULL clauses for all the keys . Strategy 2 below will take
* care of the case where some keys have OpExprs and others have
* IS NULL clauses .
*
* 2 ) If not , generate steps based on OpExprs we have ( if any ) .
*
* 3 ) If this doesn ' t work either , we may be able to generate steps to
* prune just the null - accepting partition ( if one exists ) , if we have
* IS NOT NULL clauses for all partition keys .
*/
if (!bms_is_empty(nullkeys) &&
(part_scheme->strategy == PARTITION_STRATEGY_LIST ||
part_scheme->strategy == PARTITION_STRATEGY_RANGE ||
(part_scheme->strategy == PARTITION_STRATEGY_HASH &&
bms_num_members(nullkeys) == part_scheme->partnatts)))
{
PartitionPruneStep *step;
/* Strategy 1 */
step = gen_prune_step_op(context, InvalidStrategy,
false , NIL, NIL, nullkeys);
result = lappend(result, step);
}
else if (generate_opsteps)
{
List *opsteps;
/* Strategy 2 */
opsteps = gen_prune_steps_from_opexps(context, keyclauses, nullkeys);
result = list_concat(result, opsteps);
}
else if (bms_num_members(notnullkeys) == part_scheme->partnatts)
{
PartitionPruneStep *step;
/* Strategy 3 */
step = gen_prune_step_op(context, InvalidStrategy,
false , NIL, NIL, NULL);
result = lappend(result, step);
}
/*
* Finally , if there are multiple steps , since the ' clauses ' are mutually
* ANDed , add an INTERSECT step to combine the partition sets resulting
* from them and append it to the result list .
*/
if (list_length(result) > 1 )
{
List *step_ids = NIL;
PartitionPruneStep *final;
foreach(lc, result)
{
PartitionPruneStep *step = lfirst(lc);
step_ids = lappend_int(step_ids, step->step_id);
}
final = gen_prune_step_combine(context, step_ids,
PARTPRUNE_COMBINE_INTERSECT);
result = lappend(result, final);
}
return result;
}
/*
* gen_prune_step_op
* Generate a pruning step for a specific operator
*
* The step is assigned a unique step identifier and added to context ' s ' steps '
* list .
*/
static PartitionPruneStep *
gen_prune_step_op(GeneratePruningStepsContext *context,
StrategyNumber opstrategy, bool op_is_ne,
List *exprs, List *cmpfns,
Bitmapset *nullkeys)
{
PartitionPruneStepOp *opstep = makeNode(PartitionPruneStepOp);
opstep->step.step_id = context->next_step_id++;
/*
* For clauses that contain an < > operator , set opstrategy to
* InvalidStrategy to signal get_matching_list_bounds to do the right
* thing .
*/
opstep->opstrategy = op_is_ne ? InvalidStrategy : opstrategy;
Assert(list_length(exprs) == list_length(cmpfns));
opstep->exprs = exprs;
opstep->cmpfns = cmpfns;
opstep->nullkeys = nullkeys;
context->steps = lappend(context->steps, opstep);
return (PartitionPruneStep *) opstep;
}
/*
* gen_prune_step_combine
* Generate a pruning step for a combination of several other steps
*
* The step is assigned a unique step identifier and added to context ' s
* ' steps ' list .
*/
static PartitionPruneStep *
gen_prune_step_combine(GeneratePruningStepsContext *context,
List *source_stepids,
PartitionPruneCombineOp combineOp)
{
PartitionPruneStepCombine *cstep = makeNode(PartitionPruneStepCombine);
cstep->step.step_id = context->next_step_id++;
cstep->combineOp = combineOp;
cstep->source_stepids = source_stepids;
context->steps = lappend(context->steps, cstep);
return (PartitionPruneStep *) cstep;
}
/*
* gen_prune_steps_from_opexps
* Generate and return a list of PartitionPruneStepOp that are based on
* OpExpr and BooleanTest clauses that have been matched to the partition
* key .
*
* ' keyclauses ' is an array of List pointers , indexed by the partition key ' s
* index . Each List element in the array can contain clauses that match to
* the corresponding partition key column . Partition key columns without any
* matched clauses will have an empty List .
*
* Some partitioning strategies allow pruning to still occur when we only have
* clauses for a prefix of the partition key columns , for example , RANGE
* partitioning . Other strategies , such as HASH partitioning , require clauses
* for all partition key columns .
*
* When we return multiple pruning steps here , it ' s up to the caller to add a
* relevant " combine " step to combine the returned steps . This is not done
* here as callers may wish to include additional pruning steps before
* combining them all .
*/
static List *
gen_prune_steps_from_opexps(GeneratePruningStepsContext *context,
List **keyclauses, Bitmapset *nullkeys)
{
PartitionScheme part_scheme = context->rel->part_scheme;
List *opsteps = NIL;
List *btree_clauses[BTMaxStrategyNumber + 1 ],
*hash_clauses[HTMaxStrategyNumber + 1 ];
int i;
ListCell *lc;
memset(btree_clauses, 0 , sizeof (btree_clauses));
memset(hash_clauses, 0 , sizeof (hash_clauses));
for (i = 0 ; i < part_scheme->partnatts; i++)
{
List *clauselist = keyclauses[i];
bool consider_next_key = true ;
/*
* For range partitioning , if we have no clauses for the current key ,
* we can ' t consider any later keys either , so we can stop here .
*/
if (part_scheme->strategy == PARTITION_STRATEGY_RANGE &&
clauselist == NIL)
break ;
/*
* For hash partitioning , if a column doesn ' t have the necessary
* equality clause , there should be an IS NULL clause , otherwise
* pruning is not possible .
*/
if (part_scheme->strategy == PARTITION_STRATEGY_HASH &&
clauselist == NIL && !bms_is_member(i, nullkeys))
return NIL;
foreach(lc, clauselist)
{
PartClauseInfo *pc = (PartClauseInfo *) lfirst(lc);
Oid lefttype,
righttype;
/* Look up the operator's btree/hash strategy number. */
if (pc->op_strategy == InvalidStrategy)
get_op_opfamily_properties(pc->opno,
part_scheme->partopfamily[i],
false ,
&pc->op_strategy,
&lefttype,
&righttype);
switch (part_scheme->strategy)
{
case PARTITION_STRATEGY_LIST:
case PARTITION_STRATEGY_RANGE:
btree_clauses[pc->op_strategy] =
lappend(btree_clauses[pc->op_strategy], pc);
/*
* We can ' t consider subsequent partition keys if the
* clause for the current key contains a non - inclusive
* operator .
*/
if (pc->op_strategy == BTLessStrategyNumber ||
pc->op_strategy == BTGreaterStrategyNumber)
consider_next_key = false ;
break ;
case PARTITION_STRATEGY_HASH:
if (pc->op_strategy != HTEqualStrategyNumber)
elog(ERROR, "invalid clause for hash partitioning" );
hash_clauses[pc->op_strategy] =
lappend(hash_clauses[pc->op_strategy], pc);
break ;
default :
elog(ERROR, "invalid partition strategy: %c" ,
part_scheme->strategy);
break ;
}
}
/*
* If we ' ve decided that clauses for subsequent partition keys
* wouldn ' t be useful for pruning , don ' t search any further .
*/
if (!consider_next_key)
break ;
}
/*
* Now , we have divided clauses according to their operator strategies .
* Check for each strategy if we can generate pruning step ( s ) by
* collecting a list of expressions whose values will constitute a vector
* that can be used as a lookup key by a partition bound searching
* function .
*/
switch (part_scheme->strategy)
{
case PARTITION_STRATEGY_LIST:
case PARTITION_STRATEGY_RANGE:
{
List *eq_clauses = btree_clauses[BTEqualStrategyNumber];
List *le_clauses = btree_clauses[BTLessEqualStrategyNumber];
List *ge_clauses = btree_clauses[BTGreaterEqualStrategyNumber];
int strat;
/*
* For each clause under consideration for a given strategy ,
* we collect expressions from clauses for earlier keys , whose
* operator strategy is inclusive , into a list called
* ' prefix ' . By appending the clause ' s own expression to the
* ' prefix ' , we ' ll generate one step using the so generated
* vector and assign the current strategy to it . Actually ,
* ' prefix ' might contain multiple clauses for the same key ,
* in which case , we must generate steps for various
* combinations of expressions of different keys , which
* get_steps_using_prefix takes care of for us .
*/
for (strat = 1 ; strat <= BTMaxStrategyNumber; strat++)
{
foreach(lc, btree_clauses[strat])
{
PartClauseInfo *pc = lfirst(lc);
ListCell *eq_start;
ListCell *le_start;
ListCell *ge_start;
ListCell *lc1;
List *prefix = NIL;
List *pc_steps;
bool prefix_valid = true ;
bool pk_has_clauses;
int keyno;
/*
* If this is a clause for the first partition key ,
* there are no preceding expressions ; generate a
* pruning step without a prefix .
*
* Note that we pass NULL for step_nullkeys , because
* we don ' t search list / range partition bounds where
* some keys are NULL .
*/
if (pc->keyno == 0 )
{
Assert(pc->op_strategy == strat);
pc_steps = get_steps_using_prefix(context, strat,
pc->op_is_ne,
pc->expr,
pc->cmpfn,
NULL,
NIL);
opsteps = list_concat(opsteps, pc_steps);
continue ;
}
eq_start = list_head(eq_clauses);
le_start = list_head(le_clauses);
ge_start = list_head(ge_clauses);
/*
* We arrange clauses into prefix in ascending order
* of their partition key numbers .
*/
for (keyno = 0 ; keyno < pc->keyno; keyno++)
{
pk_has_clauses = false ;
/*
* Expressions from = clauses can always be in the
* prefix , provided they ' re from an earlier key .
*/
for_each_cell(lc1, eq_clauses, eq_start)
{
PartClauseInfo *eqpc = lfirst(lc1);
if (eqpc->keyno == keyno)
{
prefix = lappend(prefix, eqpc);
pk_has_clauses = true ;
}
else
{
Assert(eqpc->keyno > keyno);
break ;
}
}
eq_start = lc1;
/*
* If we ' re generating steps for < / < = strategy , we
* can add other < = clauses to the prefix ,
* provided they ' re from an earlier key .
*/
if (strat == BTLessStrategyNumber ||
strat == BTLessEqualStrategyNumber)
{
for_each_cell(lc1, le_clauses, le_start)
{
PartClauseInfo *lepc = lfirst(lc1);
if (lepc->keyno == keyno)
{
prefix = lappend(prefix, lepc);
pk_has_clauses = true ;
}
else
{
Assert(lepc->keyno > keyno);
break ;
}
}
le_start = lc1;
}
/*
* If we ' re generating steps for > / > = strategy , we
* can add other > = clauses to the prefix ,
* provided they ' re from an earlier key .
*/
if (strat == BTGreaterStrategyNumber ||
strat == BTGreaterEqualStrategyNumber)
{
for_each_cell(lc1, ge_clauses, ge_start)
{
PartClauseInfo *gepc = lfirst(lc1);
if (gepc->keyno == keyno)
{
prefix = lappend(prefix, gepc);
pk_has_clauses = true ;
}
else
{
Assert(gepc->keyno > keyno);
break ;
}
}
ge_start = lc1;
}
/*
* If this key has no clauses , prefix is not valid
* anymore .
*/
if (!pk_has_clauses)
{
prefix_valid = false ;
break ;
}
}
/*
* If prefix_valid , generate PartitionPruneStepOps .
* Otherwise , we would not find clauses for a valid
* subset of the partition keys anymore for the
* strategy ; give up on generating partition pruning
* steps further for the strategy .
*
* As mentioned above , if ' prefix ' contains multiple
* expressions for the same key , the following will
* generate multiple steps , one for each combination
* of the expressions for different keys .
*
* Note that we pass NULL for step_nullkeys , because
* we don ' t search list / range partition bounds where
* some keys are NULL .
*/
if (prefix_valid)
{
Assert(pc->op_strategy == strat);
pc_steps = get_steps_using_prefix(context, strat,
pc->op_is_ne,
pc->expr,
pc->cmpfn,
NULL,
prefix);
opsteps = list_concat(opsteps, pc_steps);
}
else
break ;
}
}
break ;
}
case PARTITION_STRATEGY_HASH:
{
List *eq_clauses = hash_clauses[HTEqualStrategyNumber];
/* For hash partitioning, we have just the = strategy. */
if (eq_clauses != NIL)
{
PartClauseInfo *pc;
List *pc_steps;
List *prefix = NIL;
int last_keyno;
ListCell *lc1;
/*
* Locate the clause for the greatest column . This may
* not belong to the last partition key , but it is the
* clause belonging to the last partition key we found a
* clause for above .
*/
pc = llast(eq_clauses);
/*
* There might be multiple clauses which matched to that
* partition key ; find the first such clause . While at
* it , add all the clauses before that one to ' prefix ' .
*/
last_keyno = pc->keyno;
foreach(lc, eq_clauses)
{
pc = lfirst(lc);
if (pc->keyno == last_keyno)
break ;
prefix = lappend(prefix, pc);
}
/*
* For each clause for the " last " column , after appending
* the clause ' s own expression to the ' prefix ' , we ' ll
* generate one step using the so generated vector and
* assign = as its strategy . Actually , ' prefix ' might
* contain multiple clauses for the same key , in which
* case , we must generate steps for various combinations
* of expressions of different keys , which
* get_steps_using_prefix will take care of for us .
*/
for_each_cell(lc1, eq_clauses, lc)
{
pc = lfirst(lc1);
/*
* Note that we pass nullkeys for step_nullkeys ,
* because we need to tell hash partition bound search
* function which of the keys we found IS NULL clauses
* for .
*/
Assert(pc->op_strategy == HTEqualStrategyNumber);
pc_steps =
get_steps_using_prefix(context,
HTEqualStrategyNumber,
false ,
pc->expr,
pc->cmpfn,
nullkeys,
prefix);
opsteps = list_concat(opsteps, pc_steps);
}
}
break ;
}
default :
elog(ERROR, "invalid partition strategy: %c" ,
part_scheme->strategy);
break ;
}
return opsteps;
}
/*
* If the partition key has a collation , then the clause must have the same
* input collation . If the partition key is non - collatable , we assume the
* collation doesn ' t matter , because while collation wasn ' t considered when
* performing partitioning , the clause still may have a collation assigned
* due to the other input being of a collatable type .
*
* See also IndexCollMatchesExprColl .
*/
#define PartCollMatchesExprColl(partcoll, exprcoll) \
((partcoll) == InvalidOid || (partcoll) == (exprcoll))
/*
* match_clause_to_partition_key
* Attempt to match the given ' clause ' with the specified partition key .
*
* Return value is :
* * PARTCLAUSE_NOMATCH if the clause doesn ' t match this partition key ( but
* caller should keep trying , because it might match a subsequent key ) .
* Output arguments : none set .
*
* * PARTCLAUSE_MATCH_CLAUSE if there is a match .
* Output arguments : * pc is set to a PartClauseInfo constructed for the
* matched clause .
*
* * PARTCLAUSE_MATCH_NULLNESS if there is a match , and the matched clause was
* either a " a IS NULL " or " a IS NOT NULL " clause .
* Output arguments : * clause_is_not_null is set to false in the former case
* true otherwise .
*
* * PARTCLAUSE_MATCH_STEPS if there is a match .
* Output arguments : * clause_steps is set to the list of recursively
* generated steps for the clause .
*
* * PARTCLAUSE_MATCH_CONTRADICT if the clause is self - contradictory , ie
* it provably returns FALSE or NULL .
* Output arguments : none set .
*
* * PARTCLAUSE_UNSUPPORTED if the clause doesn ' t match this partition key
* and couldn ' t possibly match any other one either , due to its form or
* properties ( such as containing a volatile function ) .
* Output arguments : none set .
*
* Note that when pulling up a subquery , the clause operands may get wrapped
* in PlaceHolderVars to enforce separate identity or as a result of outer
* joins . We must strip such no - op PlaceHolderVars before comparing operands
* to the partition key , otherwise the equal ( ) checks will fail to recognize
* valid matches . This is safe because the clauses here are always
* relation - scan - level expressions , where a PlaceHolderVar with empty
* phnullingrels is effectively a no - op . Stripping may also bring separate
* RelabelType nodes into adjacency , so we must loop when peeling those .
*/
static PartClauseMatchStatus
match_clause_to_partition_key(GeneratePruningStepsContext *context,
Expr *clause, Expr *partkey, int partkeyidx,
bool *clause_is_not_null, PartClauseInfo **pc,
List **clause_steps)
{
PartClauseMatchStatus boolmatchstatus;
PartitionScheme part_scheme = context->rel->part_scheme;
Oid partopfamily = part_scheme->partopfamily[partkeyidx],
partcoll = part_scheme->partcollation[partkeyidx];
Expr *expr;
bool notclause;
/*
* Recognize specially shaped clauses that match a Boolean partition key .
*/
boolmatchstatus = match_boolean_partition_clause(partopfamily, clause,
partkey, &expr,
¬clause);
if (boolmatchstatus == PARTCLAUSE_MATCH_CLAUSE)
{
PartClauseInfo *partclause;
/*
* For bool tests in the form of partkey IS NOT true and IS NOT false ,
* we invert these clauses . Effectively , " partkey IS NOT true "
* becomes " partkey IS false OR partkey IS NULL " . We do this by
* building an OR BoolExpr and forming a clause just like that and
* punt it off to gen_partprune_steps_internal ( ) to generate pruning
* steps .
*/
if (notclause)
{
List *new_clauses;
List *or_clause;
BooleanTest *new_booltest = (BooleanTest *) copyObject(clause);
NullTest *nulltest;
/* We expect 'notclause' to only be set to true for BooleanTests */
Assert(IsA(clause, BooleanTest));
/* reverse the bool test */
if (new_booltest->booltesttype == IS_NOT_TRUE)
new_booltest->booltesttype = IS_FALSE;
else if (new_booltest->booltesttype == IS_NOT_FALSE)
new_booltest->booltesttype = IS_TRUE;
else
{
/*
* We only expect match_boolean_partition_clause to return
* PARTCLAUSE_MATCH_CLAUSE for IS_NOT_TRUE and IS_NOT_FALSE .
*/
Assert(false );
}
nulltest = makeNode(NullTest);
nulltest->arg = copyObject(partkey);
nulltest->nulltesttype = IS_NULL;
nulltest->argisrow = false ;
nulltest->location = -1 ;
new_clauses = list_make2(new_booltest, nulltest);
or_clause = list_make1(makeBoolExpr(OR_EXPR, new_clauses, -1 ));
/* Finally, generate steps */
*clause_steps = gen_partprune_steps_internal(context, or_clause);
if (context->contradictory)
return PARTCLAUSE_MATCH_CONTRADICT; /* shouldn't happen */
else if (*clause_steps == NIL)
return PARTCLAUSE_UNSUPPORTED; /* step generation failed */
return PARTCLAUSE_MATCH_STEPS;
}
partclause = (PartClauseInfo *) palloc(sizeof (PartClauseInfo));
partclause->keyno = partkeyidx;
/* Do pruning with the Boolean equality operator. */
partclause->opno = BooleanEqualOperator;
partclause->op_is_ne = false ;
partclause->expr = expr;
/* We know that expr is of Boolean type. */
partclause->cmpfn = part_scheme->partsupfunc[partkeyidx].fn_oid;
partclause->op_strategy = InvalidStrategy;
*pc = partclause;
return PARTCLAUSE_MATCH_CLAUSE;
}
else if (boolmatchstatus == PARTCLAUSE_MATCH_NULLNESS)
{
/*
* Handle IS UNKNOWN and IS NOT UNKNOWN . These just logically
* translate to IS NULL and IS NOT NULL .
*/
*clause_is_not_null = notclause;
return PARTCLAUSE_MATCH_NULLNESS;
}
else if (IsA(clause, OpExpr) &&
list_length(((OpExpr *) clause)->args) == 2 )
{
OpExpr *opclause = (OpExpr *) clause;
Expr *leftop,
*rightop;
Oid opno,
op_lefttype,
op_righttype,
negator = InvalidOid;
Oid cmpfn;
int op_strategy;
bool is_opne_listp = false ;
PartClauseInfo *partclause;
leftop = (Expr *) get_leftop(clause);
leftop = (Expr *) strip_noop_phvs((Node *) leftop);
while (IsA(leftop, RelabelType))
leftop = ((RelabelType *) leftop)->arg;
rightop = (Expr *) get_rightop(clause);
rightop = (Expr *) strip_noop_phvs((Node *) rightop);
while (IsA(rightop, RelabelType))
rightop = ((RelabelType *) rightop)->arg;
opno = opclause->opno;
/* check if the clause matches this partition key */
if (equal(leftop, partkey))
expr = rightop;
else if (equal(rightop, partkey))
{
/*
* It ' s only useful if we can commute the operator to put the
* partkey on the left . If we can ' t , the clause can be deemed
* UNSUPPORTED . Even if its leftop matches some later partkey , we
* now know it has Vars on the right , so it ' s no use .
*/
opno = get_commutator(opno);
if (!OidIsValid(opno))
return PARTCLAUSE_UNSUPPORTED;
expr = leftop;
}
else
/* clause does not match this partition key, but perhaps next. */
return PARTCLAUSE_NOMATCH;
/*
* Partition key match also requires collation match . There may be
* multiple partkeys with the same expression but different
* collations , so failure is NOMATCH .
*/
if (!PartCollMatchesExprColl(partcoll, opclause->inputcollid))
return PARTCLAUSE_NOMATCH;
/*
* See if the operator is relevant to the partitioning opfamily .
*
* Normally we only care about operators that are listed as being part
* of the partitioning operator family . But there is one exception :
* the not - equals operators are not listed in any operator family
* whatsoever , but their negators ( equality ) are . We can use one of
* those if we find it , but only for list partitioning .
*
* Note : we report NOMATCH on failure if the negator isn ' t the
* equality operator for the partkey ' s opfamily as other partkeys may
* have the same expression but different opfamily . That ' s unlikely ,
* but not much more so than duplicate expressions with different
* collations .
*/
if (op_in_opfamily(opno, partopfamily))
{
get_op_opfamily_properties(opno, partopfamily, false ,
&op_strategy, &op_lefttype,
&op_righttype);
}
else
{
/* not supported for anything apart from LIST partitioned tables */
if (part_scheme->strategy != PARTITION_STRATEGY_LIST)
return PARTCLAUSE_UNSUPPORTED;
/* See if the negator is equality */
negator = get_negator(opno);
if (OidIsValid(negator) && op_in_opfamily(negator, partopfamily))
{
get_op_opfamily_properties(negator, partopfamily, false ,
&op_strategy, &op_lefttype,
&op_righttype);
if (op_strategy == BTEqualStrategyNumber)
is_opne_listp = true ; /* bingo */
}
/* Nope, it's not <> either. */
if (!is_opne_listp)
return PARTCLAUSE_NOMATCH;
}
/*
* Only allow strict operators . This will guarantee nulls are
* filtered . ( This test is likely useless , since btree and hash
* comparison operators are generally strict . )
*/
if (!op_strict(opno))
return PARTCLAUSE_UNSUPPORTED;
/*
* OK , we have a match to the partition key and a suitable operator .
* Examine the other argument to see if it ' s usable for pruning .
*
* In most of these cases , we can return UNSUPPORTED because the same
* failure would occur no matter which partkey it ' s matched to . ( In
* particular , now that we ' ve successfully matched one side of the
* opclause to a partkey , there is no chance that matching the other
* side to another partkey will produce a usable result , since that ' d
* mean there are Vars on both sides . )
*
* Also , if we reject an argument for a target - dependent reason , set
* appropriate fields of * context to report that . We postpone these
* tests until after matching the partkey and the operator , so as to
* reduce the odds of setting the context fields for clauses that do
* not end up contributing to pruning steps .
*
* First , check for non - Const argument . ( We assume that any immutable
* subexpression will have been folded to a Const already . )
*/
if (!IsA(expr, Const ))
{
Bitmapset *paramids;
/*
* When pruning in the planner , we only support pruning using
* comparisons to constants . We cannot prune on the basis of
* anything that ' s not immutable . ( Note that has_mutable_arg and
* has_exec_param do not get set for this target value . )
*/
if (context->target == PARTTARGET_PLANNER)
return PARTCLAUSE_UNSUPPORTED;
/*
* We can never prune using an expression that contains Vars .
*/
if (contain_var_clause((Node *) expr))
return PARTCLAUSE_UNSUPPORTED;
/*
* And we must reject anything containing a volatile function .
* Stable functions are OK though .
*/
if (contain_volatile_functions((Node *) expr))
return PARTCLAUSE_UNSUPPORTED;
/*
* See if there are any exec Params . If so , we can only use this
* expression during per - scan pruning .
*/
paramids = pull_exec_paramids(expr);
if (!bms_is_empty(paramids))
{
context->has_exec_param = true ;
if (context->target != PARTTARGET_EXEC)
return PARTCLAUSE_UNSUPPORTED;
}
else
{
/* It's potentially usable, but mutable */
context->has_mutable_arg = true ;
}
}
/*
* Check whether the comparison operator itself is immutable . ( We
* assume anything that ' s in a btree or hash opclass is at least
* stable , but we need to check for immutability . )
*/
if (op_volatile(opno) != PROVOLATILE_IMMUTABLE)
{
context->has_mutable_op = true ;
/*
* When pruning in the planner , we cannot prune with mutable
* operators .
*/
if (context->target == PARTTARGET_PLANNER)
return PARTCLAUSE_UNSUPPORTED;
}
/*
* Now find the procedure to use , based on the types . If the clause ' s
* other argument is of the same type as the partitioning opclass ' s
* declared input type , we can use the procedure cached in
* PartitionKey . If not , search for a cross - type one in the same
* opfamily ; if one doesn ' t exist , report no match .
*/
if (op_righttype == part_scheme->partopcintype[partkeyidx])
cmpfn = part_scheme->partsupfunc[partkeyidx].fn_oid;
else
{
switch (part_scheme->strategy)
{
/*
* For range and list partitioning , we need the ordering
* procedure with lefttype being the partition key ' s type ,
* and righttype the clause ' s operator ' s right type .
*/
case PARTITION_STRATEGY_LIST:
case PARTITION_STRATEGY_RANGE:
cmpfn =
get_opfamily_proc(part_scheme->partopfamily[partkeyidx],
part_scheme->partopcintype[partkeyidx],
op_righttype, BTORDER_PROC);
break ;
/*
* For hash partitioning , we need the hashing procedure
* for the clause ' s type .
*/
case PARTITION_STRATEGY_HASH:
cmpfn =
get_opfamily_proc(part_scheme->partopfamily[partkeyidx],
op_righttype, op_righttype,
HASHEXTENDED_PROC);
break ;
default :
elog(ERROR, "invalid partition strategy: %c" ,
part_scheme->strategy);
cmpfn = InvalidOid; /* keep compiler quiet */
break ;
}
if (!OidIsValid(cmpfn))
return PARTCLAUSE_NOMATCH;
}
/*
* Build the clause , passing the negator if applicable .
*/
partclause = (PartClauseInfo *) palloc(sizeof (PartClauseInfo));
partclause->keyno = partkeyidx;
if (is_opne_listp)
{
Assert(OidIsValid(negator));
partclause->opno = negator;
partclause->op_is_ne = true ;
partclause->op_strategy = InvalidStrategy;
}
else
{
partclause->opno = opno;
partclause->op_is_ne = false ;
partclause->op_strategy = op_strategy;
}
partclause->expr = expr;
partclause->cmpfn = cmpfn;
*pc = partclause;
return PARTCLAUSE_MATCH_CLAUSE;
}
else if (IsA(clause, ScalarArrayOpExpr))
{
ScalarArrayOpExpr *saop = (ScalarArrayOpExpr *) clause;
Oid saop_op = saop->opno;
Oid saop_coll = saop->inputcollid;
Expr *leftop = (Expr *) linitial(saop->args),
*rightop = (Expr *) lsecond(saop->args);
List *elem_exprs,
*elem_clauses;
ListCell *lc1;
leftop = (Expr *) strip_noop_phvs((Node *) leftop);
while (IsA(leftop, RelabelType))
leftop = ((RelabelType *) leftop)->arg;
/* check if the LHS matches this partition key */
if (!equal(leftop, partkey) ||
!PartCollMatchesExprColl(partcoll, saop->inputcollid))
return PARTCLAUSE_NOMATCH;
/*
* See if the operator is relevant to the partitioning opfamily .
*
* In case of NOT IN ( . . ) , we get a ' < > ' , which we handle if list
* partitioning is in use and we ' re able to confirm that it ' s negator
* is a btree equality operator belonging to the partitioning operator
* family . As above , report NOMATCH for non - matching operator .
*/
if (!op_in_opfamily(saop_op, partopfamily))
{
Oid negator;
if (part_scheme->strategy != PARTITION_STRATEGY_LIST)
return PARTCLAUSE_NOMATCH;
negator = get_negator(saop_op);
if (OidIsValid(negator) && op_in_opfamily(negator, partopfamily))
{
int strategy;
Oid lefttype,
righttype;
get_op_opfamily_properties(negator, partopfamily,
false , &strategy,
&lefttype, &righttype);
if (strategy != BTEqualStrategyNumber)
return PARTCLAUSE_NOMATCH;
}
else
return PARTCLAUSE_NOMATCH; /* no useful negator */
}
/*
* Only allow strict operators . This will guarantee nulls are
* filtered . ( This test is likely useless , since btree and hash
* comparison operators are generally strict . )
*/
if (!op_strict(saop_op))
return PARTCLAUSE_UNSUPPORTED;
/*
* OK , we have a match to the partition key and a suitable operator .
* Examine the array argument to see if it ' s usable for pruning . This
* is identical to the logic for a plain OpExpr .
*/
if (!IsA(rightop, Const ))
{
Bitmapset *paramids;
/*
* When pruning in the planner , we only support pruning using
* comparisons to constants . We cannot prune on the basis of
* anything that ' s not immutable . ( Note that has_mutable_arg and
* has_exec_param do not get set for this target value . )
*/
if (context->target == PARTTARGET_PLANNER)
return PARTCLAUSE_UNSUPPORTED;
/*
* We can never prune using an expression that contains Vars .
*/
if (contain_var_clause((Node *) rightop))
return PARTCLAUSE_UNSUPPORTED;
/*
* And we must reject anything containing a volatile function .
* Stable functions are OK though .
*/
if (contain_volatile_functions((Node *) rightop))
return PARTCLAUSE_UNSUPPORTED;
/*
* See if there are any exec Params . If so , we can only use this
* expression during per - scan pruning .
*/
paramids = pull_exec_paramids(rightop);
if (!bms_is_empty(paramids))
{
context->has_exec_param = true ;
if (context->target != PARTTARGET_EXEC)
return PARTCLAUSE_UNSUPPORTED;
}
else
{
/* It's potentially usable, but mutable */
context->has_mutable_arg = true ;
}
}
/*
* Check whether the comparison operator itself is immutable . ( We
* assume anything that ' s in a btree or hash opclass is at least
* stable , but we need to check for immutability . )
*/
if (op_volatile(saop_op) != PROVOLATILE_IMMUTABLE)
{
context->has_mutable_op = true ;
/*
* When pruning in the planner , we cannot prune with mutable
* operators .
*/
if (context->target == PARTTARGET_PLANNER)
return PARTCLAUSE_UNSUPPORTED;
}
/*
* Examine the contents of the array argument .
*/
elem_exprs = NIL;
if (IsA(rightop, Const ))
{
/*
* For a constant array , convert the elements to a list of Const
* nodes , one for each array element ( excepting nulls ) .
*/
Const *arr = (Const *) rightop;
ArrayType *arrval;
int16 elemlen;
bool elembyval;
char elemalign;
Datum *elem_values;
bool *elem_nulls;
int num_elems,
i;
/* If the array itself is null, the saop returns null */
if (arr->constisnull)
return PARTCLAUSE_MATCH_CONTRADICT;
arrval = DatumGetArrayTypeP(arr->constvalue);
get_typlenbyvalalign(ARR_ELEMTYPE(arrval),
&elemlen, &elembyval, &elemalign);
deconstruct_array(arrval,
ARR_ELEMTYPE(arrval),
elemlen, elembyval, elemalign,
&elem_values, &elem_nulls,
&num_elems);
for (i = 0 ; i < num_elems; i++)
{
Const *elem_expr;
/*
* A null array element must lead to a null comparison result ,
* since saop_op is known strict . We can ignore it in the
* useOr case , but otherwise it implies self - contradiction .
*/
if (elem_nulls[i])
{
if (saop->useOr)
continue ;
return PARTCLAUSE_MATCH_CONTRADICT;
}
elem_expr = makeConst(ARR_ELEMTYPE(arrval), -1 ,
arr->constcollid, elemlen,
elem_values[i], false , elembyval);
elem_exprs = lappend(elem_exprs, elem_expr);
}
}
else if (IsA(rightop, ArrayExpr))
{
ArrayExpr *arrexpr = castNode(ArrayExpr, rightop);
/*
* For a nested ArrayExpr , we don ' t know how to get the actual
* scalar values out into a flat list , so we give up doing
* anything with this ScalarArrayOpExpr .
*/
if (arrexpr->multidims)
return PARTCLAUSE_UNSUPPORTED;
/*
* Otherwise , we can just use the list of element values .
*/
elem_exprs = arrexpr->elements;
}
else
{
/* Give up on any other clause types. */
return PARTCLAUSE_UNSUPPORTED;
}
/*
* Now generate a list of clauses , one for each array element , of the
* form leftop saop_op elem_expr
*/
elem_clauses = NIL;
foreach(lc1, elem_exprs)
{
Expr *elem_clause;
elem_clause = make_opclause(saop_op, BOOLOID, false ,
leftop, lfirst(lc1),
InvalidOid, saop_coll);
elem_clauses = lappend(elem_clauses, elem_clause);
}
/*
* If we have an ANY clause and multiple elements , now turn the list
* of clauses into an OR expression .
*/
if (saop->useOr && list_length(elem_clauses) > 1 )
elem_clauses = list_make1(makeBoolExpr(OR_EXPR, elem_clauses, -1 ));
/* Finally, generate steps */
*clause_steps = gen_partprune_steps_internal(context, elem_clauses);
if (context->contradictory)
return PARTCLAUSE_MATCH_CONTRADICT;
else if (*clause_steps == NIL)
return PARTCLAUSE_UNSUPPORTED; /* step generation failed */
return PARTCLAUSE_MATCH_STEPS;
}
else if (IsA(clause, NullTest))
{
NullTest *nulltest = (NullTest *) clause;
Expr *arg = nulltest->arg;
arg = (Expr *) strip_noop_phvs((Node *) arg);
while (IsA(arg, RelabelType))
arg = ((RelabelType *) arg)->arg;
/* Does arg match with this partition key column? */
if (!equal(arg, partkey))
return PARTCLAUSE_NOMATCH;
*clause_is_not_null = (nulltest->nulltesttype == IS_NOT_NULL);
return PARTCLAUSE_MATCH_NULLNESS;
}
/*
* If we get here then the return value depends on the result of the
* match_boolean_partition_clause call above . If the call returned
* PARTCLAUSE_UNSUPPORTED then we ' re either not dealing with a bool qual
* or the bool qual is not suitable for pruning . Since the qual didn ' t
* match up to any of the other qual types supported here , then trying to
* match it against any other partition key is a waste of time , so just
* return PARTCLAUSE_UNSUPPORTED . If the qual just couldn ' t be matched to
* this partition key , then it may match another , so return
* PARTCLAUSE_NOMATCH . The only other value that
* match_boolean_partition_clause can return is PARTCLAUSE_MATCH_CLAUSE ,
* and since that value was already dealt with above , then we can just
* return boolmatchstatus .
*/
return boolmatchstatus;
}
/*
* get_steps_using_prefix
* Generate a list of PartitionPruneStepOps based on the given input .
*
* ' step_lastexpr ' and ' step_lastcmpfn ' are the Expr and comparison function
* belonging to the final partition key that we have a clause for . ' prefix '
* is a list of PartClauseInfos for partition key numbers prior to the given
* ' step_lastexpr ' and ' step_lastcmpfn ' . ' prefix ' may contain multiple
* PartClauseInfos belonging to a single partition key . We will generate a
* PartitionPruneStepOp for each combination of the given PartClauseInfos
* using , at most , one PartClauseInfo per partition key .
*
* For LIST and RANGE partitioned tables , callers must ensure that
* step_nullkeys is NULL , and that prefix contains at least one clause for
* each of the partition keys prior to the key that ' step_lastexpr ' and
* ' step_lastcmpfn ' belong to .
*
* For HASH partitioned tables , callers must ensure that ' prefix ' contains at
* least one clause for each of the partition keys apart from the final key
* ( the expr and comparison function for the final key are in ' step_lastexpr '
* and ' step_lastcmpfn ' ) . A bit set in step_nullkeys can substitute clauses
* in the ' prefix ' list for any given key . If a bit is set in ' step_nullkeys '
* for a given key , then there must be no PartClauseInfo for that key in the
* ' prefix ' list .
*
* For each of the above cases , callers must ensure that PartClauseInfos in
* ' prefix ' are sorted in ascending order of keyno .
*/
static List *
get_steps_using_prefix(GeneratePruningStepsContext *context,
StrategyNumber step_opstrategy,
bool step_op_is_ne,
Expr *step_lastexpr,
Oid step_lastcmpfn,
Bitmapset *step_nullkeys,
List *prefix)
{
/* step_nullkeys must be empty for RANGE and LIST partitioned tables */
Assert(step_nullkeys == NULL ||
context->rel->part_scheme->strategy == PARTITION_STRATEGY_HASH);
/*
* No recursive processing is required when ' prefix ' is an empty list .
* This occurs when there is only 1 partition key column .
*/
if (prefix == NIL)
{
PartitionPruneStep *step;
step = gen_prune_step_op(context,
step_opstrategy,
step_op_is_ne,
list_make1(step_lastexpr),
list_make1_oid(step_lastcmpfn),
step_nullkeys);
return list_make1(step);
}
/* Recurse to generate steps for every combination of clauses. */
return get_steps_using_prefix_recurse(context,
step_opstrategy,
step_op_is_ne,
step_lastexpr,
step_lastcmpfn,
step_nullkeys,
prefix,
list_head(prefix),
NIL, NIL);
}
/*
* get_steps_using_prefix_recurse
* Generate and return a list of PartitionPruneStepOps using the ' prefix '
* list of PartClauseInfos starting at the ' start ' cell .
*
* When ' prefix ' contains multiple PartClauseInfos for a single partition key
* we create a PartitionPruneStepOp for each combination of duplicated
* PartClauseInfos . The returned list will contain a PartitionPruneStepOp
* for each unique combination of input PartClauseInfos containing at most one
* PartClauseInfo per partition key .
*
* ' prefix ' is the input list of PartClauseInfos sorted by keyno .
* ' start ' marks the cell that searching the ' prefix ' list should start from .
* ' step_exprs ' and ' step_cmpfns ' each contains the expressions and cmpfns
* we ' ve generated so far from the clauses for the previous part keys .
*/
static List *
get_steps_using_prefix_recurse(GeneratePruningStepsContext *context,
StrategyNumber step_opstrategy,
bool step_op_is_ne,
Expr *step_lastexpr,
Oid step_lastcmpfn,
Bitmapset *step_nullkeys,
List *prefix,
ListCell *start,
List *step_exprs,
List *step_cmpfns)
{
List *result = NIL;
ListCell *lc;
int cur_keyno;
int final_keyno;
/* Actually, recursion would be limited by PARTITION_MAX_KEYS. */
check_stack_depth();
Assert(start != NULL);
cur_keyno = ((PartClauseInfo *) lfirst(start))->keyno;
final_keyno = ((PartClauseInfo *) llast(prefix))->keyno;
/* Check if we need to recurse. */
if (cur_keyno < final_keyno)
{
PartClauseInfo *pc;
ListCell *next_start;
/*
* Find the first PartClauseInfo belonging to the next partition key ,
* the next recursive call must start iteration of the prefix list
* from that point .
*/
for_each_cell(lc, prefix, start)
{
pc = lfirst(lc);
if (pc->keyno > cur_keyno)
break ;
}
/* record where to start iterating in the next recursive call */
next_start = lc;
/*
* For each PartClauseInfo with keyno set to cur_keyno , add its expr
* and cmpfn to step_exprs and step_cmpfns , respectively , and recurse
* using ' next_start ' as the starting point in the ' prefix ' list .
*/
for_each_cell(lc, prefix, start)
{
List *moresteps;
List *step_exprs1,
*step_cmpfns1;
pc = lfirst(lc);
if (pc->keyno == cur_keyno)
{
/* Leave the original step_exprs unmodified. */
step_exprs1 = list_copy(step_exprs);
step_exprs1 = lappend(step_exprs1, pc->expr);
/* Leave the original step_cmpfns unmodified. */
step_cmpfns1 = list_copy(step_cmpfns);
step_cmpfns1 = lappend_oid(step_cmpfns1, pc->cmpfn);
}
else
{
/* check the 'prefix' list is sorted correctly */
Assert(pc->keyno > cur_keyno);
break ;
}
moresteps = get_steps_using_prefix_recurse(context,
step_opstrategy,
step_op_is_ne,
step_lastexpr,
step_lastcmpfn,
step_nullkeys,
prefix,
next_start,
step_exprs1,
step_cmpfns1);
result = list_concat(result, moresteps);
list_free(step_exprs1);
list_free(step_cmpfns1);
}
}
else
{
/*
* End the current recursion cycle and start generating steps , one for
* each clause with cur_keyno , which is all clauses from here onward
* till the end of the list . Note that for hash partitioning ,
* step_nullkeys is allowed to be non - empty , in which case step_exprs
* would only contain expressions for the partition keys that are not
* specified in step_nullkeys .
*/
Assert(list_length(step_exprs) == cur_keyno ||
!bms_is_empty(step_nullkeys));
/*
* Note also that for hash partitioning , each partition key should
* have either equality clauses or an IS NULL clause , so if a
* partition key doesn ' t have an expression , it would be specified in
* step_nullkeys .
*/
Assert(context->rel->part_scheme->strategy
!= PARTITION_STRATEGY_HASH ||
list_length(step_exprs) + 2 + bms_num_members(step_nullkeys) ==
context->rel->part_scheme->partnatts);
for_each_cell(lc, prefix, start)
{
PartClauseInfo *pc = lfirst(lc);
PartitionPruneStep *step;
List *step_exprs1,
*step_cmpfns1;
Assert(pc->keyno == cur_keyno);
/* Leave the original step_exprs unmodified. */
step_exprs1 = list_copy(step_exprs);
step_exprs1 = lappend(step_exprs1, pc->expr);
step_exprs1 = lappend(step_exprs1, step_lastexpr);
/* Leave the original step_cmpfns unmodified. */
step_cmpfns1 = list_copy(step_cmpfns);
step_cmpfns1 = lappend_oid(step_cmpfns1, pc->cmpfn);
step_cmpfns1 = lappend_oid(step_cmpfns1, step_lastcmpfn);
step = gen_prune_step_op(context,
step_opstrategy, step_op_is_ne,
step_exprs1, step_cmpfns1,
step_nullkeys);
result = lappend(result, step);
}
}
return result;
}
/*
* get_matching_hash_bounds
* Determine offset of the hash bound matching the specified values ,
* considering that all the non - null values come from clauses containing
* a compatible hash equality operator and any keys that are null come
* from an IS NULL clause .
*
* Generally this function will return a single matching bound offset ,
* although if a partition has not been setup for a given modulus then we may
* return no matches . If the number of clauses found don ' t cover the entire
* partition key , then we ' ll need to return all offsets .
*
* ' opstrategy ' if non - zero must be HTEqualStrategyNumber .
*
* ' values ' contains Datums indexed by the partition key to use for pruning .
*
* ' nvalues ' , the number of Datums in the ' values ' array .
*
* ' partsupfunc ' contains partition hashing functions that can produce correct
* hash for the type of the values contained in ' values ' .
*
* ' nullkeys ' is the set of partition keys that are null .
*/
static PruneStepResult *
get_matching_hash_bounds(PartitionPruneContext *context,
StrategyNumber opstrategy, Datum *values, int nvalues,
FmgrInfo *partsupfunc, Bitmapset *nullkeys)
{
PruneStepResult *result = (PruneStepResult *) palloc0(sizeof (PruneStepResult));
PartitionBoundInfo boundinfo = context->boundinfo;
int *partindices = boundinfo->indexes;
int partnatts = context->partnatts;
bool isnull[PARTITION_MAX_KEYS];
int i;
uint64 rowHash;
int greatest_modulus;
Oid *partcollation = context->partcollation;
Assert(context->strategy == PARTITION_STRATEGY_HASH);
/*
* For hash partitioning we can only perform pruning based on equality
* clauses to the partition key or IS NULL clauses . We also can only
* prune if we got values for all keys .
*/
if (nvalues + bms_num_members(nullkeys) == partnatts)
{
/*
* If there are any values , they must have come from clauses
* containing an equality operator compatible with hash partitioning .
*/
Assert(opstrategy == HTEqualStrategyNumber || nvalues == 0 );
for (i = 0 ; i < partnatts; i++)
isnull[i] = bms_is_member(i, nullkeys);
rowHash = compute_partition_hash_value(partnatts, partsupfunc, partcollation,
values, isnull);
greatest_modulus = boundinfo->nindexes;
if (partindices[rowHash % greatest_modulus] >= 0 )
result->bound_offsets =
bms_make_singleton(rowHash % greatest_modulus);
}
else
{
/* Report all valid offsets into the boundinfo->indexes array. */
result->bound_offsets = bms_add_range(NULL, 0 ,
boundinfo->nindexes - 1 );
}
/*
* There is neither a special hash null partition or the default hash
* partition .
*/
result->scan_null = result->scan_default = false ;
return result;
}
/*
* get_matching_list_bounds
* Determine the offsets of list bounds matching the specified value ,
* according to the semantics of the given operator strategy
*
* scan_default will be set in the returned struct , if the default partition
* needs to be scanned , provided one exists at all . scan_null will be set if
* the special null - accepting partition needs to be scanned .
*
* ' opstrategy ' if non - zero must be a btree strategy number .
*
* ' value ' contains the value to use for pruning .
*
* ' nvalues ' , if non - zero , should be exactly 1 , because of list partitioning .
*
* ' partsupfunc ' contains the list partitioning comparison function to be used
* to perform partition_list_bsearch
*
* ' nullkeys ' is the set of partition keys that are null .
*/
static PruneStepResult *
get_matching_list_bounds(PartitionPruneContext *context,
StrategyNumber opstrategy, Datum value, int nvalues,
FmgrInfo *partsupfunc, Bitmapset *nullkeys)
{
PruneStepResult *result = (PruneStepResult *) palloc0(sizeof (PruneStepResult));
PartitionBoundInfo boundinfo = context->boundinfo;
int off,
minoff,
maxoff;
bool is_equal;
bool inclusive = false ;
Oid *partcollation = context->partcollation;
Assert(context->strategy == PARTITION_STRATEGY_LIST);
Assert(context->partnatts == 1 );
result->scan_null = result->scan_default = false ;
if (!bms_is_empty(nullkeys))
{
/*
* Nulls may exist in only one partition - the partition whose
* accepted set of values includes null or the default partition if
* the former doesn ' t exist .
*/
if (partition_bound_accepts_nulls(boundinfo))
result->scan_null = true ;
else
result->scan_default = partition_bound_has_default(boundinfo);
return result;
}
/*
* If there are no datums to compare keys with , but there are partitions ,
* just return the default partition if one exists .
*/
if (boundinfo->ndatums == 0 )
{
result->scan_default = partition_bound_has_default(boundinfo);
return result;
}
minoff = 0 ;
maxoff = boundinfo->ndatums - 1 ;
/*
* If there are no values to compare with the datums in boundinfo , it
* means the caller asked for partitions for all non - null datums . Add
* indexes of * all * partitions , including the default if any .
*/
if (nvalues == 0 )
{
Assert(boundinfo->ndatums > 0 );
result->bound_offsets = bms_add_range(NULL, 0 ,
boundinfo->ndatums - 1 );
result->scan_default = partition_bound_has_default(boundinfo);
return result;
}
/* Special case handling of values coming from a <> operator clause. */
if (opstrategy == InvalidStrategy)
{
/*
* First match to all bounds . We ' ll remove any matching datums below .
*/
Assert(boundinfo->ndatums > 0 );
result->bound_offsets = bms_add_range(NULL, 0 ,
boundinfo->ndatums - 1 );
off = partition_list_bsearch(partsupfunc, partcollation, boundinfo,
value, &is_equal);
if (off >= 0 && is_equal)
{
/* We have a match. Remove from the result. */
Assert(boundinfo->indexes[off] >= 0 );
result->bound_offsets = bms_del_member(result->bound_offsets,
off);
}
/* Always include the default partition if any. */
result->scan_default = partition_bound_has_default(boundinfo);
return result;
}
/*
* With range queries , always include the default list partition , because
* list partitions divide the key space in a discontinuous manner , not all
* values in the given range will have a partition assigned . This may not
* technically be true for some data types ( e . g . integer types ) , however ,
* we currently lack any sort of infrastructure to provide us with proofs
* that would allow us to do anything smarter here .
*/
if (opstrategy != BTEqualStrategyNumber)
result->scan_default = partition_bound_has_default(boundinfo);
switch (opstrategy)
{
case BTEqualStrategyNumber:
off = partition_list_bsearch(partsupfunc,
partcollation,
boundinfo, value,
&is_equal);
if (off >= 0 && is_equal)
{
Assert(boundinfo->indexes[off] >= 0 );
result->bound_offsets = bms_make_singleton(off);
}
else
result->scan_default = partition_bound_has_default(boundinfo);
return result;
case BTGreaterEqualStrategyNumber:
inclusive = true ;
/* fall through */
case BTGreaterStrategyNumber:
off = partition_list_bsearch(partsupfunc,
partcollation,
boundinfo, value,
&is_equal);
if (off >= 0 )
{
/* We don't want the matched datum to be in the result. */
if (!is_equal || !inclusive)
off++;
}
else
{
/*
* This case means all partition bounds are greater , which in
* turn means that all partitions satisfy this key .
*/
off = 0 ;
}
/*
* off is greater than the numbers of datums we have partitions
* for . The only possible partition that could contain a match is
* the default partition , but we must ' ve set context - > scan_default
* above anyway if one exists .
*/
if (off > boundinfo->ndatums - 1 )
return result;
minoff = off;
break ;
case BTLessEqualStrategyNumber:
inclusive = true ;
/* fall through */
case BTLessStrategyNumber:
off = partition_list_bsearch(partsupfunc,
partcollation,
boundinfo, value,
&is_equal);
if (off >= 0 && is_equal && !inclusive)
off--;
/*
* off is smaller than the datums of all non - default partitions .
* The only possible partition that could contain a match is the
* default partition , but we must ' ve set context - > scan_default
* above anyway if one exists .
*/
if (off < 0 )
return result;
maxoff = off;
break ;
default :
elog(ERROR, "invalid strategy number %d" , opstrategy);
break ;
}
Assert(minoff >= 0 && maxoff >= 0 );
result->bound_offsets = bms_add_range(NULL, minoff, maxoff);
return result;
}
/*
* get_matching_range_bounds
* Determine the offsets of range bounds matching the specified values ,
* according to the semantics of the given operator strategy
*
* Each datum whose offset is in result is to be treated as the upper bound of
* the partition that will contain the desired values .
*
* scan_default is set in the returned struct if a default partition exists
* and we ' re absolutely certain that it needs to be scanned . We do * not * set
* it just because values match portions of the key space uncovered by
* partitions other than default ( space which we normally assume to belong to
* the default partition ) : the final set of bounds obtained after combining
* multiple pruning steps might exclude it , so we infer its inclusion
* elsewhere .
*
* ' opstrategy ' must be a btree strategy number .
*
* ' values ' contains Datums indexed by the partition key to use for pruning .
*
* ' nvalues ' , number of Datums in ' values ' array . Must be < = context - > partnatts .
*
* ' partsupfunc ' contains the range partitioning comparison functions to be
* used to perform partition_range_datum_bsearch or partition_rbound_datum_cmp
* using .
*
* ' nullkeys ' is the set of partition keys that are null .
*/
static PruneStepResult *
get_matching_range_bounds(PartitionPruneContext *context,
StrategyNumber opstrategy, Datum *values, int nvalues,
FmgrInfo *partsupfunc, Bitmapset *nullkeys)
{
PruneStepResult *result = (PruneStepResult *) palloc0(sizeof (PruneStepResult));
PartitionBoundInfo boundinfo = context->boundinfo;
Oid *partcollation = context->partcollation;
int partnatts = context->partnatts;
int *partindices = boundinfo->indexes;
int off,
minoff,
maxoff;
bool is_equal;
bool inclusive = false ;
Assert(context->strategy == PARTITION_STRATEGY_RANGE);
Assert(nvalues <= partnatts);
result->scan_null = result->scan_default = false ;
/*
* If there are no datums to compare keys with , or if we got an IS NULL
* clause just return the default partition , if it exists .
*/
if (boundinfo->ndatums == 0 || !bms_is_empty(nullkeys))
{
result->scan_default = partition_bound_has_default(boundinfo);
return result;
}
minoff = 0 ;
maxoff = boundinfo->ndatums;
/*
* If there are no values to compare with the datums in boundinfo , it
* means the caller asked for partitions for all non - null datums . Add
* indexes of * all * partitions , including the default partition if one
* exists .
*/
if (nvalues == 0 )
{
/* ignore key space not covered by any partitions */
if (partindices[minoff] < 0 )
minoff++;
if (partindices[maxoff] < 0 )
maxoff--;
result->scan_default = partition_bound_has_default(boundinfo);
Assert(partindices[minoff] >= 0 &&
partindices[maxoff] >= 0 );
result->bound_offsets = bms_add_range(NULL, minoff, maxoff);
return result;
}
/*
* If the query does not constrain all key columns , we ' ll need to scan the
* default partition , if any .
*/
if (nvalues < partnatts)
result->scan_default = partition_bound_has_default(boundinfo);
switch (opstrategy)
{
case BTEqualStrategyNumber:
/* Look for the smallest bound that is = lookup value. */
off = partition_range_datum_bsearch(partsupfunc,
partcollation,
boundinfo,
nvalues, values,
&is_equal);
if (off >= 0 && is_equal)
{
if (nvalues == partnatts)
{
/* There can only be zero or one matching partition. */
result->bound_offsets = bms_make_singleton(off + 1 );
return result;
}
else
{
int saved_off = off;
/*
* Since the lookup value contains only a prefix of keys ,
* we must find other bounds that may also match the
* prefix . partition_range_datum_bsearch ( ) returns the
* offset of one of them , find others by checking adjacent
* bounds .
*/
/*
* First find greatest bound that ' s smaller than the
* lookup value .
*/
while (off >= 1 )
{
int32 cmpval;
cmpval =
partition_rbound_datum_cmp(partsupfunc,
partcollation,
boundinfo->datums[off - 1 ],
boundinfo->kind[off - 1 ],
values, nvalues);
if (cmpval != 0 )
break ;
off--;
}
Assert(0 ==
partition_rbound_datum_cmp(partsupfunc,
partcollation,
boundinfo->datums[off],
boundinfo->kind[off],
values, nvalues));
/*
* We can treat ' off ' as the offset of the smallest bound
* to be included in the result , if we know it is the
* upper bound of the partition in which the lookup value
* could possibly exist . One case it couldn ' t is if the
* bound , or precisely the matched portion of its prefix ,
* is not inclusive .
*/
if (boundinfo->kind[off][nvalues] ==
PARTITION_RANGE_DATUM_MINVALUE)
off++;
minoff = off;
/*
* Now find smallest bound that ' s greater than the lookup
* value .
*/
off = saved_off;
while (off < boundinfo->ndatums - 1 )
{
int32 cmpval;
cmpval = partition_rbound_datum_cmp(partsupfunc,
partcollation,
boundinfo->datums[off + 1 ],
boundinfo->kind[off + 1 ],
values, nvalues);
if (cmpval != 0 )
break ;
off++;
}
Assert(0 ==
partition_rbound_datum_cmp(partsupfunc,
partcollation,
boundinfo->datums[off],
boundinfo->kind[off],
values, nvalues));
/*
* off + 1 , then would be the offset of the greatest bound
* to be included in the result .
*/
maxoff = off + 1 ;
}
Assert(minoff >= 0 && maxoff >= 0 );
result->bound_offsets = bms_add_range(NULL, minoff, maxoff);
}
else
{
/*
* The lookup value falls in the range between some bounds in
* boundinfo . ' off ' would be the offset of the greatest bound
* that is < = lookup value , so add off + 1 to the result
* instead as the offset of the upper bound of the only
* partition that may contain the lookup value . If ' off ' is
* - 1 indicating that all bounds are greater , then we simply
* end up adding the first bound ' s offset , that is , 0 .
*/
result->bound_offsets = bms_make_singleton(off + 1 );
}
return result;
case BTGreaterEqualStrategyNumber:
inclusive = true ;
/* fall through */
case BTGreaterStrategyNumber:
/*
* Look for the smallest bound that is > or > = lookup value and
* set minoff to its offset .
*/
off = partition_range_datum_bsearch(partsupfunc,
partcollation,
boundinfo,
nvalues, values,
&is_equal);
if (off < 0 )
{
/*
* All bounds are greater than the lookup value , so include
* all of them in the result .
*/
minoff = 0 ;
}
else
{
if (is_equal && nvalues < partnatts)
{
/*
* Since the lookup value contains only a prefix of keys ,
* we must find other bounds that may also match the
* prefix . partition_range_datum_bsearch ( ) returns the
* offset of one of them , find others by checking adjacent
* bounds .
*
* Based on whether the lookup values are inclusive or
* not , we must either include the indexes of all such
* bounds in the result ( that is , set minoff to the index
* of smallest such bound ) or find the smallest one that ' s
* greater than the lookup values and set minoff to that .
*/
while (off >= 1 && off < boundinfo->ndatums - 1 )
{
int32 cmpval;
int nextoff;
nextoff = inclusive ? off - 1 : off + 1 ;
cmpval =
partition_rbound_datum_cmp(partsupfunc,
partcollation,
boundinfo->datums[nextoff],
boundinfo->kind[nextoff],
values, nvalues);
if (cmpval != 0 )
break ;
off = nextoff;
}
Assert(0 ==
partition_rbound_datum_cmp(partsupfunc,
partcollation,
boundinfo->datums[off],
boundinfo->kind[off],
values, nvalues));
minoff = inclusive ? off : off + 1 ;
}
else
{
/*
* lookup value falls in the range between some bounds in
* boundinfo . off would be the offset of the greatest
* bound that is < = lookup value , so add off + 1 to the
* result instead as the offset of the upper bound of the
* smallest partition that may contain the lookup value .
*/
minoff = off + 1 ;
}
}
break ;
case BTLessEqualStrategyNumber:
inclusive = true ;
/* fall through */
case BTLessStrategyNumber:
/*
* Look for the greatest bound that is < or < = lookup value and
* set maxoff to its offset .
*/
off = partition_range_datum_bsearch(partsupfunc,
partcollation,
boundinfo,
nvalues, values,
&is_equal);
if (off >= 0 )
{
/*
* See the comment above .
*/
if (is_equal && nvalues < partnatts)
{
while (off >= 1 && off < boundinfo->ndatums - 1 )
{
int32 cmpval;
int nextoff;
nextoff = inclusive ? off + 1 : off - 1 ;
cmpval = partition_rbound_datum_cmp(partsupfunc,
partcollation,
boundinfo->datums[nextoff],
boundinfo->kind[nextoff],
values, nvalues);
if (cmpval != 0 )
break ;
off = nextoff;
}
Assert(0 ==
partition_rbound_datum_cmp(partsupfunc,
partcollation,
boundinfo->datums[off],
boundinfo->kind[off],
values, nvalues));
maxoff = inclusive ? off + 1 : off;
}
/*
* The lookup value falls in the range between some bounds in
* boundinfo . ' off ' would be the offset of the greatest bound
* that is < = lookup value , so add off + 1 to the result
* instead as the offset of the upper bound of the greatest
* partition that may contain lookup value . If the lookup
* value had exactly matched the bound , but it isn ' t
* inclusive , no need add the adjacent partition .
*/
else if (!is_equal || inclusive)
maxoff = off + 1 ;
else
maxoff = off;
}
else
{
/*
* ' off ' is - 1 indicating that all bounds are greater , so just
* set the first bound ' s offset as maxoff .
*/
maxoff = off + 1 ;
}
break ;
default :
elog(ERROR, "invalid strategy number %d" , opstrategy);
break ;
}
Assert(minoff >= 0 && minoff <= boundinfo->ndatums);
Assert(maxoff >= 0 && maxoff <= boundinfo->ndatums);
/*
* If the smallest partition to return has MINVALUE ( negative infinity ) as
* its lower bound , increment it to point to the next finite bound
* ( supposedly its upper bound ) , so that we don ' t inadvertently end up
* scanning the default partition .
*/
if (minoff < boundinfo->ndatums && partindices[minoff] < 0 )
{
int lastkey = nvalues - 1 ;
if (boundinfo->kind[minoff][lastkey] ==
PARTITION_RANGE_DATUM_MINVALUE)
{
minoff++;
Assert(boundinfo->indexes[minoff] >= 0 );
}
}
/*
* If the previous greatest partition has MAXVALUE ( positive infinity ) as
* its upper bound ( something only possible to do with multi - column range
* partitioning ) , we scan switch to it as the greatest partition to
* return . Again , so that we don ' t inadvertently end up scanning the
* default partition .
*/
if (maxoff >= 1 && partindices[maxoff] < 0 )
{
int lastkey = nvalues - 1 ;
if (boundinfo->kind[maxoff - 1 ][lastkey] ==
PARTITION_RANGE_DATUM_MAXVALUE)
{
maxoff--;
Assert(boundinfo->indexes[maxoff] >= 0 );
}
}
Assert(minoff >= 0 && maxoff >= 0 );
if (minoff <= maxoff)
result->bound_offsets = bms_add_range(NULL, minoff, maxoff);
return result;
}
/*
* pull_exec_paramids
* Returns a Bitmapset containing the paramids of all Params with
* paramkind = PARAM_EXEC in ' expr ' .
*/
static Bitmapset *
pull_exec_paramids(Expr *expr)
{
Bitmapset *result = NULL;
(void ) pull_exec_paramids_walker((Node *) expr, &result);
return result;
}
static bool
pull_exec_paramids_walker(Node *node, Bitmapset **context)
{
if (node == NULL)
return false ;
if (IsA(node, Param))
{
Param *param = (Param *) node;
if (param->paramkind == PARAM_EXEC)
*context = bms_add_member(*context, param->paramid);
return false ;
}
return expression_tree_walker(node, pull_exec_paramids_walker, context);
}
/*
* get_partkey_exec_paramids
* Loop through given pruning steps and find out which exec Params
* are used .
*
* Returns a Bitmapset of Param IDs .
*/
static Bitmapset *
get_partkey_exec_paramids(List *steps)
{
Bitmapset *execparamids = NULL;
ListCell *lc;
foreach(lc, steps)
{
PartitionPruneStepOp *step = (PartitionPruneStepOp *) lfirst(lc);
ListCell *lc2;
if (!IsA(step, PartitionPruneStepOp))
continue ;
foreach(lc2, step->exprs)
{
Expr *expr = lfirst(lc2);
/* We can be quick for plain Consts */
if (!IsA(expr, Const ))
execparamids = bms_join(execparamids,
pull_exec_paramids(expr));
}
}
return execparamids;
}
/*
* perform_pruning_base_step
* Determines the indexes of datums that satisfy conditions specified in
* ' opstep ' .
*
* Result also contains whether special null - accepting and / or default
* partition need to be scanned .
*/
static PruneStepResult *
perform_pruning_base_step(PartitionPruneContext *context,
PartitionPruneStepOp *opstep)
{
ListCell *lc1,
*lc2;
int keyno,
nvalues;
Datum values[PARTITION_MAX_KEYS];
FmgrInfo *partsupfunc;
int stateidx;
/*
* There better be the same number of expressions and compare functions .
*/
Assert(list_length(opstep->exprs) == list_length(opstep->cmpfns));
nvalues = 0 ;
lc1 = list_head(opstep->exprs);
lc2 = list_head(opstep->cmpfns);
/*
* Generate the partition lookup key that will be used by one of the
* get_matching_ * _ bounds functions called below .
*/
for (keyno = 0 ; keyno < context->partnatts; keyno++)
{
/*
* For hash partitioning , it is possible that values of some keys are
* not provided in operator clauses , but instead the planner found
* that they appeared in a IS NULL clause .
*/
if (bms_is_member(keyno, opstep->nullkeys))
continue ;
/*
* For range partitioning , we must only perform pruning with values
* for either all partition keys or a prefix thereof .
*/
if (keyno > nvalues && context->strategy == PARTITION_STRATEGY_RANGE)
break ;
if (lc1 != NULL)
{
Expr *expr;
Datum datum;
bool isnull;
Oid cmpfn;
expr = lfirst(lc1);
stateidx = PruneCxtStateIdx(context->partnatts,
opstep->step.step_id, keyno);
partkey_datum_from_expr(context, expr, stateidx,
&datum, &isnull);
/*
* Since we only allow strict operators in pruning steps , any
* null - valued comparison value must cause the comparison to fail ,
* so that no partitions could match .
*/
if (isnull)
{
PruneStepResult *result;
result = (PruneStepResult *) palloc(sizeof (PruneStepResult));
result->bound_offsets = NULL;
result->scan_default = false ;
result->scan_null = false ;
return result;
}
/* Set up the stepcmpfuncs entry, unless we already did */
cmpfn = lfirst_oid(lc2);
Assert(OidIsValid(cmpfn));
if (cmpfn != context->stepcmpfuncs[stateidx].fn_oid)
{
/*
* If the needed support function is the same one cached in
* the relation ' s partition key , copy the cached FmgrInfo .
* Otherwise ( i . e . , when we have a cross - type comparison ) , an
* actual lookup is required .
*/
if (cmpfn == context->partsupfunc[keyno].fn_oid)
fmgr_info_copy(&context->stepcmpfuncs[stateidx],
&context->partsupfunc[keyno],
context->ppccontext);
else
fmgr_info_cxt(cmpfn, &context->stepcmpfuncs[stateidx],
context->ppccontext);
}
values[keyno] = datum;
nvalues++;
lc1 = lnext(opstep->exprs, lc1);
lc2 = lnext(opstep->cmpfns, lc2);
}
}
/*
* Point partsupfunc to the entry for the 0 th key of this step ; the
* additional support functions , if any , follow consecutively .
*/
stateidx = PruneCxtStateIdx(context->partnatts, opstep->step.step_id, 0 );
partsupfunc = &context->stepcmpfuncs[stateidx];
switch (context->strategy)
{
case PARTITION_STRATEGY_HASH:
return get_matching_hash_bounds(context,
opstep->opstrategy,
values, nvalues,
partsupfunc,
opstep->nullkeys);
case PARTITION_STRATEGY_LIST:
return get_matching_list_bounds(context,
opstep->opstrategy,
values[0 ], nvalues,
&partsupfunc[0 ],
opstep->nullkeys);
case PARTITION_STRATEGY_RANGE:
return get_matching_range_bounds(context,
opstep->opstrategy,
values, nvalues,
partsupfunc,
opstep->nullkeys);
default :
elog(ERROR, "unexpected partition strategy: %d" ,
(int ) context->strategy);
break ;
}
return NULL;
}
/*
* perform_pruning_combine_step
* Determines the indexes of datums obtained by combining those given
* by the steps identified by cstep - > source_stepids using the specified
* combination method
*
* Since cstep may refer to the result of earlier steps , we also receive
* step_results here .
*/
static PruneStepResult *
perform_pruning_combine_step(PartitionPruneContext *context,
PartitionPruneStepCombine *cstep,
PruneStepResult **step_results)
{
PruneStepResult *result = (PruneStepResult *) palloc0(sizeof (PruneStepResult));
bool firststep;
ListCell *lc1;
/*
* A combine step without any source steps is an indication to not perform
* any partition pruning . Return all datum indexes in that case .
*/
if (cstep->source_stepids == NIL)
{
PartitionBoundInfo boundinfo = context->boundinfo;
result->bound_offsets =
bms_add_range(NULL, 0 , boundinfo->nindexes - 1 );
result->scan_default = partition_bound_has_default(boundinfo);
result->scan_null = partition_bound_accepts_nulls(boundinfo);
return result;
}
switch (cstep->combineOp)
{
case PARTPRUNE_COMBINE_UNION:
foreach(lc1, cstep->source_stepids)
{
int step_id = lfirst_int(lc1);
PruneStepResult *step_result;
/*
* step_results [ step_id ] must contain a valid result , which is
* confirmed by the fact that cstep ' s step_id is greater than
* step_id and the fact that results of the individual steps
* are evaluated in sequence of their step_ids .
*/
if (step_id >= cstep->step.step_id)
elog(ERROR, "invalid pruning combine step argument" );
step_result = step_results[step_id];
Assert(step_result != NULL);
/* Record any additional datum indexes from this step */
result->bound_offsets = bms_add_members(result->bound_offsets,
step_result->bound_offsets);
/* Update whether to scan null and default partitions. */
if (!result->scan_null)
result->scan_null = step_result->scan_null;
if (!result->scan_default)
result->scan_default = step_result->scan_default;
}
break ;
case PARTPRUNE_COMBINE_INTERSECT:
firststep = true ;
foreach(lc1, cstep->source_stepids)
{
int step_id = lfirst_int(lc1);
PruneStepResult *step_result;
if (step_id >= cstep->step.step_id)
elog(ERROR, "invalid pruning combine step argument" );
step_result = step_results[step_id];
Assert(step_result != NULL);
if (firststep)
{
/* Copy step's result the first time. */
result->bound_offsets =
bms_copy(step_result->bound_offsets);
result->scan_null = step_result->scan_null;
result->scan_default = step_result->scan_default;
firststep = false ;
}
else
{
/* Record datum indexes common to both steps */
result->bound_offsets =
bms_int_members(result->bound_offsets,
step_result->bound_offsets);
/* Update whether to scan null and default partitions. */
if (result->scan_null)
result->scan_null = step_result->scan_null;
if (result->scan_default)
result->scan_default = step_result->scan_default;
}
}
break ;
}
return result;
}
/*
* match_boolean_partition_clause
*
* If we ' re able to match the clause to the partition key as specially - shaped
* boolean clause , set * outconst to a Const containing a true , false or NULL
* value , set * notclause according to if the clause was in the " not " form ,
* i . e . " IS NOT TRUE " , " IS NOT FALSE " or " IS NOT UNKNOWN " and return
* PARTCLAUSE_MATCH_CLAUSE for " IS [ NOT ] ( TRUE | FALSE ) " clauses and
* PARTCLAUSE_MATCH_NULLNESS for " IS [ NOT ] UNKNOWN " clauses . Otherwise ,
* return PARTCLAUSE_UNSUPPORTED if the clause cannot be used for partition
* pruning , and PARTCLAUSE_NOMATCH for supported clauses that do not match this
* ' partkey ' .
*/
static PartClauseMatchStatus
match_boolean_partition_clause(Oid partopfamily, Expr *clause, Expr *partkey,
Expr **outconst, bool *notclause)
{
Expr *leftop;
*outconst = NULL;
*notclause = false ;
/*
* Partitioning currently can only use built - in AMs , so checking for
* built - in boolean opfamilies is good enough .
*/
if (!IsBuiltinBooleanOpfamily(partopfamily))
return PARTCLAUSE_UNSUPPORTED;
if (IsA(clause, BooleanTest))
{
BooleanTest *btest = (BooleanTest *) clause;
leftop = btest->arg;
leftop = (Expr *) strip_noop_phvs((Node *) leftop);
while (IsA(leftop, RelabelType))
leftop = ((RelabelType *) leftop)->arg;
if (equal(leftop, partkey))
{
switch (btest->booltesttype)
{
case IS_NOT_TRUE:
*notclause = true ;
/* fall through */
case IS_TRUE:
*outconst = (Expr *) makeBoolConst(true , false );
return PARTCLAUSE_MATCH_CLAUSE;
case IS_NOT_FALSE:
*notclause = true ;
/* fall through */
case IS_FALSE:
*outconst = (Expr *) makeBoolConst(false , false );
return PARTCLAUSE_MATCH_CLAUSE;
case IS_NOT_UNKNOWN:
*notclause = true ;
/* fall through */
case IS_UNKNOWN:
return PARTCLAUSE_MATCH_NULLNESS;
default :
return PARTCLAUSE_UNSUPPORTED;
}
}
/* does not match partition key */
return PARTCLAUSE_NOMATCH;
}
else
{
bool is_not_clause = is_notclause(clause);
leftop = is_not_clause ? get_notclausearg(clause) : clause;
leftop = (Expr *) strip_noop_phvs((Node *) leftop);
while (IsA(leftop, RelabelType))
leftop = ((RelabelType *) leftop)->arg;
/* Compare to the partition key, and make up a clause ... */
if (equal(leftop, partkey))
*outconst = (Expr *) makeBoolConst(!is_not_clause, false );
else if (equal(negate_clause((Node *) leftop), partkey))
*outconst = (Expr *) makeBoolConst(is_not_clause, false );
else
return PARTCLAUSE_NOMATCH;
return PARTCLAUSE_MATCH_CLAUSE;
}
}
/*
* partkey_datum_from_expr
* Evaluate expression for potential partition pruning
*
* Evaluate ' expr ' ; set * value and * isnull to the resulting Datum and nullflag .
*
* If expr isn ' t a Const , its ExprState is in stateidx of the context
* exprstate array .
*
* Note that the evaluated result may be in the per - tuple memory context of
* context - > exprcontext , and we may have leaked other memory there too .
* This memory must be recovered by resetting that ExprContext after
* we ' re done with the pruning operation ( see execPartition . c ) .
*/
static void
partkey_datum_from_expr(PartitionPruneContext *context,
Expr *expr, int stateidx,
Datum *value, bool *isnull)
{
if (IsA(expr, Const ))
{
/* We can always determine the value of a constant */
Const *con = (Const *) expr;
*value = con->constvalue;
*isnull = con->constisnull;
}
else
{
ExprState *exprstate;
ExprContext *ectx;
/*
* We should never see a non - Const in a step unless the caller has
* passed a valid ExprContext .
*/
Assert(context->exprcontext != NULL);
exprstate = context->exprstates[stateidx];
ectx = context->exprcontext;
*value = ExecEvalExprSwitchContext(exprstate, ectx, isnull);
}
}
Messung V0.5 in Prozent C=94 H=84 G=88
¤ Dauer der Verarbeitung: 0.174 Sekunden
(vorverarbeitet am 2026-08-08)
¤
*© Formatika GbR, Deutschland
2026-08-25
Neuigkeiten
Aktuelles
Motto des Tages
Open Source Software
Jenseits des Üblichen ....
Besucherstatistik
Statistik
#Sources=277311
#Domains=752002