• Dmitry Shulga's avatar
    MDEV-20325: Assertion `outer_context || !*from_field || *from_field ==... · 89c870b2
    Dmitry Shulga authored
    MDEV-20325: Assertion `outer_context || !*from_field || *from_field == not_found_field' failed in Item_field::fix_outer_field | `!derived->is_excluded()' failed in TABLE_LIST::set_check_materialized | SIGEGV in st_select_lex::mark_as_dependent (optimized builds)
    
    Re-execution of a query containing subquery in the FROM clause results
    in assert failure in case the query is run as part of a stored routine or
    as a prepared statement AND derived table merge optimization is off.
    As an example, the following test case
      CREATE TABLE t1 (a INT) ;
      CREATE PROCEDURE sp() SELECT * FROM (SELECT a FROM t1) tb;
      CALL sp();
      SET optimizer_switch='derived_merge=off';
      CALL sp();
    results in assert failure on the second invocation of the 'sp' stored routine.
    
    The reason for assertion failure is that the expression
      derived->is_excluded()
    returns the value true where the value false expected.
    
    The method is_excluded() returns the value true for a derived table
    that has been merged to a parent select. Such transformation happens as part
    of Derived Table Merge Optimization that is performed on first invocation of
    a stored routine or a prepared statement containing a query with subquery
    in the FROM clause of the main SELECT.
    
    When the same routine or prepared statement is run the second time and
    Derived Table Merge Optimization is OFF the MariaDB server tries to materialize
    a derived table specified by the subquery that fails since this subquery
    has already been merged to the top-most SELECT. This transformation is permanent
    and can't be reverted. That is the reason why the assert
      DBUG_ASSERT(!derived->is_excluded());
    fails inside the function TABLE_LIST::set_check_materialized().
    
    Similar behaviour can be observed in case a stored routine or prepared statement
    containing a SELECT statement with subquery in the FROM clause, first is run
    with the optimizer_switch option set to derived_merge=off and re-run after this
    option has been switched to derived_merge=on. In this case a derived table for
    subquery is materialized on the first execution and marked as merged derived
    table on the second execution that results in error with misleading error
    message:
    
    MariaDB [test]> CALL sp1();
    ERROR 1030 (HY000): Got error 1 "Operation not permitted" from storage engine MEMORY
    
    To fix the issue, a derived table that has been already optimized shouldn't be
    re-marked for one more round of optimization.
    
    One significant consequence following from suggested change is that the data
    member TABLE_LIST::derived_type is not updated once the table optimization
    has been done. This fact should be taken into account when Prepared Statement
    being handled since once a table listed in a query has been optimized on
    execution of the statement PREPARE FROM it won't be touched anymore on handling
    the statement EXECUTE.
    
    One side effect caused by this change could be observed for the following
    test case:
      CREATE TABLE t1 (s1 INT);
      CREATE VIEW v1 AS
        SELECT s1,s2 FROM (SELECT s1 as s2 FROM t1 WHERE s1 <100) x, t1 WHERE t1.s1=x.s2;
      INSERT INTO v1 (s1) VALUES (-300);
    
      PREPARE stmt FROM "INSERT INTO v1 (s1) VALUES (-300)";
      EXECUTE stmt;
    
    Execution of the above EXECUTE statement results in issuing the error
    ER_COLUMNACCESS_DENIED_ERROR since table_ref->is_merged_derived() is false
    and check_column_grant_in_table_ref() called for a temporary table that
    shouldn't be. To fix this issue the function find_field_in_tables has been
    modified in such a way that the function check_column_grant_in_table_ref()
    is not called for a temporary table.
    89c870b2
table.cc 262 KB