mirror of
https://github.com/MariaDB/server.git
synced 2025-01-16 03:52:35 +01:00
442 lines
14 KiB
C++
442 lines
14 KiB
C++
/*
|
|
Copyright (c) 2009, 2012, Monty Program Ab
|
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation; version 2 of the License.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; if not, write to the Free Software
|
|
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335 USA */
|
|
|
|
#include "mariadb.h"
|
|
#include "sql_select.h"
|
|
#include "sql_test.h"
|
|
|
|
/****************************************************************************
|
|
* Index Condition Pushdown code starts
|
|
***************************************************************************/
|
|
/*
|
|
Check if given expression uses only table fields covered by the given index
|
|
|
|
SYNOPSIS
|
|
uses_index_fields_only()
|
|
item Expression to check
|
|
tbl The table having the index
|
|
keyno The index number
|
|
other_tbls_ok TRUE <=> Fields of other non-const tables are allowed
|
|
|
|
DESCRIPTION
|
|
Check if given expression only uses fields covered by index #keyno in the
|
|
table tbl. The expression can use any fields in any other tables.
|
|
|
|
The expression is guaranteed not to be AND or OR - those constructs are
|
|
handled outside of this function.
|
|
|
|
RETURN
|
|
TRUE Yes
|
|
FALSE No
|
|
*/
|
|
|
|
bool uses_index_fields_only(Item *item, TABLE *tbl, uint keyno,
|
|
bool other_tbls_ok)
|
|
{
|
|
if (item->walk(&Item::limit_index_condition_pushdown_processor, FALSE, NULL))
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
if (item->const_item())
|
|
return TRUE;
|
|
|
|
/*
|
|
Don't push down the triggered conditions. Nested outer joins execution
|
|
code may need to evaluate a condition several times (both triggered and
|
|
untriggered), and there is no way to put thi
|
|
TODO: Consider cloning the triggered condition and using the copies for:
|
|
1. push the first copy down, to have most restrictive index condition
|
|
possible
|
|
2. Put the second copy into tab->select_cond.
|
|
*/
|
|
if (item->type() == Item::FUNC_ITEM &&
|
|
((Item_func*)item)->functype() == Item_func::TRIG_COND_FUNC)
|
|
return FALSE;
|
|
|
|
if (!(item->used_tables() & tbl->map))
|
|
return other_tbls_ok;
|
|
|
|
Item::Type item_type= item->type();
|
|
switch (item_type) {
|
|
case Item::FUNC_ITEM:
|
|
{
|
|
/* This is a function, apply condition recursively to arguments */
|
|
Item_func *item_func= (Item_func*)item;
|
|
Item **child;
|
|
Item **item_end= (item_func->arguments()) + item_func->argument_count();
|
|
for (child= item_func->arguments(); child != item_end; child++)
|
|
{
|
|
if (!uses_index_fields_only(*child, tbl, keyno, other_tbls_ok))
|
|
return FALSE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
case Item::COND_ITEM:
|
|
{
|
|
/*
|
|
This is a AND/OR condition. Regular AND/OR clauses are handled by
|
|
make_cond_for_index() which will chop off the part that can be
|
|
checked with index. This code is for handling non-top-level AND/ORs,
|
|
e.g. func(x AND y).
|
|
*/
|
|
List_iterator<Item> li(*((Item_cond*)item)->argument_list());
|
|
Item *item;
|
|
while ((item=li++))
|
|
{
|
|
if (!uses_index_fields_only(item, tbl, keyno, other_tbls_ok))
|
|
return FALSE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
case Item::FIELD_ITEM:
|
|
{
|
|
Item_field *item_field= (Item_field*)item;
|
|
Field *field= item_field->field;
|
|
if (field->table != tbl)
|
|
return TRUE;
|
|
/*
|
|
The below is probably a repetition - the first part checks the
|
|
other two, but let's play it safe:
|
|
*/
|
|
if(!field->part_of_key.is_set(keyno) ||
|
|
field->type() == MYSQL_TYPE_GEOMETRY ||
|
|
field->type() == MYSQL_TYPE_BLOB)
|
|
return FALSE;
|
|
KEY *key_info= tbl->key_info + keyno;
|
|
KEY_PART_INFO *key_part= key_info->key_part;
|
|
KEY_PART_INFO *key_part_end= key_part + key_info->user_defined_key_parts;
|
|
for ( ; key_part < key_part_end; key_part++)
|
|
{
|
|
if (field->eq(key_part->field))
|
|
return !(key_part->key_part_flag & HA_PART_KEY_SEG);
|
|
}
|
|
if ((tbl->file->ha_table_flags() & HA_PRIMARY_KEY_IN_READ_INDEX) &&
|
|
tbl->s->primary_key != MAX_KEY &&
|
|
tbl->s->primary_key != keyno)
|
|
{
|
|
key_info= tbl->key_info + tbl->s->primary_key;
|
|
key_part= key_info->key_part;
|
|
key_part_end= key_part + key_info->user_defined_key_parts;
|
|
for ( ; key_part < key_part_end; key_part++)
|
|
{
|
|
/*
|
|
It does not make sense to use the fact that the engine can read in
|
|
a full field if the key if the index is built only over a part
|
|
of this field.
|
|
*/
|
|
if (field->eq(key_part->field))
|
|
return !(key_part->key_part_flag & HA_PART_KEY_SEG);
|
|
}
|
|
}
|
|
return FALSE;
|
|
}
|
|
case Item::REF_ITEM:
|
|
return uses_index_fields_only(item->real_item(), tbl, keyno,
|
|
other_tbls_ok);
|
|
default:
|
|
return FALSE; /* Play it safe, don't push unknown non-const items */
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
Get a part of the condition that can be checked using only index fields
|
|
|
|
SYNOPSIS
|
|
make_cond_for_index()
|
|
cond The source condition
|
|
table The table that is partially available
|
|
keyno The index in the above table. Only fields covered by the
|
|
index are available
|
|
other_tbls_ok TRUE <=> Fields of other non-const tables are allowed
|
|
|
|
DESCRIPTION
|
|
Get a part of the condition that can be checked when for the given table
|
|
we have values only of fields covered by some index. The condition may
|
|
refer to other tables, it is assumed that we have values of all of their
|
|
fields.
|
|
|
|
Example:
|
|
make_cond_for_index(
|
|
"cond(t1.field) AND cond(t2.key1) AND cond(t2.non_key) AND cond(t2.key2)",
|
|
t2, keyno(t2.key1))
|
|
will return
|
|
"cond(t1.field) AND cond(t2.key2)"
|
|
|
|
RETURN
|
|
Index condition, or NULL if no condition could be inferred.
|
|
*/
|
|
|
|
static Item *make_cond_for_index(THD *thd, Item *cond, TABLE *table, uint keyno,
|
|
bool other_tbls_ok)
|
|
{
|
|
if (!cond || cond->basic_const_item())
|
|
return cond;
|
|
if (cond->type() == Item::COND_ITEM)
|
|
{
|
|
if (((Item_cond*) cond)->functype() == Item_func::COND_AND_FUNC)
|
|
{
|
|
table_map used_tables= 0;
|
|
Item_cond_and *new_cond= new (thd->mem_root) Item_cond_and(thd);
|
|
if (!new_cond)
|
|
return (COND*) 0;
|
|
List_iterator<Item> li(*((Item_cond*) cond)->argument_list());
|
|
Item *item;
|
|
while ((item=li++))
|
|
{
|
|
Item *fix= make_cond_for_index(thd, item, table, keyno, other_tbls_ok);
|
|
if (fix)
|
|
{
|
|
new_cond->argument_list()->push_back(fix, thd->mem_root);
|
|
used_tables|= fix->used_tables();
|
|
}
|
|
}
|
|
switch (new_cond->argument_list()->elements) {
|
|
case 0:
|
|
return (COND*) 0;
|
|
case 1:
|
|
/* remove AND level if there is only one argument */
|
|
return new_cond->argument_list()->head();
|
|
default:
|
|
new_cond->quick_fix_field();
|
|
new_cond->used_tables_cache= used_tables;
|
|
return new_cond;
|
|
}
|
|
}
|
|
else /* It's OR */
|
|
{
|
|
Item_cond_or *new_cond= new (thd->mem_root) Item_cond_or(thd);
|
|
if (!new_cond)
|
|
return (COND*) 0;
|
|
List_iterator<Item> li(*((Item_cond*) cond)->argument_list());
|
|
Item *item;
|
|
while ((item=li++))
|
|
{
|
|
Item *fix= make_cond_for_index(thd, item, table, keyno, other_tbls_ok);
|
|
if (!fix)
|
|
return (COND*) 0;
|
|
new_cond->argument_list()->push_back(fix, thd->mem_root);
|
|
}
|
|
new_cond->quick_fix_field();
|
|
new_cond->used_tables_cache= ((Item_cond_or*) cond)->used_tables_cache;
|
|
new_cond->top_level_item();
|
|
return new_cond;
|
|
}
|
|
}
|
|
|
|
if (!uses_index_fields_only(cond, table, keyno, other_tbls_ok))
|
|
return (COND*) 0;
|
|
return cond;
|
|
}
|
|
|
|
|
|
static Item *make_cond_remainder(THD *thd, Item *cond, TABLE *table, uint keyno,
|
|
bool other_tbls_ok, bool exclude_index)
|
|
{
|
|
if (exclude_index &&
|
|
uses_index_fields_only(cond, table, keyno, other_tbls_ok))
|
|
return 0;
|
|
|
|
if (cond->type() == Item::COND_ITEM)
|
|
{
|
|
table_map tbl_map= 0;
|
|
if (((Item_cond*) cond)->functype() == Item_func::COND_AND_FUNC)
|
|
{
|
|
/* Create new top level AND item */
|
|
Item_cond_and *new_cond= new (thd->mem_root) Item_cond_and(thd);
|
|
if (!new_cond)
|
|
return (COND*) 0;
|
|
List_iterator<Item> li(*((Item_cond*) cond)->argument_list());
|
|
Item *item;
|
|
while ((item=li++))
|
|
{
|
|
Item *fix= make_cond_remainder(thd, item, table, keyno,
|
|
other_tbls_ok, exclude_index);
|
|
if (fix)
|
|
{
|
|
new_cond->argument_list()->push_back(fix, thd->mem_root);
|
|
tbl_map |= fix->used_tables();
|
|
}
|
|
}
|
|
switch (new_cond->argument_list()->elements) {
|
|
case 0:
|
|
return (COND*) 0;
|
|
case 1:
|
|
return new_cond->argument_list()->head();
|
|
default:
|
|
new_cond->quick_fix_field();
|
|
((Item_cond*)new_cond)->used_tables_cache= tbl_map;
|
|
return new_cond;
|
|
}
|
|
}
|
|
else /* It's OR */
|
|
{
|
|
Item_cond_or *new_cond= new (thd->mem_root) Item_cond_or(thd);
|
|
if (!new_cond)
|
|
return (COND*) 0;
|
|
List_iterator<Item> li(*((Item_cond*) cond)->argument_list());
|
|
Item *item;
|
|
while ((item=li++))
|
|
{
|
|
Item *fix= make_cond_remainder(thd, item, table, keyno,
|
|
other_tbls_ok, FALSE);
|
|
if (!fix)
|
|
return (COND*) 0;
|
|
new_cond->argument_list()->push_back(fix, thd->mem_root);
|
|
tbl_map |= fix->used_tables();
|
|
}
|
|
new_cond->quick_fix_field();
|
|
((Item_cond*)new_cond)->used_tables_cache= tbl_map;
|
|
new_cond->top_level_item();
|
|
return new_cond;
|
|
}
|
|
}
|
|
return cond;
|
|
}
|
|
|
|
|
|
/*
|
|
Try to extract and push the index condition
|
|
|
|
SYNOPSIS
|
|
push_index_cond()
|
|
tab A join tab that has tab->table->file and its condition
|
|
in tab->select_cond
|
|
keyno Index for which extract and push the condition
|
|
|
|
DESCRIPTION
|
|
Try to extract and push the index condition down to table handler
|
|
*/
|
|
|
|
void push_index_cond(JOIN_TAB *tab, uint keyno)
|
|
{
|
|
DBUG_ENTER("push_index_cond");
|
|
Item *idx_cond;
|
|
|
|
/*
|
|
Backported the following from MySQL 5.6:
|
|
6. The index is not a clustered index. The performance improvement
|
|
of pushing an index condition on a clustered key is much lower
|
|
than on a non-clustered key. This restriction should be
|
|
re-evaluated when WL#6061 is implemented.
|
|
*/
|
|
if ((tab->table->file->index_flags(keyno, 0, 1) &
|
|
HA_DO_INDEX_COND_PUSHDOWN) &&
|
|
optimizer_flag(tab->join->thd, OPTIMIZER_SWITCH_INDEX_COND_PUSHDOWN) &&
|
|
tab->join->thd->lex->sql_command != SQLCOM_UPDATE_MULTI &&
|
|
tab->join->thd->lex->sql_command != SQLCOM_DELETE_MULTI &&
|
|
tab->type != JT_CONST && tab->type != JT_SYSTEM &&
|
|
!tab->table->file->is_clustering_key(keyno)) // 6
|
|
{
|
|
DBUG_EXECUTE("where",
|
|
print_where(tab->select_cond, "full cond", QT_ORDINARY););
|
|
|
|
idx_cond= make_cond_for_index(tab->join->thd, tab->select_cond, tab->table,
|
|
keyno, tab->icp_other_tables_ok);
|
|
|
|
DBUG_EXECUTE("where",
|
|
print_where(idx_cond, "idx cond", QT_ORDINARY););
|
|
|
|
if (idx_cond)
|
|
{
|
|
Item *idx_remainder_cond= 0;
|
|
tab->pre_idx_push_select_cond= tab->select_cond;
|
|
/*
|
|
For BKA cache we store condition to special BKA cache field
|
|
because evaluation of the condition requires additional operations
|
|
before the evaluation. This condition is used in
|
|
JOIN_CACHE_BKA[_UNIQUE]::skip_index_tuple() functions.
|
|
*/
|
|
if (tab->use_join_cache &&
|
|
/*
|
|
if cache is used then the value is TRUE only
|
|
for BKA[_UNIQUE] cache (see check_join_cache_usage func).
|
|
*/
|
|
tab->icp_other_tables_ok &&
|
|
(idx_cond->used_tables() &
|
|
~(tab->table->map | tab->join->const_table_map)))
|
|
tab->cache_idx_cond= idx_cond;
|
|
else
|
|
{
|
|
idx_remainder_cond= tab->table->file->idx_cond_push(keyno, idx_cond);
|
|
|
|
/*
|
|
If (1) there is an index condition that we couldn't push using ICP,
|
|
(2) we are using Join Buffering
|
|
(3) and we are using BKA
|
|
then use BKA's Index Condition Pushdown mechanism to check it.
|
|
*/
|
|
if (idx_remainder_cond && tab->use_join_cache && // (1) && (2)
|
|
tab->icp_other_tables_ok) // (3)
|
|
{
|
|
tab->cache_idx_cond= idx_remainder_cond;
|
|
idx_remainder_cond= NULL;
|
|
}
|
|
}
|
|
|
|
/*
|
|
Disable eq_ref's "lookup cache" if we've pushed down an index
|
|
condition.
|
|
TODO: This check happens to work on current ICP implementations, but
|
|
there may exist a compliant implementation that will not work
|
|
correctly with it. Sort this out when we stabilize the condition
|
|
pushdown APIs.
|
|
*/
|
|
if (idx_remainder_cond != idx_cond)
|
|
tab->ref.disable_cache= TRUE;
|
|
|
|
Item *row_cond= tab->idx_cond_fact_out ?
|
|
make_cond_remainder(tab->join->thd, tab->select_cond,
|
|
tab->table, keyno,
|
|
tab->icp_other_tables_ok, TRUE) :
|
|
tab->pre_idx_push_select_cond;
|
|
|
|
DBUG_EXECUTE("where",
|
|
print_where(row_cond, "remainder cond", QT_ORDINARY););
|
|
|
|
if (row_cond)
|
|
{
|
|
if (!idx_remainder_cond)
|
|
tab->select_cond= row_cond;
|
|
else
|
|
{
|
|
COND *new_cond= new (tab->join->thd->mem_root)
|
|
Item_cond_and(tab->join->thd, row_cond, idx_remainder_cond);
|
|
tab->select_cond= new_cond;
|
|
tab->select_cond->quick_fix_field();
|
|
((Item_cond_and*)tab->select_cond)->used_tables_cache=
|
|
row_cond->used_tables() | idx_remainder_cond->used_tables();
|
|
}
|
|
}
|
|
else
|
|
tab->select_cond= idx_remainder_cond;
|
|
if (tab->select)
|
|
{
|
|
DBUG_EXECUTE("where",
|
|
print_where(tab->select->cond,
|
|
"select_cond",
|
|
QT_ORDINARY););
|
|
|
|
tab->select->cond= tab->select_cond;
|
|
tab->select->pre_idx_push_select_cond= tab->pre_idx_push_select_cond;
|
|
}
|
|
}
|
|
}
|
|
DBUG_VOID_RETURN;
|
|
}
|
|
|
|
|