summaryrefslogtreecommitdiffstats
path: root/sql/sql_cte.cc
diff options
context:
space:
mode:
Diffstat (limited to 'sql/sql_cte.cc')
-rw-r--r--sql/sql_cte.cc1746
1 files changed, 1746 insertions, 0 deletions
diff --git a/sql/sql_cte.cc b/sql/sql_cte.cc
new file mode 100644
index 00000000..c53ae300
--- /dev/null
+++ b/sql/sql_cte.cc
@@ -0,0 +1,1746 @@
+/*
+ Copyright (c) 2016, 2017 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-1301 USA */
+
+#include "mariadb.h"
+#include "sql_class.h"
+#include "sql_lex.h"
+#include "sql_cte.h"
+#include "sql_view.h" // for make_valid_column_names
+#include "sql_parse.h"
+#include "sql_select.h"
+#include "sql_show.h" // append_definer, append_identifier
+
+
+/**
+ @brief
+ Add a new element to this with clause
+
+ @param elem The with element to add to this with clause
+
+ @details
+ The method adds the with element 'elem' to the elements
+ in this with clause. The method reports an error if
+ the number of the added element exceeds the value
+ of the constant max_number_of_elements_in_with_clause.
+
+ @retval
+ true if an error is reported
+ false otherwise
+*/
+
+bool With_clause::add_with_element(With_element *elem)
+{
+ if (with_list.elements == max_number_of_elements_in_with_clause)
+ {
+ my_error(ER_TOO_MANY_DEFINITIONS_IN_WITH_CLAUSE, MYF(0));
+ return true;
+ }
+ elem->owner= this;
+ elem->number= with_list.elements;
+ elem->spec->with_element= elem;
+ with_list.link_in_list(elem, &elem->next);
+ return false;
+}
+
+
+void st_select_lex_unit::set_with_clause(With_clause *with_cl)
+{
+ with_clause= with_cl;
+ if (with_clause)
+ with_clause->set_owner(this);
+}
+
+
+/**
+ @brief
+ Check dependencies between tables defined in a list of with clauses
+
+ @param
+ with_clauses_list Pointer to the first clause in the list
+
+ @details
+ For each with clause from the given list the procedure finds all
+ dependencies between tables defined in the clause by calling the
+ method With_clause::checked_dependencies.
+ Additionally, based on the info collected by this method the procedure
+ finds anchors for each recursive definition and moves them at the head
+ of the definition.
+
+ @retval
+ false on success
+ true on failure
+*/
+
+bool LEX::check_dependencies_in_with_clauses()
+{
+ for (With_clause *with_clause= with_clauses_list;
+ with_clause;
+ with_clause= with_clause->next_with_clause)
+ {
+ if (with_clause->check_dependencies())
+ return true;
+ if (with_clause->check_anchors())
+ return true;
+ with_clause->move_anchors_ahead();
+ }
+ return false;
+}
+
+
+/**
+ @brief
+ Resolve references to CTE in specification of hanging CTE
+
+ @details
+ A CTE to which there are no references in the query is called hanging CTE.
+ Although such CTE is not used for execution its specification must be
+ subject to context analysis. All errors concerning references to
+ non-existing tables or fields occurred in the specification must be
+ reported as well as all other errors caught at the prepare stage.
+ The specification of a hanging CTE might contain references to other
+ CTE outside of the specification and within it if the specification
+ contains a with clause. This function resolves all such references for
+ all hanging CTEs encountered in the processed query.
+
+ @retval
+ false on success
+ true on failure
+*/
+
+bool
+LEX::resolve_references_to_cte_in_hanging_cte()
+{
+ for (With_clause *with_clause= with_clauses_list;
+ with_clause; with_clause= with_clause->next_with_clause)
+ {
+ for (With_element *with_elem= with_clause->with_list.first;
+ with_elem; with_elem= with_elem->next)
+ {
+ if (!with_elem->is_referenced())
+ {
+ TABLE_LIST *first_tbl=
+ with_elem->spec->first_select()->table_list.first;
+ TABLE_LIST **with_elem_end_pos= with_elem->head->tables_pos.end_pos;
+ if (first_tbl && resolve_references_to_cte(first_tbl, with_elem_end_pos))
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+
+/**
+ @brief
+ Resolve table references to CTE from a sub-chain of table references
+
+ @param tables Points to the beginning of the sub-chain
+ @param tables_last Points to the address with the sub-chain barrier
+
+ @details
+ The method resolves tables references to CTE from the chain of
+ table references specified by the parameters 'tables' and 'tables_last'.
+ It resolves the references against the CTE definition occurred in a query
+ or the specification of a CTE whose parsing tree is represented by
+ this LEX structure. The method is always called right after the process
+ of parsing the query or of the specification of a CTE has been finished,
+ thus the chain of table references used in the parsed fragment has been
+ already built. It is assumed that parameters of the method specify a
+ a sub-chain of this chain.
+ If a table reference can be potentially a table reference to a CTE and it
+ has not been resolved yet then the method tries to find the definition
+ of the CTE against which the reference can be resolved. If it succeeds
+ it sets the field TABLE_LIST::with to point to the found definition.
+ It also sets the field TABLE_LIST::derived to point to the specification
+ of the found CTE and sets TABLE::db.str to empty_c_string. This will
+ allow to handle this table reference like a reference to a derived handle.
+ If another table reference has been already resolved against this CTE
+ and this CTE is not recursive then a clone of the CTE specification is
+ constructed using the function With_element::clone_parsed_spec() and
+ TABLE_LIST::derived is set to point to this clone rather than to the
+ original specification.
+ If the method does not find a matched CTE definition in the parsed fragment
+ then in the case when the flag this->only_cte_resolution is set to true
+ it just moves to the resolution of the next table reference from the
+ specified sub-chain while in the case when this->only_cte_resolution is set
+ to false the method additionally sets an mdl request for this table
+ reference.
+
+ @notes
+ The flag this->only_cte_resolution is set to true in the cases when
+ the failure to resolve a table reference as a CTE reference within
+ the fragment associated with this LEX structure does not imply that
+ this table reference cannot be resolved as such at all.
+
+ @retval false On success: no errors reported, no memory allocations failed
+ @retval true Otherwise
+*/
+
+bool LEX::resolve_references_to_cte(TABLE_LIST *tables,
+ TABLE_LIST **tables_last)
+{
+ With_element *with_elem= 0;
+
+ for (TABLE_LIST *tbl= tables; tbl != *tables_last; tbl= tbl->next_global)
+ {
+ if (tbl->derived)
+ continue;
+ if (!tbl->db.str && !tbl->with)
+ tbl->with= tbl->select_lex->find_table_def_in_with_clauses(tbl);
+ if (!tbl->with) // no CTE matches table reference tbl
+ {
+ if (only_cte_resolution)
+ continue;
+ if (!tbl->db.str) // no database specified in table reference tbl
+ {
+ if (!thd->db.str) // no default database is set
+ {
+ my_message(ER_NO_DB_ERROR, ER(ER_NO_DB_ERROR), MYF(0));
+ return true;
+ }
+ if (copy_db_to(&tbl->db))
+ return true;
+ if (!(tbl->table_options & TL_OPTION_ALIAS))
+ MDL_REQUEST_INIT(&tbl->mdl_request, MDL_key::TABLE,
+ tbl->db.str, tbl->table_name.str,
+ tbl->mdl_type, MDL_TRANSACTION);
+ tbl->mdl_request.set_type((tbl->lock_type >= TL_WRITE_ALLOW_WRITE) ?
+ MDL_SHARED_WRITE : MDL_SHARED_READ);
+ }
+ continue;
+ }
+ with_elem= tbl->with;
+ if (tbl->is_recursive_with_table() &&
+ !tbl->is_with_table_recursive_reference())
+ {
+ tbl->with->rec_outer_references++;
+ while ((with_elem= with_elem->get_next_mutually_recursive()) !=
+ tbl->with)
+ with_elem->rec_outer_references++;
+ }
+ if (!with_elem->is_used_in_query || with_elem->is_recursive)
+ {
+ tbl->derived= with_elem->spec;
+ if (tbl->derived != tbl->select_lex->master_unit() &&
+ !with_elem->is_recursive &&
+ !tbl->is_with_table_recursive_reference())
+ {
+ tbl->derived->move_as_slave(tbl->select_lex);
+ }
+ with_elem->is_used_in_query= true;
+ }
+ else
+ {
+ if (!(tbl->derived= tbl->with->clone_parsed_spec(thd->lex, tbl)))
+ return true;
+ }
+ tbl->db.str= empty_c_string;
+ tbl->db.length= 0;
+ tbl->schema_table= 0;
+ if (tbl->derived)
+ {
+ tbl->derived->first_select()->set_linkage(DERIVED_TABLE_TYPE);
+ tbl->select_lex->add_statistics(tbl->derived);
+ }
+ if (tbl->with->is_recursive && tbl->is_with_table_recursive_reference())
+ continue;
+ with_elem->inc_references();
+ }
+ return false;
+}
+
+
+/**
+ @brief
+ Find out dependencies between CTEs, resolve references to them
+
+ @details
+ The function can be called in two modes. With this->with_cte_resolution
+ set to false the function only finds out all dependencies between CTEs
+ used in a query expression with a WITH clause whose parsing has been
+ just finished. Based on these dependencies recursive CTEs are detected.
+ If this->with_cte_resolution is set to true the function additionally
+ resolves all references to CTE occurred in this query expression.
+
+ @retval
+ true on failure
+ false on success
+*/
+
+bool
+LEX::check_cte_dependencies_and_resolve_references()
+{
+ if (check_dependencies_in_with_clauses())
+ return true;
+ if (!with_cte_resolution)
+ return false;
+ if (resolve_references_to_cte(query_tables, query_tables_last))
+ return true;
+ if (resolve_references_to_cte_in_hanging_cte())
+ return true;
+ return false;
+}
+
+
+/**
+ @brief
+ Check dependencies between tables defined in this with clause
+
+ @details
+ The method performs the following for this with clause:
+ - checks that there are no definitions of the tables with the same name
+ - for each table T defined in this with clause looks for the tables
+ from the same with clause that are used in the query that specifies T
+ and set the dependencies of T on these tables in a bitmap.
+ - builds the transitive closure of the above direct dependencies
+ to find out all recursive definitions.
+
+ @retval
+ true if an error is reported
+ false otherwise
+*/
+
+bool With_clause::check_dependencies()
+{
+ if (dependencies_are_checked)
+ return false;
+ /*
+ Look for for definitions with the same query name.
+ When found report an error and return true immediately.
+ For each table T defined in this with clause look for all other tables
+ from the same with clause that are used in the specification of T.
+ For each such table set the dependency bit in the dependency map of
+ the with element for T.
+ */
+ for (With_element *with_elem= with_list.first;
+ with_elem;
+ with_elem= with_elem->next)
+ {
+ for (With_element *elem= with_list.first;
+ elem != with_elem;
+ elem= elem->next)
+ {
+ if (lex_string_cmp(system_charset_info, with_elem->get_name(),
+ elem->get_name()) == 0)
+ {
+ my_error(ER_DUP_QUERY_NAME, MYF(0),
+ with_elem->get_name_str());
+ return true;
+ }
+ }
+ if (with_elem->check_dependencies_in_spec())
+ return true;
+ }
+ /* Build the transitive closure of the direct dependencies found above */
+ for (With_element *with_elem= with_list.first;
+ with_elem;
+ with_elem= with_elem->next)
+ with_elem->derived_dep_map= with_elem->base_dep_map;
+ for (With_element *with_elem= with_list.first;
+ with_elem;
+ with_elem= with_elem->next)
+ {
+ table_map with_elem_map= with_elem->get_elem_map();
+ for (With_element *elem= with_list.first; elem; elem= elem->next)
+ {
+ if (elem->derived_dep_map & with_elem_map)
+ elem->derived_dep_map |= with_elem->derived_dep_map;
+ }
+ }
+
+ /*
+ Mark those elements where tables are defined with direct or indirect
+ recursion.
+ */
+ for (With_element *with_elem= with_list.first;
+ with_elem;
+ with_elem= with_elem->next)
+ {
+ if (with_elem->derived_dep_map & with_elem->get_elem_map())
+ with_elem->is_recursive= true;
+ }
+
+ dependencies_are_checked= true;
+ return false;
+}
+
+
+/*
+ This structure describes an element of the stack of embedded units.
+ The stack is used when looking for a definition of a table in
+ with clauses. The definition can be found only in the scopes
+ of the with clauses attached to the units from the stack.
+ The with clauses are looked through from starting from the top
+ element of the stack.
+*/
+
+struct st_unit_ctxt_elem
+{
+ st_unit_ctxt_elem *prev; // the previous element of the stack
+ st_select_lex_unit *unit;
+};
+
+
+/**
+ @brief
+ Find the dependencies of this element on its siblings in its specification
+
+ @details
+ For each table reference ref(T) from the FROM list of every select sl
+ immediately contained in the specification query of this element this
+ method searches for the definition of T in the the with clause which
+ this element belongs to. If such definition is found then the dependency
+ on it is set in sl->with_dep and in this->base_dep_map.
+*/
+
+bool With_element::check_dependencies_in_spec()
+{
+ for (st_select_lex *sl= spec->first_select(); sl; sl= sl->next_select())
+ {
+ st_unit_ctxt_elem ctxt0= {NULL, owner->owner};
+ st_unit_ctxt_elem ctxt1= {&ctxt0, spec};
+ check_dependencies_in_select(sl, &ctxt1, false, &sl->with_dep);
+ base_dep_map|= sl->with_dep;
+ }
+ return false;
+}
+
+
+/**
+ @brief
+ Search for the definition of a table among the elements of this with clause
+
+ @param table The reference to the table that is looked for
+ @param barrier The barrier with element for the search
+
+ @details
+ The function looks through the elements of this with clause trying to find
+ the definition of the given table. When it encounters the element with
+ the same query name as the table's name it returns this element. If no
+ such definitions are found the function returns NULL.
+
+ @retval
+ found with element if the search succeeded
+ NULL - otherwise
+*/
+
+With_element *With_clause::find_table_def(TABLE_LIST *table,
+ With_element *barrier)
+{
+ for (With_element *with_elem= with_list.first;
+ with_elem != barrier;
+ with_elem= with_elem->next)
+ {
+ if (my_strcasecmp(system_charset_info, with_elem->get_name_str(),
+ table->table_name.str) == 0 &&
+ !table->is_fqtn)
+ {
+ table->set_derived();
+ with_elem->referenced= true;
+ return with_elem;
+ }
+ }
+ return NULL;
+}
+
+
+/**
+ @brief
+ Search for the definition of a table in with clauses
+
+ @param tbl The reference to the table that is looked for
+ @param ctxt The context describing in what with clauses of the upper
+ levels the table has to be searched for.
+
+ @details
+ The function looks for the definition of the table tbl in the definitions
+ of the with clauses from the upper levels specified by the parameter ctxt.
+ When it encounters the element with the same query name as the table's name
+ it returns this element. If no such definitions are found the function
+ returns NULL.
+
+ @retval
+ found with element if the search succeeded
+ NULL - otherwise
+*/
+
+With_element *find_table_def_in_with_clauses(TABLE_LIST *tbl,
+ st_unit_ctxt_elem *ctxt)
+{
+ With_element *barrier= NULL;
+ for (st_unit_ctxt_elem *unit_ctxt_elem= ctxt;
+ unit_ctxt_elem;
+ unit_ctxt_elem= unit_ctxt_elem->prev)
+ {
+ st_select_lex_unit *unit= unit_ctxt_elem->unit;
+ With_clause *with_clause= unit->with_clause;
+ if (with_clause &&
+ (tbl->with= with_clause->find_table_def(tbl, barrier)))
+ return tbl->with;
+ barrier= NULL;
+ if (unit->with_element && !unit->with_element->get_owner()->with_recursive)
+ {
+ /*
+ This unit is the specification if the with element unit->with_element.
+ The with element belongs to a with clause without the specifier RECURSIVE.
+ So when searching for the matching definition of tbl this with clause must
+ be looked up to this with element
+ */
+ barrier= unit->with_element;
+ }
+ }
+ return NULL;
+}
+
+
+/**
+ @brief
+ Find the dependencies of this element on its siblings in a select
+
+ @param sl The select where to look for the dependencies
+ @param ctxt The structure specifying the scope of the definitions
+ of the with elements of the upper levels
+ @param in_sbq if true mark dependencies found in subqueries in
+ this->sq_dep_map
+ @param dep_map IN/OUT The bit where to mark the found dependencies
+
+ @details
+ For each table reference ref(T) from the FROM list of the select sl
+ the method searches in with clauses for the definition of the table T.
+ If the found definition belongs to the same with clause as this with
+ element then the method set dependency on T in the in/out parameter
+ dep_map, add if required - in this->sq_dep_map.
+ The parameter ctxt describes the proper context for the search
+ of the definition of T.
+*/
+
+void With_element::check_dependencies_in_select(st_select_lex *sl,
+ st_unit_ctxt_elem *ctxt,
+ bool in_subq,
+ table_map *dep_map)
+{
+ With_clause *with_clause= sl->get_with_clause();
+ for (TABLE_LIST *tbl= sl->table_list.first; tbl; tbl= tbl->next_local)
+ {
+ if (tbl->derived || tbl->nested_join)
+ continue;
+ tbl->with_internal_reference_map= 0;
+ /*
+ If there is a with clause attached to the unit containing sl
+ look first for the definition of tbl in this with clause.
+ If such definition is not found there look in the with
+ clauses of the upper levels.
+ If the definition of tbl is found somewhere in with clauses
+ then tbl->with is set to point to this definition
+ */
+ if (with_clause && !tbl->with)
+ tbl->with= with_clause->find_table_def(tbl, NULL);
+ if (!tbl->with)
+ tbl->with= find_table_def_in_with_clauses(tbl, ctxt);
+
+ if (tbl->with && tbl->with->owner== this->owner)
+ {
+ /*
+ The found definition T of tbl belongs to the same
+ with clause as this with element. In this case:
+ - set the dependence on T in the bitmap dep_map
+ - set tbl->with_internal_reference_map with
+ the bitmap for this definition
+ - set the dependence on T in the bitmap this->sq_dep_map
+ if needed
+ */
+ *dep_map|= tbl->with->get_elem_map();
+ tbl->with_internal_reference_map= get_elem_map();
+ if (in_subq)
+ sq_dep_map|= tbl->with->get_elem_map();
+ else
+ top_level_dep_map|= tbl->with->get_elem_map();
+ }
+ }
+ /* Now look for the dependencies in the subqueries of sl */
+ st_select_lex_unit *inner_unit= sl->first_inner_unit();
+ for (; inner_unit; inner_unit= inner_unit->next_unit())
+ {
+ if (!inner_unit->with_element)
+ check_dependencies_in_unit(inner_unit, ctxt, in_subq, dep_map);
+ }
+}
+
+
+/**
+ @brief
+ Find a recursive reference to this with element in subqueries of a select
+
+ @param sel The select in whose subqueries the reference
+ to be looked for
+
+ @details
+ The function looks for a recursive reference to this with element in
+ subqueries of select sl. When the first such reference is found
+ it is returned as the result.
+ The function assumes that the identification of all CTE references
+ has been performed earlier.
+
+ @retval
+ Pointer to the found recursive reference if the search succeeded
+ NULL - otherwise
+*/
+
+TABLE_LIST *With_element::find_first_sq_rec_ref_in_select(st_select_lex *sel)
+{
+ TABLE_LIST *rec_ref= NULL;
+ st_select_lex_unit *inner_unit= sel->first_inner_unit();
+ for (; inner_unit; inner_unit= inner_unit->next_unit())
+ {
+ st_select_lex *sl= inner_unit->first_select();
+ for (; sl; sl= sl->next_select())
+ {
+ for (TABLE_LIST *tbl= sl->table_list.first; tbl; tbl= tbl->next_local)
+ {
+ if (tbl->derived || tbl->nested_join)
+ continue;
+ if (tbl->with && tbl->with->owner== this->owner &&
+ (tbl->with_internal_reference_map & mutually_recursive))
+ {
+ rec_ref= tbl;
+ return rec_ref;
+ }
+ }
+ if ((rec_ref= find_first_sq_rec_ref_in_select(sl)))
+ return rec_ref;
+ }
+ }
+ return 0;
+}
+
+
+/**
+ @brief
+ Find the dependencies of this element on its siblings in a unit
+
+ @param unit The unit where to look for the dependencies
+ @param ctxt The structure specifying the scope of the definitions
+ of the with elements of the upper levels
+ @param in_sbq if true mark dependencies found in subqueries in
+ this->sq_dep_map
+ @param dep_map IN/OUT The bit where to mark the found dependencies
+
+ @details
+ This method searches in the unit 'unit' for the the references in FROM
+ lists of all selects contained in this unit and in the with clause
+ attached to this unit that refer to definitions of tables from the
+ same with clause as this element.
+ If such definitions are found then the dependencies on them are
+ set in the in/out parameter dep_map and optionally in this->sq_dep_map.
+ The parameter ctxt describes the proper context for the search.
+*/
+
+void With_element::check_dependencies_in_unit(st_select_lex_unit *unit,
+ st_unit_ctxt_elem *ctxt,
+ bool in_subq,
+ table_map *dep_map)
+{
+ if (unit->with_clause)
+ check_dependencies_in_with_clause(unit->with_clause, ctxt, in_subq, dep_map);
+ in_subq |= unit->item != NULL;
+ st_unit_ctxt_elem unit_ctxt_elem= {ctxt, unit};
+ st_select_lex *sl= unit->first_select();
+ for (; sl; sl= sl->next_select())
+ {
+ check_dependencies_in_select(sl, &unit_ctxt_elem, in_subq, dep_map);
+ }
+}
+
+
+/**
+ @brief
+ Find the dependencies of this element on its siblings in a with clause
+
+ @param witt_clause The with clause where to look for the dependencies
+ @param ctxt The structure specifying the scope of the definitions
+ of the with elements of the upper levels
+ @param in_sbq if true mark dependencies found in subqueries in
+ this->sq_dep_map
+ @param dep_map IN/OUT The bit where to mark the found dependencies
+
+ @details
+ This method searches in the with_clause for the the references in FROM
+ lists of all selects contained in the specifications of the with elements
+ from this with_clause that refer to definitions of tables from the
+ same with clause as this element.
+ If such definitions are found then the dependencies on them are
+ set in the in/out parameter dep_map and optionally in this->sq_dep_map.
+ The parameter ctxt describes the proper context for the search.
+*/
+
+void
+With_element::check_dependencies_in_with_clause(With_clause *with_clause,
+ st_unit_ctxt_elem *ctxt,
+ bool in_subq,
+ table_map *dep_map)
+{
+ for (With_element *with_elem= with_clause->with_list.first;
+ with_elem;
+ with_elem= with_elem->next)
+ {
+ check_dependencies_in_unit(with_elem->spec, ctxt, in_subq, dep_map);
+ }
+}
+
+
+/**
+ @brief
+ Find mutually recursive with elements and check that they have ancors
+
+ @details
+ This method performs the following:
+ - for each recursive with element finds all mutually recursive with it
+ - links each group of mutually recursive with elements into a ring chain
+ - checks that every group of mutually recursive with elements contains
+ at least one anchor
+ - checks that after removing any with element with anchor the remaining
+ with elements mutually recursive with the removed one are not recursive
+ anymore
+
+ @retval
+ true if an error is reported
+ false otherwise
+*/
+
+bool With_clause::check_anchors()
+{
+ for (With_element *with_elem= with_list.first;
+ with_elem;
+ with_elem= with_elem->next)
+ {
+ if (!with_elem->is_recursive)
+ continue;
+
+ /*
+ It with_elem is recursive with element find all elements mutually recursive
+ with it (any recursive element is mutually recursive with itself). Mark all
+ these elements in the bitmap->mutually_recursive. Also link all these
+ elements into a ring chain.
+ */
+ if (!with_elem->next_mutually_recursive)
+ {
+ With_element *last_mutually_recursive= with_elem;
+ table_map with_elem_dep= with_elem->derived_dep_map;
+ table_map with_elem_map= with_elem->get_elem_map();
+ for (With_element *elem= with_elem; elem; elem= elem->next)
+ {
+ if (!elem->is_recursive)
+ continue;
+
+ if (elem == with_elem ||
+ ((elem->derived_dep_map & with_elem_map) &&
+ (with_elem_dep & elem->get_elem_map())))
+ {
+ elem->next_mutually_recursive= with_elem;
+ last_mutually_recursive->next_mutually_recursive= elem;
+ last_mutually_recursive= elem;
+ with_elem->mutually_recursive|= elem->get_elem_map();
+ }
+ }
+ for (With_element *elem= with_elem->next_mutually_recursive;
+ elem != with_elem;
+ elem= elem->next_mutually_recursive)
+ elem->mutually_recursive= with_elem->mutually_recursive;
+ }
+
+ /*
+ For each select from the specification of 'with_elem' check whether
+ it is an anchor i.e. does not depend on any with elements mutually
+ recursive with 'with_elem".
+ */
+ for (st_select_lex *sl= with_elem->spec->first_select();
+ sl;
+ sl= sl->next_select())
+ {
+ if (with_elem->is_anchor(sl))
+ {
+ with_elem->with_anchor= true;
+ break;
+ }
+ }
+ }
+
+ /*
+ Check that for any group of mutually recursive with elements
+ - there is at least one anchor
+ - after removing any with element with anchor the remaining with elements
+ mutually recursive with the removed one are not recursive anymore
+ */
+ for (With_element *with_elem= with_list.first;
+ with_elem;
+ with_elem= with_elem->next)
+ {
+ if (!with_elem->is_recursive)
+ continue;
+
+ if (!with_elem->with_anchor)
+ {
+ /*
+ Check that the other with elements mutually recursive with 'with_elem'
+ contain at least one anchor.
+ */
+ With_element *elem= with_elem;
+ while ((elem= elem->get_next_mutually_recursive()) != with_elem)
+ {
+ if (elem->with_anchor)
+ break;
+ }
+ if (elem == with_elem)
+ {
+ my_error(ER_RECURSIVE_WITHOUT_ANCHORS, MYF(0),
+ with_elem->get_name_str());
+ return true;
+ }
+ }
+ else
+ {
+ /* 'with_elem' is a with element with an anchor */
+ With_element *elem= with_elem;
+ /*
+ For the other with elements mutually recursive with 'with_elem'
+ set dependency bits between those elements in the field work_dep_map
+ and build transitive closure of these dependencies
+ */
+ while ((elem= elem->get_next_mutually_recursive()) != with_elem)
+ elem->work_dep_map= elem->base_dep_map & elem->mutually_recursive;
+ elem= with_elem;
+ while ((elem= elem->get_next_mutually_recursive()) != with_elem)
+ {
+ table_map elem_map= elem->get_elem_map();
+ With_element *el= with_elem;
+ while ((el= el->get_next_mutually_recursive()) != with_elem)
+ {
+ if (el->work_dep_map & elem_map)
+ el->work_dep_map|= elem->work_dep_map;
+ }
+ }
+ /* If the transitive closure displays any cycle report an arror */
+ elem= with_elem;
+ while ((elem= elem->get_next_mutually_recursive()) != with_elem)
+ {
+ if (elem->work_dep_map & elem->get_elem_map())
+ {
+ my_error(ER_UNACCEPTABLE_MUTUAL_RECURSION, MYF(0),
+ with_elem->get_name_str());
+ return true;
+ }
+ }
+ }
+ }
+
+ return false;
+}
+
+
+/**
+ @brief
+ Move anchors at the beginning of the specifications for with elements
+
+ @details
+ This method moves anchors at the beginning of the specifications for
+ all recursive with elements.
+*/
+
+void With_clause::move_anchors_ahead()
+{
+ for (With_element *with_elem= with_list.first;
+ with_elem;
+ with_elem= with_elem->next)
+ {
+ if (with_elem->is_recursive)
+ with_elem->move_anchors_ahead();
+ }
+}
+
+
+/**
+ @brief
+ Move anchors at the beginning of the specification of this with element
+
+ @details
+ If the specification of this with element contains anchors the method
+ moves them at the very beginning of the specification.
+ Additionally for the other selects of the specification if none of them
+ contains a recursive reference to this with element or a mutually recursive
+ one the method looks for the first such reference in the first recursive
+ select and set a pointer to it in this->sq_rec_ref.
+*/
+
+void With_element::move_anchors_ahead()
+{
+ st_select_lex *next_sl;
+ st_select_lex *new_pos= spec->first_select();
+ new_pos->set_linkage(UNION_TYPE);
+ for (st_select_lex *sl= new_pos; sl; sl= next_sl)
+ {
+ next_sl= sl->next_select();
+ if (is_anchor(sl))
+ {
+ sl->move_node(new_pos);
+ if (new_pos == spec->first_select())
+ {
+ enum sub_select_type type= new_pos->get_linkage();
+ new_pos->set_linkage(sl->get_linkage());
+ sl->set_linkage(type);
+ new_pos->with_all_modifier= sl->with_all_modifier;
+ sl->with_all_modifier= false;
+ }
+ new_pos= sl->next_select();
+ }
+ else if (!sq_rec_ref && no_rec_ref_on_top_level())
+ {
+ sq_rec_ref= find_first_sq_rec_ref_in_select(sl);
+ DBUG_ASSERT(sq_rec_ref != NULL);
+ }
+ }
+ first_recursive= new_pos;
+ spec->first_select()->set_linkage(DERIVED_TABLE_TYPE);
+}
+
+
+/**
+ @brief
+ Perform context analysis for all unreferenced tables defined in with clause
+
+ @param thd The context of the statement containing this with clause
+
+ @details
+ For each unreferenced table T defined in this with clause the method
+ calls the method With_element::prepare_unreferenced that performs
+ context analysis of the element with the definition of T.
+
+ @retval
+ false If context analysis does not report any error
+ true Otherwise
+*/
+
+bool With_clause::prepare_unreferenced_elements(THD *thd)
+{
+ for (With_element *with_elem= with_list.first;
+ with_elem;
+ with_elem= with_elem->next)
+ {
+ if ((with_elem->is_hanging_recursive() || !with_elem->is_referenced()) &&
+ with_elem->prepare_unreferenced(thd))
+ return true;
+ }
+
+ return false;
+}
+
+
+/**
+ @brief
+ Save the specification of the given with table as a string
+
+ @param thd The context of the statement containing this with element
+ @param spec_start The beginning of the specification in the input string
+ @param spec_end The end of the specification in the input string
+ @param spec_offset The offset of the specification in the input string
+
+ @details
+ The method creates for a string copy of the specification used in this
+ element. The method is called when the element is parsed. The copy may be
+ used to create clones of the specification whenever they are needed.
+
+ @retval
+ false on success
+ true on failure
+*/
+
+bool With_element::set_unparsed_spec(THD *thd,
+ const char *spec_start,
+ const char *spec_end,
+ my_ptrdiff_t spec_offset)
+{
+ stmt_prepare_mode= thd->m_parser_state->m_lip.stmt_prepare_mode;
+ unparsed_spec.length= spec_end - spec_start;
+
+ if (stmt_prepare_mode || !thd->lex->sphead)
+ unparsed_spec.str= spec_start;
+ else
+ unparsed_spec.str= thd->strmake(spec_start, unparsed_spec.length);
+ unparsed_spec_offset= spec_offset;
+
+ if (!unparsed_spec.str)
+ {
+ my_error(ER_OUTOFMEMORY, MYF(ME_FATAL),
+ static_cast<int>(unparsed_spec.length));
+ return true;
+ }
+ return false;
+}
+
+
+/**
+ @brief
+ Create a clone of the specification for the given with table
+
+ @param old_lex The LEX structure created for the query or CTE specification
+ where this With_element is defined
+ @param with_table The reference to the table defined in this element for which
+ the clone is created.
+
+ @details
+ The method creates a clone of the specification used in this element.
+ The clone is created for the given reference to the table defined by
+ this element.
+ The clone is created when the string with the specification saved in
+ unparsed_spec is fed into the parser as an input string. The parsing
+ this string a unit object representing the specification is built.
+ A chain of all table references occurred in the specification is also
+ formed.
+ The method includes the new unit and its sub-unit into hierarchy of
+ the units of the main query. I also insert the constructed chain of the
+ table references into the chain of all table references of the main query.
+ The method resolves all references to CTE in the clone.
+
+ @note
+ Clones is created only for not first references to tables defined in
+ the with clause. They are necessary for merged specifications because
+ the optimizer handles any such specification as independent on the others.
+ When a table defined in the with clause is materialized in a temporary table
+ one could do without specification clones. However in this case they
+ are created as well, because currently different table references to a
+ the same temporary table cannot share the same definition structure.
+
+ @retval
+ pointer to the built clone if succeeds
+ NULL - otherwise
+*/
+
+st_select_lex_unit *With_element::clone_parsed_spec(LEX *old_lex,
+ TABLE_LIST *with_table)
+{
+ THD *thd= old_lex->thd;
+ LEX *lex;
+ st_select_lex_unit *res= NULL;
+
+ if (!(lex= (LEX*) new(thd->mem_root) st_lex_local))
+ return res;
+ thd->lex= lex;
+
+ bool parse_status= false;
+ st_select_lex *with_select;
+ st_select_lex *last_clone_select;
+
+ char save_end= unparsed_spec.str[unparsed_spec.length];
+ ((char*) &unparsed_spec.str[unparsed_spec.length])[0]= '\0';
+
+ lex_start(thd);
+ lex->clone_spec_offset= unparsed_spec_offset;
+ lex->with_cte_resolution= true;
+
+ /*
+ The specification of a CTE is to be parsed as a regular query.
+ At the very end of the parsing query the function
+ check_cte_dependencies_and_resolve_references() will be called.
+ It will check the dependencies between CTEs that are defined
+ within the query and will resolve CTE references in this query.
+ If a table reference is not resolved as a CTE reference within
+ this query it still can be resolved as a reference to a CTE defined
+ in the same clause as the CTE whose specification is to be parsed
+ or defined in an embedding CTE definition.
+
+ Example:
+ with
+ cte1 as ( ... ),
+ cte2 as ([WITH ...] select ... from cte1 ...)
+ select ... from cte2 as r, ..., cte2 as s ...
+
+ Here the specification of cte2 has be cloned for table reference
+ with alias s1. The specification contains a reference to cte1
+ that is defined outside this specification. If the reference to
+ cte1 cannot be resolved within the specification of cte2 it's
+ not necessarily has to be a reference to a non-CTE table. That's
+ why the flag lex->only_cte_resolution has to be set to true
+ before parsing of the specification of cte2 invoked by this
+ function starts. Otherwise an mdl_lock would be requested for s
+ and this would not be correct.
+ */
+
+ lex->only_cte_resolution= true;
+
+ lex->stmt_lex= old_lex->stmt_lex ? old_lex->stmt_lex : old_lex;
+
+ parse_status= thd->sql_parser(old_lex, lex,
+ (char*) unparsed_spec.str,
+ (unsigned int)unparsed_spec.length,
+ stmt_prepare_mode);
+
+ ((char*) &unparsed_spec.str[unparsed_spec.length])[0]= save_end;
+ with_select= lex->unit.first_select();
+
+ if (parse_status)
+ goto err;
+
+ /*
+ The global chain of TABLE_LIST objects created for the specification that
+ just has been parsed is added to such chain that contains the reference
+ to the CTE whose specification is parsed right after the TABLE_LIST object
+ created for the reference.
+ */
+ if (lex->query_tables)
+ {
+ head->tables_pos.set_start_pos(&with_table->next_global);
+ head->tables_pos.set_end_pos(lex->query_tables_last);
+ TABLE_LIST *next_tbl= with_table->next_global;
+ if (next_tbl)
+ {
+ *(lex->query_tables->prev_global= next_tbl->prev_global)=
+ lex->query_tables;
+ *(next_tbl->prev_global= lex->query_tables_last)= next_tbl;
+ }
+ else
+ {
+ *(lex->query_tables->prev_global= old_lex->query_tables_last)=
+ lex->query_tables;
+ old_lex->query_tables_last= lex->query_tables_last;
+ }
+ }
+ res= &lex->unit;
+ res->with_element= this;
+
+ /*
+ The unit of the specification that just has been parsed is included
+ as a slave of the select that contained in its from list the table
+ reference for which the unit has been created.
+ */
+ lex->unit.include_down(with_table->select_lex);
+ lex->unit.set_slave(with_select);
+ lex->unit.cloned_from= spec;
+
+ /*
+ Now all references to the CTE defined outside of the cloned specification
+ has to be resolved. Additionally if old_lex->only_cte_resolution == false
+ for the table references that has not been resolved requests for mdl_locks
+ has to be set.
+ */
+ lex->only_cte_resolution= old_lex->only_cte_resolution;
+ if (lex->resolve_references_to_cte(lex->query_tables,
+ lex->query_tables_last))
+ {
+ res= NULL;
+ goto err;
+ }
+
+ last_clone_select= lex->all_selects_list;
+ while (last_clone_select->next_select_in_list())
+ last_clone_select= last_clone_select->next_select_in_list();
+ old_lex->all_selects_list=
+ (st_select_lex*) (lex->all_selects_list->
+ insert_chain_before(
+ (st_select_lex_node **) &(old_lex->all_selects_list),
+ last_clone_select));
+
+ lex->sphead= NULL; // in order not to delete lex->sphead
+ lex_end(lex);
+err:
+ thd->lex= old_lex;
+ return res;
+}
+
+
+/**
+ @brief
+ Rename columns of the unit derived from the spec of this with element
+ @param thd The context of the statement containing the with element
+ @param unit The specification of the with element or its clone
+
+ @details
+ The method assumes that the parameter unit is either specification itself
+ of this with element or a clone of this specification. The looks through
+ the column list in this with element. It reports an error if the cardinality
+ of this list differs from the cardinality of select lists in 'unit'.
+ Otherwise it renames the columns of the first select list and sets the flag
+ unit->column_list_is_processed to true preventing renaming columns for the
+ second time.
+
+ @retval
+ true if an error was reported
+ false otherwise
+*/
+
+bool
+With_element::process_columns_of_derived_unit(THD *thd,
+ st_select_lex_unit *unit)
+{
+ if (unit->columns_are_renamed)
+ return false;
+
+ st_select_lex *select= unit->first_select();
+
+ if (column_list.elements) // The column list is optional
+ {
+ List_iterator_fast<Item> it(select->item_list);
+ List_iterator_fast<Lex_ident_sys> nm(column_list);
+ Item *item;
+ LEX_CSTRING *name;
+
+ if (column_list.elements != select->item_list.elements)
+ {
+ my_error(ER_WITH_COL_WRONG_LIST, MYF(0));
+ return true;
+ }
+
+ Query_arena *arena, backup;
+ arena= thd->activate_stmt_arena_if_needed(&backup);
+
+ /* Rename the columns of the first select in the unit */
+ while ((item= it++, name= nm++))
+ {
+ item->set_name(thd, *name);
+ item->common_flags&= ~IS_AUTO_GENERATED_NAME;
+ }
+
+ if (arena)
+ thd->restore_active_arena(arena, &backup);
+ }
+ else
+ make_valid_column_names(thd, select->item_list);
+
+ if (cycle_list)
+ {
+ List_iterator_fast<Item> it(select->item_list);
+ List_iterator_fast<Lex_ident_sys> nm(*cycle_list);
+ List_iterator_fast<Lex_ident_sys> nm_check(*cycle_list);
+ DBUG_ASSERT(cycle_list->elements != 0);
+ while (LEX_CSTRING *name= nm++)
+ {
+ Item *item;
+ /*
+ Check for uniqueness of each element in the cycle list:
+ It's sufficient to check that there is no duplicate of 'name'
+ among the elements that precede it.
+ */
+ LEX_CSTRING *check;
+ nm_check.rewind();
+ while ((check= nm_check++) && check != name)
+ {
+ if (check->length == name->length &&
+ strncmp(check->str, name->str, name->length) == 0)
+ {
+ my_error(ER_DUP_FIELDNAME, MYF(0), check->str);
+ return true;
+ }
+ }
+ /* Check that 'name' is the name of a column of the processed CTE */
+ while ((item= it++) &&
+ (item->name.length != name->length ||
+ strncmp(item->name.str, name->str, name->length) != 0));
+ if (item == NULL)
+ {
+ my_error(ER_BAD_FIELD_ERROR, MYF(0), name->str, "CYCLE clause");
+ return true;
+ }
+ item->common_flags|= IS_IN_WITH_CYCLE;
+ }
+ }
+ unit->columns_are_renamed= true;
+
+ return false;
+}
+
+
+/**
+ @brief
+ Perform context analysis the definition of an unreferenced table
+
+ @param thd The context of the statement containing this with element
+
+ @details
+ The method assumes that this with element contains the definition
+ of a table that is not used anywhere. In this case one has to check
+ that context conditions are met.
+
+ @retval
+ true if an error was reported
+ false otherwise
+*/
+
+bool With_element::prepare_unreferenced(THD *thd)
+{
+ bool rc= false;
+ st_select_lex *first_sl= spec->first_select();
+
+ /* Prevent name resolution for field references out of with elements */
+ for (st_select_lex *sl= first_sl;
+ sl;
+ sl= sl->next_select())
+ sl->context.outer_context= 0;
+
+ thd->lex->context_analysis_only|= CONTEXT_ANALYSIS_ONLY_DERIVED;
+ if (!spec->prepared &&
+ (spec->prepare(spec->derived, 0, 0) ||
+ process_columns_of_derived_unit(thd, spec) ||
+ check_duplicate_names(thd, first_sl->item_list, 1)))
+ rc= true;
+
+ thd->lex->context_analysis_only&= ~CONTEXT_ANALYSIS_ONLY_DERIVED;
+ return rc;
+}
+
+
+bool With_element::is_anchor(st_select_lex *sel)
+{
+ return !(mutually_recursive & sel->with_dep);
+}
+
+
+/**
+ @brief
+ Search for the definition of the given table referred in this select node
+
+ @param table reference to the table whose definition is searched for
+
+ @details
+ The method looks for the definition of the table whose reference is occurred
+ in the FROM list of this select node. First it searches for it in the
+ with clause attached to the unit this select node belongs to. If such a
+ definition is not found then the embedding units are looked through.
+
+ @retval
+ pointer to the found definition if the search has been successful
+ NULL - otherwise
+*/
+
+With_element *st_select_lex::find_table_def_in_with_clauses(TABLE_LIST *table)
+{
+ With_element *found= NULL;
+ With_clause *containing_with_clause= NULL;
+ st_select_lex_unit *master_unit;
+ st_select_lex *outer_sl;
+ for (st_select_lex *sl= this; sl; sl= outer_sl)
+ {
+ /*
+ If sl->master_unit() is the spec of a with element then the search for
+ a definition was already done by With_element::check_dependencies_in_spec
+ and it was unsuccesful. Yet for units cloned from the spec it has not
+ been done yet.
+ */
+ With_clause *attached_with_clause= sl->get_with_clause();
+ if (attached_with_clause &&
+ attached_with_clause != containing_with_clause &&
+ (found= attached_with_clause->find_table_def(table, NULL)))
+ break;
+ master_unit= sl->master_unit();
+ outer_sl= master_unit->outer_select();
+ With_element *with_elem= sl->get_with_element();
+ if (with_elem)
+ {
+ containing_with_clause= with_elem->get_owner();
+ With_element *barrier= containing_with_clause->with_recursive ?
+ NULL : with_elem;
+ if ((found= containing_with_clause->find_table_def(table, barrier)))
+ break;
+ if (outer_sl && !outer_sl->get_with_element())
+ break;
+ }
+ /* Do not look for the table's definition beyond the scope of the view */
+ if (master_unit->is_view)
+ break;
+ }
+ return found;
+}
+
+
+bool TABLE_LIST::is_recursive_with_table()
+{
+ return with && with->is_recursive;
+}
+
+
+/*
+ A reference to a with table T is recursive if it occurs somewhere
+ in the query specifying T or in the query specifying one of the tables
+ mutually recursive with T.
+*/
+
+bool TABLE_LIST::is_with_table_recursive_reference()
+{
+ return (with_internal_reference_map &&
+ (with->get_mutually_recursive() & with_internal_reference_map));
+}
+
+
+/*
+ Specifications of with tables with recursive table references
+ in non-mergeable derived tables are not allowed in this
+ implementation.
+*/
+
+
+/*
+ We say that the specification of a with table T is restricted
+ if all below is true.
+ 1. Any immediate select of the specification contains at most one
+ recursive table reference taking into account table references
+ from mergeable derived tables.
+ 2. Any recursive table reference is not an inner operand of an
+ outer join operation used in an immediate select of the
+ specification.
+ 3. Any immediate select from the specification of T does not
+ contain aggregate functions.
+ 4. The specification of T does not contain recursive table references.
+
+ If the specification of T is not restricted we call the corresponding
+ with element unrestricted.
+
+ The SQL standards allows only with elements with restricted specification.
+ By default we comply with the standards here.
+
+ Yet we allow unrestricted specification if the status variable
+ 'standards_compliant_cte' set to 'off'(0).
+*/
+
+
+/**
+ @brief
+ Check if this select makes the including specification unrestricted
+
+ @param
+ only_standards_compliant true if the system variable
+ 'standards_compliant_cte' is set to 'on'
+ @details
+ This method checks whether the conditions 1-4 (see the comment above)
+ are satisfied for this select. If not then mark this element as
+ unrestricted and report an error if 'only_standards_compliant' is true.
+
+ @retval
+ true if an error is reported
+ false otherwise
+*/
+
+bool st_select_lex::check_unrestricted_recursive(bool only_standard_compliant)
+{
+ With_element *with_elem= get_with_element();
+ if (!with_elem ||!with_elem->is_recursive)
+ {
+ /*
+ If this select is not from the specifiocation of a with elememt or
+ if this not a recursive with element then there is nothing to check.
+ */
+ return false;
+ }
+
+ /* Check conditions 1-2 for restricted specification*/
+ table_map unrestricted= 0;
+ table_map encountered= 0;
+ if (with_elem->check_unrestricted_recursive(this,
+ unrestricted,
+ encountered))
+ return true;
+ with_elem->get_owner()->add_unrestricted(unrestricted);
+
+
+ /* Check conditions 3-4 for restricted specification*/
+ if ((with_sum_func && !with_elem->is_anchor(this)) ||
+ (with_elem->contains_sq_with_recursive_reference()))
+ with_elem->get_owner()->add_unrestricted(
+ with_elem->get_mutually_recursive());
+
+ /* Report an error on unrestricted specification if this is required */
+ if (only_standard_compliant && with_elem->is_unrestricted())
+ {
+ my_error(ER_NOT_STANDARD_COMPLIANT_RECURSIVE,
+ MYF(0), with_elem->get_name_str());
+ return true;
+ }
+
+ return false;
+}
+
+
+/**
+ @brief
+ Check if a select from the spec of this with element is partially restricted
+
+ @param
+ sel select from the specification of this element where to check
+ whether conditions 1-2 are satisfied
+ unrestricted IN/OUT bitmap where to mark unrestricted specs
+ encountered IN/OUT bitmap where to mark encountered recursive references
+ @details
+ This method checks whether the conditions 1-2 (see the comment above)
+ are satisfied for the select sel.
+ This method is called recursively for derived tables.
+
+ @retval
+ true if an error is reported
+ false otherwise
+*/
+
+bool With_element::check_unrestricted_recursive(st_select_lex *sel,
+ table_map &unrestricted,
+ table_map &encountered)
+{
+ /* Check conditions 1 for restricted specification*/
+ List_iterator<TABLE_LIST> ti(sel->leaf_tables);
+ TABLE_LIST *tbl;
+ while ((tbl= ti++))
+ {
+ st_select_lex_unit *unit= tbl->get_unit();
+ if (unit)
+ {
+ if(!tbl->is_with_table())
+ {
+ if (check_unrestricted_recursive(unit->first_select(),
+ unrestricted,
+ encountered))
+ return true;
+ }
+ if (!(tbl->is_recursive_with_table() && unit->with_element->owner == owner))
+ continue;
+ With_element *with_elem= unit->with_element;
+ if (encountered & with_elem->get_elem_map())
+ unrestricted|= with_elem->mutually_recursive;
+ else if (with_elem ==this)
+ encountered|= with_elem->get_elem_map();
+ }
+ }
+ for (With_element *with_elem= owner->with_list.first;
+ with_elem;
+ with_elem= with_elem->next)
+ {
+ if (!with_elem->is_recursive && (unrestricted & with_elem->get_elem_map()))
+ continue;
+ if (encountered & with_elem->get_elem_map())
+ {
+ uint cnt= 0;
+ table_map encountered_mr= encountered & with_elem->mutually_recursive;
+ for (table_map map= encountered_mr >> with_elem->number;
+ map != 0;
+ map>>= 1)
+ {
+ if (map & 1)
+ {
+ if (cnt)
+ {
+ unrestricted|= with_elem->mutually_recursive;
+ break;
+ }
+ else
+ cnt++;
+ }
+ }
+ }
+ }
+
+
+ /* Check conditions 2 for restricted specification*/
+ ti.rewind();
+ while ((tbl= ti++))
+ {
+ if (!tbl->is_with_table_recursive_reference())
+ continue;
+ for (TABLE_LIST *tab= tbl; tab; tab= tab->embedding)
+ {
+ if (tab->outer_join & (JOIN_TYPE_LEFT | JOIN_TYPE_RIGHT))
+ {
+ unrestricted|= mutually_recursive;
+ break;
+ }
+ }
+ }
+ return false;
+}
+
+
+/**
+ @brief
+ Check subqueries with recursive table references from FROM list of this select
+
+ @details
+ For each recursive table reference from the FROM list of this select
+ this method checks:
+ - whether this reference is within a materialized derived table and
+ if so it report an error
+ - whether this reference is within a subquery and if so it set a flag
+ in this subquery that disallows some optimization strategies for
+ this subquery.
+
+ @retval
+ true if an error is reported
+ false otherwise
+*/
+
+bool st_select_lex::check_subqueries_with_recursive_references()
+{
+ List_iterator<TABLE_LIST> ti(leaf_tables);
+ TABLE_LIST *tbl;
+ while ((tbl= ti++))
+ {
+ if (!(tbl->is_with_table_recursive_reference()))
+ continue;
+ With_element *rec_elem= tbl->with;
+ st_select_lex_unit *sl_master;
+ for (st_select_lex *sl= this; sl; sl= sl_master->outer_select())
+ {
+ sl_master= sl->master_unit();
+ if (sl_master->with_element &&
+ sl_master->with_element->get_owner() == rec_elem->get_owner())
+ break;
+ sl->uncacheable|= UNCACHEABLE_DEPENDENT;
+ sl_master->uncacheable|= UNCACHEABLE_DEPENDENT;
+ if (sl_master->derived)
+ sl_master->derived->register_as_derived_with_rec_ref(rec_elem);
+ if (sl_master->item)
+ {
+ Item_subselect *subq= (Item_subselect *) (sl_master->item);
+ subq->register_as_with_rec_ref(rec_elem);
+ }
+ }
+ }
+ return false;
+}
+
+
+/**
+ @brief
+ Print this with clause
+
+ @param thd Thread handle
+ @param str Where to print to
+ @param query_type The mode of printing
+
+ @details
+ The method prints a string representation of this clause in the
+ string str. The parameter query_type specifies the mode of printing.
+*/
+
+void With_clause::print(THD *thd, String *str, enum_query_type query_type)
+{
+ /*
+ Any with clause contains just definitions of CTE tables.
+ No data expansion is applied to these definitions.
+ */
+ query_type= (enum_query_type) (query_type | QT_NO_DATA_EXPANSION);
+
+ str->append(STRING_WITH_LEN("with "));
+ if (with_recursive)
+ str->append(STRING_WITH_LEN("recursive "));
+ for (With_element *with_elem= with_list.first;
+ with_elem;
+ with_elem= with_elem->next)
+ {
+ if (with_elem != with_list.first)
+ str->append(", ");
+ with_elem->print(thd, str, query_type);
+ }
+}
+
+
+static void list_strlex_print(THD *thd, String *str, List<Lex_ident_sys> *list)
+{
+ List_iterator_fast<Lex_ident_sys> li(*list);
+ bool first= TRUE;
+ while(Lex_ident_sys *col_name= li++)
+ {
+ if (first)
+ first= FALSE;
+ else
+ str->append(',');
+ append_identifier(thd, str, col_name);
+ }
+}
+
+
+/**
+ @brief
+ Print this with element
+
+ @param thd Thread handle
+ @param str Where to print to
+ @param query_type The mode of printing
+
+ @details
+ The method prints a string representation of this with element in the
+ string str. The parameter query_type specifies the mode of printing.
+*/
+
+void With_element::print(THD *thd, String *str, enum_query_type query_type)
+{
+ str->append(get_name());
+ if (column_list.elements)
+ {
+ List_iterator_fast<Lex_ident_sys> li(column_list);
+ str->append('(');
+ list_strlex_print(thd, str, &column_list);
+ str->append(')');
+ }
+ str->append(STRING_WITH_LEN(" as ("));
+ spec->print(str, query_type);
+ str->append(')');
+
+ if (cycle_list)
+ {
+ DBUG_ASSERT(cycle_list->elements != 0);
+ str->append(STRING_WITH_LEN(" CYCLE "));
+ list_strlex_print(thd, str, cycle_list);
+ str->append(STRING_WITH_LEN(" RESTRICT "));
+ }
+}
+
+
+bool With_element::instantiate_tmp_tables()
+{
+ List_iterator_fast<TABLE_LIST> li(rec_result->rec_table_refs);
+ TABLE_LIST *rec_tbl;
+ while ((rec_tbl= li++))
+ {
+ TABLE *rec_table= rec_tbl->table;
+ if (!rec_table->is_created() &&
+ instantiate_tmp_table(rec_table,
+ rec_table->s->key_info,
+ rec_result->tmp_table_param.start_recinfo,
+ &rec_result->tmp_table_param.recinfo,
+ 0))
+ return true;
+
+ rec_table->file->extra(HA_EXTRA_WRITE_CACHE);
+ rec_table->file->extra(HA_EXTRA_IGNORE_DUP_KEY);
+ }
+ return false;
+}
+
+void With_element::set_cycle_list(List<Lex_ident_sys> *cycle_list_arg)
+{
+ cycle_list= cycle_list_arg;
+
+ /*
+ If a CTE table with columns c1,...,cn is defined with a cycle
+ clause CYCLE(ci1,...,cik) then no two rows r1 and r2 from the
+ table shall have r1.ci1=r2.ci1 && ... && r1.cik=r2.cik.
+
+ If a cycle clause is used in the specification of a CTE then
+ each UNION ALL at the top level of the specification is interpreted
+ as a UNION DISTINCT over the cycle columns.
+ */
+ for (st_select_lex *sl= spec->first_select(); sl; sl= sl->next_select())
+ {
+ spec->union_distinct= sl;
+ if (sl != spec->first_select())
+ {
+ sl->distinct= TRUE;
+ sl->with_all_modifier= FALSE;
+ }
+ }
+}