|
|
|
@ -9,7 +9,7 @@ |
|
|
|
|
* |
|
|
|
|
* |
|
|
|
|
* IDENTIFICATION |
|
|
|
|
* $PostgreSQL: pgsql/src/backend/optimizer/path/indxpath.c,v 1.196 2005/12/06 16:50:36 tgl Exp $ |
|
|
|
|
* $PostgreSQL: pgsql/src/backend/optimizer/path/indxpath.c,v 1.197 2006/01/25 20:29:23 tgl Exp $ |
|
|
|
|
* |
|
|
|
|
*------------------------------------------------------------------------- |
|
|
|
|
*/ |
|
|
|
@ -61,6 +61,11 @@ static bool match_clause_to_indexcol(IndexOptInfo *index, |
|
|
|
|
SaOpControl saop_control); |
|
|
|
|
static bool is_indexable_operator(Oid expr_op, Oid opclass, |
|
|
|
|
bool indexkey_on_left); |
|
|
|
|
static bool match_rowcompare_to_indexcol(IndexOptInfo *index, |
|
|
|
|
int indexcol, |
|
|
|
|
Oid opclass, |
|
|
|
|
RowCompareExpr *clause, |
|
|
|
|
Relids outer_relids); |
|
|
|
|
static Relids indexable_outerrelids(RelOptInfo *rel); |
|
|
|
|
static bool matches_any_index(RestrictInfo *rinfo, RelOptInfo *rel, |
|
|
|
|
Relids outer_relids); |
|
|
|
@ -82,7 +87,10 @@ static bool match_special_index_operator(Expr *clause, Oid opclass, |
|
|
|
|
bool indexkey_on_left); |
|
|
|
|
static Expr *expand_boolean_index_clause(Node *clause, int indexcol, |
|
|
|
|
IndexOptInfo *index); |
|
|
|
|
static List *expand_indexqual_condition(RestrictInfo *rinfo, Oid opclass); |
|
|
|
|
static List *expand_indexqual_opclause(RestrictInfo *rinfo, Oid opclass); |
|
|
|
|
static RestrictInfo *expand_indexqual_rowcompare(RestrictInfo *rinfo, |
|
|
|
|
IndexOptInfo *index, |
|
|
|
|
int indexcol); |
|
|
|
|
static List *prefix_quals(Node *leftop, Oid opclass, |
|
|
|
|
Const *prefix, Pattern_Prefix_Status pstatus); |
|
|
|
|
static List *network_prefix_quals(Node *leftop, Oid expr_op, Oid opclass, |
|
|
|
@ -900,6 +908,14 @@ group_clauses_by_indexkey(IndexOptInfo *index, |
|
|
|
|
* We do not actually do the commuting here, but we check whether a |
|
|
|
|
* suitable commutator operator is available. |
|
|
|
|
* |
|
|
|
|
* It is also possible to match RowCompareExpr clauses to indexes (but |
|
|
|
|
* currently, only btree indexes handle this). In this routine we will |
|
|
|
|
* report a match if the first column of the row comparison matches the |
|
|
|
|
* target index column. This is sufficient to guarantee that some index |
|
|
|
|
* condition can be constructed from the RowCompareExpr --- whether the |
|
|
|
|
* remaining columns match the index too is considered in |
|
|
|
|
* expand_indexqual_rowcompare(). |
|
|
|
|
* |
|
|
|
|
* It is also possible to match ScalarArrayOpExpr clauses to indexes, when |
|
|
|
|
* the clause is of the form "indexkey op ANY (arrayconst)". Since the |
|
|
|
|
* executor can only handle these in the context of bitmap index scans, |
|
|
|
@ -944,7 +960,8 @@ match_clause_to_indexcol(IndexOptInfo *index, |
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Clause must be a binary opclause, or possibly a ScalarArrayOpExpr |
|
|
|
|
* (which is always binary, by definition). |
|
|
|
|
* (which is always binary, by definition). Or it could be a |
|
|
|
|
* RowCompareExpr, which we pass off to match_rowcompare_to_indexcol(). |
|
|
|
|
*/ |
|
|
|
|
if (is_opclause(clause)) |
|
|
|
|
{ |
|
|
|
@ -972,6 +989,12 @@ match_clause_to_indexcol(IndexOptInfo *index, |
|
|
|
|
expr_op = saop->opno; |
|
|
|
|
plain_op = false; |
|
|
|
|
} |
|
|
|
|
else if (clause && IsA(clause, RowCompareExpr)) |
|
|
|
|
{ |
|
|
|
|
return match_rowcompare_to_indexcol(index, indexcol, opclass, |
|
|
|
|
(RowCompareExpr *) clause, |
|
|
|
|
outer_relids); |
|
|
|
|
} |
|
|
|
|
else |
|
|
|
|
return false; |
|
|
|
|
|
|
|
|
@ -1039,6 +1062,74 @@ is_indexable_operator(Oid expr_op, Oid opclass, bool indexkey_on_left) |
|
|
|
|
return op_in_opclass(expr_op, opclass); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* match_rowcompare_to_indexcol() |
|
|
|
|
* Handles the RowCompareExpr case for match_clause_to_indexcol(), |
|
|
|
|
* which see for comments. |
|
|
|
|
*/ |
|
|
|
|
static bool |
|
|
|
|
match_rowcompare_to_indexcol(IndexOptInfo *index, |
|
|
|
|
int indexcol, |
|
|
|
|
Oid opclass, |
|
|
|
|
RowCompareExpr *clause, |
|
|
|
|
Relids outer_relids) |
|
|
|
|
{ |
|
|
|
|
Node *leftop, |
|
|
|
|
*rightop; |
|
|
|
|
Oid expr_op; |
|
|
|
|
|
|
|
|
|
/* Forget it if we're not dealing with a btree index */ |
|
|
|
|
if (index->relam != BTREE_AM_OID) |
|
|
|
|
return false; |
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* We could do the matching on the basis of insisting that the opclass |
|
|
|
|
* shown in the RowCompareExpr be the same as the index column's opclass, |
|
|
|
|
* but that does not work well for cross-type comparisons (the opclass |
|
|
|
|
* could be for the other datatype). Also it would fail to handle indexes |
|
|
|
|
* using reverse-sort opclasses. Instead, match if the operator listed in |
|
|
|
|
* the RowCompareExpr is the < <= > or >= member of the index opclass |
|
|
|
|
* (after commutation, if the indexkey is on the right). |
|
|
|
|
*/ |
|
|
|
|
leftop = (Node *) linitial(clause->largs); |
|
|
|
|
rightop = (Node *) linitial(clause->rargs); |
|
|
|
|
expr_op = linitial_oid(clause->opnos); |
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* These syntactic tests are the same as in match_clause_to_indexcol() |
|
|
|
|
*/ |
|
|
|
|
if (match_index_to_operand(leftop, indexcol, index) && |
|
|
|
|
bms_is_subset(pull_varnos(rightop), outer_relids) && |
|
|
|
|
!contain_volatile_functions(rightop)) |
|
|
|
|
{ |
|
|
|
|
/* OK, indexkey is on left */ |
|
|
|
|
} |
|
|
|
|
else if (match_index_to_operand(rightop, indexcol, index) && |
|
|
|
|
bms_is_subset(pull_varnos(leftop), outer_relids) && |
|
|
|
|
!contain_volatile_functions(leftop)) |
|
|
|
|
{ |
|
|
|
|
/* indexkey is on right, so commute the operator */ |
|
|
|
|
expr_op = get_commutator(expr_op); |
|
|
|
|
if (expr_op == InvalidOid) |
|
|
|
|
return false; |
|
|
|
|
} |
|
|
|
|
else |
|
|
|
|
return false; |
|
|
|
|
|
|
|
|
|
/* We're good if the operator is the right type of opclass member */ |
|
|
|
|
switch (get_op_opclass_strategy(expr_op, opclass)) |
|
|
|
|
{ |
|
|
|
|
case BTLessStrategyNumber: |
|
|
|
|
case BTLessEqualStrategyNumber: |
|
|
|
|
case BTGreaterEqualStrategyNumber: |
|
|
|
|
case BTGreaterStrategyNumber: |
|
|
|
|
return true; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return false; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/****************************************************************************
|
|
|
|
|
* ---- ROUTINES TO DO PARTIAL INDEX PREDICATE TESTS ---- |
|
|
|
|
****************************************************************************/ |
|
|
|
@ -2014,7 +2105,8 @@ match_special_index_operator(Expr *clause, Oid opclass, |
|
|
|
|
* of index qual clauses. Standard qual clauses (those in the index's |
|
|
|
|
* opclass) are passed through unchanged. Boolean clauses and "special" |
|
|
|
|
* index operators are expanded into clauses that the indexscan machinery |
|
|
|
|
* will know what to do with. |
|
|
|
|
* will know what to do with. RowCompare clauses are simplified if |
|
|
|
|
* necessary to create a clause that is fully checkable by the index. |
|
|
|
|
* |
|
|
|
|
* The input list is ordered by index key, and so the output list is too. |
|
|
|
|
* (The latter is not depended on by any part of the core planner, I believe, |
|
|
|
@ -2041,13 +2133,14 @@ expand_indexqual_conditions(IndexOptInfo *index, List *clausegroups) |
|
|
|
|
foreach(l, (List *) lfirst(clausegroup_item)) |
|
|
|
|
{ |
|
|
|
|
RestrictInfo *rinfo = (RestrictInfo *) lfirst(l); |
|
|
|
|
Expr *clause = rinfo->clause; |
|
|
|
|
|
|
|
|
|
/* First check for boolean cases */ |
|
|
|
|
if (IsBooleanOpclass(curClass)) |
|
|
|
|
{ |
|
|
|
|
Expr *boolqual; |
|
|
|
|
|
|
|
|
|
boolqual = expand_boolean_index_clause((Node *) rinfo->clause, |
|
|
|
|
boolqual = expand_boolean_index_clause((Node *) clause, |
|
|
|
|
indexcol, |
|
|
|
|
index); |
|
|
|
|
if (boolqual) |
|
|
|
@ -2061,16 +2154,31 @@ expand_indexqual_conditions(IndexOptInfo *index, List *clausegroups) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/* Next check for ScalarArrayOp cases */ |
|
|
|
|
if (IsA(rinfo->clause, ScalarArrayOpExpr)) |
|
|
|
|
/*
|
|
|
|
|
* Else it must be an opclause (usual case), ScalarArrayOp, or |
|
|
|
|
* RowCompare |
|
|
|
|
*/ |
|
|
|
|
if (is_opclause(clause)) |
|
|
|
|
{ |
|
|
|
|
resultquals = list_concat(resultquals, |
|
|
|
|
expand_indexqual_opclause(rinfo, |
|
|
|
|
curClass)); |
|
|
|
|
} |
|
|
|
|
else if (IsA(clause, ScalarArrayOpExpr)) |
|
|
|
|
{ |
|
|
|
|
/* no extra work at this time */ |
|
|
|
|
resultquals = lappend(resultquals, rinfo); |
|
|
|
|
continue; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
resultquals = list_concat(resultquals, |
|
|
|
|
expand_indexqual_condition(rinfo, |
|
|
|
|
curClass)); |
|
|
|
|
else if (IsA(clause, RowCompareExpr)) |
|
|
|
|
{ |
|
|
|
|
resultquals = lappend(resultquals, |
|
|
|
|
expand_indexqual_rowcompare(rinfo, |
|
|
|
|
index, |
|
|
|
|
indexcol)); |
|
|
|
|
} |
|
|
|
|
else |
|
|
|
|
elog(ERROR, "unsupported indexqual type: %d", |
|
|
|
|
(int) nodeTag(clause)); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
clausegroup_item = lnext(clausegroup_item); |
|
|
|
@ -2145,16 +2253,15 @@ expand_boolean_index_clause(Node *clause, |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* expand_indexqual_condition --- expand a single indexqual condition |
|
|
|
|
* (other than a boolean-qual or ScalarArrayOp case) |
|
|
|
|
* expand_indexqual_opclause --- expand a single indexqual condition |
|
|
|
|
* that is an operator clause |
|
|
|
|
* |
|
|
|
|
* The input is a single RestrictInfo, the output a list of RestrictInfos |
|
|
|
|
*/ |
|
|
|
|
static List * |
|
|
|
|
expand_indexqual_condition(RestrictInfo *rinfo, Oid opclass) |
|
|
|
|
expand_indexqual_opclause(RestrictInfo *rinfo, Oid opclass) |
|
|
|
|
{ |
|
|
|
|
Expr *clause = rinfo->clause; |
|
|
|
|
|
|
|
|
|
/* we know these will succeed */ |
|
|
|
|
Node *leftop = get_leftop(clause); |
|
|
|
|
Node *rightop = get_rightop(clause); |
|
|
|
@ -2224,6 +2331,204 @@ expand_indexqual_condition(RestrictInfo *rinfo, Oid opclass) |
|
|
|
|
return result; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* expand_indexqual_rowcompare --- expand a single indexqual condition |
|
|
|
|
* that is a RowCompareExpr |
|
|
|
|
* |
|
|
|
|
* It's already known that the first column of the row comparison matches |
|
|
|
|
* the specified column of the index. We can use additional columns of the |
|
|
|
|
* row comparison as index qualifications, so long as they match the index |
|
|
|
|
* in the "same direction", ie, the indexkeys are all on the same side of the |
|
|
|
|
* clause and the operators are all the same-type members of the opclasses. |
|
|
|
|
* If all the columns of the RowCompareExpr match in this way, we just use it |
|
|
|
|
* as-is. Otherwise, we build a shortened RowCompareExpr (if more than one |
|
|
|
|
* column matches) or a simple OpExpr (if the first-column match is all |
|
|
|
|
* there is). In these cases the modified clause is always "<=" or ">=" |
|
|
|
|
* even when the original was "<" or ">" --- this is necessary to match all |
|
|
|
|
* the rows that could match the original. (We are essentially building a |
|
|
|
|
* lossy version of the row comparison when we do this.) |
|
|
|
|
*/ |
|
|
|
|
static RestrictInfo * |
|
|
|
|
expand_indexqual_rowcompare(RestrictInfo *rinfo, |
|
|
|
|
IndexOptInfo *index, |
|
|
|
|
int indexcol) |
|
|
|
|
{ |
|
|
|
|
RowCompareExpr *clause = (RowCompareExpr *) rinfo->clause; |
|
|
|
|
bool var_on_left; |
|
|
|
|
int op_strategy; |
|
|
|
|
Oid op_subtype; |
|
|
|
|
bool op_recheck; |
|
|
|
|
int matching_cols; |
|
|
|
|
Oid expr_op; |
|
|
|
|
List *opclasses; |
|
|
|
|
List *subtypes; |
|
|
|
|
List *new_ops; |
|
|
|
|
ListCell *largs_cell; |
|
|
|
|
ListCell *rargs_cell; |
|
|
|
|
ListCell *opnos_cell; |
|
|
|
|
|
|
|
|
|
/* We have to figure out (again) how the first col matches */ |
|
|
|
|
var_on_left = match_index_to_operand((Node *) linitial(clause->largs), |
|
|
|
|
indexcol, index); |
|
|
|
|
Assert(var_on_left || |
|
|
|
|
match_index_to_operand((Node *) linitial(clause->rargs), |
|
|
|
|
indexcol, index)); |
|
|
|
|
expr_op = linitial_oid(clause->opnos); |
|
|
|
|
if (!var_on_left) |
|
|
|
|
expr_op = get_commutator(expr_op); |
|
|
|
|
get_op_opclass_properties(expr_op, index->classlist[indexcol], |
|
|
|
|
&op_strategy, &op_subtype, &op_recheck); |
|
|
|
|
/* Build lists of the opclasses and operator subtypes in case needed */ |
|
|
|
|
opclasses = list_make1_oid(index->classlist[indexcol]); |
|
|
|
|
subtypes = list_make1_oid(op_subtype); |
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* See how many of the remaining columns match some index column |
|
|
|
|
* in the same way. A note about rel membership tests: we assume |
|
|
|
|
* that the clause as a whole is already known to use only Vars from |
|
|
|
|
* the indexed relation and possibly some acceptable outer relations. |
|
|
|
|
* So the "other" side of any potential index condition is OK as long |
|
|
|
|
* as it doesn't use Vars from the indexed relation. |
|
|
|
|
*/ |
|
|
|
|
matching_cols = 1; |
|
|
|
|
largs_cell = lnext(list_head(clause->largs)); |
|
|
|
|
rargs_cell = lnext(list_head(clause->rargs)); |
|
|
|
|
opnos_cell = lnext(list_head(clause->opnos)); |
|
|
|
|
|
|
|
|
|
while (largs_cell != NULL) |
|
|
|
|
{ |
|
|
|
|
Node *varop; |
|
|
|
|
Node *constop; |
|
|
|
|
int i; |
|
|
|
|
|
|
|
|
|
expr_op = lfirst_oid(opnos_cell); |
|
|
|
|
if (var_on_left) |
|
|
|
|
{ |
|
|
|
|
varop = (Node *) lfirst(largs_cell); |
|
|
|
|
constop = (Node *) lfirst(rargs_cell); |
|
|
|
|
} |
|
|
|
|
else |
|
|
|
|
{ |
|
|
|
|
varop = (Node *) lfirst(rargs_cell); |
|
|
|
|
constop = (Node *) lfirst(largs_cell); |
|
|
|
|
/* indexkey is on right, so commute the operator */ |
|
|
|
|
expr_op = get_commutator(expr_op); |
|
|
|
|
if (expr_op == InvalidOid) |
|
|
|
|
break; /* operator is not usable */ |
|
|
|
|
} |
|
|
|
|
if (bms_is_member(index->rel->relid, pull_varnos(constop))) |
|
|
|
|
break; /* no good, Var on wrong side */ |
|
|
|
|
if (contain_volatile_functions(constop)) |
|
|
|
|
break; /* no good, volatile comparison value */ |
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* The Var side can match any column of the index. If the user |
|
|
|
|
* does something weird like having multiple identical index |
|
|
|
|
* columns, we insist the match be on the first such column, |
|
|
|
|
* to avoid confusing the executor. |
|
|
|
|
*/ |
|
|
|
|
for (i = 0; i < index->ncolumns; i++) |
|
|
|
|
{ |
|
|
|
|
if (match_index_to_operand(varop, i, index)) |
|
|
|
|
break; |
|
|
|
|
} |
|
|
|
|
if (i >= index->ncolumns) |
|
|
|
|
break; /* no match found */ |
|
|
|
|
|
|
|
|
|
/* Now, do we have the right operator for this column? */ |
|
|
|
|
if (get_op_opclass_strategy(expr_op, index->classlist[i]) |
|
|
|
|
!= op_strategy) |
|
|
|
|
break; |
|
|
|
|
|
|
|
|
|
/* Add opclass and subtype to lists */ |
|
|
|
|
get_op_opclass_properties(expr_op, index->classlist[i], |
|
|
|
|
&op_strategy, &op_subtype, &op_recheck); |
|
|
|
|
opclasses = lappend_oid(opclasses, index->classlist[i]); |
|
|
|
|
subtypes = lappend_oid(subtypes, op_subtype); |
|
|
|
|
|
|
|
|
|
/* This column matches, keep scanning */ |
|
|
|
|
matching_cols++; |
|
|
|
|
largs_cell = lnext(largs_cell); |
|
|
|
|
rargs_cell = lnext(rargs_cell); |
|
|
|
|
opnos_cell = lnext(opnos_cell); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/* Return clause as-is if it's all usable as index quals */ |
|
|
|
|
if (matching_cols == list_length(clause->opnos)) |
|
|
|
|
return rinfo; |
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* We have to generate a subset rowcompare (possibly just one OpExpr). |
|
|
|
|
* The painful part of this is changing < to <= or > to >=, so deal with |
|
|
|
|
* that first. |
|
|
|
|
*/ |
|
|
|
|
if (op_strategy == BTLessEqualStrategyNumber || |
|
|
|
|
op_strategy == BTGreaterEqualStrategyNumber) |
|
|
|
|
{ |
|
|
|
|
/* easy, just use the same operators */ |
|
|
|
|
new_ops = list_truncate(list_copy(clause->opnos), matching_cols); |
|
|
|
|
} |
|
|
|
|
else |
|
|
|
|
{ |
|
|
|
|
ListCell *opclasses_cell; |
|
|
|
|
ListCell *subtypes_cell; |
|
|
|
|
|
|
|
|
|
if (op_strategy == BTLessStrategyNumber) |
|
|
|
|
op_strategy = BTLessEqualStrategyNumber; |
|
|
|
|
else if (op_strategy == BTGreaterStrategyNumber) |
|
|
|
|
op_strategy = BTGreaterEqualStrategyNumber; |
|
|
|
|
else |
|
|
|
|
elog(ERROR, "unexpected strategy number %d", op_strategy); |
|
|
|
|
new_ops = NIL; |
|
|
|
|
forboth(opclasses_cell, opclasses, subtypes_cell, subtypes) |
|
|
|
|
{ |
|
|
|
|
expr_op = get_opclass_member(lfirst_oid(opclasses_cell), |
|
|
|
|
lfirst_oid(subtypes_cell), |
|
|
|
|
op_strategy); |
|
|
|
|
if (!OidIsValid(expr_op)) /* should not happen */ |
|
|
|
|
elog(ERROR, "could not find member %d of opclass %u", |
|
|
|
|
op_strategy, lfirst_oid(opclasses_cell)); |
|
|
|
|
if (!var_on_left) |
|
|
|
|
{ |
|
|
|
|
expr_op = get_commutator(expr_op); |
|
|
|
|
if (!OidIsValid(expr_op)) /* should not happen */ |
|
|
|
|
elog(ERROR, "could not find commutator of member %d of opclass %u", |
|
|
|
|
op_strategy, lfirst_oid(opclasses_cell)); |
|
|
|
|
} |
|
|
|
|
new_ops = lappend_oid(new_ops, expr_op); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/* If we have more than one matching col, create a subset rowcompare */ |
|
|
|
|
if (matching_cols > 1) |
|
|
|
|
{ |
|
|
|
|
RowCompareExpr *rc = makeNode(RowCompareExpr); |
|
|
|
|
|
|
|
|
|
if (var_on_left) |
|
|
|
|
rc->rctype = (RowCompareType) op_strategy; |
|
|
|
|
else |
|
|
|
|
rc->rctype = (op_strategy == BTLessEqualStrategyNumber) ? |
|
|
|
|
ROWCOMPARE_GE : ROWCOMPARE_LE; |
|
|
|
|
rc->opnos = new_ops; |
|
|
|
|
rc->opclasses = list_truncate(list_copy(clause->opclasses), |
|
|
|
|
matching_cols); |
|
|
|
|
rc->largs = list_truncate((List *) copyObject(clause->largs), |
|
|
|
|
matching_cols); |
|
|
|
|
rc->rargs = list_truncate((List *) copyObject(clause->rargs), |
|
|
|
|
matching_cols); |
|
|
|
|
return make_restrictinfo((Expr *) rc, true, false, NULL); |
|
|
|
|
} |
|
|
|
|
else |
|
|
|
|
{ |
|
|
|
|
Expr *opexpr; |
|
|
|
|
|
|
|
|
|
opexpr = make_opclause(linitial_oid(new_ops), BOOLOID, false, |
|
|
|
|
copyObject(linitial(clause->largs)), |
|
|
|
|
copyObject(linitial(clause->rargs))); |
|
|
|
|
return make_restrictinfo(opexpr, true, false, NULL); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Given a fixed prefix that all the "leftop" values must have, |
|
|
|
|
* generate suitable indexqual condition(s). opclass is the index |
|
|
|
|