[Commits] a97b533aebb: MDEV-25154: JSON_TABLE: Queries involving ordinality columns are unsafe ...
by psergey 13 May '21
by psergey 13 May '21
13 May '21
revision-id: a97b533aebbdbdae72c1ada4161c894de01ec549 (mariadb-10.6.0-42-ga97b533aebb)
parent(s): 370b310b1d67ad42df96b75c3876fdcf67a8694f
author: Sergei Petrunia
committer: Sergei Petrunia
timestamp: 2021-05-13 15:33:57 +0300
message:
MDEV-25154: JSON_TABLE: Queries involving ordinality columns are unsafe ...
Mark the JSON_TABLE function as SBR-unsafe.
It is not unsafe for the current implementation. But we still mark it as such
in order to be future-proof and keep it possible to change JSON data
representation in the future.
---
mysql-test/suite/json/r/json_table_binlog.result | 26 ++++++++++++++++++++++++
mysql-test/suite/json/t/json_table_binlog.test | 25 +++++++++++++++++++++++
sql/json_table.h | 16 +++++++++++++++
sql/sql_yacc.yy | 2 ++
4 files changed, 69 insertions(+)
diff --git a/mysql-test/suite/json/r/json_table_binlog.result b/mysql-test/suite/json/r/json_table_binlog.result
new file mode 100644
index 00000000000..472f7395648
--- /dev/null
+++ b/mysql-test/suite/json/r/json_table_binlog.result
@@ -0,0 +1,26 @@
+#
+# MDEV-25154: JSON_TABLE: Queries involving ordinality columns are unsafe for statement binlog and should be marked as such
+#
+create table t1 (a int);
+call mtr.add_suppression("Unsafe statement written to the binary log using statement format since BINLOG_FORMAT = STATEMENT");
+set binlog_format='statement';
+insert into t1
+select *
+from json_table('[1,2,3]', '$[*]' columns (a for ordinality)) as T ;
+Warnings:
+Note 1592 Unsafe statement written to the binary log using statement format since BINLOG_FORMAT = STATEMENT. Statement is unsafe because it uses a system function that may return a different value on the slave
+set binlog_format='mixed';
+insert into t1
+select *
+from json_table('[1,2,3]', '$[*]' columns (a for ordinality)) as T ;
+# This must show Annotate_rows, Write_rows_v1 events. Not the statement event
+include/show_binlog_events.inc
+Log_name Pos Event_type Server_id End_log_pos Info
+master-bin.000001 # Gtid # # BEGIN GTID #-#-#
+master-bin.000001 # Annotate_rows # # insert into t1
+select *
+from json_table('[1,2,3]', '$[*]' columns (a for ordinality)) as T
+master-bin.000001 # Table_map # # table_id: # (test.t1)
+master-bin.000001 # Write_rows_v1 # # table_id: # flags: STMT_END_F
+master-bin.000001 # Query # # COMMIT
+drop table t1;
diff --git a/mysql-test/suite/json/t/json_table_binlog.test b/mysql-test/suite/json/t/json_table_binlog.test
new file mode 100644
index 00000000000..dcc05fb855d
--- /dev/null
+++ b/mysql-test/suite/json/t/json_table_binlog.test
@@ -0,0 +1,25 @@
+--source include/have_binlog_format_mixed.inc
+
+--echo #
+--echo # MDEV-25154: JSON_TABLE: Queries involving ordinality columns are unsafe for statement binlog and should be marked as such
+--echo #
+
+create table t1 (a int);
+
+call mtr.add_suppression("Unsafe statement written to the binary log using statement format since BINLOG_FORMAT = STATEMENT");
+set binlog_format='statement';
+insert into t1
+select *
+from json_table('[1,2,3]', '$[*]' columns (a for ordinality)) as T ;
+
+set binlog_format='mixed';
+let $binlog_start= query_get_value(SHOW MASTER STATUS, Position, 1);
+let $binlog_file= LAST;
+
+insert into t1
+select *
+from json_table('[1,2,3]', '$[*]' columns (a for ordinality)) as T ;
+
+--echo # This must show Annotate_rows, Write_rows_v1 events. Not the statement event
+--source include/show_binlog_events.inc
+drop table t1;
diff --git a/sql/json_table.h b/sql/json_table.h
index beae5405d25..3560b4ca137 100644
--- a/sql/json_table.h
+++ b/sql/json_table.h
@@ -183,6 +183,22 @@ class Json_table_column : public Sql_alloc
into the TABLE_LIST::table_function.
Then the ha_json_table instance is created based on it in
the create_table_for_function().
+
+ == Replication: whether JSON_TABLE is deterministic ==
+
+ In sql_yacc.yy, we set BINLOG_STMT_UNSAFE_SYSTEM_FUNCTION whenever
+ JSON_TABLE is used. The reasoning behind this is as follows:
+
+ In the current MariaDB code, evaluation of JSON_TABLE is deterministic,
+ that is, for a given input string JSON_TABLE will always produce the same
+ set of rows in the same order. However one can think of JSON documents
+ that one can consider indentical which will produce different output.
+ In order to be feature-proof and withstand changes like:
+ - sorting JSON object members by name (like MySQL does)
+ - changing the way duplicate object members are handled
+ we mark the function as SBR-unsafe.
+ (If there is ever an issue with this, marking the function as SBR-safe
+ is a non-intrusive change we will always be able to make)
*/
class Table_function_json_table : public Sql_alloc
diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy
index fe6112e9592..92769dc01f1 100644
--- a/sql/sql_yacc.yy
+++ b/sql/sql_yacc.yy
@@ -11675,6 +11675,8 @@ table_function:
new (thd->mem_root) Table_function_json_table($4);
if (unlikely(!jt))
MYSQL_YYABORT;
+ /* See comment for class Table_function_json_table: */
+ Lex->set_stmt_unsafe(LEX::BINLOG_STMT_UNSAFE_SYSTEM_FUNCTION);
Lex->json_table= jt;
Select->parsing_place= NO_MATTER;
1
0
[Commits] c67d69abb9b: MDEV-25154: JSON_TABLE: Queries involving ordinality columns are unsafe ...
by psergey 13 May '21
by psergey 13 May '21
13 May '21
revision-id: c67d69abb9b6d05a1c837dc92e1faad770741f55 (mariadb-10.6.0-42-gc67d69abb9b)
parent(s): 370b310b1d67ad42df96b75c3876fdcf67a8694f
author: Sergei Petrunia
committer: Sergei Petrunia
timestamp: 2021-05-13 12:25:01 +0300
message:
MDEV-25154: JSON_TABLE: Queries involving ordinality columns are unsafe ...
Mark the JSON_TABLE function as SBR-unsafe.
It is not unsafe for the current implementation. But we still mark it as such
in order to be future-proof and keep it possible to change JSON data
representation in the future.
---
mysql-test/suite/json/r/json_table_binlog.result | 26 ++++++++++++++++++++++++
mysql-test/suite/json/t/json_table_binlog.test | 25 +++++++++++++++++++++++
sql/json_table.h | 16 +++++++++++++++
sql/sql_yacc.yy | 2 ++
4 files changed, 69 insertions(+)
diff --git a/mysql-test/suite/json/r/json_table_binlog.result b/mysql-test/suite/json/r/json_table_binlog.result
new file mode 100644
index 00000000000..472f7395648
--- /dev/null
+++ b/mysql-test/suite/json/r/json_table_binlog.result
@@ -0,0 +1,26 @@
+#
+# MDEV-25154: JSON_TABLE: Queries involving ordinality columns are unsafe for statement binlog and should be marked as such
+#
+create table t1 (a int);
+call mtr.add_suppression("Unsafe statement written to the binary log using statement format since BINLOG_FORMAT = STATEMENT");
+set binlog_format='statement';
+insert into t1
+select *
+from json_table('[1,2,3]', '$[*]' columns (a for ordinality)) as T ;
+Warnings:
+Note 1592 Unsafe statement written to the binary log using statement format since BINLOG_FORMAT = STATEMENT. Statement is unsafe because it uses a system function that may return a different value on the slave
+set binlog_format='mixed';
+insert into t1
+select *
+from json_table('[1,2,3]', '$[*]' columns (a for ordinality)) as T ;
+# This must show Annotate_rows, Write_rows_v1 events. Not the statement event
+include/show_binlog_events.inc
+Log_name Pos Event_type Server_id End_log_pos Info
+master-bin.000001 # Gtid # # BEGIN GTID #-#-#
+master-bin.000001 # Annotate_rows # # insert into t1
+select *
+from json_table('[1,2,3]', '$[*]' columns (a for ordinality)) as T
+master-bin.000001 # Table_map # # table_id: # (test.t1)
+master-bin.000001 # Write_rows_v1 # # table_id: # flags: STMT_END_F
+master-bin.000001 # Query # # COMMIT
+drop table t1;
diff --git a/mysql-test/suite/json/t/json_table_binlog.test b/mysql-test/suite/json/t/json_table_binlog.test
new file mode 100644
index 00000000000..dcc05fb855d
--- /dev/null
+++ b/mysql-test/suite/json/t/json_table_binlog.test
@@ -0,0 +1,25 @@
+--source include/have_binlog_format_mixed.inc
+
+--echo #
+--echo # MDEV-25154: JSON_TABLE: Queries involving ordinality columns are unsafe for statement binlog and should be marked as such
+--echo #
+
+create table t1 (a int);
+
+call mtr.add_suppression("Unsafe statement written to the binary log using statement format since BINLOG_FORMAT = STATEMENT");
+set binlog_format='statement';
+insert into t1
+select *
+from json_table('[1,2,3]', '$[*]' columns (a for ordinality)) as T ;
+
+set binlog_format='mixed';
+let $binlog_start= query_get_value(SHOW MASTER STATUS, Position, 1);
+let $binlog_file= LAST;
+
+insert into t1
+select *
+from json_table('[1,2,3]', '$[*]' columns (a for ordinality)) as T ;
+
+--echo # This must show Annotate_rows, Write_rows_v1 events. Not the statement event
+--source include/show_binlog_events.inc
+drop table t1;
diff --git a/sql/json_table.h b/sql/json_table.h
index beae5405d25..3560b4ca137 100644
--- a/sql/json_table.h
+++ b/sql/json_table.h
@@ -183,6 +183,22 @@ class Json_table_column : public Sql_alloc
into the TABLE_LIST::table_function.
Then the ha_json_table instance is created based on it in
the create_table_for_function().
+
+ == Replication: whether JSON_TABLE is deterministic ==
+
+ In sql_yacc.yy, we set BINLOG_STMT_UNSAFE_SYSTEM_FUNCTION whenever
+ JSON_TABLE is used. The reasoning behind this is as follows:
+
+ In the current MariaDB code, evaluation of JSON_TABLE is deterministic,
+ that is, for a given input string JSON_TABLE will always produce the same
+ set of rows in the same order. However one can think of JSON documents
+ that one can consider indentical which will produce different output.
+ In order to be feature-proof and withstand changes like:
+ - sorting JSON object members by name (like MySQL does)
+ - changing the way duplicate object members are handled
+ we mark the function as SBR-unsafe.
+ (If there is ever an issue with this, marking the function as SBR-safe
+ is a non-intrusive change we will always be able to make)
*/
class Table_function_json_table : public Sql_alloc
diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy
index fe6112e9592..92769dc01f1 100644
--- a/sql/sql_yacc.yy
+++ b/sql/sql_yacc.yy
@@ -11675,6 +11675,8 @@ table_function:
new (thd->mem_root) Table_function_json_table($4);
if (unlikely(!jt))
MYSQL_YYABORT;
+ /* See comment for class Table_function_json_table: */
+ Lex->set_stmt_unsafe(LEX::BINLOG_STMT_UNSAFE_SYSTEM_FUNCTION);
Lex->json_table= jt;
Select->parsing_place= NO_MATTER;
1
0
[Commits] ec833dd: MDEV-23886 Reusing CTE inside a function fails with table doesn't exist
by IgorBabaev 13 May '21
by IgorBabaev 13 May '21
13 May '21
revision-id: ec833dde82a86cb0bd39900866e88165318e88c0 (mariadb-10.2.31-945-gec833dd)
parent(s): 089d82a74be3a96ecaa3e703fb64f1440749d5a6
author: Igor Babaev
committer: Igor Babaev
timestamp: 2021-05-12 19:32:29 -0700
message:
MDEV-23886 Reusing CTE inside a function fails with table doesn't exist
In the code existed just before this patch binding of a table reference to
the specification of the corresponding CTE happens in the function
open_and_process_table(). If the table reference is not the first in the
query the specification is cloned in the same way as the specification of
a view is cloned for any reference of the view. This works fine for
standalone queries, but does not work for stored procedures / functions
for the following reason.
When the first call of a stored procedure/ function SP is processed the
body of SP is parsed. When a query of SP is parsed the info on each
encountered table reference is put into a TABLE_LIST object linked into
a global chain associated with the query. When parsing of the query is
finished the basic info on the table references from this chain except
table references to derived tables and information schema tables is put
in one hash table associated with SP. When parsing of the body of SP is
finished this hash table is used to construct TABLE_LIST objects for all
table references mentioned in SP and link them into the list of such
objects passed to a pre-locking process that calls open_and_process_table()
for each table from the list.
When a TABLE_LIST for a view is encountered the view is opened and its
specification is parsed. For any table reference occurred in
the specification a new TABLE_LIST object is created to be included into
the list for pre-locking. After all objects in the pre-locking have been
looked through the tables mentioned in the list are locked. Note that the
objects referenced CTEs are just skipped here as it is impossible to
resolve these references without any info on the context where they occur.
Now the statements from the body of SP are executed one by one that.
At the very beginning of the execution of a query the tables used in the
query are opened and open_and_process_table() now is called for each table
reference mentioned in the list of TABLE_LIST objects associated with the
query that was built when the query was parsed.
For each table reference first the reference is checked against CTEs
definitions in whose scope it occurred. If such definition is found the
reference is considered resolved and if this is not the first reference
to the found CTE the the specification of the CTE is re-parsed and the
result of the parsing is added to the parsing tree of the query as a
sub-tree. If this sub-tree contains table references to other tables they
are added to the list of TABLE_LIST objects associated with the query in
order the referenced tables to be opened. When the procedure that opens
the tables comes to the TABLE_LIST object created for a non-first
reference to a CTE it discovers that the referenced table instance is not
locked and reports an error.
Thus processing non-first table references to a CTE similar to how
references to view are processed does not work for queries used in stored
procedures / functions. And the main problem is that the current
pre-locking mechanism employed for stored procedures / functions does not
allow to save the context in which a CTE reference occur. It's not trivial
to save the info about the context where a CTE reference occurs while the
resolution of the table reference cannot be done without this context and
consequentially the specification for the table reference cannot be
determined.
This patch solves the above problem by moving resolution of all CTE
references at the parsing stage. More exactly references to CTEs occurred in
a query are resolved right after parsing of the query has finished. After
resolution any CTE reference it is marked as a reference to to derived
table. So it is excluded from the hash table created for pre-locking used
base tables and view when the first call of a stored procedure / function
is processed.
This solution required recursive calls of the parser. The function
THD::sql_parser() has been added specifically for recursive invocations of
the parser.
---
mysql-test/r/cte_nonrecursive.result | 213 ++++++++++++++++-
mysql-test/r/cte_recursive.result | 8 +-
mysql-test/t/cte_nonrecursive.test | 202 ++++++++++++++++
sql/item_subselect.cc | 1 -
sql/sp_head.cc | 3 +-
sql/sql_base.cc | 33 +--
sql/sql_class.cc | 54 +++++
sql/sql_class.h | 9 +-
sql/sql_cte.cc | 437 ++++++++++++++++++++++++-----------
sql/sql_cte.h | 84 ++++++-
sql/sql_lex.cc | 3 +
sql/sql_lex.h | 27 ++-
sql/sql_parse.cc | 16 +-
sql/sql_prepare.cc | 3 -
sql/sql_view.cc | 9 -
sql/sql_yacc.yy | 66 +++++-
sql/table.h | 37 +++
17 files changed, 987 insertions(+), 218 deletions(-)
diff --git a/mysql-test/r/cte_nonrecursive.result b/mysql-test/r/cte_nonrecursive.result
index da954c1..7c6c6e8 100644
--- a/mysql-test/r/cte_nonrecursive.result
+++ b/mysql-test/r/cte_nonrecursive.result
@@ -606,7 +606,7 @@ with t(c) as (select a from t1 where b >= 'c')
select * from t r1 where r1.c=4;
show create view v3;
View Create View character_set_client collation_connection
-v3 CREATE ALGORITHM=UNDEFINED DEFINER=`root`@`localhost` SQL SECURITY DEFINER VIEW `v3` AS with t as (select `test`.`t1`.`a` AS `c` from `test`.`t1` where `test`.`t1`.`b` >= 'c')select `r1`.`c` AS `c` from `t` `r1` where `r1`.`c` = 4 latin1 latin1_swedish_ci
+v3 CREATE ALGORITHM=UNDEFINED DEFINER=`root`@`localhost` SQL SECURITY DEFINER VIEW `v3` AS with t(c) as (select `test`.`t1`.`a` AS `c` from `test`.`t1` where `test`.`t1`.`b` >= 'c')select `r1`.`c` AS `c` from `t` `r1` where `r1`.`c` = 4 latin1 latin1_swedish_ci
select * from v3;
c
4
@@ -618,7 +618,7 @@ with t(c) as (select a from t1 where b >= 'c')
select * from t r1, t r2 where r1.c=r2.c and r2.c=4;
show create view v4;
View Create View character_set_client collation_connection
-v4 CREATE ALGORITHM=UNDEFINED DEFINER=`root`@`localhost` SQL SECURITY DEFINER VIEW `v4` AS with t as (select `test`.`t1`.`a` AS `c` from `test`.`t1` where `test`.`t1`.`b` >= 'c')select `r1`.`c` AS `c`,`r2`.`c` AS `d` from (`t` `r1` join `t` `r2`) where `r1`.`c` = `r2`.`c` and `r2`.`c` = 4 latin1 latin1_swedish_ci
+v4 CREATE ALGORITHM=UNDEFINED DEFINER=`root`@`localhost` SQL SECURITY DEFINER VIEW `v4` AS with t(c) as (select `test`.`t1`.`a` AS `c` from `test`.`t1` where `test`.`t1`.`b` >= 'c')select `r1`.`c` AS `c`,`r2`.`c` AS `d` from (`t` `r1` join `t` `r2`) where `r1`.`c` = `r2`.`c` and `r2`.`c` = 4 latin1 latin1_swedish_ci
select * from v4;
c d
4 4
@@ -1120,10 +1120,10 @@ id select_type table type possible_keys key key_len ref rows filtered Extra
2 DERIVED t1 ALL NULL NULL NULL NULL 7 100.00 Using where
5 UNION t1 ALL NULL NULL NULL NULL 7 100.00 Using where
NULL UNION RESULT <union2,5> ALL NULL NULL NULL NULL NULL NULL
-6 UNION <derived9> ALL NULL NULL NULL NULL 14 100.00
-9 DERIVED t1 ALL NULL NULL NULL NULL 7 100.00 Using where
-12 UNION t1 ALL NULL NULL NULL NULL 7 100.00 Using where
-NULL UNION RESULT <union9,12> ALL NULL NULL NULL NULL NULL NULL
+6 UNION <derived14> ALL NULL NULL NULL NULL 14 100.00
+14 DERIVED t1 ALL NULL NULL NULL NULL 7 100.00 Using where
+11 UNION t1 ALL NULL NULL NULL NULL 7 100.00 Using where
+NULL UNION RESULT <union14,11> ALL NULL NULL NULL NULL NULL NULL
NULL UNION RESULT <union1,6> ALL NULL NULL NULL NULL NULL NULL
Warnings:
Note 1003 with cte_e as (with cte_o as (with cte_i as (select `test`.`t1`.`a` AS `a` from `test`.`t1` where `test`.`t1`.`a` < 7)select `test`.`t1`.`a` AS `a` from `test`.`t1` where `test`.`t1`.`a` > 1)select `test`.`t1`.`a` AS `a` from `test`.`t1` where `test`.`t1`.`a` < 3 and `test`.`t1`.`a` > 1 and `test`.`t1`.`a` < 7 and `test`.`t1`.`a` > 1 union select `test`.`t1`.`a` AS `a` from `test`.`t1` where `test`.`t1`.`a` > 4 and `test`.`t1`.`a` > 1 and `test`.`t1`.`a` < 7 and `test`.`t1`.`a` > 1)select `cte_e1`.`a` AS `a` from `cte_e` `cte_e1` where `cte_e1`.`a` > 1 union select `cte_e2`.`a` AS `a` from `cte_e` `cte_e2`
@@ -1763,4 +1763,205 @@ a c
2 1
7 3
drop table t1;
+#
+# MDEV-23886: Stored Function returning the result of a query
+# that uses CTE over a table twice
+#
+create table t1 (c1 int);
+insert into t1 values (1),(2),(6);
+create function f1() returns int return
+( with cte1 as (select c1 from t1)
+select sum(c1) from
+(select * from cte1 union all select * from cte1) dt
+);
+select f1();
+f1()
+18
+create function f2() returns int return
+( with cte1 as (select c1 from t1)
+select sum(s.c1) from cte1 as s, cte1 as t where s.c1=t.c1
+);
+select f2();
+f2()
+9
+create function f3() returns int return
+( with cte1 as (select c1 from t1)
+select
+case
+when exists(select 1 from cte1 where c1 between 1 and 2) then 1
+when exists(select 1 from cte1 where c1 between 5 and 6) then 2
+else 0
+end
+);
+select f3();
+f3()
+1
+create view v1 as (select c1 from t1);
+create function f4() returns int return
+( select sum(c1) from
+(select * from v1 union all select * from v1) dt
+);
+select f4();
+f4()
+18
+create function f5() returns int return
+( select sum(s.c1) from v1 as s, v1 as t where s.c1=t.c1
+);
+select f5();
+f5()
+9
+create view v2(s) as
+with cte1 as (select c1 from t1)
+select sum(c1) from (select * from cte1 union all select * from cte1) dt;
+create function f6() returns int return
+(select s from v2);
+select f6();
+f6()
+18
+create function f7() returns int return
+( select r.s from v2 as r, v2 as t where r.s=t.s
+);
+select f7();
+f7()
+18
+select f5() + f6();
+f5() + f6()
+27
+prepare stmt from "select f5() + f6();";
+execute stmt;
+f5() + f6()
+27
+execute stmt;
+f5() + f6()
+27
+deallocate prepare stmt;
+drop function f1;
+drop function f2;
+drop function f3;
+drop function f4;
+drop function f5;
+drop function f6;
+drop function f7;
+drop view v1;
+drop view v2;
+create table t2 (a int, b int);
+insert into t2
+with cte1 as (select c1 from t1)
+select * from cte1 as s, cte1 as t where s.c1=t.c1 and s.c1 > 5;
+select * from t2;
+a b
+6 6
+create procedure p1()
+begin
+insert into t2
+with cte1 as (select c1 from t1)
+select * from cte1 as s, cte1 as t where s.c1=t.c1 and s.c1 <= 2 and t.c1 >= 2;
+end |
+call p1();
+select * from t2;
+a b
+6 6
+2 2
+drop procedure p1;
+# checking CTE resolution for queries with hanging CTEs
+with
+cte1(a) as (select * from t1 where c1 <= 2),
+cte2(b) as (select * from cte1 where a >= 2),
+cte3 as (select * from cte1,cte2 where cte1.a < cte2.b)
+select * from cte3;
+a b
+1 2
+select * from t2;
+a b
+6 6
+2 2
+with
+cte1(a) as (select * from t1 where c1 <= 2),
+cte2(b) as (select * from cte1 where a >= 2),
+cte3 as (select * from cte1,cte2 where cte1.a < cte2.b)
+select * from t2;
+a b
+6 6
+2 2
+with
+cte1(a) as (select * from t1 where c1 <= 2),
+cte2(b) as (select * from cte1 where c1 >= 2),
+cte3 as (select * from cte1,cte2 where cte1.a < cte2.b)
+select * from t2;
+ERROR 42S22: Unknown column 'c1' in 'where clause'
+with
+cte1(a) as (select * from t1 where c1 <= 2),
+cte2(b) as (select * from cte1 where a >= 2),
+cte3 as (select * from cte1,cte2 where cte1.a < cte2.c1)
+select * from t2;
+ERROR 42S22: Unknown column 'cte2.c1' in 'where clause'
+with
+cte1 as (select * from t1 where c1 <= 2),
+cte2(a,b) as (select * from cte1 as s1, cte1 as s2 where s1.c1=s2.c1)
+select * from cte2;
+a b
+1 1
+2 2
+with
+cte1 as (select * from t1 where c1 <= 2),
+cte2(a,b) as (select * from cte1 as s1, cte1 as s2 where s1.c1=s2.c1)
+select * from t2;
+a b
+6 6
+2 2
+with
+cte1 as (select * from t1 where c1 <= 2),
+cte2(a,b) as (select * from cte1 as s1, cte1 as s2 where s1.c1=c1)
+select * from t2;
+ERROR 23000: Column 'c1' in where clause is ambiguous
+with cte3 as
+( with cte2(a,b) as
+( with cte1 as (select * from t1 where c1 <= 2)
+select * from cte1 as s1, cte1 as s2 where s1.c1=s2.c1)
+select r1.a,r2.b from cte2 as r1, cte2 as r2)
+select * from cte3;
+a b
+1 1
+2 1
+1 2
+2 2
+with cte3 as
+( with cte2(a,b) as
+( with cte1 as (select * from t1 where c1 <= 2)
+select * from cte1 as s1, cte1 as s2 where s1.c1=s2.c1)
+select r1.a,r2.b from cte2 as r1, cte2 as r2)
+select * from t2;
+a b
+6 6
+2 2
+with cte3 as
+( with cte2(a,b) as
+( with cte1 as (select * from t1 where c1 <= 2)
+select * from cte1 as s1, cte1 as s2 where s1.c1=s2.c1)
+select r1.c1,r2.c1 from cte2 as r1, cte2 as r2)
+select * from t2;
+ERROR 42S22: Unknown column 'r1.c1' in 'field list'
+create procedure p1()
+begin
+insert into t2
+with cte1 as (select c1 from t1)
+select * from t1 as s, t1 as t where s.c1=t.c1 and s.c1 <= 2 and t.c1 >= 2;
+end |
+call p1();
+select * from t2;
+a b
+6 6
+2 2
+2 2
+drop procedure p1;
+create procedure p1()
+begin
+insert into t2
+with cte1 as (select a from t1)
+select * from t1 as s, t1 as t where s.c1=t.c1 and s.c1 <= 2 and t.c1 >= 2;
+end |
+call p1();
+ERROR 42S22: Unknown column 'a' in 'field list'
+drop procedure p1;
+drop table t1,t2;
# End of 10.2 tests
diff --git a/mysql-test/r/cte_recursive.result b/mysql-test/r/cte_recursive.result
index b6b4ed7..3e92652 100644
--- a/mysql-test/r/cte_recursive.result
+++ b/mysql-test/r/cte_recursive.result
@@ -699,7 +699,7 @@ id select_type table type possible_keys key key_len ref rows filtered Extra
NULL UNION RESULT <union3,4,5> ALL NULL NULL NULL NULL NULL NULL
2 DERIVED <derived3> ALL NULL NULL NULL NULL 12 100.00 Using where
Warnings:
-Note 1003 with recursive ancestor_couple_ids as (select `a`.`father` AS `h_id`,`a`.`mother` AS `w_id` from `coupled_ancestors` `a` where `a`.`father` is not null and `a`.`mother` is not null), coupled_ancestors as (select `test`.`folks`.`id` AS `id`,`test`.`folks`.`name` AS `name`,`test`.`folks`.`dob` AS `dob`,`test`.`folks`.`father` AS `father`,`test`.`folks`.`mother` AS `mother` from `test`.`folks` where `test`.`folks`.`name` = 'Me' union all select `test`.`p`.`id` AS `id`,`test`.`p`.`name` AS `name`,`test`.`p`.`dob` AS `dob`,`test`.`p`.`father` AS `father`,`test`.`p`.`mother` AS `mother` from `test`.`folks` `p` join `ancestor_couple_ids` `fa` where `test`.`p`.`id` = `fa`.`h_id` union all select `test`.`p`.`id` AS `id`,`test`.`p`.`name` AS `name`,`test`.`p`.`dob` AS `dob`,`test`.`p`.`father` AS `father`,`test`.`p`.`mother` AS `mother` from `test`.`folks` `p` join `ancestor_couple_ids` `ma` where `test`.`p`.`id` = `ma`.`w_id`)select `h`.`name` AS `name`,`h`.`dob` AS `dob`,`w`.`name
` AS `name`,`w`.`dob` AS `dob` from `ancestor_couple_ids` `c` join `coupled_ancestors` `h` join `coupled_ancestors` `w` where `h`.`id` = `c`.`h_id` and `w`.`id` = `c`.`w_id`
+Note 1003 with recursive ancestor_couple_ids(h_id,w_id) as (select `a`.`father` AS `h_id`,`a`.`mother` AS `w_id` from `coupled_ancestors` `a` where `a`.`father` is not null and `a`.`mother` is not null), coupled_ancestors(id,name,dob,father,mother) as (select `test`.`folks`.`id` AS `id`,`test`.`folks`.`name` AS `name`,`test`.`folks`.`dob` AS `dob`,`test`.`folks`.`father` AS `father`,`test`.`folks`.`mother` AS `mother` from `test`.`folks` where `test`.`folks`.`name` = 'Me' union all select `test`.`p`.`id` AS `id`,`test`.`p`.`name` AS `name`,`test`.`p`.`dob` AS `dob`,`test`.`p`.`father` AS `father`,`test`.`p`.`mother` AS `mother` from `test`.`folks` `p` join `ancestor_couple_ids` `fa` where `test`.`p`.`id` = `fa`.`h_id` union all select `test`.`p`.`id` AS `id`,`test`.`p`.`name` AS `name`,`test`.`p`.`dob` AS `dob`,`test`.`p`.`father` AS `father`,`test`.`p`.`mother` AS `mother` from `test`.`folks` `p` join `ancestor_couple_ids` `ma` where `test`.`p`.`id` = `ma`.`w_id`)select `h`.`name`
AS `name`,`h`.`dob` AS `dob`,`w`.`name` AS `name`,`w`.`dob` AS `dob` from `ancestor_couple_ids` `c` join `coupled_ancestors` `h` join `coupled_ancestors` `w` where `h`.`id` = `c`.`h_id` and `w`.`id` = `c`.`w_id`
# simple mutual recursion
with recursive
ancestor_couple_ids(h_id, w_id)
@@ -3047,7 +3047,7 @@ id select_type table type possible_keys key key_len ref rows filtered Extra
4 DEPENDENT SUBQUERY <derived2> ALL NULL NULL NULL NULL 16 100.00 Using where
NULL UNION RESULT <union2,3> ALL NULL NULL NULL NULL NULL NULL
Warnings:
-Note 1003 with recursive destinations as (select `test`.`a`.`arrival` AS `city`,1 AS `legs` from `test`.`flights` `a` where `test`.`a`.`departure` = 'Cairo' union select `test`.`b`.`arrival` AS `arrival`,`r`.`legs` + 1 AS `r.legs + 1` from `destinations` `r` join `test`.`flights` `b` where `r`.`city` = `test`.`b`.`departure` and !<in_optimizer>(`test`.`b`.`arrival`,<exists>(select `destinations`.`city` from `destinations` where trigcond(`test`.`b`.`arrival` = `destinations`.`city` or `destinations`.`city` is null) having trigcond(`destinations`.`city` is null))))select `destinations`.`city` AS `city`,`destinations`.`legs` AS `legs` from `destinations`
+Note 1003 with recursive destinations(city,legs) as (select `test`.`a`.`arrival` AS `city`,1 AS `legs` from `test`.`flights` `a` where `test`.`a`.`departure` = 'Cairo' union select `test`.`b`.`arrival` AS `arrival`,`r`.`legs` + 1 AS `r.legs + 1` from `destinations` `r` join `test`.`flights` `b` where `r`.`city` = `test`.`b`.`departure` and !<in_optimizer>(`test`.`b`.`arrival`,<exists>(select `destinations`.`city` from `destinations` where trigcond(`test`.`b`.`arrival` = `destinations`.`city` or `destinations`.`city` is null) having trigcond(`destinations`.`city` is null))))select `destinations`.`city` AS `city`,`destinations`.`legs` AS `legs` from `destinations`
set standard_compliant_cte=default;
drop table flights;
#
@@ -3334,7 +3334,7 @@ NULL UNION RESULT <union2,3> ALL NULL NULL NULL NULL NULL NULL
5 DERIVED <derived2> ALL NULL NULL NULL NULL 2 100.00 Using where
5 DERIVED t1 ALL NULL NULL NULL NULL 3 100.00 Using where; Using join buffer (flat, BNL join)
Warnings:
-Note 1003 with recursive rcte as (select 1 AS `a` union select cast(`rcte`.`a` + 1 as unsigned) AS `cast(a+1 as unsigned)` from `rcte` where `rcte`.`a` < 10), cte1 as (select count(0) AS `c1` from `rcte` join `test`.`t1` where `rcte`.`a` between 3 and 5 and `test`.`t1`.`id` = `rcte`.`a` - 3), cte2 as (select count(0) AS `c2` from `rcte` join `test`.`t1` where `rcte`.`a` between 7 and 8 and `test`.`t1`.`id` = `rcte`.`a` - 7)select `cte1`.`c1` AS `c1`,`cte2`.`c2` AS `c2` from `cte1` join `cte2`
+Note 1003 with recursive rcte(a) as (select 1 AS `a` union select cast(`rcte`.`a` + 1 as unsigned) AS `cast(a+1 as unsigned)` from `rcte` where `rcte`.`a` < 10), cte1 as (select count(0) AS `c1` from `rcte` join `test`.`t1` where `rcte`.`a` between 3 and 5 and `test`.`t1`.`id` = `rcte`.`a` - 3), cte2 as (select count(0) AS `c2` from `rcte` join `test`.`t1` where `rcte`.`a` between 7 and 8 and `test`.`t1`.`id` = `rcte`.`a` - 7)select `cte1`.`c1` AS `c1`,`cte2`.`c2` AS `c2` from `cte1` join `cte2`
prepare stmt from "with recursive
rcte(a) as
(select 1 union select cast(a+1 as unsigned) from rcte where a < 10),
@@ -3420,7 +3420,7 @@ NULL UNION RESULT <union2,3> ALL NULL NULL NULL NULL NULL NULL
4 DERIVED <derived2> ALL NULL NULL NULL NULL 2 100.00 Using where
4 DERIVED t1 ALL NULL NULL NULL NULL 3 100.00 Using where; Using join buffer (flat, BNL join)
Warnings:
-Note 1003 with recursive rcte as (select 1 AS `a` union select cast(`rcte`.`a` + 1 as unsigned) AS `cast(a+1 as unsigned)` from `rcte` where `rcte`.`a` < 10), cte1 as (select count(0) AS `c1` from `rcte` join `test`.`t1` where `rcte`.`a` between 3 and 5 and `test`.`t1`.`id` = `rcte`.`a` - 3), cte2 as (select count(0) AS `c2` from `rcte` join `test`.`t1` where `rcte`.`a` between 7 and 8 and `test`.`t1`.`id` = `rcte`.`a` - 7)select `cte2`.`c2` AS `c2`,`cte1`.`c1` AS `c1` from `cte2` join `cte1`
+Note 1003 with recursive rcte(a) as (select 1 AS `a` union select cast(`rcte`.`a` + 1 as unsigned) AS `cast(a+1 as unsigned)` from `rcte` where `rcte`.`a` < 10), cte1 as (select count(0) AS `c1` from `rcte` join `test`.`t1` where `rcte`.`a` between 3 and 5 and `test`.`t1`.`id` = `rcte`.`a` - 3), cte2 as (select count(0) AS `c2` from `rcte` join `test`.`t1` where `rcte`.`a` between 7 and 8 and `test`.`t1`.`id` = `rcte`.`a` - 7)select `cte2`.`c2` AS `c2`,`cte1`.`c1` AS `c1` from `cte2` join `cte1`
prepare stmt from "with recursive
rcte(a) as
(select 1 union select cast(a+1 as unsigned) from rcte where a < 10),
diff --git a/mysql-test/t/cte_nonrecursive.test b/mysql-test/t/cte_nonrecursive.test
index c2ebc92..f994781 100644
--- a/mysql-test/t/cte_nonrecursive.test
+++ b/mysql-test/t/cte_nonrecursive.test
@@ -1261,4 +1261,206 @@ select a, c from cte as r2 where a > 4;
drop table t1;
+--echo #
+--echo # MDEV-23886: Stored Function returning the result of a query
+--echo # that uses CTE over a table twice
+--echo #
+
+create table t1 (c1 int);
+insert into t1 values (1),(2),(6);
+
+create function f1() returns int return
+( with cte1 as (select c1 from t1)
+ select sum(c1) from
+ (select * from cte1 union all select * from cte1) dt
+);
+select f1();
+
+create function f2() returns int return
+( with cte1 as (select c1 from t1)
+ select sum(s.c1) from cte1 as s, cte1 as t where s.c1=t.c1
+);
+select f2();
+
+create function f3() returns int return
+( with cte1 as (select c1 from t1)
+ select
+ case
+ when exists(select 1 from cte1 where c1 between 1 and 2) then 1
+ when exists(select 1 from cte1 where c1 between 5 and 6) then 2
+ else 0
+ end
+);
+select f3();
+
+create view v1 as (select c1 from t1);
+
+create function f4() returns int return
+( select sum(c1) from
+ (select * from v1 union all select * from v1) dt
+);
+select f4();
+
+create function f5() returns int return
+( select sum(s.c1) from v1 as s, v1 as t where s.c1=t.c1
+);
+select f5();
+
+create view v2(s) as
+with cte1 as (select c1 from t1)
+select sum(c1) from (select * from cte1 union all select * from cte1) dt;
+
+create function f6() returns int return
+(select s from v2);
+select f6();
+
+create function f7() returns int return
+( select r.s from v2 as r, v2 as t where r.s=t.s
+);
+select f7();
+
+select f5() + f6();
+
+prepare stmt from "select f5() + f6();";
+execute stmt;
+execute stmt;
+deallocate prepare stmt;
+
+drop function f1;
+drop function f2;
+drop function f3;
+drop function f4;
+drop function f5;
+drop function f6;
+drop function f7;
+
+drop view v1;
+drop view v2;
+
+create table t2 (a int, b int);
+
+insert into t2
+with cte1 as (select c1 from t1)
+select * from cte1 as s, cte1 as t where s.c1=t.c1 and s.c1 > 5;
+
+select * from t2;
+
+delimiter |;
+
+create procedure p1()
+begin
+insert into t2
+with cte1 as (select c1 from t1)
+select * from cte1 as s, cte1 as t where s.c1=t.c1 and s.c1 <= 2 and t.c1 >= 2;
+end |
+
+delimiter ;|
+
+call p1();
+select * from t2;
+
+drop procedure p1;
+
+--echo # checking CTE resolution for queries with hanging CTEs
+
+with
+cte1(a) as (select * from t1 where c1 <= 2),
+cte2(b) as (select * from cte1 where a >= 2),
+cte3 as (select * from cte1,cte2 where cte1.a < cte2.b)
+select * from cte3;
+
+select * from t2;
+
+with
+cte1(a) as (select * from t1 where c1 <= 2),
+cte2(b) as (select * from cte1 where a >= 2),
+cte3 as (select * from cte1,cte2 where cte1.a < cte2.b)
+select * from t2;
+
+--error ER_BAD_FIELD_ERROR
+with
+cte1(a) as (select * from t1 where c1 <= 2),
+cte2(b) as (select * from cte1 where c1 >= 2),
+cte3 as (select * from cte1,cte2 where cte1.a < cte2.b)
+select * from t2;
+
+--error ER_BAD_FIELD_ERROR
+with
+cte1(a) as (select * from t1 where c1 <= 2),
+cte2(b) as (select * from cte1 where a >= 2),
+cte3 as (select * from cte1,cte2 where cte1.a < cte2.c1)
+select * from t2;
+
+with
+cte1 as (select * from t1 where c1 <= 2),
+cte2(a,b) as (select * from cte1 as s1, cte1 as s2 where s1.c1=s2.c1)
+select * from cte2;
+
+with
+cte1 as (select * from t1 where c1 <= 2),
+cte2(a,b) as (select * from cte1 as s1, cte1 as s2 where s1.c1=s2.c1)
+select * from t2;
+
+--error ER_NON_UNIQ_ERROR
+with
+cte1 as (select * from t1 where c1 <= 2),
+cte2(a,b) as (select * from cte1 as s1, cte1 as s2 where s1.c1=c1)
+select * from t2;
+
+with cte3 as
+( with cte2(a,b) as
+ ( with cte1 as (select * from t1 where c1 <= 2)
+ select * from cte1 as s1, cte1 as s2 where s1.c1=s2.c1)
+ select r1.a,r2.b from cte2 as r1, cte2 as r2)
+select * from cte3;
+
+with cte3 as
+( with cte2(a,b) as
+ ( with cte1 as (select * from t1 where c1 <= 2)
+ select * from cte1 as s1, cte1 as s2 where s1.c1=s2.c1)
+ select r1.a,r2.b from cte2 as r1, cte2 as r2)
+select * from t2;
+
+--error ER_BAD_FIELD_ERROR
+with cte3 as
+( with cte2(a,b) as
+ ( with cte1 as (select * from t1 where c1 <= 2)
+ select * from cte1 as s1, cte1 as s2 where s1.c1=s2.c1)
+ select r1.c1,r2.c1 from cte2 as r1, cte2 as r2)
+select * from t2;
+
+delimiter |;
+
+create procedure p1()
+begin
+insert into t2
+with cte1 as (select c1 from t1)
+select * from t1 as s, t1 as t where s.c1=t.c1 and s.c1 <= 2 and t.c1 >= 2;
+end |
+
+delimiter ;|
+
+call p1();
+select * from t2;
+
+drop procedure p1;
+
+delimiter |;
+
+create procedure p1()
+begin
+insert into t2
+with cte1 as (select a from t1)
+select * from t1 as s, t1 as t where s.c1=t.c1 and s.c1 <= 2 and t.c1 >= 2;
+end |
+
+delimiter ;|
+
+--error ER_BAD_FIELD_ERROR
+call p1();
+
+drop procedure p1;
+
+drop table t1,t2;
+
--echo # End of 10.2 tests
diff --git a/sql/item_subselect.cc b/sql/item_subselect.cc
index ed8e5e9..21021f14 100644
--- a/sql/item_subselect.cc
+++ b/sql/item_subselect.cc
@@ -1774,7 +1774,6 @@ double Item_in_subselect::val_real()
As far as Item_in_subselect called only from Item_in_optimizer this
method should not be used
*/
- DBUG_ASSERT(0);
DBUG_ASSERT(fixed == 1);
if (forced_const)
return value;
diff --git a/sql/sp_head.cc b/sql/sp_head.cc
index b205d25..6a6bdf8 100644
--- a/sql/sp_head.cc
+++ b/sql/sp_head.cc
@@ -3087,8 +3087,7 @@ sp_lex_keeper::reset_lex_and_exec_core(THD *thd, uint *nextp,
#endif
if (open_tables)
- res= check_dependencies_in_with_clauses(m_lex->with_clauses_list) ||
- instr->exec_open_and_lock_tables(thd, m_lex->query_tables);
+ res= instr->exec_open_and_lock_tables(thd, m_lex->query_tables);
if (!res)
{
diff --git a/sql/sql_base.cc b/sql/sql_base.cc
index e3c30c7..c9a3422 100644
--- a/sql/sql_base.cc
+++ b/sql/sql_base.cc
@@ -3406,7 +3406,11 @@ open_and_process_table(THD *thd, TABLE_LIST *tables, uint *counter, uint flags,
if (tables->derived)
{
if (!tables->view)
+ {
+ if (!tables->is_derived())
+ tables->set_derived();
goto end;
+ }
/*
We restore view's name and database wiped out by derived tables
processing and fall back to standard open process in order to
@@ -3418,35 +3422,6 @@ open_and_process_table(THD *thd, TABLE_LIST *tables, uint *counter, uint flags,
tables->table_name= tables->view_name.str;
tables->table_name_length= tables->view_name.length;
}
- else if (tables->select_lex)
- {
- /*
- Check whether 'tables' refers to a table defined in a with clause.
- If so set the reference to the definition in tables->with.
- */
- if (!tables->with)
- tables->with= tables->select_lex->find_table_def_in_with_clauses(tables);
- /*
- If 'tables' is defined in a with clause set the pointer to the
- specification from its definition in tables->derived.
- */
- if (tables->with)
- {
- if (tables->is_recursive_with_table() &&
- !tables->is_with_table_recursive_reference())
- {
- tables->with->rec_outer_references++;
- With_element *with_elem= tables->with;
- while ((with_elem= with_elem->get_next_mutually_recursive()) !=
- tables->with)
- with_elem->rec_outer_references++;
- }
- if (tables->set_as_with_table(thd, tables->with))
- DBUG_RETURN(1);
- else
- goto end;
- }
- }
if (!tables->derived &&
is_infoschema_db(tables->db, tables->db_length))
diff --git a/sql/sql_class.cc b/sql/sql_class.cc
index ac9df47..80ff0aa 100644
--- a/sql/sql_class.cc
+++ b/sql/sql_class.cc
@@ -2525,6 +2525,60 @@ void THD::close_active_vio()
#endif
+/*
+ @brief MySQL parser used for recursive invocations
+
+ @param old_lex The LEX structure in the state when this parser
+ is called recursively
+ @param lex The LEX structure used to parse a new SQL fragment
+ @param str The SQL fragment to parse
+ @param str_len The length of the SQL fragment to parse
+ @param stmt_prepare_mode true <=> when parsing a prepare statement
+
+ @details
+ This function is to be used when parsing of an SQL fragment is
+ needed within one of the grammar rules.
+
+ @notes
+ Currently the function is used only when the specification of a CTE
+ is parsed for the not first and not recursive references of the CTE.
+
+ @retval false On a successful parsing of the fragment
+ @retval true Otherwise
+*/
+
+bool THD::sql_parser(LEX *old_lex, LEX *lex,
+ char *str, uint str_len, bool stmt_prepare_mode)
+{
+ extern int MYSQLparse(THD * thd);
+
+ bool parse_status= false;
+ Parser_state parser_state;
+ Parser_state *old_parser_state= m_parser_state;
+
+ if (parser_state.init(this, str, str_len))
+ return true;
+
+ m_parser_state= &parser_state;
+ parser_state.m_lip.stmt_prepare_mode= stmt_prepare_mode;
+ parser_state.m_lip.multi_statements= false;
+ parser_state.m_lip.m_digest= NULL;
+
+ lex->param_list= old_lex->param_list;
+ lex->sphead= old_lex->sphead;
+ lex->spname= old_lex->spname;
+ lex->spcont= old_lex->spcont;
+ lex->sp_chistics= old_lex->sp_chistics;
+ lex->trg_chistics= old_lex->trg_chistics;
+
+ parse_status= MYSQLparse(this) != 0;
+
+ m_parser_state= old_parser_state;
+
+ return parse_status;
+}
+
+
struct Item_change_record: public ilink
{
Item **place;
diff --git a/sql/sql_class.h b/sql/sql_class.h
index e08bb3e..7c546b6 100644
--- a/sql/sql_class.h
+++ b/sql/sql_class.h
@@ -4002,14 +4002,11 @@ class THD :public Statement,
to resolve all CTE names as we don't need this message to be thrown
for any CTE references.
*/
- if (!lex->with_clauses_list)
+ if (!lex->with_cte_resolution)
{
my_message(ER_NO_DB_ERROR, ER(ER_NO_DB_ERROR), MYF(0));
return TRUE;
}
- /* This will allow to throw an error later for non-CTE references */
- *p_db= NULL;
- *p_db_length= 0;
}
else
{
@@ -4544,6 +4541,10 @@ class THD :public Statement,
current_linfo= 0;
mysql_mutex_unlock(&LOCK_thread_count);
}
+
+ bool sql_parser(LEX *old_lex, LEX *lex,
+ char *str, uint str_len, bool stmt_prepare_mode);
+
};
inline void add_to_active_threads(THD *thd)
diff --git a/sql/sql_cte.cc b/sql/sql_cte.cc
index 3a2301a..9dad33d 100644
--- a/sql/sql_cte.cc
+++ b/sql/sql_cte.cc
@@ -58,7 +58,7 @@ bool With_clause::add_with_element(With_element *elem)
true on failure
*/
-bool check_dependencies_in_with_clauses(With_clause *with_clauses_list)
+bool LEX::check_dependencies_in_with_clauses()
{
for (With_clause *with_clause= with_clauses_list;
with_clause;
@@ -76,6 +76,200 @@ bool check_dependencies_in_with_clauses(With_clause *with_clauses_list)
/**
@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 && !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) // no database specified in table reference tbl
+ {
+ if (!thd->db) // 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, &tbl->db_length))
+ return true;
+ if (!(tbl->table_options & TL_OPTION_ALIAS))
+ tbl->mdl_request.init(MDL_key::TABLE, tbl->db,
+ tbl->table_name,
+ 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= empty_c_string;
+ tbl->db_length= 0;
+ tbl->schema_table= 0;
+ if (tbl->derived)
+ {
+ tbl->derived->first_select()->linkage= DERIVED_TABLE_TYPE;
+ }
+ 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
@@ -112,10 +306,11 @@ bool With_clause::check_dependencies()
elem != with_elem;
elem= elem->next)
{
- if (my_strcasecmp(system_charset_info, with_elem->query_name->str,
- elem->query_name->str) == 0)
+ if (my_strcasecmp(system_charset_info, with_elem->get_name_str(),
+ elem->get_name_str()) == 0)
{
- my_error(ER_DUP_QUERY_NAME, MYF(0), with_elem->query_name->str);
+ my_error(ER_DUP_QUERY_NAME, MYF(0),
+ with_elem->get_name_str());
return true;
}
}
@@ -222,12 +417,12 @@ With_element *With_clause::find_table_def(TABLE_LIST *table,
with_elem != barrier;
with_elem= with_elem->next)
{
- if (my_strcasecmp(system_charset_info, with_elem->query_name->str,
- table->table_name) == 0 &&
+ if (my_strcasecmp(system_charset_info, with_elem->get_name_str(),
+ table->table_name) == 0 &&
!table->is_fqtn)
{
table->set_derived();
- table->db= empty_c_string;
+ with_elem->referenced= true;
return with_elem;
}
}
@@ -584,7 +779,7 @@ bool With_clause::check_anchors()
if (elem == with_elem)
{
my_error(ER_RECURSIVE_WITHOUT_ANCHORS, MYF(0),
- with_elem->query_name->str);
+ with_elem->get_name_str());
return true;
}
}
@@ -617,7 +812,7 @@ bool With_clause::check_anchors()
if (elem->work_dep_map & elem->get_elem_map())
{
my_error(ER_UNACCEPTABLE_MUTUAL_RECURSION, MYF(0),
- with_elem->query_name->str);
+ with_elem->get_name_str());
return true;
}
}
@@ -771,7 +966,8 @@ bool With_element::set_unparsed_spec(THD *thd, char *spec_start, char *spec_end,
@brief
Create a clone of the specification for the given with table
- @param thd The context of the statement containing this with element
+ @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.
@@ -781,12 +977,13 @@ bool With_element::set_unparsed_spec(THD *thd, char *spec_start, char *spec_end,
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 build.
+ 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
@@ -802,115 +999,129 @@ bool With_element::set_unparsed_spec(THD *thd, char *spec_start, char *spec_end,
NULL - otherwise
*/
-st_select_lex_unit *With_element::clone_parsed_spec(THD *thd,
+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;
- Query_arena backup;
- Query_arena *arena= thd->activate_stmt_arena_if_needed(&backup);
+ st_select_lex_unit *res= NULL;
if (!(lex= (LEX*) new(thd->mem_root) st_lex_local))
- {
- if (arena)
- thd->restore_active_arena(arena, &backup);
return res;
- }
- LEX *old_lex= thd->lex;
thd->lex= lex;
bool parse_status= false;
- Parser_state parser_state;
- TABLE_LIST *spec_tables;
- TABLE_LIST *spec_tables_tail;
st_select_lex *with_select;
char save_end= unparsed_spec.str[unparsed_spec.length];
unparsed_spec.str[unparsed_spec.length]= '\0';
- if (parser_state.init(thd, unparsed_spec.str, unparsed_spec.length))
- goto err;
- parser_state.m_lip.stmt_prepare_mode= stmt_prepare_mode;
- parser_state.m_lip.multi_statements= false;
- parser_state.m_lip.m_digest= NULL;
lex_start(thd);
lex->clone_spec_offset= unparsed_spec_offset;
- lex->param_list= old_lex->param_list;
- lex->sphead= old_lex->sphead;
- lex->spname= old_lex->spname;
- lex->spcont= old_lex->spcont;
- lex->sp_chistics= old_lex->sp_chistics;
-
- lex->stmt_lex= old_lex;
- with_select= &lex->select_lex;
- with_select->select_number= ++thd->lex->stmt_lex->current_select_number;
- parse_status= parse_sql(thd, &parser_state, 0);
+ 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);
+
unparsed_spec.str[unparsed_spec.length]= save_end;
+ with_select= lex->unit.first_select();
+ with_select->select_number= ++lex->stmt_lex->current_select_number;
if (parse_status)
goto err;
- if (check_dependencies_in_with_clauses(lex->with_clauses_list))
- goto err;
-
- spec_tables= lex->query_tables;
- spec_tables_tail= 0;
- for (TABLE_LIST *tbl= spec_tables;
- tbl;
- tbl= tbl->next_global)
- {
- if (!tbl->derived && !tbl->schema_table &&
- thd->open_temporary_table(tbl))
- goto err;
- spec_tables_tail= tbl;
- }
- if (spec_tables)
+ /*
+ 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)
{
- if (with_table->next_global)
+ 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)
{
- spec_tables_tail->next_global= with_table->next_global;
- with_table->next_global->prev_global= &spec_tables_tail->next_global;
+ *(lex->query_tables->prev_global= next_tbl->prev_global)=
+ lex->query_tables;
+ *(next_tbl->prev_global= lex->query_tables_last)= next_tbl;
}
else
{
- old_lex->query_tables_last= &spec_tables_tail->next_global;
+ *(lex->query_tables->prev_global= old_lex->query_tables_last)=
+ lex->query_tables;
+ old_lex->query_tables_last= lex->query_tables_last;
}
- spec_tables->prev_global= &with_table->next_global;
- with_table->next_global= spec_tables;
}
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.set_slave(with_select);
+ lex->unit.cloned_from= spec;
old_lex->all_selects_list=
(st_select_lex*) (lex->all_selects_list->
insert_chain_before(
(st_select_lex_node **) &(old_lex->all_selects_list),
with_select));
- if (check_dependencies_in_with_clauses(lex->with_clauses_list))
- res= NULL;
+
/*
- Resolve references to CTE from the spec_tables list that has not
- been resolved yet.
+ 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.
*/
- for (TABLE_LIST *tbl= spec_tables;
- tbl;
- tbl= tbl->next_global)
+ lex->only_cte_resolution= old_lex->only_cte_resolution;
+ if (lex->resolve_references_to_cte(lex->query_tables,
+ lex->query_tables_last))
{
- if (!tbl->with)
- tbl->with= with_select->find_table_def_in_with_clauses(tbl);
- if (tbl == spec_tables_tail)
- break;
- }
- if (check_table_access(thd, SELECT_ACL, spec_tables, FALSE, UINT_MAX, FALSE))
+ res= NULL;
goto err;
+ }
- lex->sphead= NULL; // in order not to delete lex->sphead
+ lex->sphead= NULL; // in order not to delete lex->sphead
lex_end(lex);
err:
- if (arena)
- thd->restore_active_arena(arena, &backup);
thd->lex= old_lex;
return res;
}
@@ -1080,58 +1291,6 @@ With_element *st_select_lex::find_table_def_in_with_clauses(TABLE_LIST *table)
}
-/**
- @brief
- Set the specifying unit in this reference to a with table
-
- @details
- The method assumes that the given element with_elem defines the table T
- this table reference refers to.
- If this is the first reference to T the method just sets its specification
- in the field 'derived' as the unit that yields T. Otherwise the method
- first creates a clone specification and sets rather this clone in this field.
-
- @retval
- false on success
- true on failure
-*/
-
-bool TABLE_LIST::set_as_with_table(THD *thd, With_element *with_elem)
-{
- if (table)
- {
- /*
- This table was prematurely identified as a temporary table.
- We correct it here, but it's not a nice solution in the case
- when the temporary table with this name is not used anywhere
- else in the query.
- */
- thd->mark_tmp_table_as_free_for_reuse(table);
- table= 0;
- }
- with= with_elem;
- schema_table= NULL;
- if (!with_elem->is_referenced() || with_elem->is_recursive)
- {
- derived= with_elem->spec;
- if (derived != select_lex->master_unit() &&
- !with_elem->is_recursive &&
- !is_with_table_recursive_reference())
- {
- derived->move_as_slave(select_lex);
- }
- }
- else
- {
- if(!(derived= with_elem->clone_parsed_spec(thd, this)))
- return true;
- }
- derived->first_select()->linkage= DERIVED_TABLE_TYPE;
- with_elem->inc_references();
- return false;
-}
-
-
bool TABLE_LIST::is_recursive_with_table()
{
return with && with->is_recursive;
@@ -1231,7 +1390,7 @@ bool st_select_lex::check_unrestricted_recursive(bool only_standard_compliant)
if (only_standard_compliant && with_elem->is_unrestricted())
{
my_error(ER_NOT_STANDARD_COMPLIANT_RECURSIVE,
- MYF(0), with_elem->query_name->str);
+ MYF(0), with_elem->get_name_str());
return true;
}
@@ -1372,7 +1531,7 @@ bool st_select_lex::check_subqueries_with_recursive_references()
sl_master->derived && sl_master->derived->is_materialized_derived())
{
my_error(ER_REF_TO_RECURSIVE_WITH_TABLE_IN_DERIVED,
- MYF(0), with_elem->query_name->str);
+ MYF(0), with_elem->get_name_str());
return true;
}
if (!sl_master->item)
@@ -1434,7 +1593,23 @@ void With_clause::print(String *str, enum_query_type query_type)
void With_element::print(String *str, enum_query_type query_type)
{
- str->append(query_name);
+ str->append(get_name());
+ if (column_list.elements)
+ {
+ List_iterator_fast<LEX_STRING> li(column_list);
+ str->append('(');
+ for (LEX_STRING *col_name= li++; ; )
+ {
+ str->append(col_name);
+ col_name= li++;
+ if (!col_name)
+ {
+ str->append(')');
+ break;
+ }
+ str->append(',');
+ }
+ }
str->append(STRING_WITH_LEN(" as "));
str->append('(');
spec->print(str, query_type);
diff --git a/sql/sql_cte.h b/sql/sql_cte.h
index 58f371d..5f30894 100644
--- a/sql/sql_cte.h
+++ b/sql/sql_cte.h
@@ -9,6 +9,39 @@ struct st_unit_ctxt_elem;
/**
+ @class With_element_head
+ @brief Head of the definition of a CTE table
+
+ It contains the name of the CTE and it contains the position of the subchain
+ of table references used in the definition in the global chain of table
+ references used in the query where this definition is encountered.
+*/
+
+class With_element_head : public Sql_alloc
+{
+ /* The name of the defined CTE */
+ LEX_CSTRING *query_name;
+
+public:
+ /*
+ The structure describing the subchain of the table references used in
+ the specification of the defined CTE in the global chain of table
+ references used in the query. The structure is fully defined only
+ after the CTE definition has been parsed.
+ */
+ TABLE_CHAIN tables_pos;
+
+ With_element_head(LEX_CSTRING *name)
+ : query_name(name)
+ {
+ tables_pos.set_start_pos(0);
+ tables_pos.set_end_pos(0);
+ }
+ friend class With_element;
+};
+
+
+/**
@class With_element
@brief Definition of a CTE table
@@ -69,9 +102,22 @@ class With_element : public Sql_alloc
subqueries and specifications of other with elements).
*/
uint references;
+
+ /*
+ true <=> this With_element is referred in the query in which the
+ element is defined
+ */
+ bool referenced;
+
+ /*
+ true <=> this With_element is needed for the execution of the query
+ in which the element is defined
+ */
+ bool is_used_in_query;
+
/*
Unparsed specification of the query that specifies this element.
- It used to build clones of the specification if they are needed.
+ It's used to build clones of the specification if they are needed.
*/
LEX_STRING unparsed_spec;
/* Offset of the specification in the input string */
@@ -85,10 +131,11 @@ class With_element : public Sql_alloc
public:
/*
- The name of the table introduced by this with elememt. The name
- can be used in FROM lists of the queries in the scope of the element.
+ Contains the name of the defined With element and the position of
+ the subchain of the tables references used by its definition in the
+ global chain of TABLE_LIST objects created for the whole query.
*/
- LEX_STRING *query_name;
+ With_element_head *head;
/*
Optional list of column names to name the columns of the table introduced
by this with element. It is used in the case when the names are not
@@ -144,18 +191,27 @@ class With_element : public Sql_alloc
/* List of Item_subselects containing recursive references to this CTE */
SQL_I_List<Item_subselect> sq_with_rec_ref;
- With_element(LEX_STRING *name,
+ With_element(With_element_head *h,
List <LEX_STRING> list,
st_select_lex_unit *unit)
: next(NULL), base_dep_map(0), derived_dep_map(0),
sq_dep_map(0), work_dep_map(0), mutually_recursive(0),
top_level_dep_map(0), sq_rec_ref(NULL),
next_mutually_recursive(NULL), references(0),
- query_name(name), column_list(list), spec(unit),
+ referenced(false), is_used_in_query(false),
+ head(h), column_list(list), spec(unit),
is_recursive(false), rec_outer_references(0), with_anchor(false),
level(0), rec_result(NULL)
{ unit->with_element= this; }
+ LEX_CSTRING *get_name() { return head->query_name; }
+ const char *get_name_str() { return get_name()->str; }
+
+ void set_tables_start_pos(TABLE_LIST **pos)
+ { head->tables_pos.set_start_pos(pos); }
+ void set_tables_end_pos(TABLE_LIST **pos)
+ { head->tables_pos.set_end_pos(pos); }
+
bool check_dependencies_in_spec();
void check_dependencies_in_select(st_select_lex *sl, st_unit_ctxt_elem *ctxt,
@@ -182,9 +238,9 @@ class With_element : public Sql_alloc
bool set_unparsed_spec(THD *thd, char *spec_start, char *spec_end,
uint spec_offset);
- st_select_lex_unit *clone_parsed_spec(THD *thd, TABLE_LIST *with_table);
+ st_select_lex_unit *clone_parsed_spec(LEX *old_lex, TABLE_LIST *with_table);
- bool is_referenced() { return references != 0; }
+ bool is_referenced() { return referenced; }
void inc_references() { references++; }
@@ -242,6 +298,12 @@ class With_element : public Sql_alloc
void prepare_for_next_iteration();
friend class With_clause;
+
+ friend
+ bool LEX::resolve_references_to_cte(TABLE_LIST *tables,
+ TABLE_LIST **tables_last);
+ friend
+ bool LEX::resolve_references_to_cte_in_hanging_cte();
};
const uint max_number_of_elements_in_with_clause= sizeof(table_map)*8;
@@ -338,8 +400,10 @@ class With_clause : public Sql_alloc
friend class With_element;
friend
- bool
- check_dependencies_in_with_clauses(With_clause *with_clauses_list);
+ bool LEX::check_dependencies_in_with_clauses();
+
+ friend
+ bool LEX::resolve_references_to_cte_in_hanging_cte();
};
inline
diff --git a/sql/sql_lex.cc b/sql/sql_lex.cc
index c534ba7..eaaa313 100644
--- a/sql/sql_lex.cc
+++ b/sql/sql_lex.cc
@@ -704,6 +704,8 @@ void lex_start(THD *thd)
lex->subqueries= FALSE;
lex->context_analysis_only= 0;
lex->derived_tables= 0;
+ lex->with_cte_resolution= false;
+ lex->only_cte_resolution= false;
lex->safe_to_cache_query= 1;
lex->parsing_options.reset();
lex->empty_field_list_on_rset= 0;
@@ -2089,6 +2091,7 @@ void st_select_lex_unit::init_query()
is_view= false;
with_clause= 0;
with_element= 0;
+ cloned_from= 0;
columns_are_renamed= false;
}
diff --git a/sql/sql_lex.h b/sql/sql_lex.h
index fbc2bf7..03c06b9 100644
--- a/sql/sql_lex.h
+++ b/sql/sql_lex.h
@@ -666,6 +666,8 @@ class st_select_lex_unit: public st_select_lex_node {
With_clause *with_clause;
/* With element where this unit is used as the specification (if any) */
With_element *with_element;
+ /* The unit used as a CTE specification from which this unit is cloned */
+ st_select_lex_unit *cloned_from;
/* thread handler */
THD *thd;
/*
@@ -1169,7 +1171,9 @@ class st_select_lex: public st_select_lex_node
}
With_element *get_with_element()
{
- return master_unit()->with_element;
+ return master_unit()->cloned_from ?
+ master_unit()->cloned_from->with_element :
+ master_unit()->with_element;
}
With_element *find_table_def_in_with_clauses(TABLE_LIST *table);
bool check_unrestricted_recursive(bool only_standard_compliant);
@@ -2772,6 +2776,20 @@ struct LEX: public Query_tables_list
uint16 create_view_algorithm;
uint8 create_view_check;
uint8 context_analysis_only;
+ /*
+ true <=> The parsed fragment requires resolution of references to CTE
+ at the end of parsing. This name resolution process involves searching
+ for possible dependencies between CTE defined in the parsed fragment and
+ detecting possible recursive references.
+ The flag is set to true if the fragment contains CTE definitions.
+ */
+ bool with_cte_resolution;
+ /*
+ true <=> only resolution of references to CTE are required in the parsed
+ fragment, no checking of dependencies between CTE is required.
+ This flag is used only when parsing clones of CTE specifications.
+ */
+ bool only_cte_resolution;
bool local_file;
bool check_exists;
bool autocommit;
@@ -3224,6 +3242,13 @@ struct LEX: public Query_tables_list
{
return !create_like() && !create_select();
}
+
+ bool check_dependencies_in_with_clauses();
+ bool resolve_references_to_cte_in_hanging_cte();
+ bool check_cte_dependencies_and_resolve_references();
+ bool resolve_references_to_cte(TABLE_LIST *tables,
+ TABLE_LIST **tables_last);
+
};
diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc
index 9436e11..f782c5c 100644
--- a/sql/sql_parse.cc
+++ b/sql/sql_parse.cc
@@ -3220,9 +3220,6 @@ mysql_execute_command(THD *thd)
thd->get_stmt_da()->opt_clear_warning_info(thd->query_id);
}
- if (check_dependencies_in_with_clauses(thd->lex->with_clauses_list))
- DBUG_RETURN(1);
-
#ifdef HAVE_REPLICATION
if (unlikely(thd->slave_thread))
{
@@ -7980,7 +7977,8 @@ TABLE_LIST *st_select_lex::add_table_to_list(THD *thd,
ptr->db= table->db.str;
ptr->db_length= table->db.length;
}
- else if (lex->copy_db_to(&ptr->db, &ptr->db_length))
+ else if (!lex->with_cte_resolution &&
+ lex->copy_db_to(&ptr->db, &ptr->db_length))
DBUG_RETURN(0);
else
ptr->is_fqtn= FALSE;
@@ -7997,7 +7995,9 @@ TABLE_LIST *st_select_lex::add_table_to_list(THD *thd,
ptr->table_name=table->table.str;
ptr->table_name_length=table->table.length;
- ptr->lock_type= lock_type;
+ ptr->lock_type= lock_type;
+ ptr->mdl_type= mdl_type;
+ ptr->table_options= table_options;
ptr->updating= MY_TEST(table_options & TL_OPTION_UPDATING);
/* TODO: remove TL_OPTION_FORCE_INDEX as it looks like it's not used */
ptr->force_index= MY_TEST(table_options & TL_OPTION_FORCE_INDEX);
@@ -8676,8 +8676,10 @@ void st_select_lex::set_lock_for_tables(thr_lock_type lock_type, bool for_update
{
tables->lock_type= lock_type;
tables->updating= for_update;
- tables->mdl_request.set_type((lock_type >= TL_WRITE_ALLOW_WRITE) ?
- MDL_SHARED_WRITE : MDL_SHARED_READ);
+
+ if (tables->db && tables->db[0])
+ tables->mdl_request.set_type((lock_type >= TL_WRITE_ALLOW_WRITE) ?
+ MDL_SHARED_WRITE : MDL_SHARED_READ);
}
DBUG_VOID_RETURN;
}
diff --git a/sql/sql_prepare.cc b/sql/sql_prepare.cc
index a8a6710..922d7c9 100644
--- a/sql/sql_prepare.cc
+++ b/sql/sql_prepare.cc
@@ -2413,9 +2413,6 @@ static bool check_prepared_statement(Prepared_statement *stmt)
if (tables)
thd->get_stmt_da()->opt_clear_warning_info(thd->query_id);
- if (check_dependencies_in_with_clauses(thd->lex->with_clauses_list))
- goto error;
-
if (sql_command_flags[sql_command] & CF_HA_CLOSE)
mysql_ha_rm_tables(thd, tables);
diff --git a/sql/sql_view.cc b/sql/sql_view.cc
index 0701c52..675eeb8 100644
--- a/sql/sql_view.cc
+++ b/sql/sql_view.cc
@@ -432,12 +432,6 @@ bool mysql_create_view(THD *thd, TABLE_LIST *views,
lex->link_first_table_back(view, link_to_local);
view->open_type= OT_BASE_ONLY;
- if (check_dependencies_in_with_clauses(lex->with_clauses_list))
- {
- res= TRUE;
- goto err;
- }
-
WSREP_TO_ISOLATION_BEGIN(WSREP_MYSQL_DB, NULL, NULL);
/*
@@ -1399,9 +1393,6 @@ bool mysql_make_view(THD *thd, TABLE_SHARE *share, TABLE_LIST *table,
TABLE_LIST *tbl;
Security_context *security_ctx= 0;
- if (check_dependencies_in_with_clauses(thd->lex->with_clauses_list))
- goto err;
-
/*
Check rights to run commands (ANALYZE SELECT, EXPLAIN SELECT &
SHOW CREATE) which show underlying tables.
diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy
index 387c77a..25826d2 100644
--- a/sql/sql_yacc.yy
+++ b/sql/sql_yacc.yy
@@ -981,6 +981,7 @@ Virtual_column_info *add_virtual_expression(THD *thd, Item *expr)
class sp_label *splabel;
class sp_name *spname;
class sp_variable *spvar;
+ class With_element_head *with_element_head;
class With_clause *with_clause;
class Virtual_column_info *virtual_column;
@@ -2067,7 +2068,7 @@ END_OF_INPUT
%type <with_clause> opt_with_clause with_clause
-%type <lex_str_ptr> query_name
+%type <with_element_head> with_element_head
%type <lex_str_list> opt_with_column_list
@@ -2959,7 +2960,11 @@ call:
lex->value_list.empty();
sp_add_used_routine(lex, thd, $2, TYPE_ENUM_PROCEDURE);
}
- opt_sp_cparam_list {}
+ opt_sp_cparam_list
+ {
+ if (Lex->check_cte_dependencies_and_resolve_references())
+ MYSQL_YYABORT;
+ }
;
/* CALL parameters */
@@ -3805,6 +3810,8 @@ sp_proc_stmt_return:
{
LEX *lex= Lex;
sp_head *sp= lex->sphead;
+ if (Lex->check_cte_dependencies_and_resolve_references())
+ MYSQL_YYABORT;
if (sp->m_type != TYPE_ENUM_FUNCTION)
my_yyabort_error((ER_SP_BADRETURN, MYF(0)));
@@ -4866,12 +4873,16 @@ create_select_query_expression:
{
Select->set_braces(0);
Select->set_with_clause($1);
+ if (Lex->check_cte_dependencies_and_resolve_references())
+ MYSQL_YYABORT;
}
union_clause
| opt_with_clause SELECT_SYM create_select_part2
create_select_part3_union_not_ready create_select_part4
{
Select->set_with_clause($1);
+ if (Lex->check_cte_dependencies_and_resolve_references())
+ MYSQL_YYABORT;
}
| '(' create_select_query_specification ')'
| '(' create_select_query_specification ')'
@@ -5578,6 +5589,8 @@ create_select_query_specification:
create_select_part4
{
Select->set_with_clause($1);
+ if (Lex->check_cte_dependencies_and_resolve_references())
+ MYSQL_YYABORT;
}
;
@@ -8454,6 +8467,8 @@ select:
LEX *lex= Lex;
lex->sql_command= SQLCOM_SELECT;
lex->current_select->set_with_clause($1);
+ if (Lex->check_cte_dependencies_and_resolve_references())
+ MYSQL_YYABORT;
}
;
@@ -12161,6 +12176,8 @@ do:
expr_list
{
Lex->insert_list= $3;
+ if (Lex->check_cte_dependencies_and_resolve_references())
+ MYSQL_YYABORT;
}
;
@@ -12377,7 +12394,10 @@ insert:
Lex->current_select= &Lex->select_lex;
}
insert_field_spec opt_insert_update
- {}
+ {
+ if (Lex->check_cte_dependencies_and_resolve_references())
+ MYSQL_YYABORT;
+ }
;
replace:
@@ -12394,7 +12414,10 @@ replace:
Lex->current_select= &Lex->select_lex;
}
insert_field_spec
- {}
+ {
+ if (Lex->check_cte_dependencies_and_resolve_references())
+ MYSQL_YYABORT;
+ }
;
insert_lock_option:
@@ -12586,7 +12609,11 @@ update:
*/
slex->set_lock_for_tables($3, slex->table_list.elements == 1);
}
- opt_where_clause opt_order_clause delete_limit_clause {}
+ opt_where_clause opt_order_clause delete_limit_clause
+ {
+ if (Lex->check_cte_dependencies_and_resolve_references())
+ MYSQL_YYABORT;
+ }
;
update_list:
@@ -12675,6 +12702,8 @@ single_multi:
{
if (multi_delete_set_locks_and_link_aux_tables(Lex))
MYSQL_YYABORT;
+ if (Lex->check_cte_dependencies_and_resolve_references())
+ MYSQL_YYABORT;
}
;
@@ -13641,7 +13670,10 @@ load:
opt_xml_rows_identified_by
opt_field_term opt_line_term opt_ignore_lines opt_field_or_var_spec
opt_load_data_set_spec
- {}
+ {
+ if (Lex->check_cte_dependencies_and_resolve_references())
+ MYSQL_YYABORT;
+ }
;
data_or_xml:
@@ -14096,6 +14128,8 @@ with_clause:
if (with_clause == NULL)
MYSQL_YYABORT;
Lex->derived_tables|= DERIVED_WITH;
+ Lex->with_cte_resolution= true;
+ Lex->with_cte_resolution= true;
Lex->curr_with_clause= with_clause;
with_clause->add_to_list(Lex->with_clauses_list_last_next);
}
@@ -14120,7 +14154,7 @@ with_list:
with_list_element:
- query_name
+ with_element_head
opt_with_column_list
{
$2= new List<LEX_STRING> (Lex->with_column_list);
@@ -14140,6 +14174,7 @@ with_list_element:
if (elem->set_unparsed_spec(thd, spec_start, $8,
(uint) (spec_start - query_start)))
MYSQL_YYABORT;
+ elem->set_tables_end_pos(lex->query_tables_last);
}
;
@@ -14166,12 +14201,15 @@ with_column_list:
;
-query_name:
+with_element_head:
ident
{
- $$= (LEX_STRING *) thd->memdup(&$1, sizeof(LEX_STRING));
- if ($$ == NULL)
+ LEX_CSTRING *name=
+ (LEX_CSTRING *) thd->memdup(&$1, sizeof(LEX_CSTRING));
+ $$= new (thd->mem_root) With_element_head(name);
+ if (unlikely(name == NULL || $$ == NULL))
MYSQL_YYABORT;
+ $$->tables_pos.set_start_pos(Lex->query_tables_last);
}
;
@@ -15078,7 +15116,10 @@ set:
sp_create_assignment_lex(thd, yychar == YYEMPTY);
}
start_option_value_list
- {}
+ {
+ if (Lex->check_cte_dependencies_and_resolve_references())
+ MYSQL_YYABORT;
+ }
| SET STATEMENT_SYM
{
LEX *lex= Lex;
@@ -16691,6 +16732,9 @@ view_select:
¬_used);
lex->parsing_options.allows_variable= TRUE;
lex->current_select->set_with_clause($2);
+ if (Lex->check_cte_dependencies_and_resolve_references())
+ MYSQL_YYABORT;
+
}
;
diff --git a/sql/table.h b/sql/table.h
index 83c72f7..69bd14b 100644
--- a/sql/table.h
+++ b/sql/table.h
@@ -1860,6 +1860,29 @@ class Item_in_subselect;
struct LEX;
class Index_hint;
+
+/*
+ @struct TABLE_CHAIN
+ @brief Subchain of global chain of table references
+
+ The structure contains a pointer to the address of the next_global
+ pointer to the first TABLE_LIST objectof the subchain and the address
+ of the next_global pointer to the element right after the last
+ TABLE_LIST object of the subchain. For an empty subchain both pointers
+ have the same value.
+*/
+
+struct TABLE_CHAIN
+{
+ TABLE_CHAIN() {}
+
+ TABLE_LIST **start_pos;
+ TABLE_LIST ** end_pos;
+
+ void set_start_pos(TABLE_LIST **pos) { start_pos= pos; }
+ void set_end_pos(TABLE_LIST **pos) { end_pos= pos; }
+};
+
struct TABLE_LIST
{
TABLE_LIST() {} /* Remove gcc warning */
@@ -2166,6 +2189,20 @@ struct TABLE_LIST
/* call back function for asking handler about caching in query cache */
qc_engine_callback callback_func;
thr_lock_type lock_type;
+
+ /*
+ Two fields below are set during parsing this table reference in the cases
+ when the table reference can be potentially a reference to a CTE table.
+ In this cases the fact that the reference is a reference to a CTE or not
+ will be ascertained at the very end of parsing of the query when referencies
+ to CTE are resolved. For references to CTE and to derived tables no mdl
+ requests are needed while for other table references they are. If a request
+ is possibly postponed the info that allows to issue this request must be
+ saved in 'mdl_type' and 'table_options'.
+ */
+ enum_mdl_type mdl_type;
+ ulong table_options;
+
uint outer_join; /* Which join type */
uint shared; /* Used in multi-upd */
size_t db_length;
1
0
[Commits] 4b1c179: MDEV-23886 Reusing CTE inside a function fails with table doesn't exist
by IgorBabaev 10 May '21
by IgorBabaev 10 May '21
10 May '21
revision-id: 4b1c179d5437262b3e019c709b563fe909a5759e (mariadb-10.4.11-510-g4b1c179)
parent(s): 3454b5cf35a61e8f6cfab376638520dee4a50609
author: Igor Babaev
committer: Igor Babaev
timestamp: 2021-05-10 12:17:18 -0700
message:
MDEV-23886 Reusing CTE inside a function fails with table doesn't exist
In the code existed just before this patch binding of a table reference to
the specification of the corresponding CTE happens in the function
open_and_process_table(). If the table reference is not the first in the
query the specification is cloned in the same way as the specification of
a view is cloned for any reference of the view. This works fine for
standalone queries, but does not work for stored procedures / functions
for the following reason.
When the first call of a stored procedure/ function SP is processed the
body of SP is parsed. When a query of SP is parsed the info on each
encountered table reference is put into a TABLE_LIST object linked into
a global chain associated with the query. When parsing of the query is
finished the basic info on the table references from this chain except
table references to derived tables and information schema tables is put
in one hash table associated with SP. When parsing of the body of SP is
finished this hash table is used to construct TABLE_LIST objects for all
table references mentioned in SP and link them into the list of such
objects passed to a pre-locking process that calls open_and_process_table()
for each table from the list.
When a TABLE_LIST for a view is encountered the view is opened and its
specification is parsed. For any table reference occurred in
the specification a new TABLE_LIST object is created to be included into
the list for pre-locking. After all objects in the pre-locking have been
looked through the tables mentioned in the list are locked. Note that the
objects referenced CTEs are just skipped here as it is impossible to
resolve these references without any info on the context where they occur.
Now the statements from the body of SP are executed one by one that.
At the very beginning of the execution of a query the tables used in the
query are opened and open_and_process_table() now is called for each table
reference mentioned in the list of TABLE_LIST objects associated with the
query that was built when the query was parsed.
For each table reference first the reference is checked against CTEs
definitions in whose scope it occurred. If such definition is found the
reference is considered resolved and if this is not the first reference
to the found CTE the the specification of the CTE is re-parsed and the
result of the parsing is added to the parsing tree of the query as a
sub-tree. If this sub-tree contains table references to other tables they
are added to the list of TABLE_LIST objects associated with the query in
order the referenced tables to be opened. When the procedure that opens
the tables comes to the TABLE_LIST object created for a non-first
reference to a CTE it discovers that the referenced table instance is not
locked and reports an error.
Thus processing non-first table references to a CTE similar to how
references to view are processed does not work for queries used in stored
procedures / functions. And the main problem is that the current
pre-locking mechanism employed for stored procedures / functions does not
allow to save the context in which a CTE reference occur. It's not trivial
to save the info about the context where a CTE reference occurs while the
resolution of the table reference cannot be done without this context and
consequentially the specification for the table reference cannot be
determined.
This patch solves the above problem by moving resolution of all CTE
references at the parsing stage. More exactly references to CTEs occurred in
a query are resolved right after parsing of the query has finished. After
resolution any CTE reference it is marked as a reference to to derived
table. So it is excluded from the hash table created for pre-locking used
base tables and view when the first call of a stored procedure / function
is processed.
This solution required recursive calls of the parser. The function
THD::sql_parser() has been added specifically for recursive invocations of
the parser.
---
mysql-test/main/brackets.result | 2 +-
mysql-test/main/cte_nonrecursive.result | 201 +++++++++++++++
mysql-test/main/cte_nonrecursive.test | 203 ++++++++++++++++
sql/item_subselect.cc | 1 -
sql/sp_head.cc | 3 +-
sql/sql_base.cc | 33 +--
sql/sql_class.cc | 57 +++++
sql/sql_class.h | 8 +-
sql/sql_cte.cc | 418 ++++++++++++++++++++++----------
sql/sql_cte.h | 87 ++++++-
sql/sql_lex.cc | 9 +-
sql/sql_lex.h | 25 +-
sql/sql_parse.cc | 15 +-
sql/sql_prepare.cc | 3 -
sql/sql_view.cc | 9 -
sql/sql_yacc.yy | 27 ++-
sql/sql_yacc_ora.yy | 26 +-
sql/table.cc | 1 +
sql/table.h | 37 +++
19 files changed, 953 insertions(+), 212 deletions(-)
diff --git a/mysql-test/main/brackets.result b/mysql-test/main/brackets.result
index 99e84f9..1da5168 100644
--- a/mysql-test/main/brackets.result
+++ b/mysql-test/main/brackets.result
@@ -4508,7 +4508,7 @@ s as (select * from t where a > 3)
select a from t where a=1 union select a from s where a=7 order by a desc;
show create view v1;
View Create View character_set_client collation_connection
-v1 CREATE ALGORITHM=UNDEFINED DEFINER=`root`@`localhost` SQL SECURITY DEFINER VIEW `v1` AS with t as (select `test`.`t1`.`a` AS `a` from `test`.`t1` where `test`.`t1`.`a` < 3), s as (select `t`.`a` AS `a` from (select `test`.`t1`.`a` AS `a` from `test`.`t1` where `test`.`t1`.`a` < 3) `t` where `t`.`a` > 3)select `t`.`a` AS `a` from `t` where `t`.`a` = 1 union select `s`.`a` AS `a` from `s` where `s`.`a` = 7 order by `a` desc latin1 latin1_swedish_ci
+v1 CREATE ALGORITHM=UNDEFINED DEFINER=`root`@`localhost` SQL SECURITY DEFINER VIEW `v1` AS with t as (select `test`.`t1`.`a` AS `a` from `test`.`t1` where `test`.`t1`.`a` < 3), s as (select `t`.`a` AS `a` from `t` where `t`.`a` > 3)select `t`.`a` AS `a` from (select `test`.`t1`.`a` AS `a` from `test`.`t1` where `test`.`t1`.`a` < 3) `t` where `t`.`a` = 1 union select `s`.`a` AS `a` from `s` where `s`.`a` = 7 order by `a` desc latin1 latin1_swedish_ci
select * from v1;
a
1
diff --git a/mysql-test/main/cte_nonrecursive.result b/mysql-test/main/cte_nonrecursive.result
index ee2320c..86bbeef 100644
--- a/mysql-test/main/cte_nonrecursive.result
+++ b/mysql-test/main/cte_nonrecursive.result
@@ -1846,4 +1846,205 @@ set sql_mode="oracle";
with data as (select 1 as id)
select id into @myid from data;
set sql_mode= @save_sql_mode;
+#
+# MDEV-23886: Stored Function returning the result of a query
+# that uses CTE over a table twice
+#
+create table t1 (c1 int);
+insert into t1 values (1),(2),(6);
+create function f1() returns int return
+( with cte1 as (select c1 from t1)
+select sum(c1) from
+(select * from cte1 union all select * from cte1) dt
+);
+select f1();
+f1()
+18
+create function f2() returns int return
+( with cte1 as (select c1 from t1)
+select sum(s.c1) from cte1 as s, cte1 as t where s.c1=t.c1
+);
+select f2();
+f2()
+9
+create function f3() returns int return
+( with cte1 as (select c1 from t1)
+select
+case
+when exists(select 1 from cte1 where c1 between 1 and 2) then 1
+when exists(select 1 from cte1 where c1 between 5 and 6) then 2
+else 0
+end
+);
+select f3();
+f3()
+1
+create view v1 as (select c1 from t1);
+create function f4() returns int return
+( select sum(c1) from
+(select * from v1 union all select * from v1) dt
+);
+select f4();
+f4()
+18
+create function f5() returns int return
+( select sum(s.c1) from v1 as s, v1 as t where s.c1=t.c1
+);
+select f5();
+f5()
+9
+create view v2(s) as
+with cte1 as (select c1 from t1)
+select sum(c1) from (select * from cte1 union all select * from cte1) dt;
+create function f6() returns int return
+(select s from v2);
+select f6();
+f6()
+18
+create function f7() returns int return
+( select r.s from v2 as r, v2 as t where r.s=t.s
+);
+select f7();
+f7()
+18
+select f5() + f6();
+f5() + f6()
+27
+prepare stmt from "select f5() + f6();";
+execute stmt;
+f5() + f6()
+27
+execute stmt;
+f5() + f6()
+27
+deallocate prepare stmt;
+drop function f1;
+drop function f2;
+drop function f3;
+drop function f4;
+drop function f5;
+drop function f6;
+drop function f7;
+drop view v1;
+drop view v2;
+create table t2 (a int, b int);
+insert into t2
+with cte1 as (select c1 from t1)
+select * from cte1 as s, cte1 as t where s.c1=t.c1 and s.c1 > 5;
+select * from t2;
+a b
+6 6
+create procedure p1()
+begin
+insert into t2
+with cte1 as (select c1 from t1)
+select * from cte1 as s, cte1 as t where s.c1=t.c1 and s.c1 <= 2 and t.c1 >= 2;
+end |
+call p1();
+select * from t2;
+a b
+6 6
+2 2
+drop procedure p1;
+# checking CTE resolution for queries with hanging CTEs
+with
+cte1(a) as (select * from t1 where c1 <= 2),
+cte2(b) as (select * from cte1 where a >= 2),
+cte3 as (select * from cte1,cte2 where cte1.a < cte2.b)
+select * from cte3;
+a b
+1 2
+select * from t2;
+a b
+6 6
+2 2
+with
+cte1(a) as (select * from t1 where c1 <= 2),
+cte2(b) as (select * from cte1 where a >= 2),
+cte3 as (select * from cte1,cte2 where cte1.a < cte2.b)
+select * from t2;
+a b
+6 6
+2 2
+with
+cte1(a) as (select * from t1 where c1 <= 2),
+cte2(b) as (select * from cte1 where c1 >= 2),
+cte3 as (select * from cte1,cte2 where cte1.a < cte2.b)
+select * from t2;
+ERROR 42S22: Unknown column 'c1' in 'where clause'
+with
+cte1(a) as (select * from t1 where c1 <= 2),
+cte2(b) as (select * from cte1 where a >= 2),
+cte3 as (select * from cte1,cte2 where cte1.a < cte2.c1)
+select * from t2;
+ERROR 42S22: Unknown column 'cte2.c1' in 'where clause'
+with
+cte1 as (select * from t1 where c1 <= 2),
+cte2(a,b) as (select * from cte1 as s1, cte1 as s2 where s1.c1=s2.c1)
+select * from cte2;
+a b
+1 1
+2 2
+with
+cte1 as (select * from t1 where c1 <= 2),
+cte2(a,b) as (select * from cte1 as s1, cte1 as s2 where s1.c1=s2.c1)
+select * from t2;
+a b
+6 6
+2 2
+with
+cte1 as (select * from t1 where c1 <= 2),
+cte2(a,b) as (select * from cte1 as s1, cte1 as s2 where s1.c1=c1)
+select * from t2;
+ERROR 23000: Column 'c1' in where clause is ambiguous
+with cte3 as
+( with cte2(a,b) as
+( with cte1 as (select * from t1 where c1 <= 2)
+select * from cte1 as s1, cte1 as s2 where s1.c1=s2.c1)
+select r1.a,r2.b from cte2 as r1, cte2 as r2)
+select * from cte3;
+a b
+1 1
+2 1
+1 2
+2 2
+with cte3 as
+( with cte2(a,b) as
+( with cte1 as (select * from t1 where c1 <= 2)
+select * from cte1 as s1, cte1 as s2 where s1.c1=s2.c1)
+select r1.a,r2.b from cte2 as r1, cte2 as r2)
+select * from t2;
+a b
+6 6
+2 2
+with cte3 as
+( with cte2(a,b) as
+( with cte1 as (select * from t1 where c1 <= 2)
+select * from cte1 as s1, cte1 as s2 where s1.c1=s2.c1)
+select r1.c1,r2.c1 from cte2 as r1, cte2 as r2)
+select * from t2;
+ERROR 42S22: Unknown column 'r1.c1' in 'field list'
+create procedure p1()
+begin
+insert into t2
+with cte1 as (select c1 from t1)
+select * from t1 as s, t1 as t where s.c1=t.c1 and s.c1 <= 2 and t.c1 >= 2;
+end |
+call p1();
+select * from t2;
+a b
+6 6
+2 2
+2 2
+drop procedure p1;
+create procedure p1()
+begin
+insert into t2
+with cte1 as (select a from t1)
+select * from t1 as s, t1 as t where s.c1=t.c1 and s.c1 <= 2 and t.c1 >= 2;
+end |
+call p1();
+ERROR 42S22: Unknown column 'a' in 'field list'
+drop procedure p1;
+drop table t1,t2;
# End of 10.4 tests
diff --git a/mysql-test/main/cte_nonrecursive.test b/mysql-test/main/cte_nonrecursive.test
index 2242dce..bb5231f 100644
--- a/mysql-test/main/cte_nonrecursive.test
+++ b/mysql-test/main/cte_nonrecursive.test
@@ -1347,4 +1347,207 @@ with data as (select 1 as id)
select id into @myid from data;
set sql_mode= @save_sql_mode;
+--echo #
+--echo # MDEV-23886: Stored Function returning the result of a query
+--echo # that uses CTE over a table twice
+--echo #
+
+create table t1 (c1 int);
+insert into t1 values (1),(2),(6);
+
+create function f1() returns int return
+( with cte1 as (select c1 from t1)
+ select sum(c1) from
+ (select * from cte1 union all select * from cte1) dt
+);
+select f1();
+
+create function f2() returns int return
+( with cte1 as (select c1 from t1)
+ select sum(s.c1) from cte1 as s, cte1 as t where s.c1=t.c1
+);
+select f2();
+
+create function f3() returns int return
+( with cte1 as (select c1 from t1)
+ select
+ case
+ when exists(select 1 from cte1 where c1 between 1 and 2) then 1
+ when exists(select 1 from cte1 where c1 between 5 and 6) then 2
+ else 0
+ end
+);
+select f3();
+
+create view v1 as (select c1 from t1);
+
+create function f4() returns int return
+( select sum(c1) from
+ (select * from v1 union all select * from v1) dt
+);
+select f4();
+
+create function f5() returns int return
+( select sum(s.c1) from v1 as s, v1 as t where s.c1=t.c1
+);
+select f5();
+
+create view v2(s) as
+with cte1 as (select c1 from t1)
+select sum(c1) from (select * from cte1 union all select * from cte1) dt;
+
+create function f6() returns int return
+(select s from v2);
+select f6();
+
+create function f7() returns int return
+( select r.s from v2 as r, v2 as t where r.s=t.s
+);
+select f7();
+
+select f5() + f6();
+
+prepare stmt from "select f5() + f6();";
+execute stmt;
+execute stmt;
+deallocate prepare stmt;
+
+drop function f1;
+drop function f2;
+drop function f3;
+drop function f4;
+drop function f5;
+drop function f6;
+drop function f7;
+
+drop view v1;
+drop view v2;
+
+create table t2 (a int, b int);
+
+insert into t2
+with cte1 as (select c1 from t1)
+select * from cte1 as s, cte1 as t where s.c1=t.c1 and s.c1 > 5;
+
+select * from t2;
+
+delimiter |;
+
+create procedure p1()
+begin
+insert into t2
+with cte1 as (select c1 from t1)
+select * from cte1 as s, cte1 as t where s.c1=t.c1 and s.c1 <= 2 and t.c1 >= 2;
+end |
+
+delimiter ;|
+
+call p1();
+select * from t2;
+
+drop procedure p1;
+
+--echo # checking CTE resolution for queries with hanging CTEs
+
+with
+cte1(a) as (select * from t1 where c1 <= 2),
+cte2(b) as (select * from cte1 where a >= 2),
+cte3 as (select * from cte1,cte2 where cte1.a < cte2.b)
+select * from cte3;
+
+select * from t2;
+
+with
+cte1(a) as (select * from t1 where c1 <= 2),
+cte2(b) as (select * from cte1 where a >= 2),
+cte3 as (select * from cte1,cte2 where cte1.a < cte2.b)
+select * from t2;
+
+--error ER_BAD_FIELD_ERROR
+with
+cte1(a) as (select * from t1 where c1 <= 2),
+cte2(b) as (select * from cte1 where c1 >= 2),
+cte3 as (select * from cte1,cte2 where cte1.a < cte2.b)
+select * from t2;
+
+--error ER_BAD_FIELD_ERROR
+with
+cte1(a) as (select * from t1 where c1 <= 2),
+cte2(b) as (select * from cte1 where a >= 2),
+cte3 as (select * from cte1,cte2 where cte1.a < cte2.c1)
+select * from t2;
+
+with
+cte1 as (select * from t1 where c1 <= 2),
+cte2(a,b) as (select * from cte1 as s1, cte1 as s2 where s1.c1=s2.c1)
+select * from cte2;
+
+with
+cte1 as (select * from t1 where c1 <= 2),
+cte2(a,b) as (select * from cte1 as s1, cte1 as s2 where s1.c1=s2.c1)
+select * from t2;
+
+--error ER_NON_UNIQ_ERROR
+with
+cte1 as (select * from t1 where c1 <= 2),
+cte2(a,b) as (select * from cte1 as s1, cte1 as s2 where s1.c1=c1)
+select * from t2;
+
+with cte3 as
+( with cte2(a,b) as
+ ( with cte1 as (select * from t1 where c1 <= 2)
+ select * from cte1 as s1, cte1 as s2 where s1.c1=s2.c1)
+ select r1.a,r2.b from cte2 as r1, cte2 as r2)
+select * from cte3;
+
+with cte3 as
+( with cte2(a,b) as
+ ( with cte1 as (select * from t1 where c1 <= 2)
+ select * from cte1 as s1, cte1 as s2 where s1.c1=s2.c1)
+ select r1.a,r2.b from cte2 as r1, cte2 as r2)
+select * from t2;
+
+--error ER_BAD_FIELD_ERROR
+with cte3 as
+( with cte2(a,b) as
+ ( with cte1 as (select * from t1 where c1 <= 2)
+ select * from cte1 as s1, cte1 as s2 where s1.c1=s2.c1)
+ select r1.c1,r2.c1 from cte2 as r1, cte2 as r2)
+select * from t2;
+
+delimiter |;
+
+create procedure p1()
+begin
+insert into t2
+with cte1 as (select c1 from t1)
+select * from t1 as s, t1 as t where s.c1=t.c1 and s.c1 <= 2 and t.c1 >= 2;
+end |
+
+delimiter ;|
+
+call p1();
+select * from t2;
+
+drop procedure p1;
+
+delimiter |;
+
+create procedure p1()
+begin
+insert into t2
+with cte1 as (select a from t1)
+select * from t1 as s, t1 as t where s.c1=t.c1 and s.c1 <= 2 and t.c1 >= 2;
+end |
+
+delimiter ;|
+
+--error ER_BAD_FIELD_ERROR
+call p1();
+
+drop procedure p1;
+
+drop table t1,t2;
+
--echo # End of 10.4 tests
+
diff --git a/sql/item_subselect.cc b/sql/item_subselect.cc
index e205847..f6768d0 100644
--- a/sql/item_subselect.cc
+++ b/sql/item_subselect.cc
@@ -1723,7 +1723,6 @@ double Item_in_subselect::val_real()
As far as Item_in_subselect called only from Item_in_optimizer this
method should not be used
*/
- DBUG_ASSERT(0);
DBUG_ASSERT(fixed == 1);
if (forced_const)
return value;
diff --git a/sql/sp_head.cc b/sql/sp_head.cc
index aa4f809..3ea4938 100644
--- a/sql/sp_head.cc
+++ b/sql/sp_head.cc
@@ -3411,8 +3411,7 @@ sp_lex_keeper::reset_lex_and_exec_core(THD *thd, uint *nextp,
Json_writer_object trace_command(thd);
Json_writer_array trace_command_steps(thd, "steps");
if (open_tables)
- res= check_dependencies_in_with_clauses(m_lex->with_clauses_list) ||
- instr->exec_open_and_lock_tables(thd, m_lex->query_tables);
+ res= instr->exec_open_and_lock_tables(thd, m_lex->query_tables);
if (likely(!res))
{
diff --git a/sql/sql_base.cc b/sql/sql_base.cc
index 8bb6011..c3aeb7d 100644
--- a/sql/sql_base.cc
+++ b/sql/sql_base.cc
@@ -3695,7 +3695,11 @@ open_and_process_table(THD *thd, TABLE_LIST *tables, uint *counter, uint flags,
if (tables->derived)
{
if (!tables->view)
+ {
+ if (!tables->is_derived())
+ tables->set_derived();
goto end;
+ }
/*
We restore view's name and database wiped out by derived tables
processing and fall back to standard open process in order to
@@ -3705,35 +3709,6 @@ open_and_process_table(THD *thd, TABLE_LIST *tables, uint *counter, uint flags,
tables->db= tables->view_db;
tables->table_name= tables->view_name;
}
- else if (tables->select_lex)
- {
- /*
- Check whether 'tables' refers to a table defined in a with clause.
- If so set the reference to the definition in tables->with.
- */
- if (!tables->with)
- tables->with= tables->select_lex->find_table_def_in_with_clauses(tables);
- /*
- If 'tables' is defined in a with clause set the pointer to the
- specification from its definition in tables->derived.
- */
- if (tables->with)
- {
- if (tables->is_recursive_with_table() &&
- !tables->is_with_table_recursive_reference())
- {
- tables->with->rec_outer_references++;
- With_element *with_elem= tables->with;
- while ((with_elem= with_elem->get_next_mutually_recursive()) !=
- tables->with)
- with_elem->rec_outer_references++;
- }
- if (tables->set_as_with_table(thd, tables->with))
- DBUG_RETURN(1);
- else
- goto end;
- }
- }
if (!tables->derived && is_infoschema_db(&tables->db))
{
diff --git a/sql/sql_class.cc b/sql/sql_class.cc
index ceb8dc1..b32b792 100644
--- a/sql/sql_class.cc
+++ b/sql/sql_class.cc
@@ -2804,6 +2804,63 @@ void THD::close_active_vio()
#endif
+/*
+ @brief MySQL parser used for recursive invocations
+
+ @param old_lex The LEX structure in the state when this parser
+ is called recursively
+ @param lex The LEX structure used to parse a new SQL fragment
+ @param str The SQL fragment to parse
+ @param str_len The length of the SQL fragment to parse
+ @param stmt_prepare_mode true <=> when parsing a prepare statement
+
+ @details
+ This function is to be used when parsing of an SQL fragment is
+ needed within one of the grammar rules.
+
+ @notes
+ Currently the function is used only when the specification of a CTE
+ is parsed for the not first and not recursive references of the CTE.
+
+ @retval false On a successful parsing of the fragment
+ @retval true Otherwise
+*/
+
+bool THD::sql_parser(LEX *old_lex, LEX *lex,
+ char *str, uint str_len, bool stmt_prepare_mode)
+{
+ extern int MYSQLparse(THD * thd);
+ extern int ORAparse(THD * thd);
+
+ bool parse_status= false;
+ Parser_state parser_state;
+ Parser_state *old_parser_state= m_parser_state;
+
+ if (parser_state.init(this, str, str_len))
+ return true;
+
+ m_parser_state= &parser_state;
+ parser_state.m_lip.stmt_prepare_mode= stmt_prepare_mode;
+ parser_state.m_lip.multi_statements= false;
+ parser_state.m_lip.m_digest= NULL;
+
+ lex->param_list= old_lex->param_list;
+ lex->sphead= old_lex->sphead;
+ lex->spname= old_lex->spname;
+ lex->spcont= old_lex->spcont;
+ lex->sp_chistics= old_lex->sp_chistics;
+ lex->trg_chistics= old_lex->trg_chistics;
+
+ parse_status= (variables.sql_mode & MODE_ORACLE) ?
+ ORAparse(this) : MYSQLparse(this) != 0;
+
+ m_parser_state= old_parser_state;
+
+ return parse_status;
+}
+
+
+
struct Item_change_record: public ilink
{
Item **place;
diff --git a/sql/sql_class.h b/sql/sql_class.h
index 936437f..ecab13e 100644
--- a/sql/sql_class.h
+++ b/sql/sql_class.h
@@ -4241,14 +4241,11 @@ class THD: public THD_count, /* this must be first */
to resolve all CTE names as we don't need this message to be thrown
for any CTE references.
*/
- if (!lex->with_clauses_list)
+ if (!lex->with_cte_resolution)
{
my_message(ER_NO_DB_ERROR, ER(ER_NO_DB_ERROR), MYF(0));
return TRUE;
}
- /* This will allow to throw an error later for non-CTE references */
- to->str= NULL;
- to->length= 0;
return FALSE;
}
@@ -5043,6 +5040,9 @@ class THD: public THD_count, /* this must be first */
Item *sp_prepare_func_item(Item **it_addr, uint cols= 1);
bool sp_eval_expr(Field *result_field, Item **expr_item_ptr);
+ bool sql_parser(LEX *old_lex, LEX *lex,
+ char *str, uint str_len, bool stmt_prepare_mode);
+
};
/** A short cut for thd->get_stmt_da()->set_ok_status(). */
diff --git a/sql/sql_cte.cc b/sql/sql_cte.cc
index a9f5443..ff9524d 100644
--- a/sql/sql_cte.cc
+++ b/sql/sql_cte.cc
@@ -1,3 +1,4 @@
+
/*
Copyright (c) 2016, 2017 MariaDB
@@ -83,7 +84,7 @@ void st_select_lex_unit::set_with_clause(With_clause *with_cl)
true on failure
*/
-bool check_dependencies_in_with_clauses(With_clause *with_clauses_list)
+bool LEX::check_dependencies_in_with_clauses()
{
for (With_clause *with_clause= with_clauses_list;
with_clause;
@@ -101,6 +102,201 @@ bool check_dependencies_in_with_clauses(With_clause *with_clauses_list)
/**
@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))
+ tbl->mdl_request.init(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
@@ -137,10 +333,11 @@ bool With_clause::check_dependencies()
elem != with_elem;
elem= elem->next)
{
- if (lex_string_cmp(system_charset_info, with_elem->query_name,
- elem->query_name) == 0)
+ 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->query_name->str);
+ my_error(ER_DUP_QUERY_NAME, MYF(0),
+ with_elem->get_name_str());
return true;
}
}
@@ -247,13 +444,12 @@ With_element *With_clause::find_table_def(TABLE_LIST *table,
with_elem != barrier;
with_elem= with_elem->next)
{
- if (my_strcasecmp(system_charset_info, with_elem->query_name->str,
- table->table_name.str) == 0 &&
+ if (my_strcasecmp(system_charset_info, with_elem->get_name_str(),
+ table->table_name.str) == 0 &&
!table->is_fqtn)
{
table->set_derived();
- table->db.str= empty_c_string;
- table->db.length= 0;
+ with_elem->referenced= true;
return with_elem;
}
}
@@ -610,7 +806,7 @@ bool With_clause::check_anchors()
if (elem == with_elem)
{
my_error(ER_RECURSIVE_WITHOUT_ANCHORS, MYF(0),
- with_elem->query_name->str);
+ with_elem->get_name_str());
return true;
}
}
@@ -643,7 +839,7 @@ bool With_clause::check_anchors()
if (elem->work_dep_map & elem->get_elem_map())
{
my_error(ER_UNACCEPTABLE_MUTUAL_RECURSION, MYF(0),
- with_elem->query_name->str);
+ with_elem->get_name_str());
return true;
}
}
@@ -797,9 +993,10 @@ bool With_element::set_unparsed_spec(THD *thd,
@brief
Create a clone of the specification for the given with table
- @param thd The context of the statement containing this with element
+ @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.
+ the clone is created.
@details
The method creates a clone of the specification used in this element.
@@ -807,12 +1004,13 @@ bool With_element::set_unparsed_spec(THD *thd,
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 build.
+ 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
@@ -828,113 +1026,127 @@ bool With_element::set_unparsed_spec(THD *thd,
NULL - otherwise
*/
-st_select_lex_unit *With_element::clone_parsed_spec(THD *thd,
+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;
- Query_arena backup;
- Query_arena *arena= thd->activate_stmt_arena_if_needed(&backup);
+ st_select_lex_unit *res= NULL;
if (!(lex= (LEX*) new(thd->mem_root) st_lex_local))
- {
- if (arena)
- thd->restore_active_arena(arena, &backup);
return res;
- }
- LEX *old_lex= thd->lex;
thd->lex= lex;
bool parse_status= false;
- Parser_state parser_state;
- TABLE_LIST *spec_tables;
- TABLE_LIST *spec_tables_tail;
st_select_lex *with_select;
char save_end= unparsed_spec.str[unparsed_spec.length];
((char*) &unparsed_spec.str[unparsed_spec.length])[0]= '\0';
- if (parser_state.init(thd, (char*) unparsed_spec.str, (unsigned int)unparsed_spec.length))
- goto err;
- parser_state.m_lip.stmt_prepare_mode= stmt_prepare_mode;
- parser_state.m_lip.multi_statements= false;
- parser_state.m_lip.m_digest= NULL;
lex_start(thd);
lex->clone_spec_offset= unparsed_spec_offset;
- lex->param_list= old_lex->param_list;
- lex->sphead= old_lex->sphead;
- lex->spname= old_lex->spname;
- lex->spcont= old_lex->spcont;
- lex->sp_chistics= old_lex->sp_chistics;
-
- lex->stmt_lex= old_lex;
- parse_status= parse_sql(thd, &parser_state, 0);
+ 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->first_select_lex();
if (parse_status)
goto err;
- if (check_dependencies_in_with_clauses(lex->with_clauses_list))
- goto err;
-
- spec_tables= lex->query_tables;
- spec_tables_tail= 0;
- for (TABLE_LIST *tbl= spec_tables;
- tbl;
- tbl= tbl->next_global)
- {
- if (!tbl->derived && !tbl->schema_table &&
- thd->open_temporary_table(tbl))
- goto err;
- spec_tables_tail= tbl;
- }
- if (spec_tables)
+ /*
+ 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)
{
- if (with_table->next_global)
+ 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)
{
- spec_tables_tail->next_global= with_table->next_global;
- with_table->next_global->prev_global= &spec_tables_tail->next_global;
+ *(lex->query_tables->prev_global= next_tbl->prev_global)=
+ lex->query_tables;
+ *(next_tbl->prev_global= lex->query_tables_last)= next_tbl;
}
else
{
- old_lex->query_tables_last= &spec_tables_tail->next_global;
+ *(lex->query_tables->prev_global= old_lex->query_tables_last)=
+ lex->query_tables;
+ old_lex->query_tables_last= lex->query_tables_last;
}
- spec_tables->prev_global= &with_table->next_global;
- with_table->next_global= spec_tables;
}
res= &lex->unit;
+ /*
+ 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.set_slave(with_select);
+ lex->unit.cloned_from= spec;
old_lex->all_selects_list=
(st_select_lex*) (lex->all_selects_list->
insert_chain_before(
(st_select_lex_node **) &(old_lex->all_selects_list),
with_select));
- if (check_dependencies_in_with_clauses(lex->with_clauses_list))
- res= NULL;
+
/*
- Resolve references to CTE from the spec_tables list that has not
- been resolved yet.
+ 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.
*/
- for (TABLE_LIST *tbl= spec_tables;
- tbl;
- tbl= tbl->next_global)
+ lex->only_cte_resolution= old_lex->only_cte_resolution;
+ if (lex->resolve_references_to_cte(lex->query_tables,
+ lex->query_tables_last))
{
- if (!tbl->with)
- tbl->with= with_select->find_table_def_in_with_clauses(tbl);
- if (tbl == spec_tables_tail)
- break;
- }
- if (check_table_access(thd, SELECT_ACL, spec_tables, FALSE, UINT_MAX, FALSE))
+ res= NULL;
goto err;
+ }
lex->sphead= NULL; // in order not to delete lex->sphead
lex_end(lex);
err:
- if (arena)
- thd->restore_active_arena(arena, &backup);
thd->lex= old_lex;
return res;
}
@@ -1104,58 +1316,6 @@ With_element *st_select_lex::find_table_def_in_with_clauses(TABLE_LIST *table)
}
-/**
- @brief
- Set the specifying unit in this reference to a with table
-
- @details
- The method assumes that the given element with_elem defines the table T
- this table reference refers to.
- If this is the first reference to T the method just sets its specification
- in the field 'derived' as the unit that yields T. Otherwise the method
- first creates a clone specification and sets rather this clone in this field.
-
- @retval
- false on success
- true on failure
-*/
-
-bool TABLE_LIST::set_as_with_table(THD *thd, With_element *with_elem)
-{
- if (table)
- {
- /*
- This table was prematurely identified as a temporary table.
- We correct it here, but it's not a nice solution in the case
- when the temporary table with this name is not used anywhere
- else in the query.
- */
- thd->mark_tmp_table_as_free_for_reuse(table);
- table= 0;
- }
- with= with_elem;
- schema_table= NULL;
- if (!with_elem->is_referenced() || with_elem->is_recursive)
- {
- derived= with_elem->spec;
- if (derived != select_lex->master_unit() &&
- !with_elem->is_recursive &&
- !is_with_table_recursive_reference())
- {
- derived->move_as_slave(select_lex);
- }
- }
- else
- {
- if(!(derived= with_elem->clone_parsed_spec(thd, this)))
- return true;
- }
- derived->first_select()->set_linkage(DERIVED_TABLE_TYPE);
- select_lex->add_statistics(derived);
- with_elem->inc_references();
- return false;
-}
-
bool TABLE_LIST::is_recursive_with_table()
{
@@ -1256,7 +1416,7 @@ bool st_select_lex::check_unrestricted_recursive(bool only_standard_compliant)
if (only_standard_compliant && with_elem->is_unrestricted())
{
my_error(ER_NOT_STANDARD_COMPLIANT_RECURSIVE,
- MYF(0), with_elem->query_name->str);
+ MYF(0), with_elem->get_name_str());
return true;
}
@@ -1456,7 +1616,7 @@ void With_clause::print(String *str, enum_query_type query_type)
void With_element::print(String *str, enum_query_type query_type)
{
- str->append(query_name);
+ str->append(get_name());
if (column_list.elements)
{
List_iterator_fast<LEX_CSTRING> li(column_list);
diff --git a/sql/sql_cte.h b/sql/sql_cte.h
index 80d5664..f30c287 100644
--- a/sql/sql_cte.h
+++ b/sql/sql_cte.h
@@ -23,6 +23,38 @@
class select_unit;
struct st_unit_ctxt_elem;
+/**
+ @class With_element_head
+ @brief Head of the definition of a CTE table
+
+ It contains the name of the CTE and it contains the position of the subchain
+ of table references used in the definition in the global chain of table
+ references used in the query where this definition is encountered.
+*/
+
+class With_element_head : public Sql_alloc
+{
+ /* The name of the defined CTE */
+ LEX_CSTRING *query_name;
+
+public:
+ /*
+ The structure describing the subchain of the table references used in
+ the specification of the defined CTE in the global chain of table
+ references used in the query. The structure is fully defined only
+ after the CTE definition has been parsed.
+ */
+ TABLE_CHAIN tables_pos;
+
+ With_element_head(LEX_CSTRING *name)
+ : query_name(name)
+ {
+ tables_pos.set_start_pos(0);
+ tables_pos.set_end_pos(0);
+ }
+ friend class With_element;
+};
+
/**
@class With_element
@@ -85,9 +117,22 @@ class With_element : public Sql_alloc
subqueries and specifications of other with elements).
*/
uint references;
+
+ /*
+ true <=> this With_element is referred in the query in which the
+ element is defined
+ */
+ bool referenced;
+
+ /*
+ true <=> this With_element is needed for the execution of the query
+ in which the element is defined
+ */
+ bool is_used_in_query;
+
/*
Unparsed specification of the query that specifies this element.
- It used to build clones of the specification if they are needed.
+ It's used to build clones of the specification if they are needed.
*/
LEX_CSTRING unparsed_spec;
/* Offset of the specification in the input string */
@@ -101,10 +146,12 @@ class With_element : public Sql_alloc
public:
/*
- The name of the table introduced by this with elememt. The name
- can be used in FROM lists of the queries in the scope of the element.
+ Contains the name of the defined With element and the position of
+ the subchain of the tables references used by its definition in the
+ global chain of TABLE_LIST objects created for the whole query.
*/
- LEX_CSTRING *query_name;
+ With_element_head *head;
+
/*
Optional list of column names to name the columns of the table introduced
by this with element. It is used in the case when the names are not
@@ -162,18 +209,27 @@ class With_element : public Sql_alloc
/* List of derived tables containing recursive references to this CTE */
SQL_I_List<TABLE_LIST> derived_with_rec_ref;
- With_element(LEX_CSTRING *name,
+ With_element(With_element_head *h,
List <LEX_CSTRING> list,
st_select_lex_unit *unit)
: next(NULL), base_dep_map(0), derived_dep_map(0),
sq_dep_map(0), work_dep_map(0), mutually_recursive(0),
top_level_dep_map(0), sq_rec_ref(NULL),
next_mutually_recursive(NULL), references(0),
- query_name(name), column_list(list), spec(unit),
+ referenced(false), is_used_in_query(false),
+ head(h), column_list(list), spec(unit),
is_recursive(false), rec_outer_references(0), with_anchor(false),
level(0), rec_result(NULL)
{ unit->with_element= this; }
+ LEX_CSTRING *get_name() { return head->query_name; }
+ const char *get_name_str() { return get_name()->str; }
+
+ void set_tables_start_pos(TABLE_LIST **pos)
+ { head->tables_pos.set_start_pos(pos); }
+ void set_tables_end_pos(TABLE_LIST **pos)
+ { head->tables_pos.set_end_pos(pos); }
+
bool check_dependencies_in_spec();
void check_dependencies_in_select(st_select_lex *sl, st_unit_ctxt_elem *ctxt,
@@ -200,9 +256,9 @@ class With_element : public Sql_alloc
bool set_unparsed_spec(THD *thd, const char *spec_start, const char *spec_end,
my_ptrdiff_t spec_offset);
- st_select_lex_unit *clone_parsed_spec(THD *thd, TABLE_LIST *with_table);
+ st_select_lex_unit *clone_parsed_spec(LEX *old_lex, TABLE_LIST *with_table);
- bool is_referenced() { return references != 0; }
+ bool is_referenced() { return referenced; }
void inc_references() { references++; }
@@ -260,6 +316,12 @@ class With_element : public Sql_alloc
void prepare_for_next_iteration();
friend class With_clause;
+
+ friend
+ bool LEX::resolve_references_to_cte(TABLE_LIST *tables,
+ TABLE_LIST **tables_last);
+ friend
+ bool LEX::resolve_references_to_cte_in_hanging_cte();
};
const uint max_number_of_elements_in_with_clause= sizeof(table_map)*8;
@@ -291,6 +353,7 @@ class With_clause : public Sql_alloc
in the current statement
*/
With_clause *next_with_clause;
+
/* Set to true if dependencies between with elements have been checked */
bool dependencies_are_checked;
/*
@@ -338,7 +401,7 @@ class With_clause : public Sql_alloc
void attach_to(st_select_lex *select_lex);
With_clause *pop() { return embedding_with_clause; }
-
+
bool check_dependencies();
bool check_anchors();
@@ -358,8 +421,10 @@ class With_clause : public Sql_alloc
friend class With_element;
friend
- bool
- check_dependencies_in_with_clauses(With_clause *with_clauses_list);
+ bool LEX::check_dependencies_in_with_clauses();
+
+ friend
+ bool LEX::resolve_references_to_cte_in_hanging_cte();
};
inline
diff --git a/sql/sql_lex.cc b/sql/sql_lex.cc
index e196765..c674495 100644
--- a/sql/sql_lex.cc
+++ b/sql/sql_lex.cc
@@ -721,6 +721,8 @@ void LEX::start(THD *thd_arg)
explain_json= false;
context_analysis_only= 0;
derived_tables= 0;
+ with_cte_resolution= false;
+ only_cte_resolution= false;
safe_to_cache_query= 1;
parsing_options.reset();
empty_field_list_on_rset= 0;
@@ -2388,6 +2390,7 @@ void st_select_lex_unit::init_query()
is_view= false;
with_clause= 0;
with_element= 0;
+ cloned_from= 0;
columns_are_renamed= false;
intersect_mark= NULL;
with_wrapped_tvc= false;
@@ -8266,6 +8269,8 @@ bool LEX::check_main_unit_semantics()
if (unit.set_nest_level(0) ||
unit.check_parameters(first_select_lex()))
return TRUE;
+ if (check_cte_dependencies_and_resolve_references())
+ return TRUE;
return FALSE;
}
@@ -8948,8 +8953,8 @@ void st_select_lex::add_statistics(SELECT_LEX_UNIT *unit)
bool LEX::main_select_push(bool service)
{
DBUG_ENTER("LEX::main_select_push");
- current_select_number= 1;
- builtin_select.select_number= 1;
+ current_select_number= ++thd->lex->stmt_lex->current_select_number;
+ builtin_select.select_number= current_select_number;
builtin_select.is_service_select= service;
if (push_select(&builtin_select))
DBUG_RETURN(TRUE);
diff --git a/sql/sql_lex.h b/sql/sql_lex.h
index 66c44f2..848493b 100644
--- a/sql/sql_lex.h
+++ b/sql/sql_lex.h
@@ -917,6 +917,8 @@ class st_select_lex_unit: public st_select_lex_node {
With_clause *with_clause;
/* With element where this unit is used as the specification (if any) */
With_element *with_element;
+ /* The unit used as a CTE specification from which this unit is cloned */
+ st_select_lex_unit *cloned_from;
/* thread handler */
THD *thd;
/*
@@ -1492,7 +1494,9 @@ class st_select_lex: public st_select_lex_node
}
With_element *get_with_element()
{
- return master_unit()->with_element;
+ return master_unit()->cloned_from ?
+ master_unit()->cloned_from->with_element :
+ master_unit()->with_element;
}
With_element *find_table_def_in_with_clauses(TABLE_LIST *table);
bool check_unrestricted_recursive(bool only_standard_compliant);
@@ -3314,6 +3318,20 @@ struct LEX: public Query_tables_list
*/
uint8 derived_tables;
uint8 context_analysis_only;
+ /*
+ true <=> The parsed fragment requires resolution of references to CTE
+ at the end of parsing. This name resolution process involves searching
+ for possible dependencies between CTE defined in the parsed fragment and
+ detecting possible recursive references.
+ The flag is set to true if the fragment contains CTE definitions.
+ */
+ bool with_cte_resolution;
+ /*
+ true <=> only resolution of references to CTE are required in the parsed
+ fragment, no checking of dependencies between CTE is required.
+ This flag is used only when parsing clones of CTE specifications.
+ */
+ bool only_cte_resolution;
bool local_file;
bool check_exists;
bool autocommit;
@@ -4547,6 +4565,11 @@ struct LEX: public Query_tables_list
select_stack[0]->is_service_select);
}
+ bool check_dependencies_in_with_clauses();
+ bool resolve_references_to_cte_in_hanging_cte();
+ bool check_cte_dependencies_and_resolve_references();
+ bool resolve_references_to_cte(TABLE_LIST *tables,
+ TABLE_LIST **tables_last);
};
diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc
index 209caa9..5ce070a 100644
--- a/sql/sql_parse.cc
+++ b/sql/sql_parse.cc
@@ -3408,9 +3408,6 @@ mysql_execute_command(THD *thd)
thd->get_stmt_da()->opt_clear_warning_info(thd->query_id);
}
- if (check_dependencies_in_with_clauses(thd->lex->with_clauses_list))
- DBUG_RETURN(1);
-
#ifdef HAVE_REPLICATION
if (unlikely(thd->slave_thread))
{
@@ -8150,7 +8147,7 @@ TABLE_LIST *st_select_lex::add_table_to_list(THD *thd,
ptr->is_fqtn= TRUE;
ptr->db= table->db;
}
- else if (lex->copy_db_to(&ptr->db))
+ else if (!lex->with_cte_resolution && lex->copy_db_to(&ptr->db))
DBUG_RETURN(0);
else
ptr->is_fqtn= FALSE;
@@ -8167,7 +8164,9 @@ TABLE_LIST *st_select_lex::add_table_to_list(THD *thd,
}
ptr->table_name= table->table;
- ptr->lock_type= lock_type;
+ ptr->lock_type= lock_type;
+ ptr->mdl_type= mdl_type;
+ ptr->table_options= table_options;
ptr->updating= MY_TEST(table_options & TL_OPTION_UPDATING);
/* TODO: remove TL_OPTION_FORCE_INDEX as it looks like it's not used */
ptr->force_index= MY_TEST(table_options & TL_OPTION_FORCE_INDEX);
@@ -8849,8 +8848,10 @@ void st_select_lex::set_lock_for_tables(thr_lock_type lock_type, bool for_update
{
tables->lock_type= lock_type;
tables->updating= for_update;
- tables->mdl_request.set_type((lock_type >= TL_WRITE_ALLOW_WRITE) ?
- MDL_SHARED_WRITE : MDL_SHARED_READ);
+
+ if (tables->db.str && tables->db.str[0])
+ tables->mdl_request.set_type((lock_type >= TL_WRITE_ALLOW_WRITE) ?
+ MDL_SHARED_WRITE : MDL_SHARED_READ);
}
DBUG_VOID_RETURN;
}
diff --git a/sql/sql_prepare.cc b/sql/sql_prepare.cc
index ee50402..64e138f 100644
--- a/sql/sql_prepare.cc
+++ b/sql/sql_prepare.cc
@@ -2376,9 +2376,6 @@ static bool check_prepared_statement(Prepared_statement *stmt)
if (tables)
thd->get_stmt_da()->opt_clear_warning_info(thd->query_id);
- if (check_dependencies_in_with_clauses(thd->lex->with_clauses_list))
- goto error;
-
if (sql_command_flags[sql_command] & CF_HA_CLOSE)
mysql_ha_rm_tables(thd, tables);
diff --git a/sql/sql_view.cc b/sql/sql_view.cc
index 126db90..1e3c4ce 100644
--- a/sql/sql_view.cc
+++ b/sql/sql_view.cc
@@ -431,12 +431,6 @@ bool mysql_create_view(THD *thd, TABLE_LIST *views,
lex->link_first_table_back(view, link_to_local);
view->open_type= OT_BASE_ONLY;
- if (check_dependencies_in_with_clauses(lex->with_clauses_list))
- {
- res= TRUE;
- goto err;
- }
-
WSREP_TO_ISOLATION_BEGIN(WSREP_MYSQL_DB, NULL, NULL);
/*
@@ -1413,9 +1407,6 @@ bool mysql_make_view(THD *thd, TABLE_SHARE *share, TABLE_LIST *table,
TABLE_LIST *tbl;
Security_context *security_ctx= 0;
- if (check_dependencies_in_with_clauses(thd->lex->with_clauses_list))
- goto err;
-
/*
Check rights to run commands which show underlying tables.
In the optimizer trace we would not like to show trace for
diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy
index 2548dc2..f3e1cf2 100644
--- a/sql/sql_yacc.yy
+++ b/sql/sql_yacc.yy
@@ -770,6 +770,7 @@ Virtual_column_info *add_virtual_expression(THD *thd, Item *expr)
class sp_head *sphead;
class sp_name *spname;
class sp_variable *spvar;
+ class With_element_head *with_element_head;
class With_clause *with_clause;
class Virtual_column_info *virtual_column;
@@ -2188,7 +2189,7 @@ END_OF_INPUT
%type <with_clause> with_clause
-%type <lex_str_ptr> query_name
+%type <with_element_head> with_element_head
%type <lex_str_list> opt_with_column_list
@@ -3336,7 +3337,11 @@ call:
if (unlikely(Lex->call_statement_start(thd, $2)))
MYSQL_YYABORT;
}
- opt_sp_cparam_list {}
+ opt_sp_cparam_list
+ {
+ if (Lex->check_cte_dependencies_and_resolve_references())
+ MYSQL_YYABORT;
+ }
;
/* CALL parameters */
@@ -4185,6 +4190,8 @@ sp_proc_stmt_return:
LEX *lex= Lex;
sp_head *sp= lex->sphead;
Lex->pop_select(); //main select
+ if (Lex->check_cte_dependencies_and_resolve_references())
+ MYSQL_YYABORT;
if (unlikely(sp->m_handler->add_instr_freturn(thd, sp, lex->spcont,
$3, lex)) ||
unlikely(sp->restore_lex(thd)))
@@ -13331,6 +13338,8 @@ do:
{
Lex->insert_list= $3;
Lex->pop_select(); //main select
+ if (Lex->check_cte_dependencies_and_resolve_references())
+ MYSQL_YYABORT;
}
;
@@ -15497,6 +15506,7 @@ with_clause:
if (unlikely(with_clause == NULL))
MYSQL_YYABORT;
lex->derived_tables|= DERIVED_WITH;
+ lex->with_cte_resolution= true;
lex->curr_with_clause= with_clause;
with_clause->add_to_list(Lex->with_clauses_list_last_next);
if (lex->current_select &&
@@ -15524,7 +15534,7 @@ with_list:
with_list_element:
- query_name
+ with_element_head
opt_with_column_list
{
$2= new List<LEX_CSTRING> (Lex->with_column_list);
@@ -15544,6 +15554,7 @@ with_list_element:
if (elem->set_unparsed_spec(thd, spec_start, $7.pos(),
spec_start - query_start))
MYSQL_YYABORT;
+ elem->set_tables_end_pos(lex->query_tables_last);
}
;
@@ -15570,16 +15581,18 @@ with_column_list:
;
-query_name:
+with_element_head:
ident
{
- $$= (LEX_CSTRING *) thd->memdup(&$1, sizeof(LEX_CSTRING));
- if (unlikely($$ == NULL))
+ LEX_CSTRING *name=
+ (LEX_CSTRING *) thd->memdup(&$1, sizeof(LEX_CSTRING));
+ $$= new (thd->mem_root) With_element_head(name);
+ if (unlikely(name == NULL || $$ == NULL))
MYSQL_YYABORT;
+ $$->tables_pos.set_start_pos(Lex->query_tables_last);
}
;
-
/**********************************************************************
** Creating different items.
diff --git a/sql/sql_yacc_ora.yy b/sql/sql_yacc_ora.yy
index 30727a8..f2e112c 100644
--- a/sql/sql_yacc_ora.yy
+++ b/sql/sql_yacc_ora.yy
@@ -248,6 +248,7 @@ void ORAerror(THD *thd, const char *s)
class sp_head *sphead;
class sp_name *spname;
class sp_variable *spvar;
+ class With_element_head *with_element_head;
class With_clause *with_clause;
class Virtual_column_info *virtual_column;
@@ -1689,7 +1690,7 @@ END_OF_INPUT
%type <with_clause> with_clause
-%type <lex_str_ptr> query_name
+%type <with_element_head> with_element_head
%type <lex_str_list> opt_with_column_list
@@ -3138,7 +3139,11 @@ call:
if (unlikely(Lex->call_statement_start(thd, $2)))
MYSQL_YYABORT;
}
- opt_sp_cparam_list {}
+ opt_sp_cparam_list
+ {
+ if (Lex->check_cte_dependencies_and_resolve_references())
+ MYSQL_YYABORT;
+ }
;
/* CALL parameters */
@@ -4092,6 +4097,8 @@ sp_proc_stmt_return:
LEX *lex= Lex;
sp_head *sp= lex->sphead;
Lex->pop_select(); //main select
+ if (Lex->check_cte_dependencies_and_resolve_references())
+ MYSQL_YYABORT;
if (unlikely(sp->m_handler->add_instr_freturn(thd, sp, lex->spcont,
$3, lex)) ||
unlikely(sp->restore_lex(thd)))
@@ -13436,6 +13443,8 @@ do:
{
Lex->insert_list= $3;
Lex->pop_select(); //main select
+ if (Lex->check_cte_dependencies_and_resolve_references())
+ MYSQL_YYABORT;
}
;
@@ -15620,6 +15629,7 @@ with_clause:
if (unlikely(with_clause == NULL))
MYSQL_YYABORT;
lex->derived_tables|= DERIVED_WITH;
+ lex->with_cte_resolution= true;
lex->curr_with_clause= with_clause;
with_clause->add_to_list(Lex->with_clauses_list_last_next);
if (lex->current_select &&
@@ -15647,7 +15657,7 @@ with_list:
with_list_element:
- query_name
+ with_element_head
opt_with_column_list
{
$2= new List<LEX_CSTRING> (Lex->with_column_list);
@@ -15667,6 +15677,7 @@ with_list_element:
if (elem->set_unparsed_spec(thd, spec_start, $7.pos(),
spec_start - query_start))
MYSQL_YYABORT;
+ elem->set_tables_end_pos(lex->query_tables_last);
}
;
@@ -15693,12 +15704,15 @@ with_column_list:
;
-query_name:
+with_element_head:
ident
{
- $$= (LEX_CSTRING *) thd->memdup(&$1, sizeof(LEX_CSTRING));
- if (unlikely($$ == NULL))
+ LEX_CSTRING *name=
+ (LEX_CSTRING *) thd->memdup(&$1, sizeof(LEX_CSTRING));
+ $$= new (thd->mem_root) With_element_head(name);
+ if (unlikely(name == NULL || $$ == NULL))
MYSQL_YYABORT;
+ $$->tables_pos.set_start_pos(Lex->query_tables_last);
}
;
diff --git a/sql/table.cc b/sql/table.cc
index 084b441..67b2b14 100644
--- a/sql/table.cc
+++ b/sql/table.cc
@@ -9144,6 +9144,7 @@ void TABLE_LIST::set_lock_type(THD *thd, enum thr_lock_type lock)
}
}
+
bool TABLE_LIST::is_with_table()
{
return derived && derived->with_element;
diff --git a/sql/table.h b/sql/table.h
index 6073e35..248c239 100644
--- a/sql/table.h
+++ b/sql/table.h
@@ -2139,6 +2139,29 @@ struct vers_select_conds_t
struct LEX;
class Index_hint;
+
+/*
+ @struct TABLE_CHAIN
+ @brief Subchain of global chain of table references
+
+ The structure contains a pointer to the address of the next_global
+ pointer to the first TABLE_LIST objectof the subchain and the address
+ of the next_global pointer to the element right after the last
+ TABLE_LIST object of the subchain. For an empty subchain both pointers
+ have the same value.
+*/
+
+struct TABLE_CHAIN
+{
+ TABLE_CHAIN() {}
+
+ TABLE_LIST **start_pos;
+ TABLE_LIST ** end_pos;
+
+ void set_start_pos(TABLE_LIST **pos) { start_pos= pos; }
+ void set_end_pos(TABLE_LIST **pos) { end_pos= pos; }
+};
+
struct TABLE_LIST
{
TABLE_LIST() {} /* Remove gcc warning */
@@ -2473,6 +2496,20 @@ struct TABLE_LIST
/* call back function for asking handler about caching in query cache */
qc_engine_callback callback_func;
thr_lock_type lock_type;
+
+ /*
+ Two fields below are set during parsing this table reference in the cases
+ when the table reference can be potentially a reference to a CTE table.
+ In this cases the fact that the reference is a reference to a CTE or not
+ will be ascertained at the very end of parsing of the query when referencies
+ to CTE are resolved. For references to CTE and to derived tables no mdl
+ requests are needed while for other table references they are. If a request
+ is possibly postponed the info that allows to issue this request must be
+ saved in 'mdl_type' and 'table_options'.
+ */
+ enum_mdl_type mdl_type;
+ ulong table_options;
+
uint outer_join; /* Which join type */
uint shared; /* Used in multi-upd */
bool updatable; /* VIEW/TABLE can be updated now */
1
0
revision-id: de4aec107c76a2aaa21cf993f8c25bdf7b1708c6 (fb-prod8-202101-31-gde4aec107c7)
parent(s): ab584b0521502cc9e4c1589245144bcd43035e48
author: Sergei Petrunia
committer: Sergei Petrunia
timestamp: 2021-05-08 17:11:28 +0300
message:
Apply the Partial Iterator fix from Manuel
---
storage/rocksdb/rdb_iterator.cc | 1 +
1 file changed, 1 insertion(+)
diff --git a/storage/rocksdb/rdb_iterator.cc b/storage/rocksdb/rdb_iterator.cc
index eac051db9a7..2c527e7f439 100644
--- a/storage/rocksdb/rdb_iterator.cc
+++ b/storage/rocksdb/rdb_iterator.cc
@@ -636,6 +636,7 @@ int Rdb_iterator_partial::materialize_prefix() {
rocksdb_partial_index_rows_materialized += num_rows;
exit:
+ m_kd->get_infimum_key(m_cur_prefix_key, &tmp);
rdb_tx_release_lock(tx, *m_kd, cur_prefix_key);
return rc;
}
1
0
revision-id: ec6094c6ec0815ee5203748a32e4ef54e2e07380 (fb-prod8-202101-31-gec6094c6ec0)
parent(s): ab584b0521502cc9e4c1589245144bcd43035e48
author: Sergei Petrunia
committer: Sergei Petrunia
timestamp: 2021-05-08 17:10:49 +0300
message:
Apply the Partial Iterator fix from Manuel
---
storage/rocksdb/rdb_iterator.cc | 1 +
1 file changed, 1 insertion(+)
diff --git a/storage/rocksdb/rdb_iterator.cc b/storage/rocksdb/rdb_iterator.cc
index eac051db9a7..2c527e7f439 100644
--- a/storage/rocksdb/rdb_iterator.cc
+++ b/storage/rocksdb/rdb_iterator.cc
@@ -636,6 +636,7 @@ int Rdb_iterator_partial::materialize_prefix() {
rocksdb_partial_index_rows_materialized += num_rows;
exit:
+ m_kd->get_infimum_key(m_cur_prefix_key, &tmp);
rdb_tx_release_lock(tx, *m_kd, cur_prefix_key);
return rc;
}
1
0
[Commits] 7709b2a: MDEV-23886 Reusing CTE inside a function fails with table doesn't exist
by IgorBabaev 05 May '21
by IgorBabaev 05 May '21
05 May '21
revision-id: 7709b2a93a1da3a886c9e16e81b9f5d6e92cf788 (mariadb-10.4.11-510-g7709b2a)
parent(s): 3454b5cf35a61e8f6cfab376638520dee4a50609
author: Igor Babaev
committer: Igor Babaev
timestamp: 2021-05-05 11:09:06 -0700
message:
MDEV-23886 Reusing CTE inside a function fails with table doesn't exist
Intermediate commit (not to be pushed).
---
mysql-test/main/brackets.result | 2 +-
mysql-test/main/cte_nonrecursive.result | 156 ++++++++++++
mysql-test/main/cte_nonrecursive.test | 163 +++++++++++++
sql/item_subselect.cc | 1 -
sql/sp_head.cc | 3 +-
sql/sql_base.cc | 33 +--
sql/sql_class.cc | 57 +++++
sql/sql_class.h | 8 +-
sql/sql_cte.cc | 418 ++++++++++++++++++++++----------
sql/sql_cte.h | 87 ++++++-
sql/sql_derived.cc | 2 +
sql/sql_lex.cc | 9 +-
sql/sql_lex.h | 25 +-
sql/sql_parse.cc | 15 +-
sql/sql_prepare.cc | 3 -
sql/sql_view.cc | 9 -
sql/sql_yacc.yy | 27 ++-
sql/sql_yacc_ora.yy | 26 +-
sql/table.cc | 1 +
sql/table.h | 37 +++
20 files changed, 870 insertions(+), 212 deletions(-)
diff --git a/mysql-test/main/brackets.result b/mysql-test/main/brackets.result
index 99e84f9..1da5168 100644
--- a/mysql-test/main/brackets.result
+++ b/mysql-test/main/brackets.result
@@ -4508,7 +4508,7 @@ s as (select * from t where a > 3)
select a from t where a=1 union select a from s where a=7 order by a desc;
show create view v1;
View Create View character_set_client collation_connection
-v1 CREATE ALGORITHM=UNDEFINED DEFINER=`root`@`localhost` SQL SECURITY DEFINER VIEW `v1` AS with t as (select `test`.`t1`.`a` AS `a` from `test`.`t1` where `test`.`t1`.`a` < 3), s as (select `t`.`a` AS `a` from (select `test`.`t1`.`a` AS `a` from `test`.`t1` where `test`.`t1`.`a` < 3) `t` where `t`.`a` > 3)select `t`.`a` AS `a` from `t` where `t`.`a` = 1 union select `s`.`a` AS `a` from `s` where `s`.`a` = 7 order by `a` desc latin1 latin1_swedish_ci
+v1 CREATE ALGORITHM=UNDEFINED DEFINER=`root`@`localhost` SQL SECURITY DEFINER VIEW `v1` AS with t as (select `test`.`t1`.`a` AS `a` from `test`.`t1` where `test`.`t1`.`a` < 3), s as (select `t`.`a` AS `a` from `t` where `t`.`a` > 3)select `t`.`a` AS `a` from (select `test`.`t1`.`a` AS `a` from `test`.`t1` where `test`.`t1`.`a` < 3) `t` where `t`.`a` = 1 union select `s`.`a` AS `a` from `s` where `s`.`a` = 7 order by `a` desc latin1 latin1_swedish_ci
select * from v1;
a
1
diff --git a/mysql-test/main/cte_nonrecursive.result b/mysql-test/main/cte_nonrecursive.result
index ee2320c..b8e885e 100644
--- a/mysql-test/main/cte_nonrecursive.result
+++ b/mysql-test/main/cte_nonrecursive.result
@@ -1846,4 +1846,160 @@ set sql_mode="oracle";
with data as (select 1 as id)
select id into @myid from data;
set sql_mode= @save_sql_mode;
+#
+# MDEV-23886: Stored Function returning the result of a query
+# that uses CTE over a table twice
+#
+create table t1 (c1 int);
+insert into t1 values (1),(2),(6);
+create function f1() returns int return
+( with cte1 as (select c1 from t1)
+select sum(c1) from
+(select * from cte1 union all select * from cte1) dt
+);
+select f1();
+f1()
+18
+create function f2() returns int return
+( with cte1 as (select c1 from t1)
+select sum(s.c1) from cte1 as s, cte1 as t where s.c1=t.c1
+);
+select f2();
+f2()
+9
+create function f3() returns int return
+( with cte1 as (select c1 from t1)
+select
+case
+when exists(select 1 from cte1 where c1 between 1 and 2) then 1
+when exists(select 1 from cte1 where c1 between 5 and 6) then 2
+else 0
+end
+);
+select f3();
+f3()
+1
+drop function f1;
+drop function f2;
+drop function f3;
+create table t2 (a int, b int);
+insert into t2
+with cte1 as (select c1 from t1)
+select * from cte1 as s, cte1 as t where s.c1=t.c1 and s.c1 > 5;
+select * from t2;
+a b
+6 6
+create procedure p1()
+begin
+insert into t2
+with cte1 as (select c1 from t1)
+select * from cte1 as s, cte1 as t where s.c1=t.c1 and s.c1 <= 2 and t.c1 >= 2;
+end |
+call p1();
+select * from t2;
+a b
+6 6
+2 2
+drop procedure p1;
+# checking CTE resolution for queries with hanging CTEs
+with
+cte1(a) as (select * from t1 where c1 <= 2),
+cte2(b) as (select * from cte1 where a >= 2),
+cte3 as (select * from cte1,cte2 where cte1.a < cte2.b)
+select * from cte3;
+a b
+1 2
+select * from t2;
+a b
+6 6
+2 2
+with
+cte1(a) as (select * from t1 where c1 <= 2),
+cte2(b) as (select * from cte1 where a >= 2),
+cte3 as (select * from cte1,cte2 where cte1.a < cte2.b)
+select * from t2;
+a b
+6 6
+2 2
+with
+cte1(a) as (select * from t1 where c1 <= 2),
+cte2(b) as (select * from cte1 where c1 >= 2),
+cte3 as (select * from cte1,cte2 where cte1.a < cte2.b)
+select * from t2;
+ERROR 42S22: Unknown column 'c1' in 'where clause'
+with
+cte1(a) as (select * from t1 where c1 <= 2),
+cte2(b) as (select * from cte1 where a >= 2),
+cte3 as (select * from cte1,cte2 where cte1.a < cte2.c1)
+select * from t2;
+ERROR 42S22: Unknown column 'cte2.c1' in 'where clause'
+with
+cte1 as (select * from t1 where c1 <= 2),
+cte2(a,b) as (select * from cte1 as s1, cte1 as s2 where s1.c1=s2.c1)
+select * from cte2;
+a b
+1 1
+2 2
+with
+cte1 as (select * from t1 where c1 <= 2),
+cte2(a,b) as (select * from cte1 as s1, cte1 as s2 where s1.c1=s2.c1)
+select * from t2;
+a b
+6 6
+2 2
+with
+cte1 as (select * from t1 where c1 <= 2),
+cte2(a,b) as (select * from cte1 as s1, cte1 as s2 where s1.c1=c1)
+select * from t2;
+ERROR 23000: Column 'c1' in where clause is ambiguous
+with cte3 as
+( with cte2(a,b) as
+( with cte1 as (select * from t1 where c1 <= 2)
+select * from cte1 as s1, cte1 as s2 where s1.c1=s2.c1)
+select r1.a,r2.b from cte2 as r1, cte2 as r2)
+select * from cte3;
+a b
+1 1
+2 1
+1 2
+2 2
+with cte3 as
+( with cte2(a,b) as
+( with cte1 as (select * from t1 where c1 <= 2)
+select * from cte1 as s1, cte1 as s2 where s1.c1=s2.c1)
+select r1.a,r2.b from cte2 as r1, cte2 as r2)
+select * from t2;
+a b
+6 6
+2 2
+with cte3 as
+( with cte2(a,b) as
+( with cte1 as (select * from t1 where c1 <= 2)
+select * from cte1 as s1, cte1 as s2 where s1.c1=s2.c1)
+select r1.c1,r2.c1 from cte2 as r1, cte2 as r2)
+select * from t2;
+ERROR 42S22: Unknown column 'r1.c1' in 'field list'
+create procedure p1()
+begin
+insert into t2
+with cte1 as (select c1 from t1)
+select * from t1 as s, t1 as t where s.c1=t.c1 and s.c1 <= 2 and t.c1 >= 2;
+end |
+call p1();
+select * from t2;
+a b
+6 6
+2 2
+2 2
+drop procedure p1;
+create procedure p1()
+begin
+insert into t2
+with cte1 as (select a from t1)
+select * from t1 as s, t1 as t where s.c1=t.c1 and s.c1 <= 2 and t.c1 >= 2;
+end |
+call p1();
+ERROR 42S22: Unknown column 'a' in 'field list'
+drop procedure p1;
+drop table t1,t2;
# End of 10.4 tests
diff --git a/mysql-test/main/cte_nonrecursive.test b/mysql-test/main/cte_nonrecursive.test
index 2242dce..2dd4857 100644
--- a/mysql-test/main/cte_nonrecursive.test
+++ b/mysql-test/main/cte_nonrecursive.test
@@ -1347,4 +1347,167 @@ with data as (select 1 as id)
select id into @myid from data;
set sql_mode= @save_sql_mode;
+--echo #
+--echo # MDEV-23886: Stored Function returning the result of a query
+--echo # that uses CTE over a table twice
+--echo #
+
+create table t1 (c1 int);
+insert into t1 values (1),(2),(6);
+
+create function f1() returns int return
+( with cte1 as (select c1 from t1)
+ select sum(c1) from
+ (select * from cte1 union all select * from cte1) dt
+);
+select f1();
+
+create function f2() returns int return
+( with cte1 as (select c1 from t1)
+ select sum(s.c1) from cte1 as s, cte1 as t where s.c1=t.c1
+);
+select f2();
+
+create function f3() returns int return
+( with cte1 as (select c1 from t1)
+ select
+ case
+ when exists(select 1 from cte1 where c1 between 1 and 2) then 1
+ when exists(select 1 from cte1 where c1 between 5 and 6) then 2
+ else 0
+ end
+);
+select f3();
+
+drop function f1;
+drop function f2;
+drop function f3;
+
+create table t2 (a int, b int);
+
+insert into t2
+with cte1 as (select c1 from t1)
+select * from cte1 as s, cte1 as t where s.c1=t.c1 and s.c1 > 5;
+
+select * from t2;
+
+delimiter |;
+
+create procedure p1()
+begin
+insert into t2
+with cte1 as (select c1 from t1)
+select * from cte1 as s, cte1 as t where s.c1=t.c1 and s.c1 <= 2 and t.c1 >= 2;
+end |
+
+delimiter ;|
+
+call p1();
+select * from t2;
+
+drop procedure p1;
+
+--echo # checking CTE resolution for queries with hanging CTEs
+
+with
+cte1(a) as (select * from t1 where c1 <= 2),
+cte2(b) as (select * from cte1 where a >= 2),
+cte3 as (select * from cte1,cte2 where cte1.a < cte2.b)
+select * from cte3;
+
+select * from t2;
+
+with
+cte1(a) as (select * from t1 where c1 <= 2),
+cte2(b) as (select * from cte1 where a >= 2),
+cte3 as (select * from cte1,cte2 where cte1.a < cte2.b)
+select * from t2;
+
+--error ER_BAD_FIELD_ERROR
+with
+cte1(a) as (select * from t1 where c1 <= 2),
+cte2(b) as (select * from cte1 where c1 >= 2),
+cte3 as (select * from cte1,cte2 where cte1.a < cte2.b)
+select * from t2;
+
+--error ER_BAD_FIELD_ERROR
+with
+cte1(a) as (select * from t1 where c1 <= 2),
+cte2(b) as (select * from cte1 where a >= 2),
+cte3 as (select * from cte1,cte2 where cte1.a < cte2.c1)
+select * from t2;
+
+with
+cte1 as (select * from t1 where c1 <= 2),
+cte2(a,b) as (select * from cte1 as s1, cte1 as s2 where s1.c1=s2.c1)
+select * from cte2;
+
+with
+cte1 as (select * from t1 where c1 <= 2),
+cte2(a,b) as (select * from cte1 as s1, cte1 as s2 where s1.c1=s2.c1)
+select * from t2;
+
+--error ER_NON_UNIQ_ERROR
+with
+cte1 as (select * from t1 where c1 <= 2),
+cte2(a,b) as (select * from cte1 as s1, cte1 as s2 where s1.c1=c1)
+select * from t2;
+
+with cte3 as
+( with cte2(a,b) as
+ ( with cte1 as (select * from t1 where c1 <= 2)
+ select * from cte1 as s1, cte1 as s2 where s1.c1=s2.c1)
+ select r1.a,r2.b from cte2 as r1, cte2 as r2)
+select * from cte3;
+
+with cte3 as
+( with cte2(a,b) as
+ ( with cte1 as (select * from t1 where c1 <= 2)
+ select * from cte1 as s1, cte1 as s2 where s1.c1=s2.c1)
+ select r1.a,r2.b from cte2 as r1, cte2 as r2)
+select * from t2;
+
+--error ER_BAD_FIELD_ERROR
+with cte3 as
+( with cte2(a,b) as
+ ( with cte1 as (select * from t1 where c1 <= 2)
+ select * from cte1 as s1, cte1 as s2 where s1.c1=s2.c1)
+ select r1.c1,r2.c1 from cte2 as r1, cte2 as r2)
+select * from t2;
+
+delimiter |;
+
+create procedure p1()
+begin
+insert into t2
+with cte1 as (select c1 from t1)
+select * from t1 as s, t1 as t where s.c1=t.c1 and s.c1 <= 2 and t.c1 >= 2;
+end |
+
+delimiter ;|
+
+call p1();
+select * from t2;
+
+drop procedure p1;
+
+delimiter |;
+
+create procedure p1()
+begin
+insert into t2
+with cte1 as (select a from t1)
+select * from t1 as s, t1 as t where s.c1=t.c1 and s.c1 <= 2 and t.c1 >= 2;
+end |
+
+delimiter ;|
+
+--error ER_BAD_FIELD_ERROR
+call p1();
+
+drop procedure p1;
+
+drop table t1,t2;
+
--echo # End of 10.4 tests
+
diff --git a/sql/item_subselect.cc b/sql/item_subselect.cc
index e205847..f6768d0 100644
--- a/sql/item_subselect.cc
+++ b/sql/item_subselect.cc
@@ -1723,7 +1723,6 @@ double Item_in_subselect::val_real()
As far as Item_in_subselect called only from Item_in_optimizer this
method should not be used
*/
- DBUG_ASSERT(0);
DBUG_ASSERT(fixed == 1);
if (forced_const)
return value;
diff --git a/sql/sp_head.cc b/sql/sp_head.cc
index aa4f809..3ea4938 100644
--- a/sql/sp_head.cc
+++ b/sql/sp_head.cc
@@ -3411,8 +3411,7 @@ sp_lex_keeper::reset_lex_and_exec_core(THD *thd, uint *nextp,
Json_writer_object trace_command(thd);
Json_writer_array trace_command_steps(thd, "steps");
if (open_tables)
- res= check_dependencies_in_with_clauses(m_lex->with_clauses_list) ||
- instr->exec_open_and_lock_tables(thd, m_lex->query_tables);
+ res= instr->exec_open_and_lock_tables(thd, m_lex->query_tables);
if (likely(!res))
{
diff --git a/sql/sql_base.cc b/sql/sql_base.cc
index 8bb6011..c3aeb7d 100644
--- a/sql/sql_base.cc
+++ b/sql/sql_base.cc
@@ -3695,7 +3695,11 @@ open_and_process_table(THD *thd, TABLE_LIST *tables, uint *counter, uint flags,
if (tables->derived)
{
if (!tables->view)
+ {
+ if (!tables->is_derived())
+ tables->set_derived();
goto end;
+ }
/*
We restore view's name and database wiped out by derived tables
processing and fall back to standard open process in order to
@@ -3705,35 +3709,6 @@ open_and_process_table(THD *thd, TABLE_LIST *tables, uint *counter, uint flags,
tables->db= tables->view_db;
tables->table_name= tables->view_name;
}
- else if (tables->select_lex)
- {
- /*
- Check whether 'tables' refers to a table defined in a with clause.
- If so set the reference to the definition in tables->with.
- */
- if (!tables->with)
- tables->with= tables->select_lex->find_table_def_in_with_clauses(tables);
- /*
- If 'tables' is defined in a with clause set the pointer to the
- specification from its definition in tables->derived.
- */
- if (tables->with)
- {
- if (tables->is_recursive_with_table() &&
- !tables->is_with_table_recursive_reference())
- {
- tables->with->rec_outer_references++;
- With_element *with_elem= tables->with;
- while ((with_elem= with_elem->get_next_mutually_recursive()) !=
- tables->with)
- with_elem->rec_outer_references++;
- }
- if (tables->set_as_with_table(thd, tables->with))
- DBUG_RETURN(1);
- else
- goto end;
- }
- }
if (!tables->derived && is_infoschema_db(&tables->db))
{
diff --git a/sql/sql_class.cc b/sql/sql_class.cc
index ceb8dc1..b32b792 100644
--- a/sql/sql_class.cc
+++ b/sql/sql_class.cc
@@ -2804,6 +2804,63 @@ void THD::close_active_vio()
#endif
+/*
+ @brief MySQL parser used for recursive invocations
+
+ @param old_lex The LEX structure in the state when this parser
+ is called recursively
+ @param lex The LEX structure used to parse a new SQL fragment
+ @param str The SQL fragment to parse
+ @param str_len The length of the SQL fragment to parse
+ @param stmt_prepare_mode true <=> when parsing a prepare statement
+
+ @details
+ This function is to be used when parsing of an SQL fragment is
+ needed within one of the grammar rules.
+
+ @notes
+ Currently the function is used only when the specification of a CTE
+ is parsed for the not first and not recursive references of the CTE.
+
+ @retval false On a successful parsing of the fragment
+ @retval true Otherwise
+*/
+
+bool THD::sql_parser(LEX *old_lex, LEX *lex,
+ char *str, uint str_len, bool stmt_prepare_mode)
+{
+ extern int MYSQLparse(THD * thd);
+ extern int ORAparse(THD * thd);
+
+ bool parse_status= false;
+ Parser_state parser_state;
+ Parser_state *old_parser_state= m_parser_state;
+
+ if (parser_state.init(this, str, str_len))
+ return true;
+
+ m_parser_state= &parser_state;
+ parser_state.m_lip.stmt_prepare_mode= stmt_prepare_mode;
+ parser_state.m_lip.multi_statements= false;
+ parser_state.m_lip.m_digest= NULL;
+
+ lex->param_list= old_lex->param_list;
+ lex->sphead= old_lex->sphead;
+ lex->spname= old_lex->spname;
+ lex->spcont= old_lex->spcont;
+ lex->sp_chistics= old_lex->sp_chistics;
+ lex->trg_chistics= old_lex->trg_chistics;
+
+ parse_status= (variables.sql_mode & MODE_ORACLE) ?
+ ORAparse(this) : MYSQLparse(this) != 0;
+
+ m_parser_state= old_parser_state;
+
+ return parse_status;
+}
+
+
+
struct Item_change_record: public ilink
{
Item **place;
diff --git a/sql/sql_class.h b/sql/sql_class.h
index 936437f..ecab13e 100644
--- a/sql/sql_class.h
+++ b/sql/sql_class.h
@@ -4241,14 +4241,11 @@ class THD: public THD_count, /* this must be first */
to resolve all CTE names as we don't need this message to be thrown
for any CTE references.
*/
- if (!lex->with_clauses_list)
+ if (!lex->with_cte_resolution)
{
my_message(ER_NO_DB_ERROR, ER(ER_NO_DB_ERROR), MYF(0));
return TRUE;
}
- /* This will allow to throw an error later for non-CTE references */
- to->str= NULL;
- to->length= 0;
return FALSE;
}
@@ -5043,6 +5040,9 @@ class THD: public THD_count, /* this must be first */
Item *sp_prepare_func_item(Item **it_addr, uint cols= 1);
bool sp_eval_expr(Field *result_field, Item **expr_item_ptr);
+ bool sql_parser(LEX *old_lex, LEX *lex,
+ char *str, uint str_len, bool stmt_prepare_mode);
+
};
/** A short cut for thd->get_stmt_da()->set_ok_status(). */
diff --git a/sql/sql_cte.cc b/sql/sql_cte.cc
index a9f5443..cfabe01 100644
--- a/sql/sql_cte.cc
+++ b/sql/sql_cte.cc
@@ -1,3 +1,4 @@
+
/*
Copyright (c) 2016, 2017 MariaDB
@@ -83,7 +84,7 @@ void st_select_lex_unit::set_with_clause(With_clause *with_cl)
true on failure
*/
-bool check_dependencies_in_with_clauses(With_clause *with_clauses_list)
+bool LEX::check_dependencies_in_with_clauses()
{
for (With_clause *with_clause= with_clauses_list;
with_clause;
@@ -101,6 +102,201 @@ bool check_dependencies_in_with_clauses(With_clause *with_clauses_list)
/**
@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))
+ tbl->mdl_request.init(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
@@ -137,10 +333,11 @@ bool With_clause::check_dependencies()
elem != with_elem;
elem= elem->next)
{
- if (lex_string_cmp(system_charset_info, with_elem->query_name,
- elem->query_name) == 0)
+ 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->query_name->str);
+ my_error(ER_DUP_QUERY_NAME, MYF(0),
+ with_elem->get_name_str());
return true;
}
}
@@ -247,13 +444,12 @@ With_element *With_clause::find_table_def(TABLE_LIST *table,
with_elem != barrier;
with_elem= with_elem->next)
{
- if (my_strcasecmp(system_charset_info, with_elem->query_name->str,
- table->table_name.str) == 0 &&
+ if (my_strcasecmp(system_charset_info, with_elem->get_name_str(),
+ table->table_name.str) == 0 &&
!table->is_fqtn)
{
table->set_derived();
- table->db.str= empty_c_string;
- table->db.length= 0;
+ with_elem->referenced= true;
return with_elem;
}
}
@@ -610,7 +806,7 @@ bool With_clause::check_anchors()
if (elem == with_elem)
{
my_error(ER_RECURSIVE_WITHOUT_ANCHORS, MYF(0),
- with_elem->query_name->str);
+ with_elem->get_name_str());
return true;
}
}
@@ -643,7 +839,7 @@ bool With_clause::check_anchors()
if (elem->work_dep_map & elem->get_elem_map())
{
my_error(ER_UNACCEPTABLE_MUTUAL_RECURSION, MYF(0),
- with_elem->query_name->str);
+ with_elem->get_name_str());
return true;
}
}
@@ -797,9 +993,10 @@ bool With_element::set_unparsed_spec(THD *thd,
@brief
Create a clone of the specification for the given with table
- @param thd The context of the statement containing this with element
+ @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.
+ the clone is created.
@details
The method creates a clone of the specification used in this element.
@@ -807,12 +1004,13 @@ bool With_element::set_unparsed_spec(THD *thd,
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 build.
+ 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
@@ -828,113 +1026,127 @@ bool With_element::set_unparsed_spec(THD *thd,
NULL - otherwise
*/
-st_select_lex_unit *With_element::clone_parsed_spec(THD *thd,
+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;
- Query_arena backup;
- Query_arena *arena= thd->activate_stmt_arena_if_needed(&backup);
+ st_select_lex_unit *res= NULL;
if (!(lex= (LEX*) new(thd->mem_root) st_lex_local))
- {
- if (arena)
- thd->restore_active_arena(arena, &backup);
return res;
- }
- LEX *old_lex= thd->lex;
thd->lex= lex;
bool parse_status= false;
- Parser_state parser_state;
- TABLE_LIST *spec_tables;
- TABLE_LIST *spec_tables_tail;
st_select_lex *with_select;
char save_end= unparsed_spec.str[unparsed_spec.length];
((char*) &unparsed_spec.str[unparsed_spec.length])[0]= '\0';
- if (parser_state.init(thd, (char*) unparsed_spec.str, (unsigned int)unparsed_spec.length))
- goto err;
- parser_state.m_lip.stmt_prepare_mode= stmt_prepare_mode;
- parser_state.m_lip.multi_statements= false;
- parser_state.m_lip.m_digest= NULL;
lex_start(thd);
lex->clone_spec_offset= unparsed_spec_offset;
- lex->param_list= old_lex->param_list;
- lex->sphead= old_lex->sphead;
- lex->spname= old_lex->spname;
- lex->spcont= old_lex->spcont;
- lex->sp_chistics= old_lex->sp_chistics;
-
- lex->stmt_lex= old_lex;
- parse_status= parse_sql(thd, &parser_state, 0);
+ 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->first_select_lex();
if (parse_status)
goto err;
- if (check_dependencies_in_with_clauses(lex->with_clauses_list))
- goto err;
-
- spec_tables= lex->query_tables;
- spec_tables_tail= 0;
- for (TABLE_LIST *tbl= spec_tables;
- tbl;
- tbl= tbl->next_global)
- {
- if (!tbl->derived && !tbl->schema_table &&
- thd->open_temporary_table(tbl))
- goto err;
- spec_tables_tail= tbl;
- }
- if (spec_tables)
+ /*
+ 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)
{
- if (with_table->next_global)
+ 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)
{
- spec_tables_tail->next_global= with_table->next_global;
- with_table->next_global->prev_global= &spec_tables_tail->next_global;
+ *(lex->query_tables->prev_global= next_tbl->prev_global)=
+ lex->query_tables;
+ *(next_tbl->prev_global= lex->query_tables_last)= next_tbl;
}
else
{
- old_lex->query_tables_last= &spec_tables_tail->next_global;
+ *(lex->query_tables->prev_global= old_lex->query_tables_last)=
+ lex->query_tables;
+ old_lex->query_tables_last= lex->query_tables_last;
}
- spec_tables->prev_global= &with_table->next_global;
- with_table->next_global= spec_tables;
}
res= &lex->unit;
+ /*
+ 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.set_slave(with_select);
+ lex->unit.cloned_from= spec;
old_lex->all_selects_list=
(st_select_lex*) (lex->all_selects_list->
insert_chain_before(
(st_select_lex_node **) &(old_lex->all_selects_list),
with_select));
- if (check_dependencies_in_with_clauses(lex->with_clauses_list))
- res= NULL;
+
/*
- Resolve references to CTE from the spec_tables list that has not
- been resolved yet.
+ 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.
*/
- for (TABLE_LIST *tbl= spec_tables;
- tbl;
- tbl= tbl->next_global)
+ lex->only_cte_resolution= old_lex->only_cte_resolution;
+ if (lex->resolve_references_to_cte(lex->query_tables,
+ lex->query_tables_last))
{
- if (!tbl->with)
- tbl->with= with_select->find_table_def_in_with_clauses(tbl);
- if (tbl == spec_tables_tail)
- break;
- }
- if (check_table_access(thd, SELECT_ACL, spec_tables, FALSE, UINT_MAX, FALSE))
+ res= NULL;
goto err;
+ }
lex->sphead= NULL; // in order not to delete lex->sphead
lex_end(lex);
err:
- if (arena)
- thd->restore_active_arena(arena, &backup);
thd->lex= old_lex;
return res;
}
@@ -1104,58 +1316,6 @@ With_element *st_select_lex::find_table_def_in_with_clauses(TABLE_LIST *table)
}
-/**
- @brief
- Set the specifying unit in this reference to a with table
-
- @details
- The method assumes that the given element with_elem defines the table T
- this table reference refers to.
- If this is the first reference to T the method just sets its specification
- in the field 'derived' as the unit that yields T. Otherwise the method
- first creates a clone specification and sets rather this clone in this field.
-
- @retval
- false on success
- true on failure
-*/
-
-bool TABLE_LIST::set_as_with_table(THD *thd, With_element *with_elem)
-{
- if (table)
- {
- /*
- This table was prematurely identified as a temporary table.
- We correct it here, but it's not a nice solution in the case
- when the temporary table with this name is not used anywhere
- else in the query.
- */
- thd->mark_tmp_table_as_free_for_reuse(table);
- table= 0;
- }
- with= with_elem;
- schema_table= NULL;
- if (!with_elem->is_referenced() || with_elem->is_recursive)
- {
- derived= with_elem->spec;
- if (derived != select_lex->master_unit() &&
- !with_elem->is_recursive &&
- !is_with_table_recursive_reference())
- {
- derived->move_as_slave(select_lex);
- }
- }
- else
- {
- if(!(derived= with_elem->clone_parsed_spec(thd, this)))
- return true;
- }
- derived->first_select()->set_linkage(DERIVED_TABLE_TYPE);
- select_lex->add_statistics(derived);
- with_elem->inc_references();
- return false;
-}
-
bool TABLE_LIST::is_recursive_with_table()
{
@@ -1256,7 +1416,7 @@ bool st_select_lex::check_unrestricted_recursive(bool only_standard_compliant)
if (only_standard_compliant && with_elem->is_unrestricted())
{
my_error(ER_NOT_STANDARD_COMPLIANT_RECURSIVE,
- MYF(0), with_elem->query_name->str);
+ MYF(0), with_elem->get_name_str());
return true;
}
@@ -1456,7 +1616,7 @@ void With_clause::print(String *str, enum_query_type query_type)
void With_element::print(String *str, enum_query_type query_type)
{
- str->append(query_name);
+ str->append(get_name());
if (column_list.elements)
{
List_iterator_fast<LEX_CSTRING> li(column_list);
diff --git a/sql/sql_cte.h b/sql/sql_cte.h
index 80d5664..f30c287 100644
--- a/sql/sql_cte.h
+++ b/sql/sql_cte.h
@@ -23,6 +23,38 @@
class select_unit;
struct st_unit_ctxt_elem;
+/**
+ @class With_element_head
+ @brief Head of the definition of a CTE table
+
+ It contains the name of the CTE and it contains the position of the subchain
+ of table references used in the definition in the global chain of table
+ references used in the query where this definition is encountered.
+*/
+
+class With_element_head : public Sql_alloc
+{
+ /* The name of the defined CTE */
+ LEX_CSTRING *query_name;
+
+public:
+ /*
+ The structure describing the subchain of the table references used in
+ the specification of the defined CTE in the global chain of table
+ references used in the query. The structure is fully defined only
+ after the CTE definition has been parsed.
+ */
+ TABLE_CHAIN tables_pos;
+
+ With_element_head(LEX_CSTRING *name)
+ : query_name(name)
+ {
+ tables_pos.set_start_pos(0);
+ tables_pos.set_end_pos(0);
+ }
+ friend class With_element;
+};
+
/**
@class With_element
@@ -85,9 +117,22 @@ class With_element : public Sql_alloc
subqueries and specifications of other with elements).
*/
uint references;
+
+ /*
+ true <=> this With_element is referred in the query in which the
+ element is defined
+ */
+ bool referenced;
+
+ /*
+ true <=> this With_element is needed for the execution of the query
+ in which the element is defined
+ */
+ bool is_used_in_query;
+
/*
Unparsed specification of the query that specifies this element.
- It used to build clones of the specification if they are needed.
+ It's used to build clones of the specification if they are needed.
*/
LEX_CSTRING unparsed_spec;
/* Offset of the specification in the input string */
@@ -101,10 +146,12 @@ class With_element : public Sql_alloc
public:
/*
- The name of the table introduced by this with elememt. The name
- can be used in FROM lists of the queries in the scope of the element.
+ Contains the name of the defined With element and the position of
+ the subchain of the tables references used by its definition in the
+ global chain of TABLE_LIST objects created for the whole query.
*/
- LEX_CSTRING *query_name;
+ With_element_head *head;
+
/*
Optional list of column names to name the columns of the table introduced
by this with element. It is used in the case when the names are not
@@ -162,18 +209,27 @@ class With_element : public Sql_alloc
/* List of derived tables containing recursive references to this CTE */
SQL_I_List<TABLE_LIST> derived_with_rec_ref;
- With_element(LEX_CSTRING *name,
+ With_element(With_element_head *h,
List <LEX_CSTRING> list,
st_select_lex_unit *unit)
: next(NULL), base_dep_map(0), derived_dep_map(0),
sq_dep_map(0), work_dep_map(0), mutually_recursive(0),
top_level_dep_map(0), sq_rec_ref(NULL),
next_mutually_recursive(NULL), references(0),
- query_name(name), column_list(list), spec(unit),
+ referenced(false), is_used_in_query(false),
+ head(h), column_list(list), spec(unit),
is_recursive(false), rec_outer_references(0), with_anchor(false),
level(0), rec_result(NULL)
{ unit->with_element= this; }
+ LEX_CSTRING *get_name() { return head->query_name; }
+ const char *get_name_str() { return get_name()->str; }
+
+ void set_tables_start_pos(TABLE_LIST **pos)
+ { head->tables_pos.set_start_pos(pos); }
+ void set_tables_end_pos(TABLE_LIST **pos)
+ { head->tables_pos.set_end_pos(pos); }
+
bool check_dependencies_in_spec();
void check_dependencies_in_select(st_select_lex *sl, st_unit_ctxt_elem *ctxt,
@@ -200,9 +256,9 @@ class With_element : public Sql_alloc
bool set_unparsed_spec(THD *thd, const char *spec_start, const char *spec_end,
my_ptrdiff_t spec_offset);
- st_select_lex_unit *clone_parsed_spec(THD *thd, TABLE_LIST *with_table);
+ st_select_lex_unit *clone_parsed_spec(LEX *old_lex, TABLE_LIST *with_table);
- bool is_referenced() { return references != 0; }
+ bool is_referenced() { return referenced; }
void inc_references() { references++; }
@@ -260,6 +316,12 @@ class With_element : public Sql_alloc
void prepare_for_next_iteration();
friend class With_clause;
+
+ friend
+ bool LEX::resolve_references_to_cte(TABLE_LIST *tables,
+ TABLE_LIST **tables_last);
+ friend
+ bool LEX::resolve_references_to_cte_in_hanging_cte();
};
const uint max_number_of_elements_in_with_clause= sizeof(table_map)*8;
@@ -291,6 +353,7 @@ class With_clause : public Sql_alloc
in the current statement
*/
With_clause *next_with_clause;
+
/* Set to true if dependencies between with elements have been checked */
bool dependencies_are_checked;
/*
@@ -338,7 +401,7 @@ class With_clause : public Sql_alloc
void attach_to(st_select_lex *select_lex);
With_clause *pop() { return embedding_with_clause; }
-
+
bool check_dependencies();
bool check_anchors();
@@ -358,8 +421,10 @@ class With_clause : public Sql_alloc
friend class With_element;
friend
- bool
- check_dependencies_in_with_clauses(With_clause *with_clauses_list);
+ bool LEX::check_dependencies_in_with_clauses();
+
+ friend
+ bool LEX::resolve_references_to_cte_in_hanging_cte();
};
inline
diff --git a/sql/sql_derived.cc b/sql/sql_derived.cc
index dc079ba..89b1900 100644
--- a/sql/sql_derived.cc
+++ b/sql/sql_derived.cc
@@ -917,6 +917,7 @@ bool mysql_derived_prepare(THD *thd, LEX *lex, TABLE_LIST *derived)
#ifndef NO_EMBEDDED_ACCESS_CHECKS
if (derived->is_view())
table->grant= derived->grant;
+#if 1
else
{
DBUG_ASSERT(derived->is_derived());
@@ -925,6 +926,7 @@ bool mysql_derived_prepare(THD *thd, LEX *lex, TABLE_LIST *derived)
derived->grant.privilege= SELECT_ACL;
}
#endif
+#endif
/* Add new temporary table to list of open derived tables */
if (!derived->is_with_table_recursive_reference())
{
diff --git a/sql/sql_lex.cc b/sql/sql_lex.cc
index e196765..c674495 100644
--- a/sql/sql_lex.cc
+++ b/sql/sql_lex.cc
@@ -721,6 +721,8 @@ void LEX::start(THD *thd_arg)
explain_json= false;
context_analysis_only= 0;
derived_tables= 0;
+ with_cte_resolution= false;
+ only_cte_resolution= false;
safe_to_cache_query= 1;
parsing_options.reset();
empty_field_list_on_rset= 0;
@@ -2388,6 +2390,7 @@ void st_select_lex_unit::init_query()
is_view= false;
with_clause= 0;
with_element= 0;
+ cloned_from= 0;
columns_are_renamed= false;
intersect_mark= NULL;
with_wrapped_tvc= false;
@@ -8266,6 +8269,8 @@ bool LEX::check_main_unit_semantics()
if (unit.set_nest_level(0) ||
unit.check_parameters(first_select_lex()))
return TRUE;
+ if (check_cte_dependencies_and_resolve_references())
+ return TRUE;
return FALSE;
}
@@ -8948,8 +8953,8 @@ void st_select_lex::add_statistics(SELECT_LEX_UNIT *unit)
bool LEX::main_select_push(bool service)
{
DBUG_ENTER("LEX::main_select_push");
- current_select_number= 1;
- builtin_select.select_number= 1;
+ current_select_number= ++thd->lex->stmt_lex->current_select_number;
+ builtin_select.select_number= current_select_number;
builtin_select.is_service_select= service;
if (push_select(&builtin_select))
DBUG_RETURN(TRUE);
diff --git a/sql/sql_lex.h b/sql/sql_lex.h
index 66c44f2..f2fd67c 100644
--- a/sql/sql_lex.h
+++ b/sql/sql_lex.h
@@ -917,6 +917,8 @@ class st_select_lex_unit: public st_select_lex_node {
With_clause *with_clause;
/* With element where this unit is used as the specification (if any) */
With_element *with_element;
+ /* The unit used as a CTE specification from which this unit is cloned */
+ st_select_lex_unit *cloned_from;
/* thread handler */
THD *thd;
/*
@@ -1492,7 +1494,9 @@ class st_select_lex: public st_select_lex_node
}
With_element *get_with_element()
{
- return master_unit()->with_element;
+ return master_unit()->cloned_from ?
+ master_unit()->cloned_from->with_element :
+ master_unit()->with_element;
}
With_element *find_table_def_in_with_clauses(TABLE_LIST *table);
bool check_unrestricted_recursive(bool only_standard_compliant);
@@ -3314,6 +3318,20 @@ struct LEX: public Query_tables_list
*/
uint8 derived_tables;
uint8 context_analysis_only;
+ /*
+ true <=> The parsed fragment requires resolution of references to CTE
+ at the end of parsing. This name resolution process involves searching
+ for possible dependencies between CTE defined in the parsed fragment and
+ detecting possible recursive references.
+ The flag is set to true if the fragment contains CTE definitions.
+ */
+ bool with_cte_resolution;
+ /*
+ true <=> only resolution of references to CTE are required in the parsed
+ fragment, no checking of dependecies between CTE is required.
+ This flag is used only when parsing clones of CTE specifications.
+ */
+ bool only_cte_resolution;
bool local_file;
bool check_exists;
bool autocommit;
@@ -4547,6 +4565,11 @@ struct LEX: public Query_tables_list
select_stack[0]->is_service_select);
}
+ bool check_dependencies_in_with_clauses();
+ bool resolve_references_to_cte_in_hanging_cte();
+ bool check_cte_dependencies_and_resolve_references();
+ bool resolve_references_to_cte(TABLE_LIST *tables,
+ TABLE_LIST **tables_last);
};
diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc
index 209caa9..5ce070a 100644
--- a/sql/sql_parse.cc
+++ b/sql/sql_parse.cc
@@ -3408,9 +3408,6 @@ mysql_execute_command(THD *thd)
thd->get_stmt_da()->opt_clear_warning_info(thd->query_id);
}
- if (check_dependencies_in_with_clauses(thd->lex->with_clauses_list))
- DBUG_RETURN(1);
-
#ifdef HAVE_REPLICATION
if (unlikely(thd->slave_thread))
{
@@ -8150,7 +8147,7 @@ TABLE_LIST *st_select_lex::add_table_to_list(THD *thd,
ptr->is_fqtn= TRUE;
ptr->db= table->db;
}
- else if (lex->copy_db_to(&ptr->db))
+ else if (!lex->with_cte_resolution && lex->copy_db_to(&ptr->db))
DBUG_RETURN(0);
else
ptr->is_fqtn= FALSE;
@@ -8167,7 +8164,9 @@ TABLE_LIST *st_select_lex::add_table_to_list(THD *thd,
}
ptr->table_name= table->table;
- ptr->lock_type= lock_type;
+ ptr->lock_type= lock_type;
+ ptr->mdl_type= mdl_type;
+ ptr->table_options= table_options;
ptr->updating= MY_TEST(table_options & TL_OPTION_UPDATING);
/* TODO: remove TL_OPTION_FORCE_INDEX as it looks like it's not used */
ptr->force_index= MY_TEST(table_options & TL_OPTION_FORCE_INDEX);
@@ -8849,8 +8848,10 @@ void st_select_lex::set_lock_for_tables(thr_lock_type lock_type, bool for_update
{
tables->lock_type= lock_type;
tables->updating= for_update;
- tables->mdl_request.set_type((lock_type >= TL_WRITE_ALLOW_WRITE) ?
- MDL_SHARED_WRITE : MDL_SHARED_READ);
+
+ if (tables->db.str && tables->db.str[0])
+ tables->mdl_request.set_type((lock_type >= TL_WRITE_ALLOW_WRITE) ?
+ MDL_SHARED_WRITE : MDL_SHARED_READ);
}
DBUG_VOID_RETURN;
}
diff --git a/sql/sql_prepare.cc b/sql/sql_prepare.cc
index ee50402..64e138f 100644
--- a/sql/sql_prepare.cc
+++ b/sql/sql_prepare.cc
@@ -2376,9 +2376,6 @@ static bool check_prepared_statement(Prepared_statement *stmt)
if (tables)
thd->get_stmt_da()->opt_clear_warning_info(thd->query_id);
- if (check_dependencies_in_with_clauses(thd->lex->with_clauses_list))
- goto error;
-
if (sql_command_flags[sql_command] & CF_HA_CLOSE)
mysql_ha_rm_tables(thd, tables);
diff --git a/sql/sql_view.cc b/sql/sql_view.cc
index 126db90..1e3c4ce 100644
--- a/sql/sql_view.cc
+++ b/sql/sql_view.cc
@@ -431,12 +431,6 @@ bool mysql_create_view(THD *thd, TABLE_LIST *views,
lex->link_first_table_back(view, link_to_local);
view->open_type= OT_BASE_ONLY;
- if (check_dependencies_in_with_clauses(lex->with_clauses_list))
- {
- res= TRUE;
- goto err;
- }
-
WSREP_TO_ISOLATION_BEGIN(WSREP_MYSQL_DB, NULL, NULL);
/*
@@ -1413,9 +1407,6 @@ bool mysql_make_view(THD *thd, TABLE_SHARE *share, TABLE_LIST *table,
TABLE_LIST *tbl;
Security_context *security_ctx= 0;
- if (check_dependencies_in_with_clauses(thd->lex->with_clauses_list))
- goto err;
-
/*
Check rights to run commands which show underlying tables.
In the optimizer trace we would not like to show trace for
diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy
index 2548dc2..f3e1cf2 100644
--- a/sql/sql_yacc.yy
+++ b/sql/sql_yacc.yy
@@ -770,6 +770,7 @@ Virtual_column_info *add_virtual_expression(THD *thd, Item *expr)
class sp_head *sphead;
class sp_name *spname;
class sp_variable *spvar;
+ class With_element_head *with_element_head;
class With_clause *with_clause;
class Virtual_column_info *virtual_column;
@@ -2188,7 +2189,7 @@ END_OF_INPUT
%type <with_clause> with_clause
-%type <lex_str_ptr> query_name
+%type <with_element_head> with_element_head
%type <lex_str_list> opt_with_column_list
@@ -3336,7 +3337,11 @@ call:
if (unlikely(Lex->call_statement_start(thd, $2)))
MYSQL_YYABORT;
}
- opt_sp_cparam_list {}
+ opt_sp_cparam_list
+ {
+ if (Lex->check_cte_dependencies_and_resolve_references())
+ MYSQL_YYABORT;
+ }
;
/* CALL parameters */
@@ -4185,6 +4190,8 @@ sp_proc_stmt_return:
LEX *lex= Lex;
sp_head *sp= lex->sphead;
Lex->pop_select(); //main select
+ if (Lex->check_cte_dependencies_and_resolve_references())
+ MYSQL_YYABORT;
if (unlikely(sp->m_handler->add_instr_freturn(thd, sp, lex->spcont,
$3, lex)) ||
unlikely(sp->restore_lex(thd)))
@@ -13331,6 +13338,8 @@ do:
{
Lex->insert_list= $3;
Lex->pop_select(); //main select
+ if (Lex->check_cte_dependencies_and_resolve_references())
+ MYSQL_YYABORT;
}
;
@@ -15497,6 +15506,7 @@ with_clause:
if (unlikely(with_clause == NULL))
MYSQL_YYABORT;
lex->derived_tables|= DERIVED_WITH;
+ lex->with_cte_resolution= true;
lex->curr_with_clause= with_clause;
with_clause->add_to_list(Lex->with_clauses_list_last_next);
if (lex->current_select &&
@@ -15524,7 +15534,7 @@ with_list:
with_list_element:
- query_name
+ with_element_head
opt_with_column_list
{
$2= new List<LEX_CSTRING> (Lex->with_column_list);
@@ -15544,6 +15554,7 @@ with_list_element:
if (elem->set_unparsed_spec(thd, spec_start, $7.pos(),
spec_start - query_start))
MYSQL_YYABORT;
+ elem->set_tables_end_pos(lex->query_tables_last);
}
;
@@ -15570,16 +15581,18 @@ with_column_list:
;
-query_name:
+with_element_head:
ident
{
- $$= (LEX_CSTRING *) thd->memdup(&$1, sizeof(LEX_CSTRING));
- if (unlikely($$ == NULL))
+ LEX_CSTRING *name=
+ (LEX_CSTRING *) thd->memdup(&$1, sizeof(LEX_CSTRING));
+ $$= new (thd->mem_root) With_element_head(name);
+ if (unlikely(name == NULL || $$ == NULL))
MYSQL_YYABORT;
+ $$->tables_pos.set_start_pos(Lex->query_tables_last);
}
;
-
/**********************************************************************
** Creating different items.
diff --git a/sql/sql_yacc_ora.yy b/sql/sql_yacc_ora.yy
index 30727a8..f2e112c 100644
--- a/sql/sql_yacc_ora.yy
+++ b/sql/sql_yacc_ora.yy
@@ -248,6 +248,7 @@ void ORAerror(THD *thd, const char *s)
class sp_head *sphead;
class sp_name *spname;
class sp_variable *spvar;
+ class With_element_head *with_element_head;
class With_clause *with_clause;
class Virtual_column_info *virtual_column;
@@ -1689,7 +1690,7 @@ END_OF_INPUT
%type <with_clause> with_clause
-%type <lex_str_ptr> query_name
+%type <with_element_head> with_element_head
%type <lex_str_list> opt_with_column_list
@@ -3138,7 +3139,11 @@ call:
if (unlikely(Lex->call_statement_start(thd, $2)))
MYSQL_YYABORT;
}
- opt_sp_cparam_list {}
+ opt_sp_cparam_list
+ {
+ if (Lex->check_cte_dependencies_and_resolve_references())
+ MYSQL_YYABORT;
+ }
;
/* CALL parameters */
@@ -4092,6 +4097,8 @@ sp_proc_stmt_return:
LEX *lex= Lex;
sp_head *sp= lex->sphead;
Lex->pop_select(); //main select
+ if (Lex->check_cte_dependencies_and_resolve_references())
+ MYSQL_YYABORT;
if (unlikely(sp->m_handler->add_instr_freturn(thd, sp, lex->spcont,
$3, lex)) ||
unlikely(sp->restore_lex(thd)))
@@ -13436,6 +13443,8 @@ do:
{
Lex->insert_list= $3;
Lex->pop_select(); //main select
+ if (Lex->check_cte_dependencies_and_resolve_references())
+ MYSQL_YYABORT;
}
;
@@ -15620,6 +15629,7 @@ with_clause:
if (unlikely(with_clause == NULL))
MYSQL_YYABORT;
lex->derived_tables|= DERIVED_WITH;
+ lex->with_cte_resolution= true;
lex->curr_with_clause= with_clause;
with_clause->add_to_list(Lex->with_clauses_list_last_next);
if (lex->current_select &&
@@ -15647,7 +15657,7 @@ with_list:
with_list_element:
- query_name
+ with_element_head
opt_with_column_list
{
$2= new List<LEX_CSTRING> (Lex->with_column_list);
@@ -15667,6 +15677,7 @@ with_list_element:
if (elem->set_unparsed_spec(thd, spec_start, $7.pos(),
spec_start - query_start))
MYSQL_YYABORT;
+ elem->set_tables_end_pos(lex->query_tables_last);
}
;
@@ -15693,12 +15704,15 @@ with_column_list:
;
-query_name:
+with_element_head:
ident
{
- $$= (LEX_CSTRING *) thd->memdup(&$1, sizeof(LEX_CSTRING));
- if (unlikely($$ == NULL))
+ LEX_CSTRING *name=
+ (LEX_CSTRING *) thd->memdup(&$1, sizeof(LEX_CSTRING));
+ $$= new (thd->mem_root) With_element_head(name);
+ if (unlikely(name == NULL || $$ == NULL))
MYSQL_YYABORT;
+ $$->tables_pos.set_start_pos(Lex->query_tables_last);
}
;
diff --git a/sql/table.cc b/sql/table.cc
index 084b441..67b2b14 100644
--- a/sql/table.cc
+++ b/sql/table.cc
@@ -9144,6 +9144,7 @@ void TABLE_LIST::set_lock_type(THD *thd, enum thr_lock_type lock)
}
}
+
bool TABLE_LIST::is_with_table()
{
return derived && derived->with_element;
diff --git a/sql/table.h b/sql/table.h
index 6073e35..248c239 100644
--- a/sql/table.h
+++ b/sql/table.h
@@ -2139,6 +2139,29 @@ struct vers_select_conds_t
struct LEX;
class Index_hint;
+
+/*
+ @struct TABLE_CHAIN
+ @brief Subchain of global chain of table references
+
+ The structure contains a pointer to the address of the next_global
+ pointer to the first TABLE_LIST objectof the subchain and the address
+ of the next_global pointer to the element right after the last
+ TABLE_LIST object of the subchain. For an empty subchain both pointers
+ have the same value.
+*/
+
+struct TABLE_CHAIN
+{
+ TABLE_CHAIN() {}
+
+ TABLE_LIST **start_pos;
+ TABLE_LIST ** end_pos;
+
+ void set_start_pos(TABLE_LIST **pos) { start_pos= pos; }
+ void set_end_pos(TABLE_LIST **pos) { end_pos= pos; }
+};
+
struct TABLE_LIST
{
TABLE_LIST() {} /* Remove gcc warning */
@@ -2473,6 +2496,20 @@ struct TABLE_LIST
/* call back function for asking handler about caching in query cache */
qc_engine_callback callback_func;
thr_lock_type lock_type;
+
+ /*
+ Two fields below are set during parsing this table reference in the cases
+ when the table reference can be potentially a reference to a CTE table.
+ In this cases the fact that the reference is a reference to a CTE or not
+ will be ascertained at the very end of parsing of the query when referencies
+ to CTE are resolved. For references to CTE and to derived tables no mdl
+ requests are needed while for other table references they are. If a request
+ is possibly postponed the info that allows to issue this request must be
+ saved in 'mdl_type' and 'table_options'.
+ */
+ enum_mdl_type mdl_type;
+ ulong table_options;
+
uint outer_join; /* Which join type */
uint shared; /* Used in multi-upd */
bool updatable; /* VIEW/TABLE can be updated now */
1
0
[Commits] 231c344: MDEV-21603 Crashing SHOW TABLES with derived table in WHERE condition
by IgorBabaev 04 May '21
by IgorBabaev 04 May '21
04 May '21
revision-id: 231c344969e9dfc263066ee0335c1fe39a2c6764 (mariadb-10.2.31-918-g231c344)
parent(s): b1ac251bf1668d5d2472c2c520d6db71fb835065
author: Igor Babaev
committer: Igor Babaev
timestamp: 2021-05-03 21:44:52 -0700
message:
MDEV-21603 Crashing SHOW TABLES with derived table in WHERE condition
---
mysql-test/r/derived.result | 28 ++++++++++++++++++++++++++++
mysql-test/t/derived.test | 25 +++++++++++++++++++++++++
sql/sql_base.cc | 1 -
sql/sql_derived.cc | 6 ------
sql/sql_prepare.cc | 8 ++++----
sql/sql_show.cc | 4 ++--
6 files changed, 59 insertions(+), 13 deletions(-)
diff --git a/mysql-test/r/derived.result b/mysql-test/r/derived.result
index 2106ba5..c374669 100644
--- a/mysql-test/r/derived.result
+++ b/mysql-test/r/derived.result
@@ -1204,5 +1204,33 @@ REPLACE INTO v2 ( SELECT * FROM v4 ) UNION ( SELECT f FROM v2 );
drop view v1,v2,v3,v4;
drop table t1,t2,t3;
#
+# MDEV-21603: materialized derived used in SHOW TABLES
+#
+create table t1 (nm varchar(32), a int);
+insert into t1 values ('1',1),('2',2),('3',3);
+use mysql;
+show tables
+where tables_in_mysql in (select *
+from (select nm from test.t1 group by nm) dt);
+Tables_in_mysql
+show fields from test.t1
+where Field in (select * from (select nm from test.t1 group by nm) dt);
+Field Type Null Key Default Extra
+insert into test.t1 values ('nm',0);
+show fields from test.t1
+where Field in (select * from (select nm from test.t1 group by nm) dt);
+Field Type Null Key Default Extra
+nm varchar(32) YES NULL
+show fields from test.t1
+where Field in
+(select * from (select column_name from information_schema.columns
+where table_name='t1'
+ group by column_name) dt);
+Field Type Null Key Default Extra
+nm varchar(32) YES NULL
+a int(11) YES NULL
+use test;
+drop table t1;
+#
# End of 10.2 tests
#
diff --git a/mysql-test/t/derived.test b/mysql-test/t/derived.test
index 6d9d5e2..fc03acc 100644
--- a/mysql-test/t/derived.test
+++ b/mysql-test/t/derived.test
@@ -1037,5 +1037,30 @@ drop view v1,v2,v3,v4;
drop table t1,t2,t3;
--echo #
+--echo # MDEV-21603: materialized derived used in SHOW TABLES
+--echo #
+
+create table t1 (nm varchar(32), a int);
+insert into t1 values ('1',1),('2',2),('3',3);
+use mysql;
+show tables
+ where tables_in_mysql in (select *
+ from (select nm from test.t1 group by nm) dt);
+show fields from test.t1
+ where Field in (select * from (select nm from test.t1 group by nm) dt);
+insert into test.t1 values ('nm',0);
+show fields from test.t1
+ where Field in (select * from (select nm from test.t1 group by nm) dt);
+
+show fields from test.t1
+ where Field in
+ (select * from (select column_name from information_schema.columns
+ where table_name='t1'
+ group by column_name) dt);
+
+use test;
+drop table t1;
+
+--echo #
--echo # End of 10.2 tests
--echo #
diff --git a/sql/sql_base.cc b/sql/sql_base.cc
index 3403f9e..16689f8 100644
--- a/sql/sql_base.cc
+++ b/sql/sql_base.cc
@@ -4939,7 +4939,6 @@ bool open_normal_and_derived_tables(THD *thd, TABLE_LIST *tables, uint flags,
uint counter;
MDL_savepoint mdl_savepoint= thd->mdl_context.mdl_savepoint();
DBUG_ENTER("open_normal_and_derived_tables");
- DBUG_ASSERT(!thd->fill_derived_tables());
if (open_tables(thd, &tables, &counter, flags, &prelocking_strategy) ||
mysql_handle_derived(thd->lex, dt_phases))
goto end;
diff --git a/sql/sql_derived.cc b/sql/sql_derived.cc
index 5f90f2f..fc7dffe 100644
--- a/sql/sql_derived.cc
+++ b/sql/sql_derived.cc
@@ -70,7 +70,6 @@ bool
mysql_handle_derived(LEX *lex, uint phases)
{
bool res= FALSE;
- THD *thd= lex->thd;
DBUG_ENTER("mysql_handle_derived");
DBUG_PRINT("enter", ("phases: 0x%x", phases));
if (!lex->derived_tables)
@@ -85,8 +84,6 @@ mysql_handle_derived(LEX *lex, uint phases)
break;
if (!(phases & phase_flag))
continue;
- if (phase_flag >= DT_CREATE && !thd->fill_derived_tables())
- break;
for (SELECT_LEX *sl= lex->all_selects_list;
sl && !res;
@@ -169,7 +166,6 @@ bool
mysql_handle_single_derived(LEX *lex, TABLE_LIST *derived, uint phases)
{
bool res= FALSE;
- THD *thd= lex->thd;
uint8 allowed_phases= (derived->is_merged_derived() ? DT_PHASES_MERGE :
DT_PHASES_MATERIALIZE);
DBUG_ENTER("mysql_handle_single_derived");
@@ -192,8 +188,6 @@ mysql_handle_single_derived(LEX *lex, TABLE_LIST *derived, uint phases)
if (phase_flag != DT_PREPARE &&
!(allowed_phases & phase_flag))
continue;
- if (phase_flag >= DT_CREATE && !thd->fill_derived_tables())
- break;
if ((res= (*processors[phase])(lex->thd, lex, derived)))
break;
diff --git a/sql/sql_prepare.cc b/sql/sql_prepare.cc
index 56a3875..a8a6710 100644
--- a/sql/sql_prepare.cc
+++ b/sql/sql_prepare.cc
@@ -1659,7 +1659,7 @@ static int mysql_test_select(Prepared_statement *stmt,
}
if (open_normal_and_derived_tables(thd, tables, MYSQL_OPEN_FORCE_SHARED_MDL,
- DT_INIT | DT_PREPARE | DT_CREATE))
+ DT_INIT | DT_PREPARE))
goto error;
thd->lex->used_tables= 0; // Updated by setup_fields
@@ -1721,7 +1721,7 @@ static bool mysql_test_do_fields(Prepared_statement *stmt,
DBUG_RETURN(TRUE);
if (open_normal_and_derived_tables(thd, tables, MYSQL_OPEN_FORCE_SHARED_MDL,
- DT_INIT | DT_PREPARE | DT_CREATE))
+ DT_INIT | DT_PREPARE))
DBUG_RETURN(TRUE);
DBUG_RETURN(setup_fields(thd, Ref_ptr_array(),
*values, MARK_COLUMNS_NONE, 0, NULL, 0));
@@ -1753,7 +1753,7 @@ static bool mysql_test_set_fields(Prepared_statement *stmt,
if ((tables &&
check_table_access(thd, SELECT_ACL, tables, FALSE, UINT_MAX, FALSE)) ||
open_normal_and_derived_tables(thd, tables, MYSQL_OPEN_FORCE_SHARED_MDL,
- DT_INIT | DT_PREPARE | DT_CREATE))
+ DT_INIT | DT_PREPARE))
goto error;
while ((var= it++))
@@ -1918,7 +1918,7 @@ static bool mysql_test_create_table(Prepared_statement *stmt)
if (open_normal_and_derived_tables(stmt->thd, lex->query_tables,
MYSQL_OPEN_FORCE_SHARED_MDL,
- DT_INIT | DT_PREPARE | DT_CREATE))
+ DT_INIT | DT_PREPARE))
DBUG_RETURN(TRUE);
select_lex->context.resolve_in_select_list= TRUE;
diff --git a/sql/sql_show.cc b/sql/sql_show.cc
index 7023e5f..b562249 100644
--- a/sql/sql_show.cc
+++ b/sql/sql_show.cc
@@ -1528,7 +1528,7 @@ mysqld_list_fields(THD *thd, TABLE_LIST *table_list, const char *wild)
if (open_normal_and_derived_tables(thd, table_list,
MYSQL_OPEN_FORCE_SHARED_HIGH_PRIO_MDL,
- DT_INIT | DT_PREPARE | DT_CREATE))
+ DT_INIT | DT_PREPARE))
DBUG_VOID_RETURN;
table= table_list->table;
@@ -4414,7 +4414,7 @@ fill_schema_table_by_open(THD *thd, bool is_show_fields_or_keys,
MYSQL_OPEN_FORCE_SHARED_HIGH_PRIO_MDL |
(can_deadlock ?
MYSQL_OPEN_FAIL_ON_MDL_CONFLICT : 0)),
- DT_INIT | DT_PREPARE | DT_CREATE));
+ DT_INIT | DT_PREPARE));
/*
Restore old value of sql_command back as it is being looked at in
1
0
03 May '21
revision-id: b36d77964bf125e647554e78cd9822ea9b7438ad (fb-prod8-202101-35-gb36d77964bf)
parent(s): 74a297773bd5b5f79fe0c017e3127aa5ac5ac0b6
author: Sergei Petrunia
committer: Sergei Petrunia
timestamp: 2021-05-03 13:42:37 +0300
message:
Fix rocksdb.unique_sec_rev_cf: do proper cleanup
---
mysql-test/suite/rocksdb/t/unique_sec_rev_cf.test | 1 +
1 file changed, 1 insertion(+)
diff --git a/mysql-test/suite/rocksdb/t/unique_sec_rev_cf.test b/mysql-test/suite/rocksdb/t/unique_sec_rev_cf.test
index d6a8e3d5a1b..8d2e64e5890 100644
--- a/mysql-test/suite/rocksdb/t/unique_sec_rev_cf.test
+++ b/mysql-test/suite/rocksdb/t/unique_sec_rev_cf.test
@@ -3,3 +3,4 @@
let ddl= $MYSQL_TMP_DIR/unique_sec_rev_cf.sql;
--exec sed s/##CF##/" COMMENT 'rev:cf'"/g suite/rocksdb/t/unique_sec.inc > $ddl
--source $ddl
+--remove_file $ddl
1
0
02 May '21
revision-id: 7dd3acb9ae56eeedf82b1de75505d65f594f2e25 (mariadb-10.2.31-925-g7dd3acb9ae5)
parent(s): abe6eb10a65d5c28c221d756357b4e7f392ad13d
author: Sergei Petrunia
committer: Sergei Petrunia
timestamp: 2021-05-02 12:45:26 +0300
message:
MDEV-10674: main.show_explain failed in buildbot
Fix a race condition in the testcase. The testcase assumed that
State='Sending data' means that the thread is already in an
InnoDB lock wait. This is not case, there is a gap between the
state changing to Sending data and execution reaching the point
where it is waiting for a lock.
Use a more precise check instead, through I_S.INNODB_TRX.
---
mysql-test/t/show_explain.test | 9 ++++++++-
1 file changed, 8 insertions(+), 1 deletion(-)
diff --git a/mysql-test/t/show_explain.test b/mysql-test/t/show_explain.test
index 2a87d24cf6d..542701bc42b 100644
--- a/mysql-test/t/show_explain.test
+++ b/mysql-test/t/show_explain.test
@@ -861,7 +861,14 @@ select * from t1 where pk between 10 and 20 for update;
# run SHOW EXPLAIN on a frozen thread
connection default;
let $save_wait_condition= $wait_condition;
-let $wait_condition= select State='Sending data' from information_schema.processlist where id=$thr2;
+let $wait_condition=
+select 1
+from information_schema.INNODB_LOCK_WAITS
+where
+ requesting_trx_id=(select trx_id
+ from information_schema.INNODB_TRX
+ where trx_mysql_thread_id=$thr2);
+
let $thr_default=`select connection_id()`;
--source include/wait_condition.inc
--echo # do: send_eval show explain for thr2;
1
0