summaryrefslogtreecommitdiffstats
path: root/sql/sql_derived.cc
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-04 18:07:14 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-04 18:07:14 +0000
commita175314c3e5827eb193872241446f2f8f5c9d33c (patch)
treecd3d60ca99ae00829c52a6ca79150a5b6e62528b /sql/sql_derived.cc
parentInitial commit. (diff)
downloadmariadb-10.5-upstream.tar.xz
mariadb-10.5-upstream.zip
Adding upstream version 1:10.5.12.upstream/1%10.5.12upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--sql/sql_derived.cc1671
1 files changed, 1671 insertions, 0 deletions
diff --git a/sql/sql_derived.cc b/sql/sql_derived.cc
new file mode 100644
index 00000000..f8ee3475
--- /dev/null
+++ b/sql/sql_derived.cc
@@ -0,0 +1,1671 @@
+/*
+ Copyright (c) 2002, 2011, Oracle and/or its affiliates.
+ Copyright (c) 2010, 2021, MariaDB
+
+ 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 St, Fifth Floor, Boston, MA 02110-1335 USA */
+
+
+/*
+ Derived tables
+ These were introduced by Sinisa <sinisa@mysql.com>
+*/
+
+
+#include "mariadb.h" /* NO_EMBEDDED_ACCESS_CHECKS */
+#include "sql_priv.h"
+#include "unireg.h"
+#include "sql_select.h"
+#include "derived_handler.h"
+#include "sql_base.h"
+#include "sql_view.h" // check_duplicate_names
+#include "sql_acl.h" // SELECT_ACL
+#include "sql_class.h"
+#include "sql_derived.h"
+#include "sql_cte.h"
+#include "my_json_writer.h"
+#include "opt_trace.h"
+
+typedef bool (*dt_processor)(THD *thd, LEX *lex, TABLE_LIST *derived);
+
+static bool mysql_derived_init(THD *thd, LEX *lex, TABLE_LIST *derived);
+static bool mysql_derived_prepare(THD *thd, LEX *lex, TABLE_LIST *derived);
+static bool mysql_derived_optimize(THD *thd, LEX *lex, TABLE_LIST *derived);
+static bool mysql_derived_merge(THD *thd, LEX *lex, TABLE_LIST *derived);
+static bool mysql_derived_create(THD *thd, LEX *lex, TABLE_LIST *derived);
+static bool mysql_derived_fill(THD *thd, LEX *lex, TABLE_LIST *derived);
+static bool mysql_derived_reinit(THD *thd, LEX *lex, TABLE_LIST *derived);
+static bool mysql_derived_merge_for_insert(THD *thd, LEX *lex,
+ TABLE_LIST *derived);
+
+dt_processor processors[]=
+{
+ &mysql_derived_init,
+ &mysql_derived_prepare,
+ &mysql_derived_optimize,
+ &mysql_derived_merge,
+ &mysql_derived_merge_for_insert,
+ &mysql_derived_create,
+ &mysql_derived_fill,
+ &mysql_derived_reinit,
+};
+
+/*
+ Run specified phases on all derived tables/views in given LEX.
+
+ @param lex LEX for this thread
+ @param phases phases to run derived tables/views through
+
+ @return FALSE OK
+ @return TRUE Error
+*/
+bool
+mysql_handle_derived(LEX *lex, uint phases)
+{
+ bool res= FALSE;
+ DBUG_ENTER("mysql_handle_derived");
+ DBUG_PRINT("enter", ("phases: 0x%x", phases));
+ if (!lex->derived_tables)
+ DBUG_RETURN(FALSE);
+
+ lex->thd->derived_tables_processing= TRUE;
+
+ for (uint phase= 0; phase < DT_PHASES && !res; phase++)
+ {
+ uint phase_flag= DT_INIT << phase;
+ if (phase_flag > phases)
+ break;
+ if (!(phases & phase_flag))
+ continue;
+
+ for (SELECT_LEX *sl= lex->all_selects_list;
+ sl && !res;
+ sl= sl->next_select_in_list())
+ {
+ TABLE_LIST *cursor= sl->get_table_list();
+ sl->changed_elements|= TOUCHED_SEL_DERIVED;
+ /*
+ DT_MERGE_FOR_INSERT is not needed for views/derived tables inside
+ subqueries. Views and derived tables of subqueries should be
+ processed normally.
+ */
+ if (phases == DT_MERGE_FOR_INSERT &&
+ cursor && (cursor->top_table()->select_lex !=
+ lex->first_select_lex()))
+ continue;
+ for (;
+ cursor && !res;
+ cursor= cursor->next_local)
+ {
+ if (!cursor->is_view_or_derived() && phases == DT_MERGE_FOR_INSERT)
+ continue;
+ uint8 allowed_phases= (cursor->is_merged_derived() ? DT_PHASES_MERGE :
+ DT_PHASES_MATERIALIZE | DT_MERGE_FOR_INSERT);
+ /*
+ Skip derived tables to which the phase isn't applicable.
+ TODO: mark derived at the parse time, later set it's type
+ (merged or materialized)
+ */
+ if ((phase_flag != DT_PREPARE && !(allowed_phases & phase_flag)) ||
+ (cursor->merged_for_insert && phase_flag != DT_REINIT &&
+ phase_flag != DT_PREPARE))
+ continue;
+ res= (*processors[phase])(lex->thd, lex, cursor);
+ }
+ if (lex->describe)
+ {
+ /*
+ Force join->join_tmp creation, because we will use this JOIN
+ twice for EXPLAIN and we have to have unchanged join for EXPLAINing
+ */
+ sl->uncacheable|= UNCACHEABLE_EXPLAIN;
+ sl->master_unit()->uncacheable|= UNCACHEABLE_EXPLAIN;
+ }
+ }
+ }
+ lex->thd->derived_tables_processing= FALSE;
+ DBUG_RETURN(res);
+}
+
+/*
+ Run through phases for the given derived table/view.
+
+ @param lex LEX for this thread
+ @param derived the derived table to handle
+ @param phase_map phases to process tables/views through
+
+ @details
+
+ This function process the derived table (view) 'derived' to performs all
+ actions that are to be done on the table at the phases specified by
+ phase_map. The processing is carried out starting from the actions
+ performed at the earlier phases (those having smaller ordinal numbers).
+
+ @note
+ This function runs specified phases of the derived tables handling on the
+ given derived table/view. This function is used in the chain of calls:
+ SELECT_LEX::handle_derived ->
+ TABLE_LIST::handle_derived ->
+ mysql_handle_single_derived
+ This chain of calls implements the bottom-up handling of the derived tables:
+ i.e. most inner derived tables/views are handled first. This order is
+ required for the all phases except the merge and the create steps.
+ For the sake of code simplicity this order is kept for all phases.
+
+ @return FALSE ok
+ @return TRUE error
+*/
+
+bool
+mysql_handle_single_derived(LEX *lex, TABLE_LIST *derived, uint phases)
+{
+ bool res= FALSE;
+ uint8 allowed_phases= (derived->is_merged_derived() ? DT_PHASES_MERGE :
+ DT_PHASES_MATERIALIZE);
+ DBUG_ENTER("mysql_handle_single_derived");
+ DBUG_PRINT("enter", ("phases: 0x%x allowed: 0x%x alias: '%s'",
+ phases, allowed_phases,
+ (derived->alias.str ? derived->alias.str : "<NULL>")));
+ if (!lex->derived_tables)
+ DBUG_RETURN(FALSE);
+
+ if (derived->select_lex)
+ derived->select_lex->changed_elements|= TOUCHED_SEL_DERIVED;
+ else
+ DBUG_ASSERT(derived->prelocking_placeholder);
+ lex->thd->derived_tables_processing= TRUE;
+
+ for (uint phase= 0; phase < DT_PHASES; phase++)
+ {
+ uint phase_flag= DT_INIT << phase;
+ if (phase_flag > phases)
+ break;
+ if (!(phases & phase_flag))
+ continue;
+ /* Skip derived tables to which the phase isn't applicable. */
+ if (phase_flag != DT_PREPARE &&
+ !(allowed_phases & phase_flag))
+ continue;
+
+ if ((res= (*processors[phase])(lex->thd, lex, derived)))
+ break;
+ }
+
+ lex->thd->derived_tables_processing= FALSE;
+ DBUG_RETURN(res);
+}
+
+
+/**
+ Merge a derived table/view into the embedding select
+
+ @param thd thread handle
+ @param lex LEX of the embedding query.
+ @param derived reference to the derived table.
+
+ @details
+ This function merges the given derived table / view into the parent select
+ construction. Any derived table/reference to view occurred in the FROM
+ clause of the embedding select is represented by a TABLE_LIST structure a
+ pointer to which is passed to the function as in the parameter 'derived'.
+ This structure contains the number/map, alias, a link to SELECT_LEX of the
+ derived table and other info. If the 'derived' table is used in a nested join
+ then additionally the structure contains a reference to the ON expression
+ for this join.
+
+ The merge process results in elimination of the derived table (or the
+ reference to a view) such that:
+ - the FROM list of the derived table/view is wrapped into a nested join
+ after which the nest is added to the FROM list of the embedding select
+ - the WHERE condition of the derived table (view) is ANDed with the ON
+ condition attached to the table.
+
+ @note
+ Tables are merged into the leaf_tables list, original derived table is removed
+ from this list also. SELECT_LEX::table_list list is left untouched.
+ Where expression is merged with derived table's on_expr and can be found after
+ the merge through the SELECT_LEX::table_list.
+
+ Examples of the derived table/view merge:
+
+ Schema:
+ Tables: t1(f1), t2(f2), t3(f3)
+ View v1: SELECT f1 FROM t1 WHERE f1 < 1
+
+ Example with a view:
+ Before merge:
+
+ The query (Q1): SELECT f1,f2 FROM t2 LEFT JOIN v1 ON f1 = f2
+
+ (LEX of the main query)
+ |
+ (select_lex)
+ |
+ (FROM table list)
+ |
+ (join list)= t2, v1
+ / \
+ / (on_expr)= (f1 = f2)
+ |
+ (LEX of the v1 view)
+ |
+ (select_lex)= SELECT f1 FROM t1 WHERE f1 < 1
+
+
+ After merge:
+
+ The rewritten query Q1 (Q1'):
+ SELECT f1,f2 FROM t2 LEFT JOIN (t1) ON ((f1 = f2) and (f1 < 1))
+
+ (LEX of the main query)
+ |
+ (select_lex)
+ |
+ (FROM table list)
+ |
+ (join list)= t2, (t1)
+ \
+ (on_expr)= (f1 = f2) and (f1 < 1)
+
+ In this example table numbers are assigned as follows:
+ (outer select): t2 - 1, v1 - 2
+ (inner select): t1 - 1
+ After the merge table numbers will be:
+ (outer select): t2 - 1, t1 - 2
+
+ Example with a derived table:
+ The query Q2:
+ SELECT f1,f2
+ FROM (SELECT f1 FROM t1, t3 WHERE f1=f3 and f1 < 1) tt, t2
+ WHERE f1 = f2
+
+ Before merge:
+ (LEX of the main query)
+ |
+ (select_lex)
+ / \
+ (FROM table list) (WHERE clause)= (f1 = f2)
+ |
+ (join list)= tt, t2
+ / \
+ / (on_expr)= (empty)
+ /
+ (select_lex)= SELECT f1 FROM t1, t3 WHERE f1 = f3 and f1 < 1
+
+ After merge:
+
+ The rewritten query Q2 (Q2'):
+ SELECT f1,f2
+ FROM (t1, t3) JOIN t2 ON (f1 = f3 and f1 < 1)
+ WHERE f1 = f2
+
+ (LEX of the main query)
+ |
+ (select_lex)
+ / \
+ (FROM table list) (WHERE clause)= (f1 = f2)
+ |
+ (join list)= t2, (t1, t3)
+ \
+ (on_expr)= (f1 = f3 and f1 < 1)
+
+ In this example table numbers are assigned as follows:
+ (outer select): tt - 1, t2 - 2
+ (inner select): t1 - 1, t3 - 2
+ After the merge table numbers will be:
+ (outer select): t1 - 1, t2 - 2, t3 - 3
+
+ @return FALSE if derived table/view were successfully merged.
+ @return TRUE if an error occur.
+*/
+
+static
+bool mysql_derived_merge(THD *thd, LEX *lex, TABLE_LIST *derived)
+{
+ bool res= FALSE;
+ SELECT_LEX *dt_select= derived->get_single_select();
+ table_map map;
+ uint tablenr;
+ SELECT_LEX *parent_lex= derived->select_lex;
+ Query_arena *arena, backup;
+ DBUG_ENTER("mysql_derived_merge");
+ DBUG_PRINT("enter", ("Alias: '%s' Unit: %p",
+ (derived->alias.str ? derived->alias.str : "<NULL>"),
+ derived->get_unit()));
+ const char *cause= NULL;
+
+ if (derived->merged)
+ {
+
+ DBUG_PRINT("info", ("Irreversibly merged: exit"));
+ DBUG_RETURN(FALSE);
+ }
+
+ if (dt_select->uncacheable & UNCACHEABLE_RAND)
+ {
+ /* There is random function => fall back to materialization. */
+ cause= "Random function in the select";
+ if (unlikely(thd->trace_started()))
+ {
+ OPT_TRACE_VIEWS_TRANSFORM(thd, trace_wrapper, trace_derived,
+ derived->is_derived() ? "derived" : "view",
+ derived->alias.str ? derived->alias.str : "<NULL>",
+ derived->get_unit()->first_select()->select_number,
+ "materialized");
+ trace_derived.add("cause", cause);
+ }
+ derived->change_refs_to_fields();
+ derived->set_materialized_derived();
+ DBUG_RETURN(FALSE);
+ }
+
+ if (derived->dt_handler)
+ {
+ derived->change_refs_to_fields();
+ derived->set_materialized_derived();
+ DBUG_RETURN(FALSE);
+ }
+
+ arena= thd->activate_stmt_arena_if_needed(&backup); // For easier test
+
+ if (!derived->merged_for_insert ||
+ (derived->is_multitable() &&
+ (thd->lex->sql_command == SQLCOM_UPDATE_MULTI ||
+ thd->lex->sql_command == SQLCOM_DELETE_MULTI)))
+ {
+ /*
+ Check whether there is enough free bits in table map to merge subquery.
+ If not - materialize it. This check isn't cached so when there is a big
+ and small subqueries, and the bigger one can't be merged it wouldn't
+ block the smaller one.
+ */
+ if (parent_lex->get_free_table_map(&map, &tablenr) ||
+ dt_select->leaf_tables.elements + tablenr > MAX_TABLES)
+ {
+ /* There is no enough table bits, fall back to materialization. */
+ cause= "Not enough table bits to merge subquery";
+ goto unconditional_materialization;
+ }
+
+ if (dt_select->options & OPTION_SCHEMA_TABLE)
+ parent_lex->options |= OPTION_SCHEMA_TABLE;
+
+ if (!derived->get_unit()->prepared)
+ {
+ dt_select->leaf_tables.empty();
+ make_leaves_list(thd, dt_select->leaf_tables, derived, TRUE, 0);
+ }
+
+ derived->nested_join= (NESTED_JOIN*) thd->calloc(sizeof(NESTED_JOIN));
+ if (!derived->nested_join)
+ {
+ res= TRUE;
+ goto exit_merge;
+ }
+
+ /* Merge derived table's subquery in the parent select. */
+ if (parent_lex->merge_subquery(thd, derived, dt_select, tablenr, map))
+ {
+ res= TRUE;
+ goto exit_merge;
+ }
+
+ /*
+ exclude select lex so it doesn't show up in explain.
+ do this only for derived table as for views this is already done.
+
+ From sql_view.cc
+ Add subqueries units to SELECT into which we merging current view.
+ unit(->next)* chain starts with subqueries that are used by this
+ view and continues with subqueries that are used by other views.
+ We must not add any subquery twice (otherwise we'll form a loop),
+ to do this we remember in end_unit the first subquery that has
+ been already added.
+ */
+ derived->get_unit()->exclude_level();
+ if (parent_lex->join)
+ parent_lex->join->table_count+= dt_select->join->table_count - 1;
+ }
+ derived->merged= TRUE;
+ if (derived->get_unit()->prepared)
+ {
+ Item *expr= derived->on_expr;
+ expr= and_conds(thd, expr, dt_select->join ? dt_select->join->conds : 0);
+ if (expr)
+ expr->top_level_item();
+
+ if (expr && (derived->prep_on_expr || expr != derived->on_expr))
+ {
+ derived->on_expr= expr;
+ derived->prep_on_expr= expr->copy_andor_structure(thd);
+ }
+ thd->where= "on clause";
+ if (derived->on_expr &&
+ derived->on_expr->fix_fields_if_needed_for_bool(thd, &derived->on_expr))
+ {
+ res= TRUE; /* purecov: inspected */
+ goto exit_merge;
+ }
+ // Update used tables cache according to new table map
+ if (derived->on_expr)
+ {
+ derived->on_expr->fix_after_pullout(parent_lex, &derived->on_expr,
+ TRUE);
+ fix_list_after_tbl_changes(parent_lex, &derived->nested_join->join_list);
+ }
+ }
+
+exit_merge:
+ if (arena)
+ thd->restore_active_arena(arena, &backup);
+ DBUG_RETURN(res);
+
+unconditional_materialization:
+
+ if (unlikely(thd->trace_started()))
+ {
+ OPT_TRACE_VIEWS_TRANSFORM(thd,trace_wrapper, trace_derived,
+ derived->is_derived() ? "derived" : "view",
+ derived->alias.str ? derived->alias.str : "<NULL>",
+ derived->get_unit()->first_select()->select_number,
+ "materialized");
+ trace_derived.add("cause", cause);
+ }
+
+ derived->change_refs_to_fields();
+ derived->set_materialized_derived();
+ if (!derived->table || !derived->table->is_created())
+ res= mysql_derived_create(thd, lex, derived);
+ goto exit_merge;
+}
+
+
+/**
+ Merge a view for the embedding INSERT/UPDATE/DELETE
+
+ @param thd thread handle
+ @param lex LEX of the embedding query.
+ @param derived reference to the derived table.
+
+ @details
+ This function substitutes the derived table for the first table from
+ the query of the derived table thus making it a correct target table for the
+ INSERT/UPDATE/DELETE statements. As this operation is correct only for
+ single table views only, for multi table views this function does nothing.
+ The derived parameter isn't checked to be a view as derived tables aren't
+ allowed for INSERT/UPDATE/DELETE statements.
+
+ @return FALSE if derived table/view were successfully merged.
+ @return TRUE if an error occur.
+*/
+
+static
+bool mysql_derived_merge_for_insert(THD *thd, LEX *lex, TABLE_LIST *derived)
+{
+ DBUG_ENTER("mysql_derived_merge_for_insert");
+ DBUG_PRINT("enter", ("Alias: '%s' Unit: %p",
+ (derived->alias.str ? derived->alias.str : "<NULL>"),
+ derived->get_unit()));
+ DBUG_PRINT("info", ("merged_for_insert: %d is_materialized_derived: %d "
+ "is_multitable: %d single_table_updatable: %d "
+ "merge_underlying_list: %d",
+ derived->merged_for_insert,
+ derived->is_materialized_derived(),
+ derived->is_multitable(),
+ derived->single_table_updatable(),
+ derived->merge_underlying_list != 0));
+ if (derived->merged_for_insert)
+ DBUG_RETURN(FALSE);
+ if (derived->init_derived(thd, FALSE))
+ DBUG_RETURN(TRUE);
+ if (derived->is_materialized_derived())
+ DBUG_RETURN(mysql_derived_prepare(thd, lex, derived));
+ if ((thd->lex->sql_command == SQLCOM_UPDATE_MULTI ||
+ thd->lex->sql_command == SQLCOM_DELETE_MULTI))
+ DBUG_RETURN(FALSE);
+ if (!derived->is_multitable())
+ {
+ if (!derived->single_table_updatable())
+ DBUG_RETURN(derived->create_field_translation(thd));
+ if (derived->merge_underlying_list)
+ {
+ derived->table= derived->merge_underlying_list->table;
+ derived->schema_table= derived->merge_underlying_list->schema_table;
+ derived->merged_for_insert= TRUE;
+ DBUG_ASSERT(derived->table);
+ }
+ }
+ DBUG_RETURN(FALSE);
+}
+
+
+/*
+ Initialize a derived table/view
+
+ @param thd Thread handle
+ @param lex LEX of the embedding query.
+ @param derived reference to the derived table.
+
+ @detail
+ Fill info about derived table/view without preparing an
+ underlying select. Such as: create a field translation for views, mark it as
+ a multitable if it is and so on.
+
+ @return
+ false OK
+ true Error
+*/
+
+static
+bool mysql_derived_init(THD *thd, LEX *lex, TABLE_LIST *derived)
+{
+ SELECT_LEX_UNIT *unit= derived->get_unit();
+ DBUG_ENTER("mysql_derived_init");
+ DBUG_PRINT("enter", ("Alias: '%s' Unit: %p",
+ (derived->alias.str ? derived->alias.str : "<NULL>"),
+ derived->get_unit()));
+
+ // Skip already prepared views/DT
+ if (!unit || unit->prepared)
+ DBUG_RETURN(FALSE);
+
+ bool res= derived->init_derived(thd, TRUE);
+
+ derived->updatable= derived->updatable && derived->is_view();
+
+ DBUG_RETURN(res);
+}
+
+
+/**
+ @brief
+ Prevent name resolution out of context of ON expressions in derived tables
+
+ @param
+ join_list list of tables used in from list of a derived
+
+ @details
+ The function sets the Name_resolution_context::outer_context to NULL
+ for all ON expressions contexts in the given join list. It does this
+ recursively for all nested joins the list contains.
+*/
+
+static void nullify_outer_context_for_on_clauses(List<TABLE_LIST>& join_list)
+{
+ List_iterator<TABLE_LIST> li(join_list);
+ while (TABLE_LIST *table= li++)
+ {
+ if (table->on_context)
+ table->on_context->outer_context= NULL;
+ if (table->nested_join)
+ nullify_outer_context_for_on_clauses(table->nested_join->join_list);
+ }
+}
+
+
+/*
+ Create temporary table structure (but do not fill it)
+
+ @param thd Thread handle
+ @param lex LEX of the embedding query.
+ @param derived reference to the derived table.
+
+ @detail
+ Prepare underlying select for a derived table/view. To properly resolve
+ names in the embedding query the TABLE structure is created. Actual table
+ is created later by the mysql_derived_create function.
+
+ This function is called before any command containing derived table
+ is executed. All types of derived tables are handled by this function:
+ - Anonymous derived tables, or
+ - Named derived tables (aka views).
+
+ The table reference, contained in @c derived, is updated with the
+ fields of a new temporary table.
+ Derived tables are stored in @c thd->derived_tables and closed by
+ close_thread_tables().
+
+ This function is part of the procedure that starts in
+ open_and_lock_tables(), a procedure that - among other things - introduces
+ new table and table reference objects (to represent derived tables) that
+ don't exist in the privilege database. This means that normal privilege
+ checking cannot handle them. Hence this function does some extra tricks in
+ order to bypass normal privilege checking, by exploiting the fact that the
+ current state of privilege verification is attached as GRANT_INFO structures
+ on the relevant TABLE and TABLE_REF objects.
+
+ For table references, the current state of accrued access is stored inside
+ TABLE_LIST::grant. Hence this function must update the state of fulfilled
+ privileges for the new TABLE_LIST, an operation which is normally performed
+ exclusively by the table and database access checking functions,
+ check_access() and check_grant(), respectively. This modification is done
+ for both views and anonymous derived tables: The @c SELECT privilege is set
+ as fulfilled by the user. However, if a view is referenced and the table
+ reference is queried against directly (see TABLE_LIST::referencing_view),
+ the state of privilege checking (GRANT_INFO struct) is copied as-is to the
+ temporary table.
+
+ Only the TABLE structure is created here, actual table is created by the
+ mysql_derived_create function.
+
+ @note This function sets @c SELECT_ACL for @c TEMPTABLE views as well as
+ anonymous derived tables, but this is ok since later access checking will
+ distinguish between them.
+
+ @see mysql_handle_derived(), mysql_derived_fill(), GRANT_INFO
+
+ @return
+ false OK
+ true Error
+*/
+
+static
+bool mysql_derived_prepare(THD *thd, LEX *lex, TABLE_LIST *derived)
+{
+ SELECT_LEX_UNIT *unit= derived->get_unit();
+ bool res= FALSE;
+ DBUG_ENTER("mysql_derived_prepare");
+ DBUG_PRINT("enter", ("unit: %p table_list: %p alias: '%s'",
+ unit, derived, derived->alias.str));
+ if (!unit)
+ DBUG_RETURN(FALSE);
+
+ SELECT_LEX *first_select= unit->first_select();
+
+ if (derived->is_recursive_with_table() &&
+ !derived->is_with_table_recursive_reference() &&
+ !derived->with->rec_result && derived->with->get_sq_rec_ref())
+ {
+ /*
+ This is a non-recursive reference to a recursive CTE whose
+ specification unit has not been prepared at the regular processing of
+ derived table references. This can happen only in the case when
+ the specification unit has no recursive references at the top level.
+ Force the preparation of the specification unit. Use a recursive
+ table reference from a subquery for this.
+ */
+ DBUG_ASSERT(derived->with->get_sq_rec_ref());
+ if (unlikely(mysql_derived_prepare(lex->thd, lex,
+ derived->with->get_sq_rec_ref())))
+ DBUG_RETURN(TRUE);
+ }
+
+ if (unit->prepared && derived->is_recursive_with_table() &&
+ !derived->table)
+ {
+ /*
+ Here 'derived' is either a non-recursive table reference to a recursive
+ with table or a recursive table reference to a recursvive table whose
+ specification has been already prepared (a secondary recursive table
+ reference.
+ */
+ if (!(derived->derived_result= new (thd->mem_root) select_unit(thd)))
+ DBUG_RETURN(TRUE); // out of memory
+ thd->create_tmp_table_for_derived= TRUE;
+ res= derived->derived_result->create_result_table(
+ thd, &unit->types, FALSE,
+ (first_select->options |
+ thd->variables.option_bits |
+ TMP_TABLE_ALL_COLUMNS),
+ &derived->alias, FALSE, FALSE, FALSE, 0);
+ thd->create_tmp_table_for_derived= FALSE;
+
+ if (likely(!res) && !derived->table)
+ {
+ derived->derived_result->set_unit(unit);
+ derived->table= derived->derived_result->table;
+ if (derived->is_with_table_recursive_reference())
+ {
+ /* Here 'derived" is a secondary recursive table reference */
+ unit->with_element->rec_result->rec_table_refs.push_back(derived);
+ }
+ }
+ DBUG_ASSERT(derived->table || res);
+ goto exit;
+ }
+
+ // Skip already prepared views/DT
+ if (unit->prepared ||
+ (derived->merged_for_insert &&
+ !(derived->is_multitable() &&
+ (thd->lex->sql_command == SQLCOM_UPDATE_MULTI ||
+ thd->lex->sql_command == SQLCOM_DELETE_MULTI))))
+ {
+ /*
+ System versioned tables may still require to get versioning conditions
+ when modifying view (see vers_setup_conds()). Only UPDATE and DELETE are
+ affected because they use WHERE condition.
+ */
+ if (!unit->prepared &&
+ derived->table->versioned() &&
+ derived->merge_underlying_list &&
+ /* choose only those merged views that do not select from other views */
+ !derived->merge_underlying_list->merge_underlying_list)
+ {
+ switch (thd->lex->sql_command)
+ {
+ case SQLCOM_DELETE:
+ case SQLCOM_DELETE_MULTI:
+ case SQLCOM_UPDATE:
+ case SQLCOM_UPDATE_MULTI:
+ if ((res= first_select->vers_setup_conds(thd,
+ derived->merge_underlying_list)))
+ goto exit;
+ if (derived->merge_underlying_list->where)
+ {
+ Query_arena_stmt on_stmt_arena(thd);
+ derived->where= and_items(thd, derived->where,
+ derived->merge_underlying_list->where);
+ }
+ default:
+ break;
+ }
+ }
+ DBUG_RETURN(FALSE);
+ }
+
+ /* prevent name resolving out of derived table */
+ for (SELECT_LEX *sl= first_select; sl; sl= sl->next_select())
+ {
+ // Prevent it for the WHERE clause
+ sl->context.outer_context= 0;
+
+ // And for ON clauses, if there are any
+ nullify_outer_context_for_on_clauses(*sl->join_list);
+
+ if (!derived->is_with_table_recursive_reference() ||
+ (!derived->with->with_anchor &&
+ !derived->with->is_with_prepared_anchor()))
+ {
+ /*
+ Prepare underlying views/DT first unless 'derived' is a recursive
+ table reference and either the anchors from the specification of
+ 'derived' has been already prepared or there no anchor in this
+ specification
+ */
+ if ((res= sl->handle_derived(lex, DT_PREPARE)))
+ goto exit;
+ }
+ if (derived->outer_join && sl->first_cond_optimization)
+ {
+ /* Mark that table is part of OUTER JOIN and fields may be NULL */
+ for (TABLE_LIST *cursor= (TABLE_LIST*) sl->table_list.first;
+ cursor;
+ cursor= cursor->next_local)
+ cursor->outer_join|= JOIN_TYPE_OUTER;
+ }
+ }
+
+ if (unlikely(thd->trace_started()))
+ {
+ /*
+ Add to optimizer trace whether a derived table/view
+ is merged into the parent select or not.
+ */
+ OPT_TRACE_VIEWS_TRANSFORM(thd, trace_wrapper, trace_derived,
+ derived->is_derived() ? "derived" : "view",
+ derived->alias.str ? derived->alias.str : "<NULL>",
+ derived->get_unit()->first_select()->select_number,
+ derived->is_merged_derived() ? "merged" : "materialized");
+ }
+ /*
+ Above cascade call of prepare is important for PS protocol, but after it
+ is called we can check if we really need prepare for this derived
+ */
+ if (derived->merged)
+ {
+ DBUG_PRINT("info", ("Irreversibly merged: exit"));
+ DBUG_RETURN(FALSE);
+ }
+
+ derived->fill_me= FALSE;
+
+ if ((!derived->is_with_table_recursive_reference() ||
+ !derived->derived_result) &&
+ !(derived->derived_result= new (thd->mem_root) select_unit(thd)))
+ DBUG_RETURN(TRUE); // out of memory
+
+ // st_select_lex_unit::prepare correctly work for single select
+ if ((res= unit->prepare(derived, derived->derived_result, 0)))
+ goto exit;
+ if (derived->with &&
+ (res= derived->with->process_columns_of_derived_unit(thd, unit)))
+ goto exit;
+ if ((res= check_duplicate_names(thd, unit->types, 0)))
+ goto exit;
+
+ /*
+ Check whether we can merge this derived table into main select.
+ Depending on the result field translation will or will not
+ be created.
+ */
+ if (!derived->is_with_table_recursive_reference() &&
+ derived->init_derived(thd, FALSE))
+ goto exit;
+
+ /*
+ Temp table is created so that it hounours if UNION without ALL is to be
+ processed
+
+ As 'distinct' parameter we always pass FALSE (0), because underlying
+ query will control distinct condition by itself. Correct test of
+ distinct underlying query will be is_unit_op &&
+ !unit->union_distinct->next_select() (i.e. it is union and last distinct
+ SELECT is last SELECT of UNION).
+ */
+ thd->create_tmp_table_for_derived= TRUE;
+ if (!(derived->table) &&
+ derived->derived_result->create_result_table(thd, &unit->types, FALSE,
+ (first_select->options |
+ thd->variables.option_bits |
+ TMP_TABLE_ALL_COLUMNS),
+ &derived->alias,
+ FALSE, FALSE, FALSE,
+ 0))
+ {
+ thd->create_tmp_table_for_derived= FALSE;
+ goto exit;
+ }
+ thd->create_tmp_table_for_derived= FALSE;
+
+ if (!derived->table)
+ derived->table= derived->derived_result->table;
+ DBUG_ASSERT(derived->table);
+ if (derived->is_derived() && derived->is_merged_derived())
+ first_select->mark_as_belong_to_derived(derived);
+
+ derived->dt_handler= derived->find_derived_handler(thd);
+ if (derived->dt_handler)
+ {
+ char query_buff[4096];
+ String derived_query(query_buff, sizeof(query_buff), thd->charset());
+ derived_query.length(0);
+ derived->derived->print(&derived_query,
+ enum_query_type(QT_VIEW_INTERNAL |
+ QT_ITEM_ORIGINAL_FUNC_NULLIF |
+ QT_PARSABLE));
+ if (!thd->make_lex_string(&derived->derived_spec,
+ derived_query.ptr(), derived_query.length()))
+ {
+ delete derived->dt_handler;
+ derived->dt_handler= NULL;
+ }
+ }
+
+exit:
+ /* Hide "Unknown column" or "Unknown function" error */
+ if (derived->view)
+ {
+ if (thd->is_error() &&
+ (thd->get_stmt_da()->sql_errno() == ER_BAD_FIELD_ERROR ||
+ thd->get_stmt_da()->sql_errno() == ER_FUNC_INEXISTENT_NAME_COLLISION ||
+ thd->get_stmt_da()->sql_errno() == ER_SP_DOES_NOT_EXIST))
+ {
+ thd->clear_error();
+ my_error(ER_VIEW_INVALID, MYF(0), derived->db.str,
+ derived->table_name.str);
+ }
+ }
+
+ /*
+ if it is preparation PS only or commands that need only VIEW structure
+ then we do not need real data and we can skip execution (and parameters
+ is not defined, too)
+ */
+ if (res)
+ {
+ if (!derived->is_with_table_recursive_reference())
+ {
+ if (derived->table && derived->table->s->tmp_table)
+ free_tmp_table(thd, derived->table);
+ delete derived->derived_result;
+ }
+ }
+ else
+ {
+ TABLE *table= derived->table;
+ table->derived_select_number= first_select->select_number;
+ table->s->tmp_table= INTERNAL_TMP_TABLE;
+#ifndef NO_EMBEDDED_ACCESS_CHECKS
+ if (derived->is_view())
+ table->grant= derived->grant;
+ else
+ {
+ DBUG_ASSERT(derived->is_derived());
+ DBUG_ASSERT(derived->is_anonymous_derived_table());
+ table->grant.privilege= SELECT_ACL;
+ derived->grant.privilege= SELECT_ACL;
+ }
+#endif
+ /* Add new temporary table to list of open derived tables */
+ if (!derived->is_with_table_recursive_reference())
+ {
+ table->next= thd->derived_tables;
+ thd->derived_tables= table;
+ }
+
+ /* If table is used by a left join, mark that any column may be null */
+ if (derived->outer_join)
+ table->maybe_null= 1;
+ }
+ DBUG_RETURN(res);
+}
+
+
+/**
+ Runs optimize phase for a derived table/view.
+
+ @param thd thread handle
+ @param lex LEX of the embedding query.
+ @param derived reference to the derived table.
+
+ @details
+ Runs optimize phase for given 'derived' derived table/view.
+ If optimizer finds out that it's of the type "SELECT a_constant" then this
+ functions also materializes it.
+
+ @return FALSE ok.
+ @return TRUE if an error occur.
+*/
+
+static
+bool mysql_derived_optimize(THD *thd, LEX *lex, TABLE_LIST *derived)
+{
+ SELECT_LEX_UNIT *unit= derived->get_unit();
+ SELECT_LEX *first_select= unit->first_select();
+ SELECT_LEX *save_current_select= lex->current_select;
+ bool res= FALSE;
+ DBUG_ENTER("mysql_derived_optimize");
+ DBUG_PRINT("enter", ("Alias: '%s' Unit: %p",
+ (derived->alias.str ? derived->alias.str : "<NULL>"),
+ derived->get_unit()));
+ if (derived->merged)
+ {
+ DBUG_PRINT("info", ("Irreversibly merged: exit"));
+ DBUG_RETURN(FALSE);
+ }
+
+ if (derived->is_materialized_derived() && derived->dt_handler)
+ {
+ /* Create an object for execution of the query specifying the table */
+ if (!(derived->pushdown_derived=
+ new (thd->mem_root) Pushdown_derived(derived, derived->dt_handler)))
+ {
+ delete derived->dt_handler;
+ derived->dt_handler= NULL;
+ DBUG_RETURN(TRUE);
+ }
+ }
+
+ lex->current_select= first_select;
+
+ if (unit->is_unit_op())
+ {
+ if (unit->optimized)
+ DBUG_RETURN(FALSE);
+ // optimize union without execution
+ res= unit->optimize();
+ }
+ else if (unit->derived)
+ {
+ if (!derived->is_merged_derived())
+ {
+ JOIN *join= first_select->join;
+ unit->set_limit(unit->global_parameters());
+ if (join &&
+ join->optimization_state == JOIN::OPTIMIZATION_PHASE_1_DONE &&
+ join->with_two_phase_optimization)
+ {
+ if (unit->optimized_2)
+ DBUG_RETURN(FALSE);
+ unit->optimized_2= TRUE;
+ }
+ else
+ {
+ if (unit->optimized)
+ DBUG_RETURN(FALSE);
+ unit->optimized= TRUE;
+ if (!join)
+ {
+ /*
+ This happens when derived is used in SELECT for which
+ zer_result_cause != 0.
+ In this case join is already destroyed.
+ */
+ DBUG_RETURN(FALSE);
+ }
+ }
+ if ((res= join->optimize()))
+ goto err;
+ if (join->table_count == join->const_tables)
+ derived->fill_me= TRUE;
+ }
+ }
+ /*
+ Materialize derived tables/views of the "SELECT a_constant" type.
+ Such tables should be materialized at the optimization phase for
+ correct constant evaluation.
+ */
+ if (!res && derived->fill_me && !derived->merged_for_insert)
+ {
+ if (derived->is_merged_derived())
+ {
+ derived->change_refs_to_fields();
+ derived->set_materialized_derived();
+ }
+ if ((res= mysql_derived_create(thd, lex, derived)))
+ goto err;
+ if ((res= mysql_derived_fill(thd, lex, derived)))
+ goto err;
+ }
+err:
+ lex->current_select= save_current_select;
+ DBUG_RETURN(res);
+}
+
+
+/**
+ Actually create result table for a materialized derived table/view.
+
+ @param thd thread handle
+ @param lex LEX of the embedding query.
+ @param derived reference to the derived table.
+
+ @details
+ This function actually creates the result table for given 'derived'
+ table/view, but it doesn't fill it.
+ 'thd' and 'lex' parameters are not used by this function.
+
+ @return FALSE ok.
+ @return TRUE if an error occur.
+*/
+
+static
+bool mysql_derived_create(THD *thd, LEX *lex, TABLE_LIST *derived)
+{
+ DBUG_ENTER("mysql_derived_create");
+ DBUG_PRINT("enter", ("Alias: '%s' Unit: %p",
+ (derived->alias.str ? derived->alias.str : "<NULL>"),
+ derived->get_unit()));
+ TABLE *table= derived->table;
+ SELECT_LEX_UNIT *unit= derived->get_unit();
+
+ if (table->is_created())
+ DBUG_RETURN(FALSE);
+ select_unit *result= derived->derived_result;
+ if (table->s->db_type() == TMP_ENGINE_HTON)
+ {
+ result->tmp_table_param.keyinfo= table->s->key_info;
+ if (create_internal_tmp_table(table, result->tmp_table_param.keyinfo,
+ result->tmp_table_param.start_recinfo,
+ &result->tmp_table_param.recinfo,
+ (unit->first_select()->options |
+ thd->variables.option_bits | TMP_TABLE_ALL_COLUMNS)))
+ DBUG_RETURN(TRUE);
+ }
+ if (open_tmp_table(table))
+ DBUG_RETURN(TRUE);
+ table->file->extra(HA_EXTRA_WRITE_CACHE);
+ table->file->extra(HA_EXTRA_IGNORE_DUP_KEY);
+ DBUG_RETURN(FALSE);
+}
+
+
+void TABLE_LIST::register_as_derived_with_rec_ref(With_element *rec_elem)
+{
+ rec_elem->derived_with_rec_ref.link_in_list(this, &this->next_with_rec_ref);
+ is_derived_with_recursive_reference= true;
+ get_unit()->uncacheable|= UNCACHEABLE_DEPENDENT;
+}
+
+
+bool TABLE_LIST::is_nonrecursive_derived_with_rec_ref()
+{
+ return is_derived_with_recursive_reference;
+}
+
+
+/**
+ @brief
+ Fill the recursive with table
+
+ @param thd The thread handle
+
+ @details
+ The method is called only for recursive with tables.
+ The method executes the recursive part of the specification
+ of this with table until no more rows are added to the table
+ or the number of the performed iteration reaches the allowed
+ maximum.
+
+ @retval
+ false on success
+ true on failure
+*/
+
+bool TABLE_LIST::fill_recursive(THD *thd)
+{
+ bool rc= false;
+ st_select_lex_unit *unit= get_unit();
+ rc= with->instantiate_tmp_tables();
+ while (!rc && !with->all_are_stabilized())
+ {
+ if (with->level > thd->variables.max_recursive_iterations)
+ break;
+ with->prepare_for_next_iteration();
+ rc= unit->exec_recursive();
+ }
+ if (!rc)
+ {
+ TABLE *src= with->rec_result->table;
+ rc =src->insert_all_rows_into_tmp_table(thd,
+ table,
+ &with->rec_result->tmp_table_param,
+ true);
+ }
+ return rc;
+}
+
+
+/*
+ Execute subquery of a materialized derived table/view and fill the result
+ table.
+
+ @param thd Thread handle
+ @param lex LEX for this thread
+ @param derived reference to the derived table.
+
+ @details
+ Execute subquery of given 'derived' table/view and fill the result
+ table. After result table is filled, if this is not the EXPLAIN statement
+ and the table is not specified with a recursion the entire unit / node
+ is deleted. unit is deleted if UNION is used for derived table and node
+ is deleted is it is a simple SELECT.
+ 'lex' is unused and 'thd' is passed as an argument to an underlying function.
+
+ @note
+ If you use this function, make sure it's not called at prepare.
+ Due to evaluation of LIMIT clause it can not be used at prepared stage.
+
+ @return FALSE OK
+ @return TRUE Error
+*/
+
+static
+bool mysql_derived_fill(THD *thd, LEX *lex, TABLE_LIST *derived)
+{
+ Field_iterator_table field_iterator;
+ SELECT_LEX_UNIT *unit= derived->get_unit();
+ bool derived_is_recursive= derived->is_recursive_with_table();
+ bool res= FALSE;
+ DBUG_ENTER("mysql_derived_fill");
+ DBUG_PRINT("enter", ("Alias: '%s' Unit: %p",
+ (derived->alias.str ? derived->alias.str : "<NULL>"),
+ derived->get_unit()));
+
+ if (unit->executed && !unit->uncacheable && !unit->describe &&
+ !derived_is_recursive)
+ DBUG_RETURN(FALSE);
+ /*check that table creation passed without problems. */
+ DBUG_ASSERT(derived->table && derived->table->is_created());
+ select_unit *derived_result= derived->derived_result;
+ SELECT_LEX *save_current_select= lex->current_select;
+
+ if (derived->pushdown_derived)
+ {
+ int res;
+ if (unit->executed)
+ DBUG_RETURN(FALSE);
+ /* Execute the query that specifies the derived table by a foreign engine */
+ res= derived->pushdown_derived->execute();
+ unit->executed= true;
+ delete derived->pushdown_derived;
+ DBUG_RETURN(res);
+ }
+
+ if (unit->executed && !derived_is_recursive &&
+ (unit->uncacheable & UNCACHEABLE_DEPENDENT))
+ {
+ if ((res= derived->table->file->ha_delete_all_rows()))
+ goto err;
+ JOIN *join= unit->first_select()->join;
+ join->first_record= false;
+ for (uint i= join->top_join_tab_count;
+ i < join->top_join_tab_count + join->aggr_tables;
+ i++)
+ {
+ if ((res= join->join_tab[i].table->file->ha_delete_all_rows()))
+ goto err;
+ }
+ }
+
+ if (derived_is_recursive)
+ {
+ if (derived->is_with_table_recursive_reference())
+ {
+ /* Here only one iteration step is performed */
+ res= unit->exec_recursive();
+ }
+ else
+ {
+ /* In this case all iteration are performed */
+ res= derived->fill_recursive(thd);
+ }
+ }
+ else if (unit->is_unit_op())
+ {
+ // execute union without clean up
+ res= unit->exec();
+ }
+ else
+ {
+ SELECT_LEX *first_select= unit->first_select();
+ unit->set_limit(unit->global_parameters());
+ if (unit->lim.is_unlimited())
+ first_select->options&= ~OPTION_FOUND_ROWS;
+
+ lex->current_select= first_select;
+ res= mysql_select(thd,
+ first_select->table_list.first,
+ first_select->item_list, first_select->where,
+ (first_select->order_list.elements+
+ first_select->group_list.elements),
+ first_select->order_list.first,
+ first_select->group_list.first,
+ first_select->having, (ORDER*) NULL,
+ (first_select->options |thd->variables.option_bits |
+ SELECT_NO_UNLOCK),
+ derived_result, unit, first_select);
+ }
+
+ if (!res && !derived_is_recursive)
+ {
+ if (derived_result->flush())
+ res= TRUE;
+ unit->executed= TRUE;
+
+ if (derived->field_translation)
+ {
+ /* reset translation table to materialized table */
+ field_iterator.set_table(derived->table);
+ for (uint i= 0;
+ !field_iterator.end_of_fields();
+ field_iterator.next(), i= i + 1)
+ {
+ Item *item;
+
+ if (!(item= field_iterator.create_item(thd)))
+ {
+ res= TRUE;
+ break;
+ }
+ thd->change_item_tree(&derived->field_translation[i].item, item);
+ }
+ }
+ }
+err:
+ if (res || (!derived_is_recursive && !lex->describe && !unit->uncacheable))
+ unit->cleanup();
+ lex->current_select= save_current_select;
+
+ DBUG_RETURN(res);
+}
+
+
+/**
+ Re-initialize given derived table/view for the next execution.
+
+ @param thd thread handle
+ @param lex LEX for this thread
+ @param derived reference to the derived table.
+
+ @details
+ Re-initialize given 'derived' table/view for the next execution.
+ All underlying views/derived tables are recursively reinitialized prior
+ to re-initialization of given derived table.
+ 'thd' and 'lex' are passed as arguments to called functions.
+
+ @return FALSE OK
+ @return TRUE Error
+*/
+
+static
+bool mysql_derived_reinit(THD *thd, LEX *lex, TABLE_LIST *derived)
+{
+ DBUG_ENTER("mysql_derived_reinit");
+ DBUG_PRINT("enter", ("Alias: '%s' Unit: %p",
+ (derived->alias.str ? derived->alias.str : "<NULL>"),
+ derived->get_unit()));
+ st_select_lex_unit *unit= derived->get_unit();
+
+ derived->merged_for_insert= FALSE;
+ unit->unclean();
+ unit->types.empty();
+ /* for derived tables & PS (which can't be reset by Item_subselect) */
+ unit->reinit_exec_mechanism();
+ unit->set_thd(thd);
+ DBUG_RETURN(FALSE);
+}
+
+
+/*
+ @brief
+ Given condition cond and transformer+argument, try transforming as many
+ conjuncts as possible.
+
+ @detail
+ The motivation of this function is to convert the condition that's being
+ pushed into a WHERE clause with derived_field_transformer_for_where or
+ with derived_grouping_field_transformer_for_where.
+ The transformer may fail for some sub-condition, in this case we want to
+ convert the most restrictive part of the condition that can be pushed.
+
+ This function only does it for top-level AND: conjuncts that could not be
+ converted are dropped.
+
+ @return
+ Converted condition, or NULL if nothing could be converted
+*/
+
+Item *transform_condition_or_part(THD *thd,
+ Item *cond,
+ Item_transformer transformer,
+ uchar *arg)
+{
+ if (cond->type() != Item::COND_ITEM ||
+ ((Item_cond*) cond)->functype() != Item_func::COND_AND_FUNC)
+ {
+ Item *new_item= cond->transform(thd, transformer, arg);
+ // Indicate that the condition is not pushable
+ if (!new_item)
+ cond->clear_extraction_flag();
+ return new_item;
+ }
+
+ List_iterator<Item> li(*((Item_cond*) cond)->argument_list());
+ Item *item;
+ while ((item=li++))
+ {
+ Item *new_item= item->transform(thd, transformer, arg);
+ if (!new_item)
+ {
+ // Indicate that the condition is not pushable
+ item->clear_extraction_flag();
+ li.remove();
+ }
+ else
+ li.replace(new_item);
+ }
+
+ switch (((Item_cond*) cond)->argument_list()->elements)
+ {
+ case 0:
+ return NULL;
+ case 1:
+ return ((Item_cond*) cond)->argument_list()->head();
+ default:
+ return cond;
+ }
+}
+
+
+/**
+ @brief
+ Extract condition that can be pushed into a derived table/view
+
+ @param thd the thread handle
+ @param cond current condition
+ @param derived the reference to the derived table/view
+
+ @details
+ This function builds the most restrictive condition depending only on
+ the derived table/view (directly or indirectly through equality) that
+ can be extracted from the given condition cond and pushes it into the
+ derived table/view.
+
+ Example of the transformation:
+
+ SELECT *
+ FROM t1,
+ (
+ SELECT x,MAX(y) AS max_y
+ FROM t2
+ GROUP BY x
+ ) AS d_tab
+ WHERE d_tab.x>1 AND d_tab.max_y<30;
+
+ =>
+
+ SELECT *
+ FROM t1,
+ (
+ SELECT x,z,MAX(y) AS max_y
+ FROM t2
+ WHERE x>1
+ HAVING max_y<30
+ GROUP BY x
+ ) AS d_tab
+ WHERE d_tab.x>1 AND d_tab.max_y<30;
+
+ In details:
+ 1. Check what pushable formula can be extracted from cond
+ 2. Build a clone PC of the formula that can be extracted
+ (the clone is built only if the extracted formula is a AND subformula
+ of cond or conjunction of such subformulas)
+ Do for every select specifying derived table/view:
+ 3. If there is no HAVING clause prepare PC to be conjuncted with
+ WHERE clause of the select. Otherwise do 4-7.
+ 4. Check what formula PC_where can be extracted from PC to be pushed
+ into the WHERE clause of the select
+ 5. Build PC_where and if PC_where is a conjunct(s) of PC remove it from PC
+ getting PC_having
+ 6. Prepare PC_where to be conjuncted with the WHERE clause of the select
+ 7. Prepare PC_having to be conjuncted with the HAVING clause of the select
+ @note
+ This method is similar to pushdown_cond_for_in_subquery()
+
+ @retval TRUE if an error occurs
+ @retval FALSE otherwise
+*/
+
+bool pushdown_cond_for_derived(THD *thd, Item *cond, TABLE_LIST *derived)
+{
+ DBUG_ENTER("pushdown_cond_for_derived");
+ if (!cond)
+ DBUG_RETURN(false);
+
+ st_select_lex_unit *unit= derived->get_unit();
+ st_select_lex *first_sl= unit->first_select();
+ st_select_lex *sl= first_sl;
+
+ if (derived->prohibit_cond_pushdown)
+ DBUG_RETURN(false);
+
+ /* Do not push conditions into constant derived */
+ if (unit->executed)
+ DBUG_RETURN(false);
+
+ /* Do not push conditions into recursive with tables */
+ if (derived->is_recursive_with_table())
+ DBUG_RETURN(false);
+
+ /* Do not push conditions into unit with global ORDER BY ... LIMIT */
+ if (unit->fake_select_lex && unit->fake_select_lex->explicit_limit)
+ DBUG_RETURN(false);
+
+ /* Check whether any select of 'unit' allows condition pushdown */
+ bool some_select_allows_cond_pushdown= false;
+ for (; sl; sl= sl->next_select())
+ {
+ if (sl->cond_pushdown_is_allowed())
+ {
+ some_select_allows_cond_pushdown= true;
+ break;
+ }
+ }
+ if (!some_select_allows_cond_pushdown)
+ DBUG_RETURN(false);
+
+ /* 1. Check what pushable formula can be extracted from cond */
+ Item *extracted_cond;
+ cond->check_pushable_cond(&Item::pushable_cond_checker_for_derived,
+ (uchar *)(&derived->table->map));
+ /* 2. Build a clone PC of the formula that can be extracted */
+ extracted_cond=
+ cond->build_pushable_cond(thd,
+ &Item::pushable_equality_checker_for_derived,
+ ((uchar *)&derived->table->map));
+ if (!extracted_cond)
+ {
+ /* Nothing can be pushed into the derived table */
+ DBUG_RETURN(false);
+ }
+
+ st_select_lex *save_curr_select= thd->lex->current_select;
+ for (; sl; sl= sl->next_select())
+ {
+ Item *extracted_cond_copy;
+ if (!sl->cond_pushdown_is_allowed())
+ continue;
+ /*
+ For each select of the unit except the last one
+ create a clone of extracted_cond
+ */
+ extracted_cond_copy= !sl->next_select() ?
+ extracted_cond :
+ extracted_cond->build_clone(thd);
+ if (!extracted_cond_copy)
+ continue;
+
+ /*
+ Rename the columns of all non-first selects of a union to be compatible
+ by names with the columns of the first select. It will allow to use copies
+ of the same expression pushed into having clauses of different selects.
+ */
+ if (sl != first_sl)
+ {
+ DBUG_ASSERT(sl->item_list.elements == first_sl->item_list.elements);
+ List_iterator_fast<Item> it(sl->item_list);
+ List_iterator_fast<Item> nm_it(unit->types);
+ while (Item *item= it++)
+ item->share_name_with(nm_it++);
+ }
+
+ /* Collect fields that are used in the GROUP BY of sl */
+ if (sl->have_window_funcs())
+ {
+ if (sl->group_list.first || sl->join->implicit_grouping)
+ continue;
+ ORDER *common_partition_fields=
+ sl->find_common_window_func_partition_fields(thd);
+ if (!common_partition_fields)
+ continue;
+ sl->collect_grouping_fields_for_derived(thd, common_partition_fields);
+ }
+ else
+ sl->collect_grouping_fields_for_derived(thd, sl->group_list.first);
+
+ Item *remaining_cond= NULL;
+ /* Do 4-6 */
+ sl->pushdown_cond_into_where_clause(thd, extracted_cond_copy,
+ &remaining_cond,
+ &Item::derived_field_transformer_for_where,
+ (uchar *) sl);
+
+ if (!remaining_cond)
+ continue;
+ /*
+ 7. Prepare PC_having to be conjuncted with the HAVING clause of
+ the select
+ */
+ remaining_cond=
+ remaining_cond->transform(thd,
+ &Item::derived_field_transformer_for_having,
+ (uchar *) sl);
+ if (!remaining_cond)
+ continue;
+
+ if (remaining_cond->walk(&Item::cleanup_excluding_const_fields_processor,
+ 0, 0))
+ continue;
+
+ mark_or_conds_to_avoid_pushdown(remaining_cond);
+
+ sl->cond_pushed_into_having= remaining_cond;
+ }
+ thd->lex->current_select= save_curr_select;
+ DBUG_RETURN(false);
+}
+
+
+/**
+ @brief
+ Look for provision of the derived_handler interface by a foreign engine
+
+ @param thd The thread handler
+
+ @details
+ The function looks through its tables of the query that specifies this
+ derived table searching for a table whose handlerton owns a
+ create_derived call-back function. If the call of this function returns
+ a derived_handler interface object then the server will push the query
+ specifying the derived table into this engine.
+ This is a responsibility of the create_derived call-back function to
+ check whether the engine can execute the query.
+
+ @retval the found derived_handler if the search is successful
+ 0 otherwise
+*/
+
+derived_handler *TABLE_LIST::find_derived_handler(THD *thd)
+{
+ if (!derived || is_recursive_with_table())
+ return 0;
+ for (SELECT_LEX *sl= derived->first_select(); sl; sl= sl->next_select())
+ {
+ if (!(sl->join))
+ continue;
+ for (TABLE_LIST *tbl= sl->join->tables_list; tbl; tbl= tbl->next_local)
+ {
+ if (!tbl->table)
+ continue;
+ handlerton *ht= tbl->table->file->partition_ht();
+ if (!ht->create_derived)
+ continue;
+ derived_handler *dh= ht->create_derived(thd, this);
+ if (dh)
+ {
+ dh->set_derived(this);
+ return dh;
+ }
+ }
+ }
+ return 0;
+}
+
+
+TABLE_LIST *TABLE_LIST::get_first_table()
+{
+ for (SELECT_LEX *sl= derived->first_select(); sl; sl= sl->next_select())
+ {
+ if (!(sl->join))
+ continue;
+ for (TABLE_LIST *tbl= sl->join->tables_list; tbl; tbl= tbl->next_local)
+ {
+ if (!tbl->table)
+ continue;
+ return tbl;
+ }
+ }
+ return 0;
+}