summaryrefslogtreecommitdiffstats
path: root/storage/innobase/pars/pars0opt.cc
diff options
context:
space:
mode:
Diffstat (limited to 'storage/innobase/pars/pars0opt.cc')
-rw-r--r--storage/innobase/pars/pars0opt.cc1263
1 files changed, 1263 insertions, 0 deletions
diff --git a/storage/innobase/pars/pars0opt.cc b/storage/innobase/pars/pars0opt.cc
new file mode 100644
index 00000000..44949ad0
--- /dev/null
+++ b/storage/innobase/pars/pars0opt.cc
@@ -0,0 +1,1263 @@
+/*****************************************************************************
+
+Copyright (c) 1997, 2016, Oracle and/or its affiliates. All Rights Reserved.
+Copyright (c) 2019, 2022, MariaDB Corporation.
+
+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
+
+*****************************************************************************/
+
+/**************************************************//**
+@file pars/pars0opt.cc
+Simple SQL optimizer
+
+Created 12/21/1997 Heikki Tuuri
+*******************************************************/
+
+#include "pars0opt.h"
+#include "row0sel.h"
+#include "row0ins.h"
+#include "row0upd.h"
+#include "dict0boot.h"
+#include "dict0dict.h"
+#include "dict0mem.h"
+#include "que0que.h"
+#include "pars0grm.h"
+#include "pars0pars.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.
+@return the equivalent operator when the order of the arguments is switched */
+static
+int
+opt_invert_cmp_op(
+/*==============*/
+ 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 {
+ /* TODO: LIKE operator */
+ 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.
+@return TRUE if already determined */
+static
+ibool
+opt_check_exp_determined_before(
+/*============================*/
+ 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 = static_cast<func_node_t*>(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 = static_cast<sym_node_t*>(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.
+@return expression restricting the value of the column, or NULL if not known */
+static
+que_node_t*
+opt_look_for_col_in_comparison_before(
+/*==================================*/
+ 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)
+ || (search_cond->func == PARS_LIKE_TOKEN_EXACT)
+ || (search_cond->func == PARS_LIKE_TOKEN_PREFIX)
+ || (search_cond->func == PARS_LIKE_TOKEN_SUFFIX)
+ || (search_cond->func == PARS_LIKE_TOKEN_SUBSTR));
+
+ table = sel_node_get_nth_plan(sel_node, nth_table)->table;
+
+ if ((cmp_type == OPT_EQUAL)
+ && (search_cond->func != '=')
+ && (search_cond->func != PARS_LIKE_TOKEN_EXACT)
+ && (search_cond->func != PARS_LIKE_TOKEN_PREFIX)) {
+
+ 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)
+ && (search_cond->func != PARS_LIKE_TOKEN_PREFIX)
+ && (search_cond->func != PARS_LIKE_TOKEN_SUFFIX)) {
+
+ return(NULL);
+ }
+
+ arg = search_cond->args;
+
+ if (que_node_get_type(arg) == QUE_NODE_SYMBOL) {
+ sym_node = static_cast<sym_node_t*>(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 = ulint(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 = static_cast<sym_node_t*>(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 = ulint(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.
+@return expression restricting the value of the column, or NULL if not known */
+static
+que_node_t*
+opt_look_for_col_in_cond_before(
+/*============================*/
+ 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 = static_cast<func_node_t*>(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 = static_cast<func_node_t*>(
+ 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.
+@return goodness */
+static
+ulint
+opt_calc_index_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 op;
+ ulint j;
+
+ /* At least for now we don't support using FTS indexes for queries
+ done through InnoDB's own SQL parser. */
+ if (dict_index_is_online_ddl(index) || (index->type & DICT_FTS)) {
+ return(0);
+ }
+
+ 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);
+
+ for (j = 0; j < n_fields; j++) {
+ if (UNIV_UNLIKELY(index->fields[j].descending)) {
+ /* The internal InnoDB SQL parser does not
+ work with indexes that use DESC order. */
+ return 0;
+ }
+
+ col_no = dict_index_get_nth_col_no(index, j);
+
+ exp = opt_look_for_col_in_cond_before(
+ OPT_EQUAL, col_no,
+ static_cast<func_node_t*>(sel_node->search_cond),
+ sel_node, nth_table, &op);
+ 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,
+ static_cast<func_node_t*>(
+ 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 (dict_index_is_clust(index)) {
+
+ goodness += 1024;
+ }
+ }
+
+ /* We have to test for goodness here, as last_op may not be set */
+ if (goodness && dict_index_is_clust(index)) {
+
+ goodness++;
+ }
+
+ return(goodness);
+}
+
+/*******************************************************************//**
+Calculates the number of matched fields based on an index goodness.
+@return number of excatly or partially matched fields */
+UNIV_INLINE
+ulint
+opt_calc_n_fields_from_goodness(
+/*============================*/
+ ulint goodness) /*!< in: goodness */
+{
+ return(((goodness % 1024) + 2) / 4);
+}
+
+/*******************************************************************//**
+Converts a comparison operator to the corresponding search mode PAGE_CUR_GE,
+...
+@return search mode */
+UNIV_INLINE
+page_cur_mode_t
+opt_op_to_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 == '='
+ || op == PARS_LIKE_TOKEN_EXACT
+ || op == PARS_LIKE_TOKEN_PREFIX
+ || op == PARS_LIKE_TOKEN_SUFFIX
+ || op == PARS_LIKE_TOKEN_SUBSTR) {
+
+ 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(PAGE_CUR_UNSUPP);
+}
+
+/*******************************************************************//**
+Determines if a node is an argument node of a function node.
+@return TRUE if is an argument */
+static
+ibool
+opt_is_arg(
+/*=======*/
+ 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;
+ ulint n_fields;
+ ulint best_last_op;
+ 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 */
+
+ plan->index = index = dict_table_get_first_index(table);
+ ulint best_goodness = opt_calc_index_goodness(
+ index, sel_node, i, best_index_plan, &best_last_op);
+
+ while ((index = dict_table_get_next_index(index))) {
+ if (!index->is_btree()) {
+ continue;
+ }
+ ulint last_op;
+ ulint goodness = opt_calc_index_goodness(index, sel_node, i,
+ index_plan, &last_op);
+ if (goodness > best_goodness) {
+ best_goodness = goodness;
+ plan->index = index;
+ n_fields = opt_calc_n_fields_from_goodness(goodness);
+
+ memcpy(best_index_plan, index_plan,
+ n_fields * sizeof *index_plan);
+ best_last_op = last_op;
+ }
+ }
+
+ 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 = static_cast<que_node_t**>(
+ mem_heap_alloc(
+ pars_sym_tab_global->heap,
+ n_fields * sizeof(void*)));
+
+ memcpy(plan->tuple_exps, best_index_plan,
+ n_fields * sizeof *best_index_plan);
+
+ switch (best_last_op) {
+ case '=':
+ case PARS_LIKE_TOKEN_EXACT:
+ case PARS_LIKE_TOKEN_PREFIX:
+ case PARS_LIKE_TOKEN_SUFFIX:
+ case PARS_LIKE_TOKEN_SUBSTR:
+ break;
+ default:
+ n_fields--;
+ }
+
+ plan->n_exact_match = n_fields;
+ plan->mode = opt_op_to_search_mode(sel_node->asc,
+ best_last_op);
+ }
+
+ plan->unique_search = plan->index->is_clust()
+ && plan->n_exact_match >= plan->index->n_uniq;
+
+ 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.
+@return 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 */
+static
+ulint
+opt_classify_comparison(
+/*====================*/
+ 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 fclass;
+ plan_t* plan;
+
+ if (cond == NULL) {
+
+ return;
+ }
+
+ if (cond->func == PARS_AND_TOKEN) {
+ new_cond = static_cast<func_node_t*>(cond->args);
+
+ opt_find_test_conds(sel_node, i, new_cond);
+
+ new_cond = static_cast<func_node_t*>(
+ 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);
+
+ fclass = opt_classify_comparison(sel_node, i, cond);
+
+ if (fclass == OPT_END_COND) {
+ UT_LIST_ADD_LAST(plan->end_conds, cond);
+
+ } else if (fclass == OPT_TEST_COND) {
+ UT_LIST_ADD_LAST(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 = static_cast<sym_node_t*>(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, &func_node_t::cond_list);
+ UT_LIST_INIT(plan->other_conds, &func_node_t::cond_list);
+
+ /* Recursively go through the conjuncts and classify them */
+
+ opt_find_test_conds(
+ sel_node,
+ i,
+ static_cast<func_node_t*>(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 = static_cast<func_node_t*>(exp);
+
+ for (arg = func_node->args;
+ arg != 0;
+ arg = que_node_get_next(arg)) {
+
+ opt_find_all_cols(
+ copy_val, index, col_list, plan, arg);
+ }
+
+ return;
+ }
+
+ ut_a(que_node_get_type(exp) == QUE_NODE_SYMBOL);
+
+ sym_node = static_cast<sym_node_t*>(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_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,
+ NULL);
+ if (!dict_index_is_clust(index)) {
+
+ ut_a(plan);
+
+ col_pos = dict_index_get_nth_col_pos(index, sym_node->col_no,
+ NULL);
+
+ 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 = static_cast<func_node_t*>(search_cond->args);
+
+ opt_find_copy_cols(sel_node, i, new_cond);
+
+ new_cond = static_cast<func_node_t*>(
+ 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, &sym_node_t::col_var_list);
+
+ /* All select list columns should be copied: therefore TRUE as the
+ first argument */
+
+ for (exp = sel_node->select_list;
+ exp != 0;
+ exp = que_node_get_next(exp)) {
+
+ opt_find_all_cols(
+ TRUE, plan->index, &(plan->columns), plan, exp);
+ }
+
+ opt_find_copy_cols(
+ sel_node, i, static_cast<func_node_t*>(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,
+ static_cast<func_node_t*>(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;
+ 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 (dict_index_is_clust(index)) {
+ 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 = static_cast<ulint*>(
+ 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_is_sys_table(index->table->id)
+ && (dict_index_get_nth_field(index, pos)->prefix_len != 0
+ || dict_index_get_nth_field(clust_index, i)
+ ->prefix_len != 0)) {
+ ib::error() << "Error in pars0opt.cc: table "
+ << index->table->name
+ << " has prefix_len != 0";
+ }
+
+ *(plan->clust_map + i) = pos;
+
+ ut_ad(pos != ULINT_UNDEFINED);
+ }
+}
+
+#ifdef UNIV_SQL_DEBUG
+/** Print info of a query plan.
+@param[in,out] sel_node select node */
+static
+void
+opt_print_query_plan(
+ sel_node_t* sel_node);
+#endif
+
+/*******************************************************************//**
+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 = static_cast<plan_t*>(
+ 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 = static_cast<sym_node_t*>(
+ 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 = static_cast<sym_node_t*>(
+ 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
+}
+
+#ifdef UNIV_SQL_DEBUG
+/** Print info of a query plan.
+@param[in,out] sel_node select node */
+static
+void
+opt_print_query_plan(
+ sel_node_t* sel_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;
+ }
+
+ fprintf(stderr,
+ "Index %s of table %s"
+ "; exact m. %lu, match %lu, end conds %lu\n",
+ plan->index->name(), plan->index->table->name.m_name,
+ (unsigned long) plan->n_exact_match,
+ (unsigned long) n_fields,
+ (unsigned long) UT_LIST_GET_LEN(plan->end_conds));
+ }
+}
+#endif /* UNIV_SQL_DEBUG */