Expand the 'special index operator' machinery to handle special cases

for boolean indexes.  Previously we would only use such an index with
WHERE clauses like 'indexkey = true' or 'indexkey = false'.  The new
code transforms the cases 'indexkey', 'NOT indexkey', 'indexkey IS TRUE',
and 'indexkey IS FALSE' into one of these.  While this is only marginally
useful in itself, I intend soon to change constant-expression simplification
so that 'foo = true' and 'foo = false' are reduced to just 'foo' and
'NOT foo' ... which would lose the ability to use boolean indexes for
such queries at all, if the indexscan machinery couldn't make the
reverse transformation.
This commit is contained in:
Tom Lane 2005-03-26 23:29:20 +00:00
parent 9d388e1f39
commit febc9a613c
5 changed files with 192 additions and 26 deletions

View File

@ -9,7 +9,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/optimizer/path/indxpath.c,v 1.169 2005/03/02 04:10:53 tgl Exp $
* $PostgreSQL: pgsql/src/backend/optimizer/path/indxpath.c,v 1.170 2005/03/26 23:29:17 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -50,6 +50,9 @@
#define is_indexable_operator(clause,opclass,indexkey_on_left) \
(indexable_operator(clause,opclass,indexkey_on_left) != InvalidOid)
#define IsBooleanOpclass(opclass) \
((opclass) == BOOL_BTREE_OPS_OID || (opclass) == BOOL_HASH_OPS_OID)
static List *group_clauses_by_indexkey(RelOptInfo *rel, IndexOptInfo *index);
static List *group_clauses_by_indexkey_for_join(Query *root,
@ -72,8 +75,16 @@ static Path *make_innerjoin_index_path(Query *root,
List *clausegroups);
static bool match_index_to_operand(Node *operand, int indexcol,
RelOptInfo *rel, IndexOptInfo *index);
static bool match_boolean_index_clause(Node *clause,
int indexcol,
RelOptInfo *rel,
IndexOptInfo *index);
static bool match_special_index_operator(Expr *clause, Oid opclass,
bool indexkey_on_left);
static Expr *expand_boolean_index_clause(Node *clause,
int indexcol,
RelOptInfo *rel,
IndexOptInfo *index);
static List *expand_indexqual_condition(RestrictInfo *rinfo, Oid opclass);
static List *prefix_quals(Node *leftop, Oid opclass,
Const *prefix, Pattern_Prefix_Status pstatus);
@ -511,7 +522,7 @@ group_clauses_by_indexkey_for_or(RelOptInfo *rel,
* match_clause_to_indexcol()
* Determines whether a restriction clause matches a column of an index.
*
* To match, the clause:
* To match a normal index, the clause:
*
* (1) must be in the form (indexkey op const) or (const op indexkey);
* and
@ -525,6 +536,9 @@ group_clauses_by_indexkey_for_or(RelOptInfo *rel,
* We do not actually do the commuting here, but we check whether a
* suitable commutator operator is available.
*
* For boolean indexes, it is also possible to match the clause directly
* to the indexkey; or perhaps the clause is (NOT indexkey).
*
* 'rel' is the relation of interest.
* 'index' is an index on 'rel'.
* 'indexcol' is a column number of 'index' (counting from 0).
@ -547,7 +561,15 @@ match_clause_to_indexcol(RelOptInfo *rel,
Node *leftop,
*rightop;
/* Clause must be a binary opclause. */
/* First check for boolean-index cases. */
if (IsBooleanOpclass(opclass))
{
if (match_boolean_index_clause((Node *) clause,
indexcol, rel, index))
return true;
}
/* Else clause must be a binary opclause. */
if (!is_opclause(clause))
return false;
leftop = get_leftop(clause);
@ -606,6 +628,8 @@ match_clause_to_indexcol(RelOptInfo *rel,
* operator for this column, or is a "special" operator as recognized
* by match_special_index_operator().
*
* The boolean-index cases don't apply.
*
* As above, we must be able to commute the clause to put the indexkey
* on the left.
*
@ -1662,7 +1686,7 @@ make_innerjoin_index_path(Query *root,
pathnode->path.pathkeys = NIL;
/* Convert clauses to indexquals the executor can handle */
indexquals = expand_indexqual_conditions(index, clausegroups);
indexquals = expand_indexqual_conditions(rel, index, clausegroups);
/* Flatten the clausegroups list to produce indexclauses list */
allclauses = flatten_clausegroups_list(clausegroups);
@ -1868,21 +1892,78 @@ match_index_to_operand(Node *operand,
* from LIKE to indexscan limits rather harder than one might think ...
* but that's the basic idea.)
*
* Two routines are provided here, match_special_index_operator() and
* expand_indexqual_conditions(). match_special_index_operator() is
* just an auxiliary function for match_clause_to_indexcol(); after
* the latter fails to recognize a restriction opclause's operator
* as a member of an index's opclass, it asks match_special_index_operator()
* whether the clause should be considered an indexqual anyway.
* Another thing that we do with this machinery is to provide special
* smarts for "boolean" indexes (that is, indexes on boolean columns
* that support boolean equality). We can transform a plain reference
* to the indexkey into "indexkey = true", or "NOT indexkey" into
* "indexkey = false", so as to make the expression indexable using the
* regular index operators. (As of Postgres 8.1, we must do this here
* because constant simplification does the reverse transformation;
* without this code there'd be no way to use such an index at all.)
*
* Three routines are provided here:
*
* match_special_index_operator() is just an auxiliary function for
* match_clause_to_indexcol(); after the latter fails to recognize a
* restriction opclause's operator as a member of an index's opclass,
* it asks match_special_index_operator() whether the clause should be
* considered an indexqual anyway.
*
* match_boolean_index_clause() similarly detects clauses that can be
* converted into boolean equality operators.
*
* expand_indexqual_conditions() converts a list of lists of RestrictInfo
* nodes (with implicit AND semantics across list elements) into
* a list of clauses that the executor can actually handle. For operators
* that are members of the index's opclass this transformation is a no-op,
* but operators recognized by match_special_index_operator() must be
* converted into one or more "regular" indexqual conditions.
* but clauses recognized by match_special_index_operator() or
* match_boolean_index_clause() must be converted into one or more "regular"
* indexqual conditions.
*----------
*/
/*
* match_boolean_index_clause
* Recognize restriction clauses that can be matched to a boolean index.
*
* This should be called only when IsBooleanOpclass() recognizes the
* index's operator class. We check to see if the clause matches the
* index's key.
*/
static bool
match_boolean_index_clause(Node *clause,
int indexcol,
RelOptInfo *rel,
IndexOptInfo *index)
{
/* Direct match? */
if (match_index_to_operand(clause, indexcol, rel, index))
return true;
/* NOT clause? */
if (not_clause(clause))
{
if (match_index_to_operand((Node *) get_notclausearg((Expr *) clause),
indexcol, rel, index))
return true;
}
/*
* Since we only consider clauses at top level of WHERE, we can convert
* indexkey IS TRUE and indexkey IS FALSE to index searches as well.
* The different meaning for NULL isn't important.
*/
else if (clause && IsA(clause, BooleanTest))
{
BooleanTest *btest = (BooleanTest *) clause;
if (btest->booltesttype == IS_TRUE ||
btest->booltesttype == IS_FALSE)
if (match_index_to_operand((Node *) btest->arg,
indexcol, rel, index))
return true;
}
return false;
}
/*
* match_special_index_operator
* Recognize restriction clauses that can be used to generate
@ -2042,9 +2123,9 @@ match_special_index_operator(Expr *clause, Oid opclass,
* expand_indexqual_conditions
* Given a list of sublists of RestrictInfo nodes, produce a flat list
* of index qual clauses. Standard qual clauses (those in the index's
* opclass) are passed through unchanged. "Special" index operators
* are expanded into clauses that the indexscan machinery will know
* what to do with.
* 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.
*
* 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 planner, so far as I can
@ -2054,10 +2135,11 @@ match_special_index_operator(Expr *clause, Oid opclass,
* someday --- tgl 7/00)
*/
List *
expand_indexqual_conditions(IndexOptInfo *index, List *clausegroups)
expand_indexqual_conditions(RelOptInfo *rel, IndexOptInfo *index, List *clausegroups)
{
List *resultquals = NIL;
ListCell *clausegroup_item;
int indexcol = 0;
Oid *classes = index->classlist;
if (clausegroups == NIL)
@ -2073,12 +2155,32 @@ expand_indexqual_conditions(IndexOptInfo *index, List *clausegroups)
{
RestrictInfo *rinfo = (RestrictInfo *) lfirst(l);
/* First check for boolean cases */
if (IsBooleanOpclass(curClass))
{
Expr *boolqual;
boolqual = expand_boolean_index_clause((Node *) rinfo->clause,
indexcol,
rel,
index);
if (boolqual)
{
resultquals = lappend(resultquals,
make_restrictinfo(boolqual,
true, true));
continue;
}
}
resultquals = list_concat(resultquals,
expand_indexqual_condition(rinfo,
curClass));
curClass));
}
clausegroup_item = lnext(clausegroup_item);
indexcol++;
classes++;
} while (clausegroup_item != NULL && !DoneMatchingIndexKeys(classes));
@ -2087,8 +2189,70 @@ expand_indexqual_conditions(IndexOptInfo *index, List *clausegroups)
return resultquals;
}
/*
* expand_boolean_index_clause
* Convert a clause recognized by match_boolean_index_clause into
* a boolean equality operator clause.
*
* Returns NULL if the clause isn't a boolean index qual.
*/
static Expr *
expand_boolean_index_clause(Node *clause,
int indexcol,
RelOptInfo *rel,
IndexOptInfo *index)
{
/* Direct match? */
if (match_index_to_operand(clause, indexcol, rel, index))
{
/* convert to indexkey = TRUE */
return make_opclause(BooleanEqualOperator, BOOLOID, false,
(Expr *) clause,
(Expr *) makeBoolConst(true, false));
}
/* NOT clause? */
if (not_clause(clause))
{
Node *arg = (Node *) get_notclausearg((Expr *) clause);
/* It must have matched the indexkey */
Assert(match_index_to_operand(arg, indexcol, rel, index));
/* convert to indexkey = FALSE */
return make_opclause(BooleanEqualOperator, BOOLOID, false,
(Expr *) arg,
(Expr *) makeBoolConst(false, false));
}
if (clause && IsA(clause, BooleanTest))
{
BooleanTest *btest = (BooleanTest *) clause;
Node *arg = (Node *) btest->arg;
/* It must have matched the indexkey */
Assert(match_index_to_operand(arg, indexcol, rel, index));
if (btest->booltesttype == IS_TRUE)
{
/* convert to indexkey = TRUE */
return make_opclause(BooleanEqualOperator, BOOLOID, false,
(Expr *) arg,
(Expr *) makeBoolConst(true, false));
}
if (btest->booltesttype == IS_FALSE)
{
/* convert to indexkey = FALSE */
return make_opclause(BooleanEqualOperator, BOOLOID, false,
(Expr *) arg,
(Expr *) makeBoolConst(false, false));
}
/* Oops */
Assert(false);
}
return NULL;
}
/*
* expand_indexqual_condition --- expand a single indexqual condition
* (other than a boolean-qual case)
*
* The input is a single RestrictInfo, the output a list of RestrictInfos
*/
@ -2096,7 +2260,6 @@ static List *
expand_indexqual_condition(RestrictInfo *rinfo, Oid opclass)
{
Expr *clause = rinfo->clause;
/* we know these will succeed */
Node *leftop = get_leftop(clause);
Node *rightop = get_rightop(clause);

View File

@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/optimizer/path/orindxpath.c,v 1.65 2005/03/01 01:40:05 tgl Exp $
* $PostgreSQL: pgsql/src/backend/optimizer/path/orindxpath.c,v 1.66 2005/03/26 23:29:17 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -398,7 +398,7 @@ best_or_subclause_index(Query *root,
continue;
/* Convert clauses to indexquals the executor can handle */
indexquals = expand_indexqual_conditions(index, indexclauses);
indexquals = expand_indexqual_conditions(rel, index, indexclauses);
cost_index(&subclause_path, root, rel, index, indexquals, false);

View File

@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/optimizer/util/pathnode.c,v 1.112 2005/03/10 23:21:22 tgl Exp $
* $PostgreSQL: pgsql/src/backend/optimizer/util/pathnode.c,v 1.113 2005/03/26 23:29:18 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -444,7 +444,7 @@ create_index_path(Query *root,
pathnode->path.pathkeys = pathkeys;
/* Convert clauses to indexquals the executor can handle */
indexquals = expand_indexqual_conditions(index, restriction_clauses);
indexquals = expand_indexqual_conditions(rel, index, restriction_clauses);
/* Flatten the clause-groups list to produce indexclauses list */
restriction_clauses = flatten_clausegroups_list(restriction_clauses);

View File

@ -27,7 +27,7 @@
* Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $PostgreSQL: pgsql/src/include/catalog/pg_opclass.h,v 1.62 2004/12/31 22:03:24 pgsql Exp $
* $PostgreSQL: pgsql/src/include/catalog/pg_opclass.h,v 1.63 2005/03/26 23:29:19 tgl Exp $
*
* NOTES
* the genbki.sh script reads this file and generates .bki
@ -92,6 +92,7 @@ DATA(insert OID = 397 ( 403 array_ops PGNSP PGUID 2277 t 0 ));
#define ARRAY_BTREE_OPS_OID 397
DATA(insert OID = 423 ( 403 bit_ops PGNSP PGUID 1560 t 0 ));
DATA(insert OID = 424 ( 403 bool_ops PGNSP PGUID 16 t 0 ));
#define BOOL_BTREE_OPS_OID 424
DATA(insert OID = 425 ( 402 box_ops PGNSP PGUID 603 t 0 ));
DATA(insert OID = 426 ( 403 bpchar_ops PGNSP PGUID 1042 t 0 ));
#define BPCHAR_BTREE_OPS_OID 426
@ -159,6 +160,7 @@ DATA(insert OID = 2098 ( 403 name_pattern_ops PGNSP PGUID 19 f 0 ));
#define NAME_PATTERN_BTREE_OPS_OID 2098
DATA(insert OID = 2099 ( 403 money_ops PGNSP PGUID 790 t 0 ));
DATA(insert OID = 2222 ( 405 bool_ops PGNSP PGUID 16 t 0 ));
#define BOOL_HASH_OPS_OID 2222
DATA(insert OID = 2223 ( 405 bytea_ops PGNSP PGUID 17 t 0 ));
DATA(insert OID = 2224 ( 405 int2vector_ops PGNSP PGUID 22 t 0 ));
DATA(insert OID = 2225 ( 405 xid_ops PGNSP PGUID 28 t 0 ));

View File

@ -8,7 +8,7 @@
* Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $PostgreSQL: pgsql/src/include/optimizer/paths.h,v 1.78 2005/01/23 02:21:36 tgl Exp $
* $PostgreSQL: pgsql/src/include/optimizer/paths.h,v 1.79 2005/03/26 23:29:20 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -41,8 +41,9 @@ extern Path *best_inner_indexscan(Query *root, RelOptInfo *rel,
extern List *group_clauses_by_indexkey_for_or(RelOptInfo *rel,
IndexOptInfo *index,
Expr *orsubclause);
extern List *expand_indexqual_conditions(IndexOptInfo *index,
List *clausegroups);
extern List *expand_indexqual_conditions(RelOptInfo *rel,
IndexOptInfo *index,
List *clausegroups);
extern void check_partial_indexes(Query *root, RelOptInfo *rel);
extern bool pred_test(List *predicate_list, List *restrictinfo_list);
extern List *flatten_clausegroups_list(List *clausegroups);