mirror of
https://github.com/MariaDB/server.git
synced 2025-01-16 20:12:31 +01:00
bbab9ec678
Noteworthy: - New HANDLER code - New multi-update-grant-check code - Table lock code in ha_innodb.cc was not applied BitKeeper/etc/logging_ok: auto-union BitKeeper/deleted/.del-ctype-latin1_de.c~c5d8f9208bceb98e: Auto merged Build-tools/mysql-copyright-2: Auto merged acinclude.m4: Auto merged client/mysqladmin.c: Auto merged client/mysqldump.c: Auto merged include/config-win.h: Auto merged include/my_global.h: Auto merged include/myisam.h: Auto merged innobase/btr/btr0btr.c: Auto merged innobase/buf/buf0buf.c: Auto merged ltmain.sh: Auto merged innobase/dict/dict0dict.c: Auto merged innobase/fsp/fsp0fsp.c: Auto merged innobase/include/dict0dict.h: Auto merged innobase/include/row0mysql.h: Auto merged innobase/log/log0log.c: Auto merged innobase/log/log0recv.c: Auto merged innobase/pars/pars0opt.c: Auto merged innobase/row/row0row.c: Auto merged innobase/sync/sync0arr.c: Auto merged innobase/ut/ut0dbg.c: Auto merged myisam/mi_check.c: Auto merged myisam/mi_close.c: Auto merged myisam/mi_create.c: Auto merged myisam/mi_locking.c: Auto merged myisam/myisampack.c: Auto merged mysql-test/r/delete.result: Auto merged mysql-test/r/func_if.result: Auto merged Build-tools/mysql-copyright: Merge with 4.0 (too most of the code from 4.0) Makefile.am: merge client/mysql.cc: Used 4.1 code configure.in: merge innobase/os/os0file.c: merge innobase/row/row0mysql.c: merge mysql-test/r/ctype_latin1_de.result: merge mysql-test/r/flush_table.result: merge mysql-test/r/func_str.result: merge mysql-test/r/handler.result: merge mysql-test/r/multi_update.result: merge mysql-test/r/type_timestamp.result: Removed testing of 'new' mode, as this is only relevant for 4.0 mysql-test/r/update.result: merge mysql-test/t/delete.test: merge mysql-test/t/flush_table.test: merge mysql-test/t/func_str.test: merge mysql-test/t/handler.test: merge mysql-test/t/multi_update.test: merge mysql-test/t/type_timestamp.test: Removed testing of 'new' mode, as this is only relevant for 4.0 mysql-test/t/update.test: merge mysys/errors.c: merge mysys/my_fstream.c: merge mysys/my_pread.c: merge mysys/my_write.c: merge mysys/mysys_priv.h: merge scripts/mysqlhotcopy.sh: merge sql/field.cc: Keep code from 4.1 sql/field.h: Keep code from 4.1 sql/ha_innodb.cc: Don't merge lock code from 4.0; Heikki will look at this sql/ha_myisam.cc: merge sql/handler.cc: merge sql/item_cmpfunc.cc: merge sql/item_cmpfunc.h: merge sql/item_strfunc.cc: merge sql/mysql_priv.h: merge sql/mysqld.cc: merge sql/protocol.cc: merge sql/records.cc: merge sql/repl_failsafe.cc: merge mysql-test/r/lock_multi.result: merge mysql-test/t/ctype_latin1_de.test: merge mysql-test/t/func_if.test: merge mysql-test/t/lock_multi.test: merge sql/repl_failsafe.h: merge Remove unnessessary header protection sql/slave.h: merge sql/sql_acl.cc: merge sql/sql_base.cc: merge sql/sql_cache.cc: auto merge sql/sql_class.cc: merge sql/sql_class.h: merge sql/sql_delete.cc: merge sql/sql_handler.cc: Get new HANDLER code into 4.1 sql/sql_parse.cc: Keep old file sql/sql_repl.cc: merge sql/sql_repl.h: merge sql/sql_show.cc: merge sql/sql_table.cc: merge sql/sql_union.cc: Applied the examine_rows bug fix from 4.0 by hand sql/sql_update.cc: New multi-update-grant-check code from 4.0 sql/sql_yacc.yy: New multi-update-grant-check code from 4.0 sql/stacktrace.c: merge sql/table.h: merge
1251 lines
30 KiB
C
1251 lines
30 KiB
C
/******************************************************
|
|
Simple SQL optimizer
|
|
|
|
(c) 1997 Innobase Oy
|
|
|
|
Created 12/21/1997 Heikki Tuuri
|
|
*******************************************************/
|
|
|
|
#include "pars0opt.h"
|
|
|
|
#ifdef UNIV_NONINL
|
|
#include "pars0opt.ic"
|
|
#endif
|
|
|
|
#include "row0sel.h"
|
|
#include "row0ins.h"
|
|
#include "row0upd.h"
|
|
#include "dict0dict.h"
|
|
#include "dict0mem.h"
|
|
#include "que0que.h"
|
|
#include "pars0grm.h"
|
|
#include "pars0pars.h"
|
|
#include "lock0lock.h"
|
|
|
|
#define OPT_EQUAL 1 /* comparison by = */
|
|
#define OPT_COMPARISON 2 /* comparison by <, >, <=, or >= */
|
|
|
|
#define OPT_NOT_COND 1
|
|
#define OPT_END_COND 2
|
|
#define OPT_TEST_COND 3
|
|
#define OPT_SCROLL_COND 4
|
|
|
|
|
|
/***********************************************************************
|
|
Inverts a comparison operator. */
|
|
static
|
|
int
|
|
opt_invert_cmp_op(
|
|
/*==============*/
|
|
/* out: the equivalent operator when the order of
|
|
the arguments is switched */
|
|
int op) /* in: operator */
|
|
{
|
|
if (op == '<') {
|
|
return('>');
|
|
} else if (op == '>') {
|
|
return('<');
|
|
} else if (op == '=') {
|
|
return('=');
|
|
} else if (op == PARS_LE_TOKEN) {
|
|
return(PARS_GE_TOKEN);
|
|
} else if (op == PARS_GE_TOKEN) {
|
|
return(PARS_LE_TOKEN);
|
|
} else {
|
|
ut_error;
|
|
}
|
|
|
|
return(0);
|
|
}
|
|
|
|
/***********************************************************************
|
|
Checks if the value of an expression can be calculated BEFORE the nth table
|
|
in a join is accessed. If this is the case, it can possibly be used in an
|
|
index search for the nth table. */
|
|
static
|
|
ibool
|
|
opt_check_exp_determined_before(
|
|
/*============================*/
|
|
/* out: TRUE if already determined */
|
|
que_node_t* exp, /* in: expression */
|
|
sel_node_t* sel_node, /* in: select node */
|
|
ulint nth_table) /* in: nth table will be accessed */
|
|
{
|
|
func_node_t* func_node;
|
|
sym_node_t* sym_node;
|
|
dict_table_t* table;
|
|
que_node_t* arg;
|
|
ulint i;
|
|
|
|
ut_ad(exp && sel_node);
|
|
|
|
if (que_node_get_type(exp) == QUE_NODE_FUNC) {
|
|
func_node = exp;
|
|
|
|
arg = func_node->args;
|
|
|
|
while (arg) {
|
|
if (!opt_check_exp_determined_before(arg, sel_node,
|
|
nth_table)) {
|
|
return(FALSE);
|
|
}
|
|
|
|
arg = que_node_get_next(arg);
|
|
}
|
|
|
|
return(TRUE);
|
|
}
|
|
|
|
ut_a(que_node_get_type(exp) == QUE_NODE_SYMBOL);
|
|
|
|
sym_node = exp;
|
|
|
|
if (sym_node->token_type != SYM_COLUMN) {
|
|
|
|
return(TRUE);
|
|
}
|
|
|
|
for (i = 0; i < nth_table; i++) {
|
|
|
|
table = sel_node_get_nth_plan(sel_node, i)->table;
|
|
|
|
if (sym_node->table == table) {
|
|
|
|
return(TRUE);
|
|
}
|
|
}
|
|
|
|
return(FALSE);
|
|
}
|
|
|
|
/***********************************************************************
|
|
Looks in a comparison condition if a column value is already restricted by
|
|
it BEFORE the nth table is accessed. */
|
|
static
|
|
que_node_t*
|
|
opt_look_for_col_in_comparison_before(
|
|
/*==================================*/
|
|
/* out: expression restricting the
|
|
value of the column, or NULL if not
|
|
known */
|
|
ulint cmp_type, /* in: OPT_EQUAL, OPT_COMPARISON */
|
|
ulint col_no, /* in: column number */
|
|
func_node_t* search_cond, /* in: comparison condition */
|
|
sel_node_t* sel_node, /* in: select node */
|
|
ulint nth_table, /* in: nth table in a join (a query
|
|
from a single table is considered a
|
|
join of 1 table) */
|
|
ulint* op) /* out: comparison operator ('=',
|
|
PARS_GE_TOKEN, ... ); this is inverted
|
|
if the column appears on the right
|
|
side */
|
|
{
|
|
sym_node_t* sym_node;
|
|
dict_table_t* table;
|
|
que_node_t* exp;
|
|
que_node_t* arg;
|
|
|
|
ut_ad(search_cond);
|
|
|
|
ut_a((search_cond->func == '<')
|
|
|| (search_cond->func == '>')
|
|
|| (search_cond->func == '=')
|
|
|| (search_cond->func == PARS_GE_TOKEN)
|
|
|| (search_cond->func == PARS_LE_TOKEN));
|
|
|
|
table = sel_node_get_nth_plan(sel_node, nth_table)->table;
|
|
|
|
if ((cmp_type == OPT_EQUAL) && (search_cond->func != '=')) {
|
|
|
|
return(NULL);
|
|
|
|
} else if ((cmp_type == OPT_COMPARISON)
|
|
&& (search_cond->func != '<')
|
|
&& (search_cond->func != '>')
|
|
&& (search_cond->func != PARS_GE_TOKEN)
|
|
&& (search_cond->func != PARS_LE_TOKEN)) {
|
|
|
|
return(NULL);
|
|
}
|
|
|
|
arg = search_cond->args;
|
|
|
|
if (que_node_get_type(arg) == QUE_NODE_SYMBOL) {
|
|
sym_node = arg;
|
|
|
|
if ((sym_node->token_type == SYM_COLUMN)
|
|
&& (sym_node->table == table)
|
|
&& (sym_node->col_no == col_no)) {
|
|
|
|
/* sym_node contains the desired column id */
|
|
|
|
/* Check if the expression on the right side of the
|
|
operator is already determined */
|
|
|
|
exp = que_node_get_next(arg);
|
|
|
|
if (opt_check_exp_determined_before(exp, sel_node,
|
|
nth_table)) {
|
|
*op = search_cond->func;
|
|
|
|
return(exp);
|
|
}
|
|
}
|
|
}
|
|
|
|
exp = search_cond->args;
|
|
arg = que_node_get_next(arg);
|
|
|
|
if (que_node_get_type(arg) == QUE_NODE_SYMBOL) {
|
|
sym_node = arg;
|
|
|
|
if ((sym_node->token_type == SYM_COLUMN)
|
|
&& (sym_node->table == table)
|
|
&& (sym_node->col_no == col_no)) {
|
|
|
|
if (opt_check_exp_determined_before(exp, sel_node,
|
|
nth_table)) {
|
|
*op = opt_invert_cmp_op(search_cond->func);
|
|
|
|
return(exp);
|
|
}
|
|
}
|
|
}
|
|
|
|
return(NULL);
|
|
}
|
|
|
|
/***********************************************************************
|
|
Looks in a search condition if a column value is already restricted by the
|
|
search condition BEFORE the nth table is accessed. Takes into account that
|
|
if we will fetch in an ascending order, we cannot utilize an upper limit for
|
|
a column value; in a descending order, respectively, a lower limit. */
|
|
static
|
|
que_node_t*
|
|
opt_look_for_col_in_cond_before(
|
|
/*============================*/
|
|
/* out: expression restricting the
|
|
value of the column, or NULL if not
|
|
known */
|
|
ulint cmp_type, /* in: OPT_EQUAL, OPT_COMPARISON */
|
|
ulint col_no, /* in: column number */
|
|
func_node_t* search_cond, /* in: search condition or NULL */
|
|
sel_node_t* sel_node, /* in: select node */
|
|
ulint nth_table, /* in: nth table in a join (a query
|
|
from a single table is considered a
|
|
join of 1 table) */
|
|
ulint* op) /* out: comparison operator ('=',
|
|
PARS_GE_TOKEN, ... ) */
|
|
{
|
|
func_node_t* new_cond;
|
|
que_node_t* exp;
|
|
|
|
if (search_cond == NULL) {
|
|
|
|
return(NULL);
|
|
}
|
|
|
|
ut_a(que_node_get_type(search_cond) == QUE_NODE_FUNC);
|
|
ut_a(search_cond->func != PARS_OR_TOKEN);
|
|
ut_a(search_cond->func != PARS_NOT_TOKEN);
|
|
|
|
if (search_cond->func == PARS_AND_TOKEN) {
|
|
new_cond = search_cond->args;
|
|
|
|
exp = opt_look_for_col_in_cond_before(cmp_type, col_no,
|
|
new_cond, sel_node, nth_table, op);
|
|
if (exp) {
|
|
|
|
return(exp);
|
|
}
|
|
|
|
new_cond = que_node_get_next(new_cond);
|
|
|
|
exp = opt_look_for_col_in_cond_before(cmp_type, col_no,
|
|
new_cond, sel_node, nth_table, op);
|
|
return(exp);
|
|
}
|
|
|
|
exp = opt_look_for_col_in_comparison_before(cmp_type, col_no,
|
|
search_cond, sel_node, nth_table, op);
|
|
if (exp == NULL) {
|
|
|
|
return(NULL);
|
|
}
|
|
|
|
/* If we will fetch in an ascending order, we cannot utilize an upper
|
|
limit for a column value; in a descending order, respectively, a lower
|
|
limit */
|
|
|
|
if (sel_node->asc && ((*op == '<') || (*op == PARS_LE_TOKEN))) {
|
|
|
|
return(NULL);
|
|
|
|
} else if (!sel_node->asc && ((*op == '>') || (*op == PARS_GE_TOKEN))) {
|
|
|
|
return(NULL);
|
|
}
|
|
|
|
return(exp);
|
|
}
|
|
|
|
/***********************************************************************
|
|
Calculates the goodness for an index according to a select node. The
|
|
goodness is 4 times the number of first fields in index whose values we
|
|
already know exactly in the query. If we have a comparison condition for
|
|
an additional field, 2 point are added. If the index is unique, and we know
|
|
all the unique fields for the index we add 1024 points. For a clustered index
|
|
we add 1 point. */
|
|
static
|
|
ulint
|
|
opt_calc_index_goodness(
|
|
/*====================*/
|
|
/* out: goodness */
|
|
dict_index_t* index, /* in: index */
|
|
sel_node_t* sel_node, /* in: parsed select node */
|
|
ulint nth_table, /* in: nth table in a join */
|
|
que_node_t** index_plan, /* in/out: comparison expressions for
|
|
this index */
|
|
ulint* last_op) /* out: last comparison operator, if
|
|
goodness > 1 */
|
|
{
|
|
que_node_t* exp;
|
|
ulint goodness;
|
|
ulint n_fields;
|
|
ulint col_no;
|
|
ulint mix_id_col_no;
|
|
ulint op;
|
|
ulint j;
|
|
|
|
goodness = 0;
|
|
|
|
/* Note that as higher level node pointers in the B-tree contain
|
|
page addresses as the last field, we must not put more fields in
|
|
the search tuple than dict_index_get_n_unique_in_tree(index); see
|
|
the note in btr_cur_search_to_nth_level. */
|
|
|
|
n_fields = dict_index_get_n_unique_in_tree(index);
|
|
|
|
mix_id_col_no = dict_table_get_sys_col_no(index->table, DATA_MIX_ID);
|
|
|
|
for (j = 0; j < n_fields; j++) {
|
|
|
|
col_no = dict_index_get_nth_col_no(index, j);
|
|
|
|
exp = opt_look_for_col_in_cond_before(OPT_EQUAL, col_no,
|
|
sel_node->search_cond,
|
|
sel_node, nth_table, &op);
|
|
if (col_no == mix_id_col_no) {
|
|
ut_ad(exp == NULL);
|
|
|
|
index_plan[j] = NULL;
|
|
*last_op = '=';
|
|
goodness += 4;
|
|
} else if (exp) {
|
|
/* The value for this column is exactly known already
|
|
at this stage of the join */
|
|
|
|
index_plan[j] = exp;
|
|
*last_op = op;
|
|
goodness += 4;
|
|
} else {
|
|
/* Look for non-equality comparisons */
|
|
|
|
exp = opt_look_for_col_in_cond_before(OPT_COMPARISON,
|
|
col_no, sel_node->search_cond,
|
|
sel_node, nth_table, &op);
|
|
if (exp) {
|
|
index_plan[j] = exp;
|
|
*last_op = op;
|
|
goodness += 2;
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (goodness >= 4 * dict_index_get_n_unique(index)) {
|
|
goodness += 1024;
|
|
|
|
if (index->type & DICT_CLUSTERED) {
|
|
|
|
goodness += 1024;
|
|
}
|
|
}
|
|
|
|
/* We have to test for goodness here, as last_op may note be set */
|
|
if (goodness && index->type & DICT_CLUSTERED) {
|
|
|
|
goodness++;
|
|
}
|
|
|
|
return(goodness);
|
|
}
|
|
|
|
/***********************************************************************
|
|
Calculates the number of matched fields based on an index goodness. */
|
|
UNIV_INLINE
|
|
ulint
|
|
opt_calc_n_fields_from_goodness(
|
|
/*============================*/
|
|
/* out: number of excatly or partially matched
|
|
fields */
|
|
ulint goodness) /* in: goodness */
|
|
{
|
|
return(((goodness % 1024) + 2) / 4);
|
|
}
|
|
|
|
/***********************************************************************
|
|
Converts a comparison operator to the corresponding search mode PAGE_CUR_GE,
|
|
... */
|
|
UNIV_INLINE
|
|
ulint
|
|
opt_op_to_search_mode(
|
|
/*==================*/
|
|
/* out: search mode */
|
|
ibool asc, /* in: TRUE if the rows should be fetched in an
|
|
ascending order */
|
|
ulint op) /* in: operator '=', PARS_GE_TOKEN, ... */
|
|
{
|
|
if (op == '=') {
|
|
if (asc) {
|
|
return(PAGE_CUR_GE);
|
|
} else {
|
|
return(PAGE_CUR_LE);
|
|
}
|
|
} else if (op == '<') {
|
|
ut_a(!asc);
|
|
return(PAGE_CUR_L);
|
|
} else if (op == '>') {
|
|
ut_a(asc);
|
|
return(PAGE_CUR_G);
|
|
} else if (op == PARS_GE_TOKEN) {
|
|
ut_a(asc);
|
|
return(PAGE_CUR_GE);
|
|
} else if (op == PARS_LE_TOKEN) {
|
|
ut_a(!asc);
|
|
return(PAGE_CUR_LE);
|
|
} else {
|
|
ut_error;
|
|
}
|
|
|
|
return(0);
|
|
}
|
|
|
|
/***********************************************************************
|
|
Determines if a node is an argument node of a function node. */
|
|
static
|
|
ibool
|
|
opt_is_arg(
|
|
/*=======*/
|
|
/* out: TRUE if is an argument */
|
|
que_node_t* arg_node, /* in: possible argument node */
|
|
func_node_t* func_node) /* in: function node */
|
|
{
|
|
que_node_t* arg;
|
|
|
|
arg = func_node->args;
|
|
|
|
while (arg) {
|
|
if (arg == arg_node) {
|
|
|
|
return(TRUE);
|
|
}
|
|
|
|
arg = que_node_get_next(arg);
|
|
}
|
|
|
|
return(FALSE);
|
|
}
|
|
|
|
/***********************************************************************
|
|
Decides if the fetching of rows should be made in a descending order, and
|
|
also checks that the chosen query plan produces a result which satisfies
|
|
the order-by. */
|
|
static
|
|
void
|
|
opt_check_order_by(
|
|
/*===============*/
|
|
sel_node_t* sel_node) /* in: select node; asserts an error
|
|
if the plan does not agree with the
|
|
order-by */
|
|
{
|
|
order_node_t* order_node;
|
|
dict_table_t* order_table;
|
|
ulint order_col_no;
|
|
plan_t* plan;
|
|
ulint i;
|
|
|
|
if (!sel_node->order_by) {
|
|
|
|
return;
|
|
}
|
|
|
|
order_node = sel_node->order_by;
|
|
order_col_no = order_node->column->col_no;
|
|
order_table = order_node->column->table;
|
|
|
|
/* If there is an order-by clause, the first non-exactly matched field
|
|
in the index used for the last table in the table list should be the
|
|
column defined in the order-by clause, and for all the other tables
|
|
we should get only at most a single row, otherwise we cannot presently
|
|
calculate the order-by, as we have no sort utility */
|
|
|
|
for (i = 0; i < sel_node->n_tables; i++) {
|
|
|
|
plan = sel_node_get_nth_plan(sel_node, i);
|
|
|
|
if (i < sel_node->n_tables - 1) {
|
|
ut_a(dict_index_get_n_unique(plan->index)
|
|
<= plan->n_exact_match);
|
|
} else {
|
|
ut_a(plan->table == order_table);
|
|
|
|
ut_a((dict_index_get_n_unique(plan->index)
|
|
<= plan->n_exact_match)
|
|
|| (dict_index_get_nth_col_no(plan->index,
|
|
plan->n_exact_match)
|
|
== order_col_no));
|
|
}
|
|
}
|
|
}
|
|
|
|
/***********************************************************************
|
|
Optimizes a select. Decides which indexes to tables to use. The tables
|
|
are accessed in the order that they were written to the FROM part in the
|
|
select statement. */
|
|
static
|
|
void
|
|
opt_search_plan_for_table(
|
|
/*======================*/
|
|
sel_node_t* sel_node, /* in: parsed select node */
|
|
ulint i, /* in: this is the ith table */
|
|
dict_table_t* table) /* in: table */
|
|
{
|
|
plan_t* plan;
|
|
dict_index_t* index;
|
|
dict_index_t* best_index;
|
|
ulint n_fields;
|
|
ulint goodness;
|
|
ulint last_op = 75946965; /* Eliminate a Purify
|
|
warning */
|
|
ulint best_goodness;
|
|
ulint best_last_op = 0; /* remove warning */
|
|
ulint mix_id_pos;
|
|
que_node_t* index_plan[256];
|
|
que_node_t* best_index_plan[256];
|
|
|
|
plan = sel_node_get_nth_plan(sel_node, i);
|
|
|
|
plan->table = table;
|
|
plan->asc = sel_node->asc;
|
|
plan->pcur_is_open = FALSE;
|
|
plan->cursor_at_end = FALSE;
|
|
|
|
/* Calculate goodness for each index of the table */
|
|
|
|
index = dict_table_get_first_index(table);
|
|
best_index = index; /* Eliminate compiler warning */
|
|
best_goodness = 0;
|
|
|
|
/* should be do ... until ? comment by Jani */
|
|
while (index) {
|
|
goodness = opt_calc_index_goodness(index, sel_node, i,
|
|
index_plan, &last_op);
|
|
if (goodness > best_goodness) {
|
|
|
|
best_index = index;
|
|
best_goodness = goodness;
|
|
n_fields = opt_calc_n_fields_from_goodness(goodness);
|
|
|
|
ut_memcpy(best_index_plan, index_plan,
|
|
n_fields * sizeof(void*));
|
|
best_last_op = last_op;
|
|
}
|
|
|
|
index = dict_table_get_next_index(index);
|
|
}
|
|
|
|
plan->index = best_index;
|
|
|
|
n_fields = opt_calc_n_fields_from_goodness(best_goodness);
|
|
|
|
if (n_fields == 0) {
|
|
plan->tuple = NULL;
|
|
plan->n_exact_match = 0;
|
|
} else {
|
|
plan->tuple = dtuple_create(pars_sym_tab_global->heap,
|
|
n_fields);
|
|
dict_index_copy_types(plan->tuple, plan->index, n_fields);
|
|
|
|
plan->tuple_exps = mem_heap_alloc(pars_sym_tab_global->heap,
|
|
n_fields * sizeof(void*));
|
|
|
|
ut_memcpy(plan->tuple_exps, best_index_plan,
|
|
n_fields * sizeof(void*));
|
|
if (best_last_op == '=') {
|
|
plan->n_exact_match = n_fields;
|
|
} else {
|
|
plan->n_exact_match = n_fields - 1;
|
|
}
|
|
|
|
plan->mode = opt_op_to_search_mode(sel_node->asc,
|
|
best_last_op);
|
|
}
|
|
|
|
if ((best_index->type & DICT_CLUSTERED)
|
|
&& (plan->n_exact_match >= dict_index_get_n_unique(best_index))) {
|
|
|
|
plan->unique_search = TRUE;
|
|
} else {
|
|
plan->unique_search = FALSE;
|
|
}
|
|
|
|
if ((table->type != DICT_TABLE_ORDINARY)
|
|
&& (best_index->type & DICT_CLUSTERED)) {
|
|
|
|
plan->mixed_index = TRUE;
|
|
|
|
mix_id_pos = table->mix_len;
|
|
|
|
if (mix_id_pos < n_fields) {
|
|
/* We have to add the mix id as a (string) literal
|
|
expression to the tuple_exps */
|
|
|
|
plan->tuple_exps[mix_id_pos] =
|
|
sym_tab_add_str_lit(pars_sym_tab_global,
|
|
table->mix_id_buf,
|
|
table->mix_id_len);
|
|
}
|
|
} else {
|
|
plan->mixed_index = FALSE;
|
|
}
|
|
|
|
plan->old_vers_heap = NULL;
|
|
|
|
btr_pcur_init(&(plan->pcur));
|
|
btr_pcur_init(&(plan->clust_pcur));
|
|
}
|
|
|
|
/***********************************************************************
|
|
Looks at a comparison condition and decides if it can, and need, be tested for
|
|
a table AFTER the table has been accessed. */
|
|
static
|
|
ulint
|
|
opt_classify_comparison(
|
|
/*====================*/
|
|
/* out: OPT_NOT_COND if not for this
|
|
table, else OPT_END_COND,
|
|
OPT_TEST_COND, or OPT_SCROLL_COND,
|
|
where the last means that the
|
|
condition need not be tested, except
|
|
when scroll cursors are used */
|
|
sel_node_t* sel_node, /* in: select node */
|
|
ulint i, /* in: ith table in the join */
|
|
func_node_t* cond) /* in: comparison condition */
|
|
{
|
|
plan_t* plan;
|
|
ulint n_fields;
|
|
ulint op;
|
|
ulint j;
|
|
|
|
ut_ad(cond && sel_node);
|
|
|
|
plan = sel_node_get_nth_plan(sel_node, i);
|
|
|
|
/* Check if the condition is determined after the ith table has been
|
|
accessed, but not after the i - 1:th */
|
|
|
|
if (!opt_check_exp_determined_before(cond, sel_node, i + 1)) {
|
|
|
|
return(OPT_NOT_COND);
|
|
}
|
|
|
|
if ((i > 0) && opt_check_exp_determined_before(cond, sel_node, i)) {
|
|
|
|
return(OPT_NOT_COND);
|
|
}
|
|
|
|
/* If the condition is an exact match condition used in constructing
|
|
the search tuple, it is classified as OPT_END_COND */
|
|
|
|
if (plan->tuple) {
|
|
n_fields = dtuple_get_n_fields(plan->tuple);
|
|
} else {
|
|
n_fields = 0;
|
|
}
|
|
|
|
for (j = 0; j < plan->n_exact_match; j++) {
|
|
|
|
if (opt_is_arg(plan->tuple_exps[j], cond)) {
|
|
|
|
return(OPT_END_COND);
|
|
}
|
|
}
|
|
|
|
/* If the condition is an non-exact match condition used in
|
|
constructing the search tuple, it is classified as OPT_SCROLL_COND.
|
|
When the cursor is positioned, and if a non-scroll cursor is used,
|
|
there is no need to test this condition; if a scroll cursor is used
|
|
the testing is necessary when the cursor is reversed. */
|
|
|
|
if ((n_fields > plan->n_exact_match)
|
|
&& opt_is_arg(plan->tuple_exps[n_fields - 1], cond)) {
|
|
|
|
return(OPT_SCROLL_COND);
|
|
}
|
|
|
|
/* If the condition is a non-exact match condition on the first field
|
|
in index for which there is no exact match, and it limits the search
|
|
range from the opposite side of the search tuple already BEFORE we
|
|
access the table, it is classified as OPT_END_COND */
|
|
|
|
if ((dict_index_get_n_fields(plan->index) > plan->n_exact_match)
|
|
&& opt_look_for_col_in_comparison_before(
|
|
OPT_COMPARISON,
|
|
dict_index_get_nth_col_no(plan->index,
|
|
plan->n_exact_match),
|
|
cond, sel_node, i, &op)) {
|
|
|
|
if (sel_node->asc && ((op == '<') || (op == PARS_LE_TOKEN))) {
|
|
|
|
return(OPT_END_COND);
|
|
}
|
|
|
|
if (!sel_node->asc && ((op == '>') || (op == PARS_GE_TOKEN))) {
|
|
|
|
return(OPT_END_COND);
|
|
}
|
|
}
|
|
|
|
/* Otherwise, cond is classified as OPT_TEST_COND */
|
|
|
|
return(OPT_TEST_COND);
|
|
}
|
|
|
|
/***********************************************************************
|
|
Recursively looks for test conditions for a table in a join. */
|
|
static
|
|
void
|
|
opt_find_test_conds(
|
|
/*================*/
|
|
sel_node_t* sel_node, /* in: select node */
|
|
ulint i, /* in: ith table in the join */
|
|
func_node_t* cond) /* in: conjunction of search
|
|
conditions or NULL */
|
|
{
|
|
func_node_t* new_cond;
|
|
ulint class;
|
|
plan_t* plan;
|
|
|
|
if (cond == NULL) {
|
|
|
|
return;
|
|
}
|
|
|
|
if (cond->func == PARS_AND_TOKEN) {
|
|
new_cond = cond->args;
|
|
|
|
opt_find_test_conds(sel_node, i, new_cond);
|
|
|
|
new_cond = que_node_get_next(new_cond);
|
|
|
|
opt_find_test_conds(sel_node, i, new_cond);
|
|
|
|
return;
|
|
}
|
|
|
|
plan = sel_node_get_nth_plan(sel_node, i);
|
|
|
|
class = opt_classify_comparison(sel_node, i, cond);
|
|
|
|
if (class == OPT_END_COND) {
|
|
UT_LIST_ADD_LAST(cond_list, plan->end_conds, cond);
|
|
|
|
} else if (class == OPT_TEST_COND) {
|
|
UT_LIST_ADD_LAST(cond_list, plan->other_conds, cond);
|
|
|
|
}
|
|
}
|
|
|
|
/***********************************************************************
|
|
Normalizes a list of comparison conditions so that a column of the table
|
|
appears on the left side of the comparison if possible. This is accomplished
|
|
by switching the arguments of the operator. */
|
|
static
|
|
void
|
|
opt_normalize_cmp_conds(
|
|
/*====================*/
|
|
func_node_t* cond, /* in: first in a list of comparison
|
|
conditions, or NULL */
|
|
dict_table_t* table) /* in: table */
|
|
{
|
|
que_node_t* arg1;
|
|
que_node_t* arg2;
|
|
sym_node_t* sym_node;
|
|
|
|
while (cond) {
|
|
arg1 = cond->args;
|
|
arg2 = que_node_get_next(arg1);
|
|
|
|
if (que_node_get_type(arg2) == QUE_NODE_SYMBOL) {
|
|
|
|
sym_node = arg2;
|
|
|
|
if ((sym_node->token_type == SYM_COLUMN)
|
|
&& (sym_node->table == table)) {
|
|
|
|
/* Switch the order of the arguments */
|
|
|
|
cond->args = arg2;
|
|
que_node_list_add_last(NULL, arg2);
|
|
que_node_list_add_last(arg2, arg1);
|
|
|
|
/* Invert the operator */
|
|
cond->func = opt_invert_cmp_op(cond->func);
|
|
}
|
|
}
|
|
|
|
cond = UT_LIST_GET_NEXT(cond_list, cond);
|
|
}
|
|
}
|
|
|
|
/***********************************************************************
|
|
Finds out the search condition conjuncts we can, and need, to test as the ith
|
|
table in a join is accessed. The search tuple can eliminate the need to test
|
|
some conjuncts. */
|
|
static
|
|
void
|
|
opt_determine_and_normalize_test_conds(
|
|
/*===================================*/
|
|
sel_node_t* sel_node, /* in: select node */
|
|
ulint i) /* in: ith table in the join */
|
|
{
|
|
plan_t* plan;
|
|
|
|
plan = sel_node_get_nth_plan(sel_node, i);
|
|
|
|
UT_LIST_INIT(plan->end_conds);
|
|
UT_LIST_INIT(plan->other_conds);
|
|
|
|
/* Recursively go through the conjuncts and classify them */
|
|
|
|
opt_find_test_conds(sel_node, i, sel_node->search_cond);
|
|
|
|
opt_normalize_cmp_conds(UT_LIST_GET_FIRST(plan->end_conds),
|
|
plan->table);
|
|
|
|
ut_a(UT_LIST_GET_LEN(plan->end_conds) >= plan->n_exact_match);
|
|
}
|
|
|
|
/***********************************************************************
|
|
Looks for occurrences of the columns of the table in the query subgraph and
|
|
adds them to the list of columns if an occurrence of the same column does not
|
|
already exist in the list. If the column is already in the list, puts a value
|
|
indirection to point to the occurrence in the column list, except if the
|
|
column occurrence we are looking at is in the column list, in which case
|
|
nothing is done. */
|
|
|
|
void
|
|
opt_find_all_cols(
|
|
/*==============*/
|
|
ibool copy_val, /* in: if TRUE, new found columns are
|
|
added as columns to copy */
|
|
dict_index_t* index, /* in: index of the table to use */
|
|
sym_node_list_t* col_list, /* in: base node of a list where
|
|
to add new found columns */
|
|
plan_t* plan, /* in: plan or NULL */
|
|
que_node_t* exp) /* in: expression or condition or
|
|
NULL */
|
|
{
|
|
func_node_t* func_node;
|
|
que_node_t* arg;
|
|
sym_node_t* sym_node;
|
|
sym_node_t* col_node;
|
|
ulint col_pos;
|
|
|
|
if (exp == NULL) {
|
|
|
|
return;
|
|
}
|
|
|
|
if (que_node_get_type(exp) == QUE_NODE_FUNC) {
|
|
func_node = exp;
|
|
|
|
arg = func_node->args;
|
|
|
|
while (arg) {
|
|
opt_find_all_cols(copy_val, index, col_list, plan,
|
|
arg);
|
|
arg = que_node_get_next(arg);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
ut_a(que_node_get_type(exp) == QUE_NODE_SYMBOL);
|
|
|
|
sym_node = exp;
|
|
|
|
if (sym_node->token_type != SYM_COLUMN) {
|
|
|
|
return;
|
|
}
|
|
|
|
if (sym_node->table != index->table) {
|
|
|
|
return;
|
|
}
|
|
|
|
/* Look for an occurrence of the same column in the plan column
|
|
list */
|
|
|
|
col_node = UT_LIST_GET_FIRST(*col_list);
|
|
|
|
while (col_node) {
|
|
if (col_node->col_no == sym_node->col_no) {
|
|
|
|
if (col_node == sym_node) {
|
|
/* sym_node was already in a list: do
|
|
nothing */
|
|
|
|
return;
|
|
}
|
|
|
|
/* Put an indirection */
|
|
sym_node->indirection = col_node;
|
|
sym_node->alias = col_node;
|
|
|
|
return;
|
|
}
|
|
|
|
col_node = UT_LIST_GET_NEXT(col_var_list, col_node);
|
|
}
|
|
|
|
/* The same column did not occur in the list: add it */
|
|
|
|
UT_LIST_ADD_LAST(col_var_list, *col_list, sym_node);
|
|
|
|
sym_node->copy_val = copy_val;
|
|
|
|
/* Fill in the field_no fields in sym_node */
|
|
|
|
sym_node->field_nos[SYM_CLUST_FIELD_NO]
|
|
= dict_index_get_nth_col_pos(
|
|
dict_table_get_first_index(index->table),
|
|
sym_node->col_no);
|
|
if (!(index->type & DICT_CLUSTERED)) {
|
|
|
|
ut_a(plan);
|
|
|
|
col_pos = dict_index_get_nth_col_pos(index, sym_node->col_no);
|
|
|
|
if (col_pos == ULINT_UNDEFINED) {
|
|
|
|
plan->must_get_clust = TRUE;
|
|
}
|
|
|
|
sym_node->field_nos[SYM_SEC_FIELD_NO] = col_pos;
|
|
}
|
|
}
|
|
|
|
/***********************************************************************
|
|
Looks for occurrences of the columns of the table in conditions which are
|
|
not yet determined AFTER the join operation has fetched a row in the ith
|
|
table. The values for these column must be copied to dynamic memory for
|
|
later use. */
|
|
static
|
|
void
|
|
opt_find_copy_cols(
|
|
/*===============*/
|
|
sel_node_t* sel_node, /* in: select node */
|
|
ulint i, /* in: ith table in the join */
|
|
func_node_t* search_cond) /* in: search condition or NULL */
|
|
{
|
|
func_node_t* new_cond;
|
|
plan_t* plan;
|
|
|
|
if (search_cond == NULL) {
|
|
|
|
return;
|
|
}
|
|
|
|
ut_ad(que_node_get_type(search_cond) == QUE_NODE_FUNC);
|
|
|
|
if (search_cond->func == PARS_AND_TOKEN) {
|
|
new_cond = search_cond->args;
|
|
|
|
opt_find_copy_cols(sel_node, i, new_cond);
|
|
|
|
new_cond = que_node_get_next(new_cond);
|
|
|
|
opt_find_copy_cols(sel_node, i, new_cond);
|
|
|
|
return;
|
|
}
|
|
|
|
if (!opt_check_exp_determined_before(search_cond, sel_node, i + 1)) {
|
|
|
|
/* Any ith table columns occurring in search_cond should be
|
|
copied, as this condition cannot be tested already on the
|
|
fetch from the ith table */
|
|
|
|
plan = sel_node_get_nth_plan(sel_node, i);
|
|
|
|
opt_find_all_cols(TRUE, plan->index, &(plan->columns), plan,
|
|
search_cond);
|
|
}
|
|
}
|
|
|
|
/***********************************************************************
|
|
Classifies the table columns according to whether we use the column only while
|
|
holding the latch on the page, or whether we have to copy the column value to
|
|
dynamic memory. Puts the first occurrence of a column to either list in the
|
|
plan node, and puts indirections to later occurrences of the column. */
|
|
static
|
|
void
|
|
opt_classify_cols(
|
|
/*==============*/
|
|
sel_node_t* sel_node, /* in: select node */
|
|
ulint i) /* in: ith table in the join */
|
|
{
|
|
plan_t* plan;
|
|
que_node_t* exp;
|
|
|
|
plan = sel_node_get_nth_plan(sel_node, i);
|
|
|
|
/* The final value of the following field will depend on the
|
|
environment of the select statement: */
|
|
|
|
plan->must_get_clust = FALSE;
|
|
|
|
UT_LIST_INIT(plan->columns);
|
|
|
|
/* All select list columns should be copied: therefore TRUE as the
|
|
first argument */
|
|
|
|
exp = sel_node->select_list;
|
|
|
|
while (exp) {
|
|
opt_find_all_cols(TRUE, plan->index, &(plan->columns), plan,
|
|
exp);
|
|
exp = que_node_get_next(exp);
|
|
}
|
|
|
|
opt_find_copy_cols(sel_node, i, sel_node->search_cond);
|
|
|
|
/* All remaining columns in the search condition are temporary
|
|
columns: therefore FALSE */
|
|
|
|
opt_find_all_cols(FALSE, plan->index, &(plan->columns), plan,
|
|
sel_node->search_cond);
|
|
}
|
|
|
|
/***********************************************************************
|
|
Fills in the info in plan which is used in accessing a clustered index
|
|
record. The columns must already be classified for the plan node. */
|
|
static
|
|
void
|
|
opt_clust_access(
|
|
/*=============*/
|
|
sel_node_t* sel_node, /* in: select node */
|
|
ulint n) /* in: nth table in select */
|
|
{
|
|
plan_t* plan;
|
|
dict_table_t* table;
|
|
dict_index_t* clust_index;
|
|
dict_index_t* index;
|
|
dfield_t* dfield;
|
|
mem_heap_t* heap;
|
|
ulint n_fields;
|
|
ulint pos;
|
|
ulint i;
|
|
|
|
plan = sel_node_get_nth_plan(sel_node, n);
|
|
|
|
index = plan->index;
|
|
|
|
/* The final value of the following field depends on the environment
|
|
of the select statement: */
|
|
|
|
plan->no_prefetch = FALSE;
|
|
|
|
if (index->type & DICT_CLUSTERED) {
|
|
plan->clust_map = NULL;
|
|
plan->clust_ref = NULL;
|
|
|
|
return;
|
|
}
|
|
|
|
table = index->table;
|
|
|
|
clust_index = dict_table_get_first_index(table);
|
|
|
|
n_fields = dict_index_get_n_unique(clust_index);
|
|
|
|
heap = pars_sym_tab_global->heap;
|
|
|
|
plan->clust_ref = dtuple_create(heap, n_fields);
|
|
|
|
dict_index_copy_types(plan->clust_ref, clust_index, n_fields);
|
|
|
|
plan->clust_map = mem_heap_alloc(heap, n_fields * sizeof(ulint));
|
|
|
|
for (i = 0; i < n_fields; i++) {
|
|
pos = dict_index_get_nth_field_pos(index, clust_index, i);
|
|
|
|
ut_a(pos != ULINT_UNDEFINED);
|
|
|
|
/* We optimize here only queries to InnoDB's internal system
|
|
tables, and they should not contain column prefix indexes. */
|
|
|
|
if (dict_index_get_nth_field(index, pos)->prefix_len != 0
|
|
|| dict_index_get_nth_field(clust_index, i)
|
|
->prefix_len != 0) {
|
|
fprintf(stderr,
|
|
"InnoDB: Error in pars0opt.c: table %s has prefix_len != 0\n",
|
|
index->table_name);
|
|
}
|
|
|
|
*(plan->clust_map + i) = pos;
|
|
|
|
ut_ad((pos != ULINT_UNDEFINED)
|
|
|| ((table->type == DICT_TABLE_CLUSTER_MEMBER)
|
|
&& (i == table->mix_len)));
|
|
}
|
|
|
|
if (table->type == DICT_TABLE_CLUSTER_MEMBER) {
|
|
|
|
/* Preset the mix id field to the mix id constant */
|
|
|
|
dfield = dtuple_get_nth_field(plan->clust_ref, table->mix_len);
|
|
|
|
dfield_set_data(dfield, mem_heap_alloc(heap,
|
|
table->mix_id_len),
|
|
table->mix_id_len);
|
|
ut_memcpy(dfield_get_data(dfield), table->mix_id_buf,
|
|
table->mix_id_len);
|
|
}
|
|
}
|
|
|
|
/***********************************************************************
|
|
Optimizes a select. Decides which indexes to tables to use. The tables
|
|
are accessed in the order that they were written to the FROM part in the
|
|
select statement. */
|
|
|
|
void
|
|
opt_search_plan(
|
|
/*============*/
|
|
sel_node_t* sel_node) /* in: parsed select node */
|
|
{
|
|
sym_node_t* table_node;
|
|
dict_table_t* table;
|
|
order_node_t* order_by;
|
|
ulint i;
|
|
|
|
sel_node->plans = mem_heap_alloc(pars_sym_tab_global->heap,
|
|
sel_node->n_tables * sizeof(plan_t));
|
|
|
|
/* Analyze the search condition to find out what we know at each
|
|
join stage about the conditions that the columns of a table should
|
|
satisfy */
|
|
|
|
table_node = sel_node->table_list;
|
|
|
|
if (sel_node->order_by == NULL) {
|
|
sel_node->asc = TRUE;
|
|
} else {
|
|
order_by = sel_node->order_by;
|
|
|
|
sel_node->asc = order_by->asc;
|
|
}
|
|
|
|
for (i = 0; i < sel_node->n_tables; i++) {
|
|
|
|
table = table_node->table;
|
|
|
|
/* Choose index through which to access the table */
|
|
|
|
opt_search_plan_for_table(sel_node, i, table);
|
|
|
|
/* Determine the search condition conjuncts we can test at
|
|
this table; normalize the end conditions */
|
|
|
|
opt_determine_and_normalize_test_conds(sel_node, i);
|
|
|
|
table_node = que_node_get_next(table_node);
|
|
}
|
|
|
|
table_node = sel_node->table_list;
|
|
|
|
for (i = 0; i < sel_node->n_tables; i++) {
|
|
|
|
/* Classify the table columns into those we only need to access
|
|
but not copy, and to those we must copy to dynamic memory */
|
|
|
|
opt_classify_cols(sel_node, i);
|
|
|
|
/* Calculate possible info for accessing the clustered index
|
|
record */
|
|
|
|
opt_clust_access(sel_node, i);
|
|
|
|
table_node = que_node_get_next(table_node);
|
|
}
|
|
|
|
/* Check that the plan obeys a possible order-by clause: if not,
|
|
an assertion error occurs */
|
|
|
|
opt_check_order_by(sel_node);
|
|
|
|
#ifdef UNIV_SQL_DEBUG
|
|
opt_print_query_plan(sel_node);
|
|
#endif
|
|
}
|
|
|
|
/************************************************************************
|
|
Prints info of a query plan. */
|
|
|
|
void
|
|
opt_print_query_plan(
|
|
/*=================*/
|
|
sel_node_t* sel_node) /* in: select node */
|
|
{
|
|
plan_t* plan;
|
|
ulint n_fields;
|
|
ulint i;
|
|
|
|
fputs("QUERY PLAN FOR A SELECT NODE\n", stderr);
|
|
|
|
fputs(sel_node->asc ? "Asc. search; " : "Desc. search; ", stderr);
|
|
|
|
if (sel_node->set_x_locks) {
|
|
fputs("sets row x-locks; ", stderr);
|
|
ut_a(sel_node->row_lock_mode == LOCK_X);
|
|
ut_a(!sel_node->consistent_read);
|
|
} else if (sel_node->consistent_read) {
|
|
fputs("consistent read; ", stderr);
|
|
} else {
|
|
ut_a(sel_node->row_lock_mode == LOCK_S);
|
|
fputs("sets row s-locks; ", stderr);
|
|
}
|
|
|
|
putc('\n', stderr);
|
|
|
|
for (i = 0; i < sel_node->n_tables; i++) {
|
|
plan = sel_node_get_nth_plan(sel_node, i);
|
|
|
|
if (plan->tuple) {
|
|
n_fields = dtuple_get_n_fields(plan->tuple);
|
|
} else {
|
|
n_fields = 0;
|
|
}
|
|
|
|
fputs("Table ", stderr);
|
|
dict_index_name_print(stderr, NULL, plan->index);
|
|
fprintf(stderr,"; exact m. %lu, match %lu, end conds %lu\n",
|
|
(unsigned long) plan->n_exact_match,
|
|
(unsigned long) n_fields,
|
|
(unsigned long) UT_LIST_GET_LEN(plan->end_conds));
|
|
}
|
|
}
|