diff --git a/mysql-test/r/ps.result b/mysql-test/r/ps.result index 7b19492faec..7790de570b0 100644 --- a/mysql-test/r/ps.result +++ b/mysql-test/r/ps.result @@ -2488,3 +2488,40 @@ execute stmt2 using @to_format, @dec; format(?, ?) 10,000.00 deallocate prepare stmt2; +DROP TABLE IF EXISTS t1, t2; +CREATE TABLE t1 (i INT); +INSERT INTO t1 VALUES (1); +CREATE TABLE t2 (i INT); +INSERT INTO t2 VALUES (2); +LOCK TABLE t1 READ, t2 WRITE; +PREPARE stmt1 FROM "SELECT i FROM t1"; +PREPARE stmt2 FROM "INSERT INTO t2 (i) VALUES (3)"; +EXECUTE stmt1; +i +1 +EXECUTE stmt2; +SELECT * FROM t2; +i +2 +UNLOCK TABLES; +SELECT * FROM t2; +i +2 +3 +ALTER TABLE t1 ADD COLUMN j INT; +ALTER TABLE t2 ADD COLUMN j INT; +INSERT INTO t1 VALUES (4, 5); +INSERT INTO t2 VALUES (4, 5); +EXECUTE stmt1; +i +1 +4 +EXECUTE stmt2; +SELECT * FROM t2; +i j +2 NULL +3 NULL +4 5 +3 NULL +DROP TABLE t1, t2; +End of 5.1 tests. diff --git a/mysql-test/t/ps.test b/mysql-test/t/ps.test index 14ee30f6026..9f670f9973f 100644 --- a/mysql-test/t/ps.test +++ b/mysql-test/t/ps.test @@ -2516,3 +2516,60 @@ set @to_format="10000"; execute stmt2 using @to_format, @dec; deallocate prepare stmt2; + +# +# BUG#18326: Do not lock table for writing during prepare of statement +# +--disable_warnings +DROP TABLE IF EXISTS t1, t2; +--enable_warnings + +CREATE TABLE t1 (i INT); +INSERT INTO t1 VALUES (1); +CREATE TABLE t2 (i INT); +INSERT INTO t2 VALUES (2); + +LOCK TABLE t1 READ, t2 WRITE; + +connect (conn1, localhost, root, , ); + +# Prepare never acquires the lock, and thus should not block. +PREPARE stmt1 FROM "SELECT i FROM t1"; +PREPARE stmt2 FROM "INSERT INTO t2 (i) VALUES (3)"; + +# This should not block because READ lock on t1 is shared. +EXECUTE stmt1; + +# This should block because WRITE lock on t2 is exclusive. +send EXECUTE stmt2; + +connection default; + +SELECT * FROM t2; +UNLOCK TABLES; +let $wait_condition= SELECT COUNT(*) = 2 FROM t2; +--source include/wait_condition.inc +SELECT * FROM t2; + +# DDL and DML works even if some client have a prepared statement +# referencing the table. +ALTER TABLE t1 ADD COLUMN j INT; +ALTER TABLE t2 ADD COLUMN j INT; +INSERT INTO t1 VALUES (4, 5); +INSERT INTO t2 VALUES (4, 5); + +connection conn1; + +reap; +EXECUTE stmt1; +EXECUTE stmt2; +SELECT * FROM t2; + +disconnect conn1; + +connection default; + +DROP TABLE t1, t2; + + +--echo End of 5.1 tests. diff --git a/sql/sql_prepare.cc b/sql/sql_prepare.cc index ce6072b2a63..3ae203a945d 100644 --- a/sql/sql_prepare.cc +++ b/sql/sql_prepare.cc @@ -34,6 +34,12 @@ When one prepares a statement: [Params meta info (stubs only for now)] (if Param_count > 0) [Columns meta info] (if Column_count > 0) + During prepare the tables used in a statement are opened, but no + locks are acquired. Table opening will block any DDL during the + operation, and we do not need any locks as we neither read nor + modify any data during prepare. Tables are closed after prepare + finishes. + When one executes a statement: - Server gets the command 'COM_STMT_EXECUTE' to execute the @@ -53,6 +59,10 @@ When one executes a statement: - Execute the query without re-parsing and send back the results to client + During execution of prepared statement tables are opened and locked + the same way they would for normal (non-prepared) statement + execution. Tables are unlocked and closed after the execution. + When one supplies long data for a placeholder: - Server gets the long data in pieces with command type @@ -1132,38 +1142,20 @@ static int mysql_test_update(Prepared_statement *stmt, bool need_reopen; DBUG_ENTER("mysql_test_update"); - if (update_precheck(thd, table_list)) + if (update_precheck(thd, table_list) || + open_normal_and_derived_tables(thd, table_list, 0)) goto error; - for ( ; ; ) + if (table_list->multitable_view) { - if (open_tables(thd, &table_list, &table_count, 0)) - goto error; - - if (table_list->multitable_view) - { - DBUG_ASSERT(table_list->view != 0); - DBUG_PRINT("info", ("Switch to multi-update")); - /* pass counter value */ - thd->lex->table_count= table_count; - /* convert to multiupdate */ - DBUG_RETURN(2); - } - - if (!lock_tables(thd, table_list, table_count, &need_reopen)) - break; - if (!need_reopen) - goto error; - close_tables_for_reopen(thd, &table_list); + DBUG_ASSERT(table_list->view != 0); + DBUG_PRINT("info", ("Switch to multi-update")); + /* pass counter value */ + thd->lex->table_count= table_count; + /* convert to multiupdate */ + DBUG_RETURN(2); } - /* - thd->fill_derived_tables() is false here for sure (because it is - preparation of PS, so we even do not check it). - */ - if (mysql_handle_derived(thd->lex, &mysql_derived_prepare)) - goto error; - #ifndef NO_EMBEDDED_ACCESS_CHECKS /* TABLE_LIST contain right privilages request */ want_privilege= table_list->grant.want_privilege; @@ -1221,7 +1213,7 @@ static bool mysql_test_delete(Prepared_statement *stmt, DBUG_ENTER("mysql_test_delete"); if (delete_precheck(thd, table_list) || - open_and_lock_tables(thd, table_list)) + open_normal_and_derived_tables(thd, table_list, 0)) goto error; if (!table_list->table) @@ -1280,7 +1272,7 @@ static int mysql_test_select(Prepared_statement *stmt, goto error; } - if (open_and_lock_tables(thd, tables)) + if (open_normal_and_derived_tables(thd, tables, 0)) goto error; thd->used_tables= 0; // Updated by setup_fields @@ -1341,7 +1333,7 @@ static bool mysql_test_do_fields(Prepared_statement *stmt, if (tables && check_table_access(thd, SELECT_ACL, tables, 0)) DBUG_RETURN(TRUE); - if (open_and_lock_tables(thd, tables)) + if (open_normal_and_derived_tables(thd, tables, 0)) DBUG_RETURN(TRUE); DBUG_RETURN(setup_fields(thd, 0, *values, MARK_COLUMNS_NONE, 0, 0)); } @@ -1371,7 +1363,7 @@ static bool mysql_test_set_fields(Prepared_statement *stmt, set_var_base *var; if (tables && check_table_access(thd, SELECT_ACL, tables, 0) || - open_and_lock_tables(thd, tables)) + open_normal_and_derived_tables(thd, tables, 0)) goto error; while ((var= it++)) @@ -1397,7 +1389,7 @@ error: NOTE This function won't directly open tables used in select. They should be opened either by calling function (and in this case you probably - should use select_like_stmt_test_with_open_n_lock()) or by + should use select_like_stmt_test_with_open()) or by "specific_prepare" call (like this happens in case of multi-update). RETURN VALUE @@ -1425,14 +1417,14 @@ static bool select_like_stmt_test(Prepared_statement *stmt, } /* - Check internal SELECT of the prepared command (with opening and - locking of used tables). + Check internal SELECT of the prepared command (with opening of used + tables). SYNOPSIS - select_like_stmt_test_with_open_n_lock() + select_like_stmt_test_with_open() stmt prepared statement - tables list of tables to be opened and locked - before calling specific_prepare function + tables list of tables to be opened before calling + specific_prepare function specific_prepare function of command specific prepare setup_tables_done_option options to be passed to LEX::unit.prepare() @@ -1442,19 +1434,20 @@ static bool select_like_stmt_test(Prepared_statement *stmt, */ static bool -select_like_stmt_test_with_open_n_lock(Prepared_statement *stmt, - TABLE_LIST *tables, - bool (*specific_prepare)(THD *thd), - ulong setup_tables_done_option) +select_like_stmt_test_with_open(Prepared_statement *stmt, + TABLE_LIST *tables, + bool (*specific_prepare)(THD *thd), + ulong setup_tables_done_option) { - DBUG_ENTER("select_like_stmt_test_with_open_n_lock"); + DBUG_ENTER("select_like_stmt_test_with_open"); /* - We should not call LEX::unit.cleanup() after this open_and_lock_tables() - call because we don't allow prepared EXPLAIN yet so derived tables will - clean up after themself. + We should not call LEX::unit.cleanup() after this + open_normal_and_derived_tables() call because we don't allow + prepared EXPLAIN yet so derived tables will clean up after + themself. */ - if (open_and_lock_tables(stmt->thd, tables)) + if (open_normal_and_derived_tables(stmt->thd, tables, 0)) DBUG_RETURN(TRUE); DBUG_RETURN(select_like_stmt_test(stmt, specific_prepare, @@ -1493,7 +1486,7 @@ static bool mysql_test_create_table(Prepared_statement *stmt) if (select_lex->item_list.elements) { select_lex->context.resolve_in_select_list= TRUE; - res= select_like_stmt_test_with_open_n_lock(stmt, tables, 0, 0); + res= select_like_stmt_test_with_open(stmt, tables, 0, 0); } /* put tables back for PS rexecuting */ @@ -1553,9 +1546,9 @@ static bool mysql_test_multidelete(Prepared_statement *stmt, } if (multi_delete_precheck(stmt->thd, tables) || - select_like_stmt_test_with_open_n_lock(stmt, tables, - &mysql_multi_delete_prepare, - OPTION_SETUP_TABLES_DONE)) + select_like_stmt_test_with_open(stmt, tables, + &mysql_multi_delete_prepare, + OPTION_SETUP_TABLES_DONE)) goto error; if (!tables->table) { @@ -1571,15 +1564,16 @@ error: /* Wrapper for mysql_insert_select_prepare, to make change of local tables - after open_and_lock_tables() call. + after open_normal_and_derived_tables() call. SYNOPSIS mysql_insert_select_prepare_tester() thd thread handle NOTE - We need to remove the first local table after open_and_lock_tables, - because mysql_handle_derived uses local tables lists. + We need to remove the first local table after + open_normal_and_derived_tables(), because mysql_handle_derived + uses local tables lists. */ static bool mysql_insert_select_prepare_tester(THD *thd) @@ -1631,9 +1625,9 @@ static bool mysql_test_insert_select(Prepared_statement *stmt, DBUG_ASSERT(first_local_table != 0); res= - select_like_stmt_test_with_open_n_lock(stmt, tables, - &mysql_insert_select_prepare_tester, - OPTION_SETUP_TABLES_DONE); + select_like_stmt_test_with_open(stmt, tables, + &mysql_insert_select_prepare_tester, + OPTION_SETUP_TABLES_DONE); /* revert changes made by mysql_insert_select_prepare_tester */ lex->select_lex.table_list.first= (byte*) first_local_table; return res;