1
0
mirror of https://github.com/postgres/postgres.git synced 2025-07-07 00:36:50 +03:00

Make some small planner API cleanups.

Move a few very simple node-creation and node-type-testing functions
from the planner's clauses.c to nodes/makefuncs and nodes/nodeFuncs.
There's nothing planner-specific about them, as evidenced by the
number of other places that were using them.

While at it, rename and_clause() etc to is_andclause() etc, to clarify
that they are node-type-testing functions not node-creation functions.
And use "static inline" implementations for the shortest ones.

Also, modify flatten_join_alias_vars() and some subsidiary functions
to take a Query not a PlannerInfo to define the join structure that
Vars should be translated according to.  They were only using the
"parse" field of the PlannerInfo anyway, so this just requires removing
one level of indirection.  The advantage is that now parse_agg.c can
use flatten_join_alias_vars() without the horrid kluge of creating an
incomplete PlannerInfo, which will allow that file to be decoupled from
relation.h in a subsequent patch.

Discussion: https://postgr.es/m/11460.1548706639@sss.pgh.pa.us
This commit is contained in:
Tom Lane
2019-01-29 15:26:44 -05:00
parent e77cfa54d7
commit a1b8c41e99
30 changed files with 309 additions and 345 deletions

View File

@ -15,6 +15,7 @@
#include "postgres.h"
#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/clauses.h"
#include "optimizer/cost.h"
#include "optimizer/pathnode.h"
@ -688,7 +689,7 @@ clause_selectivity(PlannerInfo *root,
/* XXX any way to do better than default? */
}
}
else if (not_clause(clause))
else if (is_notclause(clause))
{
/* inverse of the selectivity of the underlying clause */
s1 = 1.0 - clause_selectivity(root,
@ -697,7 +698,7 @@ clause_selectivity(PlannerInfo *root,
jointype,
sjinfo);
}
else if (and_clause(clause))
else if (is_andclause(clause))
{
/* share code with clauselist_selectivity() */
s1 = clauselist_selectivity(root,
@ -706,7 +707,7 @@ clause_selectivity(PlannerInfo *root,
jointype,
sjinfo);
}
else if (or_clause(clause))
else if (is_orclause(clause))
{
/*
* Selectivities for an OR clause are computed as s1+s2 - s1*s2 to

View File

@ -79,6 +79,7 @@
#include "executor/executor.h"
#include "executor/nodeHash.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/clauses.h"
#include "optimizer/cost.h"

View File

@ -1297,7 +1297,7 @@ generate_bitmap_or_paths(PlannerInfo *root, RelOptInfo *rel,
List *indlist;
/* OR arguments should be ANDs or sub-RestrictInfos */
if (and_clause(orarg))
if (is_andclause(orarg))
{
List *andargs = ((BoolExpr *) orarg)->args;
@ -3368,7 +3368,7 @@ match_boolean_index_clause(Node *clause,
if (match_index_to_operand(clause, indexcol, index))
return true;
/* NOT clause? */
if (not_clause(clause))
if (is_notclause(clause))
{
if (match_index_to_operand((Node *) get_notclausearg((Expr *) clause),
indexcol, index))
@ -3680,7 +3680,7 @@ expand_boolean_index_clause(Node *clause,
InvalidOid, InvalidOid);
}
/* NOT clause? */
if (not_clause(clause))
if (is_notclause(clause))
{
Node *arg = (Node *) get_notclausearg((Expr *) clause);

View File

@ -15,6 +15,7 @@
#include "postgres.h"
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/appendinfo.h"
#include "optimizer/clauses.h"
#include "optimizer/joininfo.h"
@ -1554,8 +1555,7 @@ have_partkey_equi_join(RelOptInfo *joinrel,
if (!rinfo->mergeopfamilies && !OidIsValid(rinfo->hashjoinoperator))
continue;
opexpr = (OpExpr *) rinfo->clause;
Assert(is_opclause(opexpr));
opexpr = castNode(OpExpr, rinfo->clause);
/*
* The equi-join between partition keys is strict if equi-join between

View File

@ -250,7 +250,7 @@ TidQualFromRestrictInfoList(List *rlist, RelOptInfo *rel)
List *sublist;
/* OR arguments should be ANDs or sub-RestrictInfos */
if (and_clause(orarg))
if (is_andclause(orarg))
{
List *andargs = ((BoolExpr *) orarg)->args;

View File

@ -16,6 +16,7 @@
#include "catalog/pg_type.h"
#include "catalog/pg_class.h"
#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/clauses.h"
#include "optimizer/cost.h"

View File

@ -849,7 +849,8 @@ subquery_planner(PlannerGlobal *glob, Query *parse,
*/
if (rte->lateral && root->hasJoinRTEs)
rte->subquery = (Query *)
flatten_join_alias_vars(root, (Node *) rte->subquery);
flatten_join_alias_vars(root->parse,
(Node *) rte->subquery);
}
else if (rte->rtekind == RTE_FUNCTION)
{
@ -1054,7 +1055,7 @@ preprocess_expression(PlannerInfo *root, Node *expr, int kind)
kind == EXPRKIND_VALUES ||
kind == EXPRKIND_TABLESAMPLE ||
kind == EXPRKIND_TABLEFUNC))
expr = flatten_join_alias_vars(root, expr);
expr = flatten_join_alias_vars(root->parse, expr);
/*
* Simplify constant expressions.

View File

@ -739,7 +739,7 @@ testexpr_is_hashable(Node *testexpr)
if (hash_ok_operator((OpExpr *) testexpr))
return true;
}
else if (and_clause(testexpr))
else if (is_andclause(testexpr))
{
ListCell *l;
@ -1693,7 +1693,7 @@ process_sublinks_mutator(Node *node, process_sublinks_context *context)
* propagates down in both cases. (Note that this is unlike the meaning
* of "top level qual" used in most other places in Postgres.)
*/
if (and_clause(node))
if (is_andclause(node))
{
List *newargs = NIL;
ListCell *l;
@ -1706,7 +1706,7 @@ process_sublinks_mutator(Node *node, process_sublinks_context *context)
Node *newarg;
newarg = process_sublinks_mutator(lfirst(l), &locContext);
if (and_clause(newarg))
if (is_andclause(newarg))
newargs = list_concat(newargs, ((BoolExpr *) newarg)->args);
else
newargs = lappend(newargs, newarg);
@ -1714,7 +1714,7 @@ process_sublinks_mutator(Node *node, process_sublinks_context *context)
return (Node *) make_andclause(newargs);
}
if (or_clause(node))
if (is_orclause(node))
{
List *newargs = NIL;
ListCell *l;
@ -1727,7 +1727,7 @@ process_sublinks_mutator(Node *node, process_sublinks_context *context)
Node *newarg;
newarg = process_sublinks_mutator(lfirst(l), &locContext);
if (or_clause(newarg))
if (is_orclause(newarg))
newargs = list_concat(newargs, ((BoolExpr *) newarg)->args);
else
newargs = lappend(newargs, newarg);

View File

@ -497,7 +497,7 @@ pull_up_sublinks_qual_recurse(PlannerInfo *root, Node *node,
/* Else return it unmodified */
return node;
}
if (not_clause(node))
if (is_notclause(node))
{
/* If the immediate argument of NOT is EXISTS, try to convert */
SubLink *sublink = (SubLink *) get_notclausearg((Expr *) node);
@ -564,7 +564,7 @@ pull_up_sublinks_qual_recurse(PlannerInfo *root, Node *node,
/* Else return it unmodified */
return node;
}
if (and_clause(node))
if (is_andclause(node))
{
/* Recurse into AND clause */
List *newclauses = NIL;
@ -968,7 +968,7 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte,
* maybe even in the rewriter; but for now let's just fix this case here.)
*/
subquery->targetList = (List *)
flatten_join_alias_vars(subroot, (Node *) subquery->targetList);
flatten_join_alias_vars(subroot->parse, (Node *) subquery->targetList);
/*
* Adjust level-0 varnos in subquery so that we can append its rangetable
@ -3317,11 +3317,11 @@ get_relids_in_jointree(Node *jtnode, bool include_joins)
* get_relids_for_join: get set of base RT indexes making up a join
*/
Relids
get_relids_for_join(PlannerInfo *root, int joinrelid)
get_relids_for_join(Query *query, int joinrelid)
{
Node *jtnode;
jtnode = find_jointree_node_for_rel((Node *) root->parse->jointree,
jtnode = find_jointree_node_for_rel((Node *) query->jointree,
joinrelid);
if (!jtnode)
elog(ERROR, "could not find join node %d", joinrelid);

View File

@ -32,6 +32,7 @@
#include "postgres.h"
#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/clauses.h"
#include "optimizer/prep.h"
#include "utils/lsyscache.h"
@ -333,7 +334,7 @@ pull_ands(List *andlist)
* built a new arglist not shared with any other expr. Otherwise we'd
* need a list_copy here.
*/
if (and_clause(subexpr))
if (is_andclause(subexpr))
out_list = list_concat(out_list,
pull_ands(((BoolExpr *) subexpr)->args));
else
@ -365,7 +366,7 @@ pull_ors(List *orlist)
* built a new arglist not shared with any other expr. Otherwise we'd
* need a list_copy here.
*/
if (or_clause(subexpr))
if (is_orclause(subexpr))
out_list = list_concat(out_list,
pull_ors(((BoolExpr *) subexpr)->args));
else
@ -415,7 +416,7 @@ pull_ors(List *orlist)
static Expr *
find_duplicate_ors(Expr *qual, bool is_check)
{
if (or_clause((Node *) qual))
if (is_orclause(qual))
{
List *orlist = NIL;
ListCell *temp;
@ -459,7 +460,7 @@ find_duplicate_ors(Expr *qual, bool is_check)
/* Now we can look for duplicate ORs */
return process_duplicate_ors(orlist);
}
else if (and_clause((Node *) qual))
else if (is_andclause(qual))
{
List *andlist = NIL;
ListCell *temp;
@ -550,7 +551,7 @@ process_duplicate_ors(List *orlist)
{
Expr *clause = (Expr *) lfirst(temp);
if (and_clause((Node *) clause))
if (is_andclause(clause))
{
List *subclauses = ((BoolExpr *) clause)->args;
int nclauses = list_length(subclauses);
@ -588,7 +589,7 @@ process_duplicate_ors(List *orlist)
{
Expr *clause = (Expr *) lfirst(temp2);
if (and_clause((Node *) clause))
if (is_andclause(clause))
{
if (!list_member(((BoolExpr *) clause)->args, refclause))
{
@ -631,7 +632,7 @@ process_duplicate_ors(List *orlist)
{
Expr *clause = (Expr *) lfirst(temp);
if (and_clause((Node *) clause))
if (is_andclause(clause))
{
List *subclauses = ((BoolExpr *) clause)->args;

View File

@ -157,244 +157,6 @@ static Node *substitute_actual_srf_parameters_mutator(Node *node,
static bool tlist_matches_coltypelist(List *tlist, List *coltypelist);
/*****************************************************************************
* OPERATOR clause functions
*****************************************************************************/
/*
* make_opclause
* Creates an operator clause given its operator info, left operand
* and right operand (pass NULL to create single-operand clause),
* and collation info.
*/
Expr *
make_opclause(Oid opno, Oid opresulttype, bool opretset,
Expr *leftop, Expr *rightop,
Oid opcollid, Oid inputcollid)
{
OpExpr *expr = makeNode(OpExpr);
expr->opno = opno;
expr->opfuncid = InvalidOid;
expr->opresulttype = opresulttype;
expr->opretset = opretset;
expr->opcollid = opcollid;
expr->inputcollid = inputcollid;
if (rightop)
expr->args = list_make2(leftop, rightop);
else
expr->args = list_make1(leftop);
expr->location = -1;
return (Expr *) expr;
}
/*
* get_leftop
*
* Returns the left operand of a clause of the form (op expr expr)
* or (op expr)
*/
Node *
get_leftop(const Expr *clause)
{
const OpExpr *expr = (const OpExpr *) clause;
if (expr->args != NIL)
return linitial(expr->args);
else
return NULL;
}
/*
* get_rightop
*
* Returns the right operand in a clause of the form (op expr expr).
* NB: result will be NULL if applied to a unary op clause.
*/
Node *
get_rightop(const Expr *clause)
{
const OpExpr *expr = (const OpExpr *) clause;
if (list_length(expr->args) >= 2)
return lsecond(expr->args);
else
return NULL;
}
/*****************************************************************************
* NOT clause functions
*****************************************************************************/
/*
* not_clause
*
* Returns t iff this is a 'not' clause: (NOT expr).
*/
bool
not_clause(Node *clause)
{
return (clause != NULL &&
IsA(clause, BoolExpr) &&
((BoolExpr *) clause)->boolop == NOT_EXPR);
}
/*
* make_notclause
*
* Create a 'not' clause given the expression to be negated.
*/
Expr *
make_notclause(Expr *notclause)
{
BoolExpr *expr = makeNode(BoolExpr);
expr->boolop = NOT_EXPR;
expr->args = list_make1(notclause);
expr->location = -1;
return (Expr *) expr;
}
/*
* get_notclausearg
*
* Retrieve the clause within a 'not' clause
*/
Expr *
get_notclausearg(Expr *notclause)
{
return linitial(((BoolExpr *) notclause)->args);
}
/*****************************************************************************
* OR clause functions
*****************************************************************************/
/*
* or_clause
*
* Returns t iff the clause is an 'or' clause: (OR { expr }).
*/
bool
or_clause(Node *clause)
{
return (clause != NULL &&
IsA(clause, BoolExpr) &&
((BoolExpr *) clause)->boolop == OR_EXPR);
}
/*
* make_orclause
*
* Creates an 'or' clause given a list of its subclauses.
*/
Expr *
make_orclause(List *orclauses)
{
BoolExpr *expr = makeNode(BoolExpr);
expr->boolop = OR_EXPR;
expr->args = orclauses;
expr->location = -1;
return (Expr *) expr;
}
/*****************************************************************************
* AND clause functions
*****************************************************************************/
/*
* and_clause
*
* Returns t iff its argument is an 'and' clause: (AND { expr }).
*/
bool
and_clause(Node *clause)
{
return (clause != NULL &&
IsA(clause, BoolExpr) &&
((BoolExpr *) clause)->boolop == AND_EXPR);
}
/*
* make_andclause
*
* Creates an 'and' clause given a list of its subclauses.
*/
Expr *
make_andclause(List *andclauses)
{
BoolExpr *expr = makeNode(BoolExpr);
expr->boolop = AND_EXPR;
expr->args = andclauses;
expr->location = -1;
return (Expr *) expr;
}
/*
* make_and_qual
*
* Variant of make_andclause for ANDing two qual conditions together.
* Qual conditions have the property that a NULL nodetree is interpreted
* as 'true'.
*
* NB: this makes no attempt to preserve AND/OR flatness; so it should not
* be used on a qual that has already been run through prepqual.c.
*/
Node *
make_and_qual(Node *qual1, Node *qual2)
{
if (qual1 == NULL)
return qual2;
if (qual2 == NULL)
return qual1;
return (Node *) make_andclause(list_make2(qual1, qual2));
}
/*
* The planner frequently prefers to represent qualification expressions
* as lists of boolean expressions with implicit AND semantics.
*
* These functions convert between an AND-semantics expression list and the
* ordinary representation of a boolean expression.
*
* Note that an empty list is considered equivalent to TRUE.
*/
Expr *
make_ands_explicit(List *andclauses)
{
if (andclauses == NIL)
return (Expr *) makeBoolConst(true, false);
else if (list_length(andclauses) == 1)
return (Expr *) linitial(andclauses);
else
return make_andclause(andclauses);
}
List *
make_ands_implicit(Expr *clause)
{
/*
* NB: because the parser sets the qual field to NULL in a query that has
* no WHERE clause, we must consider a NULL input clause as TRUE, even
* though one might more reasonably think it FALSE. Grumble. If this
* causes trouble, consider changing the parser's behavior.
*/
if (clause == NULL)
return NIL; /* NULL -> NIL list == TRUE */
else if (and_clause((Node *) clause))
return ((BoolExpr *) clause)->args;
else if (IsA(clause, Const) &&
!((Const *) clause)->constisnull &&
DatumGetBool(((Const *) clause)->constvalue))
return NIL; /* constant TRUE input -> NIL list */
else
return list_make1(clause);
}
/*****************************************************************************
* Aggregate-function clause manipulation
*****************************************************************************/
@ -3979,7 +3741,7 @@ simplify_or_arguments(List *args,
unprocessed_args = list_delete_first(unprocessed_args);
/* flatten nested ORs as per above comment */
if (or_clause(arg))
if (is_orclause(arg))
{
List *subargs = list_copy(((BoolExpr *) arg)->args);
@ -4005,7 +3767,7 @@ simplify_or_arguments(List *args,
* since it's not a mainstream case. In particular we don't worry
* about const-simplifying the input twice.
*/
if (or_clause(arg))
if (is_orclause(arg))
{
List *subargs = list_copy(((BoolExpr *) arg)->args);
@ -4081,7 +3843,7 @@ simplify_and_arguments(List *args,
unprocessed_args = list_delete_first(unprocessed_args);
/* flatten nested ANDs as per above comment */
if (and_clause(arg))
if (is_andclause(arg))
{
List *subargs = list_copy(((BoolExpr *) arg)->args);
@ -4107,7 +3869,7 @@ simplify_and_arguments(List *args,
* since it's not a mainstream case. In particular we don't worry
* about const-simplifying the input twice.
*/
if (and_clause(arg))
if (is_andclause(arg))
{
List *subargs = list_copy(((BoolExpr *) arg)->args);

View File

@ -15,6 +15,8 @@
#include "postgres.h"
#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/clauses.h"
#include "optimizer/cost.h"
#include "optimizer/orclauses.h"
@ -173,7 +175,7 @@ extract_or_clause(RestrictInfo *or_rinfo, RelOptInfo *rel)
* selectivity and other cached data is computed exactly the same way for
* a restriction clause as for a join clause, which seems undesirable.
*/
Assert(or_clause((Node *) or_rinfo->orclause));
Assert(is_orclause(or_rinfo->orclause));
foreach(lc, ((BoolExpr *) or_rinfo->orclause)->args)
{
Node *orarg = (Node *) lfirst(lc);
@ -181,7 +183,7 @@ extract_or_clause(RestrictInfo *or_rinfo, RelOptInfo *rel)
Node *subclause;
/* OR arguments should be ANDs or sub-RestrictInfos */
if (and_clause(orarg))
if (is_andclause(orarg))
{
List *andargs = ((BoolExpr *) orarg)->args;
ListCell *lc2;
@ -231,7 +233,7 @@ extract_or_clause(RestrictInfo *or_rinfo, RelOptInfo *rel)
* to preserve AND/OR flatness (ie, no OR directly underneath OR).
*/
subclause = (Node *) make_ands_explicit(subclauses);
if (or_clause(subclause))
if (is_orclause(subclause))
clauselist = list_concat(clauselist,
list_copy(((BoolExpr *) subclause)->args));
else

View File

@ -19,6 +19,7 @@
#include "catalog/pg_type.h"
#include "executor/executor.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/clauses.h"
#include "optimizer/predtest.h"
@ -839,14 +840,14 @@ predicate_classify(Node *clause, PredIterInfo info)
}
/* Handle normal AND and OR boolean clauses */
if (and_clause(clause))
if (is_andclause(clause))
{
info->startup_fn = boolexpr_startup_fn;
info->next_fn = list_next_fn;
info->cleanup_fn = list_cleanup_fn;
return CLASS_AND;
}
if (or_clause(clause))
if (is_orclause(clause))
{
info->startup_fn = boolexpr_startup_fn;
info->next_fn = list_next_fn;

View File

@ -14,6 +14,8 @@
*/
#include "postgres.h"
#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/clauses.h"
#include "optimizer/restrictinfo.h"
#include "optimizer/var.h"
@ -67,7 +69,7 @@ make_restrictinfo(Expr *clause,
* If it's an OR clause, build a modified copy with RestrictInfos inserted
* above each subclause of the top-level AND/OR structure.
*/
if (or_clause((Node *) clause))
if (is_orclause(clause))
return (RestrictInfo *) make_sub_restrictinfos(clause,
is_pushed_down,
outerjoin_delayed,
@ -78,7 +80,7 @@ make_restrictinfo(Expr *clause,
nullable_relids);
/* Shouldn't be an AND clause, else AND/OR flattening messed up */
Assert(!and_clause((Node *) clause));
Assert(!is_andclause(clause));
return make_restrictinfo_internal(clause,
NULL,
@ -232,7 +234,7 @@ make_sub_restrictinfos(Expr *clause,
Relids outer_relids,
Relids nullable_relids)
{
if (or_clause((Node *) clause))
if (is_orclause(clause))
{
List *orlist = NIL;
ListCell *temp;
@ -257,7 +259,7 @@ make_sub_restrictinfos(Expr *clause,
outer_relids,
nullable_relids);
}
else if (and_clause((Node *) clause))
else if (is_andclause(clause))
{
List *andlist = NIL;
ListCell *temp;

View File

@ -60,7 +60,7 @@ typedef struct
typedef struct
{
PlannerInfo *root;
Query *query; /* outer Query */
int sublevels_up;
bool possible_sublink; /* could aliases include a SubLink? */
bool inserted_sublink; /* have we inserted a SubLink? */
@ -78,7 +78,7 @@ static bool pull_var_clause_walker(Node *node,
pull_var_clause_context *context);
static Node *flatten_join_alias_vars_mutator(Node *node,
flatten_join_alias_vars_context *context);
static Relids alias_relid_set(PlannerInfo *root, Relids relids);
static Relids alias_relid_set(Query *query, Relids relids);
/*
@ -667,16 +667,16 @@ pull_var_clause_walker(Node *node, pull_var_clause_context *context)
* subqueries).
*/
Node *
flatten_join_alias_vars(PlannerInfo *root, Node *node)
flatten_join_alias_vars(Query *query, Node *node)
{
flatten_join_alias_vars_context context;
context.root = root;
context.query = query;
context.sublevels_up = 0;
/* flag whether join aliases could possibly contain SubLinks */
context.possible_sublink = root->parse->hasSubLinks;
context.possible_sublink = query->hasSubLinks;
/* if hasSubLinks is already true, no need to work hard */
context.inserted_sublink = root->parse->hasSubLinks;
context.inserted_sublink = query->hasSubLinks;
return flatten_join_alias_vars_mutator(node, &context);
}
@ -696,7 +696,7 @@ flatten_join_alias_vars_mutator(Node *node,
/* No change unless Var belongs to a JOIN of the target level */
if (var->varlevelsup != context->sublevels_up)
return node; /* no need to copy, really */
rte = rt_fetch(var->varno, context->root->parse->rtable);
rte = rt_fetch(var->varno, context->query->rtable);
if (rte->rtekind != RTE_JOIN)
return node;
if (var->varattno == InvalidAttrNumber)
@ -783,7 +783,7 @@ flatten_join_alias_vars_mutator(Node *node,
/* now fix PlaceHolderVar's relid sets */
if (phv->phlevelsup == context->sublevels_up)
{
phv->phrels = alias_relid_set(context->root,
phv->phrels = alias_relid_set(context->query,
phv->phrels);
}
return (Node *) phv;
@ -823,7 +823,7 @@ flatten_join_alias_vars_mutator(Node *node,
* underlying base relids
*/
static Relids
alias_relid_set(PlannerInfo *root, Relids relids)
alias_relid_set(Query *query, Relids relids)
{
Relids result = NULL;
int rtindex;
@ -831,10 +831,10 @@ alias_relid_set(PlannerInfo *root, Relids relids)
rtindex = -1;
while ((rtindex = bms_next_member(relids, rtindex)) >= 0)
{
RangeTblEntry *rte = rt_fetch(rtindex, root->parse->rtable);
RangeTblEntry *rte = rt_fetch(rtindex, query->rtable);
if (rte->rtekind == RTE_JOIN)
result = bms_join(result, get_relids_for_join(root, rtindex));
result = bms_join(result, get_relids_for_join(query, rtindex));
else
result = bms_add_member(result, rtindex);
}