From b6a943b6f30ba43659668508d9d59c946126a8f5 Mon Sep 17 00:00:00 2001 From: Dave Gosselin Date: Mon, 15 Jun 2026 13:53:00 -0400 Subject: [PATCH] MDEV-36059: 2nd PS exec crash w/nested VIEWs A prepared statement runs preparation again on every execution, and preparation merges any VIEWs named in the statement. A VIEW that appears only inside a subquery is meant to be skipped by the merge for insert pass during mysql_handle_derived, since it is not the target of the DELETE. The first execution prepares with the subquery still nested, so the view is correctly skipped, and the DELETE succeeds. Optimization then converts the IN subquery into a semi-join and adds its tables into the first SELECT_LEX's table list (reassigning them to that SELECT_LEX). This conversion is meant to persist for later executions. Then the PS's second execution prepares against the already flattened VIEW. mysql_handle_derived now finds the subquery's VIEW among the first SELECT_LEX's tables and tries to merge it. mysql_handle_derived already had some existing skip logic to recognize a VIEW, but the semijoin conversion has already merged the VIEW into the first SELECT_LEX, so the skip doesn't correctly apply. So a nested VIEW is merged for insert with no table, causing a crash on the NULL table pointer. Solution is to skip a table during mysql_handle_derived when it is the inner side of a semijoin, recognized by its embedding semijoin nest. --- mysql-test/main/derived_view.result | 35 ++++++++++++++++++++++++ mysql-test/main/derived_view.test | 42 +++++++++++++++++++++++++++++ sql/sql_derived.cc | 18 ++++++++++++- 3 files changed, 94 insertions(+), 1 deletion(-) diff --git a/mysql-test/main/derived_view.result b/mysql-test/main/derived_view.result index 77cc6cc01ec8b..26f4d6942685a 100644 --- a/mysql-test/main/derived_view.result +++ b/mysql-test/main/derived_view.result @@ -4506,4 +4506,39 @@ md5(a) IN (SELECT * FROM (SELECT '' FROM t1) as x) 0 DROP table t1; SET optimizer_switch=@save_optimizer_switch; +# +# MDEV-36059: 2nd PS exec crash w/nested VIEWs +# +CREATE TABLE t1 (a INT); +INSERT INTO t1 VALUES (1),(2); +CREATE TABLE t2 (b INT); +INSERT INTO t2 VALUES (1),(2); +CREATE VIEW v1 AS SELECT b FROM t2; +CREATE VIEW v2 AS SELECT b FROM v1; +PREPARE stmt FROM 'DELETE FROM t1 WHERE a IN (SELECT b FROM v2)'; +EXECUTE stmt; +EXECUTE stmt; +DROP VIEW v2, v1; +DROP TABLE t1, t2; +# +# Same crash when the subquery view sits inside deep nest. +# +CREATE TABLE t1 (a INT); +INSERT INTO t1 VALUES (1),(2),(3); +CREATE TABLE t2 (b INT); +INSERT INTO t2 VALUES (1),(2); +CREATE TABLE t3 (c INT); +INSERT INTO t3 VALUES (1),(2); +CREATE TABLE t4 (d INT); +INSERT INTO t4 VALUES (1),(2); +CREATE VIEW v1 AS SELECT b FROM t2; +CREATE VIEW v2 AS SELECT b FROM v1; +PREPARE stmt FROM 'DELETE FROM t1 WHERE a IN (SELECT t3.c FROM t3 LEFT JOIN (v2 JOIN t4 ON v2.b = t4.d) ON t3.c = v2.b)'; +EXECUTE stmt; +EXECUTE stmt; +SELECT a FROM t1; +a +3 +DROP VIEW v2, v1; +DROP TABLE t1, t2, t3, t4; # End of 11.4 tests diff --git a/mysql-test/main/derived_view.test b/mysql-test/main/derived_view.test index 0b7885cccf35d..b66be0a83e17d 100644 --- a/mysql-test/main/derived_view.test +++ b/mysql-test/main/derived_view.test @@ -2963,4 +2963,46 @@ SELECT md5(a) IN (SELECT * FROM (SELECT '' FROM t1) as x) FROM t1; DROP table t1; SET optimizer_switch=@save_optimizer_switch; +--echo # +--echo # MDEV-36059: 2nd PS exec crash w/nested VIEWs +--echo # + +CREATE TABLE t1 (a INT); +INSERT INTO t1 VALUES (1),(2); +CREATE TABLE t2 (b INT); +INSERT INTO t2 VALUES (1),(2); + +CREATE VIEW v1 AS SELECT b FROM t2; +CREATE VIEW v2 AS SELECT b FROM v1; +PREPARE stmt FROM 'DELETE FROM t1 WHERE a IN (SELECT b FROM v2)'; +EXECUTE stmt; +EXECUTE stmt; + +DROP VIEW v2, v1; +DROP TABLE t1, t2; + +--echo # +--echo # Same crash when the subquery view sits inside deep nest. +--echo # + +CREATE TABLE t1 (a INT); +INSERT INTO t1 VALUES (1),(2),(3); +CREATE TABLE t2 (b INT); +INSERT INTO t2 VALUES (1),(2); +CREATE TABLE t3 (c INT); +INSERT INTO t3 VALUES (1),(2); +CREATE TABLE t4 (d INT); +INSERT INTO t4 VALUES (1),(2); + +CREATE VIEW v1 AS SELECT b FROM t2; +CREATE VIEW v2 AS SELECT b FROM v1; +PREPARE stmt FROM 'DELETE FROM t1 WHERE a IN (SELECT t3.c FROM t3 LEFT JOIN (v2 JOIN t4 ON v2.b = t4.d) ON t3.c = v2.b)'; +EXECUTE stmt; +EXECUTE stmt; +--sorted_result +SELECT a FROM t1; + +DROP VIEW v2, v1; +DROP TABLE t1, t2, t3, t4; + --echo # End of 11.4 tests diff --git a/sql/sql_derived.cc b/sql/sql_derived.cc index 8a50a1ace01f9..531bd4d703c55 100644 --- a/sql/sql_derived.cc +++ b/sql/sql_derived.cc @@ -63,6 +63,21 @@ dt_processor processors[]= &mysql_derived_optimize_stage2 }; + +/* + Walk the embedding chain (nested tables) to see if there is + a IN converted to semijoin. +*/ + +static bool is_semijoin_inner_table(TABLE_LIST *tl) +{ + for (TABLE_LIST *emb= tl->embedding; emb; emb= emb->embedding) + if (emb->sj_subq_pred) + return true; + return false; +} + + /* Run specified phases on all derived tables/views in given LEX. @@ -110,7 +125,8 @@ mysql_handle_derived(LEX *lex, uint phases) cursor && !res; cursor= cursor->next_local) { - if (!cursor->is_view_or_derived() && phases == DT_MERGE_FOR_INSERT) + if (phases == DT_MERGE_FOR_INSERT && + (!cursor->is_view_or_derived() || is_semijoin_inner_table(cursor))) continue; uint allowed_phases= (cursor->is_merged_derived() ? DT_PHASES_MERGE : DT_PHASES_MATERIALIZE | DT_MERGE_FOR_INSERT);