mirror of
https://github.com/MariaDB/server.git
synced 2025-03-27 01:18:42 +01:00
MDEV-20632: Recursive CTE cycle detection using CYCLE clause (nonstandard)
Added CYCLE ... RESTRICT (nonstandard) clause to recursive CTE.
This commit is contained in:
parent
a5584b13d1
commit
50c0939166
20 changed files with 619 additions and 147 deletions
|
@ -206,6 +206,8 @@ enum enum_indicator_type
|
|||
itself supports it*/
|
||||
#define LONG_UNIQUE_HASH_FIELD (1<< 30) /* This field will store hash for unique
|
||||
column */
|
||||
#define FIELD_PART_OF_TMP_UNIQUE (1<< 31) /* part of an unique constrain
|
||||
for a tmporary table*/
|
||||
|
||||
#define REFRESH_GRANT (1ULL << 0) /* Refresh grant tables */
|
||||
#define REFRESH_LOG (1ULL << 1) /* Start on new log file */
|
||||
|
|
|
@ -4454,7 +4454,7 @@ create view v1 as
|
|||
with t(a) as (values (2), (1)) select a from t;
|
||||
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(a) as (values (2),(1))select `t`.`a` AS `a` from `t` latin1 latin1_swedish_ci
|
||||
v1 CREATE ALGORITHM=UNDEFINED DEFINER=`root`@`localhost` SQL SECURITY DEFINER VIEW `v1` AS with t(`a`) as (values (2),(1))select `t`.`a` AS `a` from `t` latin1 latin1_swedish_ci
|
||||
select * from v1;
|
||||
a
|
||||
2
|
||||
|
@ -4469,7 +4469,7 @@ order by 1 desc limit 3 )
|
|||
select a from t1 where a=4 union select a from t 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(a) as (values (2),(1) union (values (4),(7)) order by 1 desc limit 3)select `t1`.`a` AS `a` from `t1` where `t1`.`a` = 4 union select `t`.`a` AS `a` from `t` where `t`.`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(`a`) as (values (2),(1) union (values (4),(7)) order by 1 desc limit 3)select `t1`.`a` AS `a` from `t1` where `t1`.`a` = 4 union select `t`.`a` AS `a` from `t` where `t`.`a` = 7 order by `a` desc latin1 latin1_swedish_ci
|
||||
select * from v1;
|
||||
a
|
||||
7
|
||||
|
@ -4484,7 +4484,7 @@ order by 1 desc limit 3 )
|
|||
select a from t1 where a=1 union select a from t 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(a) as ((values (2),(1)) union (values (4),(7) order by 1 desc) order by 1 desc limit 3)select `t1`.`a` AS `a` from `t1` where `t1`.`a` = 1 union select `t`.`a` AS `a` from `t` where `t`.`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(`a`) as ((values (2),(1)) union (values (4),(7) order by 1 desc) order by 1 desc limit 3)select `t1`.`a` AS `a` from `t1` where `t1`.`a` = 1 union select `t`.`a` AS `a` from `t` where `t`.`a` = 7 order by `a` desc latin1 latin1_swedish_ci
|
||||
select * from v1;
|
||||
a
|
||||
7
|
||||
|
|
156
mysql-test/main/cte_cycle.result
Normal file
156
mysql-test/main/cte_cycle.result
Normal file
|
@ -0,0 +1,156 @@
|
|||
#
|
||||
# check errors
|
||||
#
|
||||
WITH RECURSIVE cte AS (
|
||||
SELECT 1 AS a UNION ALL
|
||||
SELECT NULL FROM cte WHERE a IS NOT NULL)
|
||||
CYCLE a, a RESTRICT
|
||||
SELECT * FROM cte;
|
||||
ERROR 42S21: Duplicate column name 'a'
|
||||
WITH RECURSIVE cte AS (
|
||||
SELECT 1 AS a UNION ALL
|
||||
SELECT NULL FROM cte WHERE a IS NOT NULL)
|
||||
CYCLE b RESTRICT
|
||||
SELECT * FROM cte;
|
||||
ERROR 42S22: Unknown column 'b' in 'CYCLE clause'
|
||||
WITH cte AS (
|
||||
SELECT 1 AS a UNION ALL
|
||||
SELECT NULL FROM cte WHERE a IS NOT NULL)
|
||||
CYCLE b RESTRICT
|
||||
SELECT * FROM cte;
|
||||
ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near 'CYCLE b RESTRICT
|
||||
SELECT * FROM cte' at line 4
|
||||
#
|
||||
# A degenerate case
|
||||
#
|
||||
WITH RECURSIVE cte AS (
|
||||
SELECT 1 AS a, 2 as b)
|
||||
CYCLE b RESTRICT
|
||||
SELECT * FROM cte;
|
||||
a b
|
||||
1 2
|
||||
#
|
||||
# A simple case
|
||||
#
|
||||
WITH RECURSIVE cte AS (
|
||||
SELECT 1 AS a, 2 as b UNION ALL
|
||||
SELECT 2, 2 FROM cte WHERE a IS NOT NULL)
|
||||
CYCLE b RESTRICT
|
||||
SELECT * FROM cte;
|
||||
a b
|
||||
1 2
|
||||
#
|
||||
# MDEV-20632 case (with fixed syntax)
|
||||
#
|
||||
create table t1 (from_ int, to_ int);
|
||||
insert into t1 values (1,2), (1,100), (2,3), (3,4), (4,1);
|
||||
WITH RECURSIVE cte (depth, from_, to_) as (
|
||||
SELECT 0,1,1
|
||||
UNION
|
||||
SELECT depth+1, t1.from_, t1.to_
|
||||
FROM t1, cte WHERE t1.from_ = cte.to_
|
||||
) CYCLE from_, to_ RESTRICT
|
||||
select * from cte;
|
||||
depth from_ to_
|
||||
0 1 1
|
||||
1 1 2
|
||||
1 1 100
|
||||
2 2 3
|
||||
3 3 4
|
||||
4 4 1
|
||||
create view v1 as WITH RECURSIVE cte (depth, from_, to_) as (
|
||||
SELECT 0,1,1
|
||||
UNION
|
||||
SELECT depth+1, t1.from_, t1.to_
|
||||
FROM t1, cte WHERE t1.from_ = cte.to_
|
||||
) CYCLE from_, to_ RESTRICT
|
||||
select * from cte;
|
||||
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 recursive cte(`depth`,`from_`,`to_`) as (select 0 AS `depth`,1 AS `from_`,1 AS `to_` union select `cte`.`depth` + 1 AS `depth+1`,`t1`.`from_` AS `from_`,`t1`.`to_` AS `to_` from (`t1` join `cte`) where `t1`.`from_` = `cte`.`to_`) CYCLE `from_`,`to_` RESTRICT select `cte`.`depth` AS `depth`,`cte`.`from_` AS `from_`,`cte`.`to_` AS `to_` from `cte` latin1 latin1_swedish_ci
|
||||
select * from v1;
|
||||
depth from_ to_
|
||||
0 1 1
|
||||
1 1 2
|
||||
1 1 100
|
||||
2 2 3
|
||||
3 3 4
|
||||
4 4 1
|
||||
delete from t1;
|
||||
insert into t1 values (1,2), (1,NULL), (NULL,NULL), (NULL, 1);
|
||||
select * from v1;
|
||||
depth from_ to_
|
||||
0 1 1
|
||||
1 1 2
|
||||
1 1 NULL
|
||||
drop view v1;
|
||||
drop table t1;
|
||||
#
|
||||
# A simple blob case
|
||||
#
|
||||
create table t1 (a int, b text);
|
||||
insert into t1 values (1, "a");
|
||||
WITH RECURSIVE cte AS (
|
||||
SELECT a, b from t1 UNION ALL
|
||||
SELECT a, b FROM cte WHERE a IS NOT NULL)
|
||||
CYCLE b RESTRICT
|
||||
SELECT * FROM cte;
|
||||
a b
|
||||
1 a
|
||||
drop table t1;
|
||||
#
|
||||
# check bit types
|
||||
#
|
||||
create table t1 (from_ bit(3), to_ bit(3));
|
||||
insert into t1 values (1,2), (1,7), (2,3), (3,4), (4,1);
|
||||
WITH RECURSIVE cte (depth, from_, to_) as (
|
||||
SELECT 0,1,1
|
||||
UNION
|
||||
SELECT depth+1, t1.from_, t1.to_
|
||||
FROM t1, cte WHERE t1.from_ = cte.to_
|
||||
) CYCLE from_, to_ RESTRICT
|
||||
select * from cte;
|
||||
depth from_ to_
|
||||
0 1 1
|
||||
1 1 2
|
||||
1 1 7
|
||||
2 2 3
|
||||
3 3 4
|
||||
4 4 1
|
||||
drop table t1;
|
||||
#
|
||||
# check bit types with BLOBs (TEXT)
|
||||
#
|
||||
create table t1 (from_ bit(3), to_ bit(3), load_ text);
|
||||
insert into t1 values (1,2,"A"), (1,7,"A"), (2,3,"A"), (3,4,"A"), (4,1,"A");
|
||||
WITH RECURSIVE cte (depth, from_, to_, load_) as (
|
||||
SELECT 0,1,1,"A"
|
||||
UNION
|
||||
SELECT depth+1, t1.from_, t1.to_, t1.load_
|
||||
FROM t1, cte WHERE t1.from_ = cte.to_
|
||||
) CYCLE from_, to_, load_ RESTRICT
|
||||
select * from cte;
|
||||
depth from_ to_ load_
|
||||
0 1 1 A
|
||||
1 1 2 A
|
||||
1 1 7 A
|
||||
2 2 3 A
|
||||
3 3 4 A
|
||||
4 4 1 A
|
||||
insert into t1 values (4,1,"B");
|
||||
WITH RECURSIVE cte (depth, from_, to_, load_) as (
|
||||
SELECT 0,1,1,"A"
|
||||
UNION
|
||||
SELECT depth+1, t1.from_, t1.to_, t1.load_
|
||||
FROM t1, cte WHERE t1.from_ = cte.to_
|
||||
) CYCLE from_, to_, load_ RESTRICT
|
||||
select * from cte;
|
||||
depth from_ to_ load_
|
||||
0 1 1 A
|
||||
1 1 2 A
|
||||
1 1 7 A
|
||||
2 2 3 A
|
||||
3 3 4 A
|
||||
4 4 1 A
|
||||
4 4 1 B
|
||||
drop table t1;
|
143
mysql-test/main/cte_cycle.test
Normal file
143
mysql-test/main/cte_cycle.test
Normal file
|
@ -0,0 +1,143 @@
|
|||
|
||||
--echo #
|
||||
--echo # check errors
|
||||
--echo #
|
||||
|
||||
--error ER_DUP_FIELDNAME
|
||||
WITH RECURSIVE cte AS (
|
||||
SELECT 1 AS a UNION ALL
|
||||
SELECT NULL FROM cte WHERE a IS NOT NULL)
|
||||
CYCLE a, a RESTRICT
|
||||
SELECT * FROM cte;
|
||||
|
||||
--error ER_BAD_FIELD_ERROR
|
||||
WITH RECURSIVE cte AS (
|
||||
SELECT 1 AS a UNION ALL
|
||||
SELECT NULL FROM cte WHERE a IS NOT NULL)
|
||||
CYCLE b RESTRICT
|
||||
SELECT * FROM cte;
|
||||
|
||||
--error ER_PARSE_ERROR
|
||||
WITH cte AS (
|
||||
SELECT 1 AS a UNION ALL
|
||||
SELECT NULL FROM cte WHERE a IS NOT NULL)
|
||||
CYCLE b RESTRICT
|
||||
SELECT * FROM cte;
|
||||
|
||||
|
||||
--echo #
|
||||
--echo # A degenerate case
|
||||
--echo #
|
||||
|
||||
WITH RECURSIVE cte AS (
|
||||
SELECT 1 AS a, 2 as b)
|
||||
CYCLE b RESTRICT
|
||||
SELECT * FROM cte;
|
||||
|
||||
|
||||
--echo #
|
||||
--echo # A simple case
|
||||
--echo #
|
||||
|
||||
WITH RECURSIVE cte AS (
|
||||
SELECT 1 AS a, 2 as b UNION ALL
|
||||
SELECT 2, 2 FROM cte WHERE a IS NOT NULL)
|
||||
CYCLE b RESTRICT
|
||||
SELECT * FROM cte;
|
||||
|
||||
|
||||
--echo #
|
||||
--echo # MDEV-20632 case (with fixed syntax)
|
||||
--echo #
|
||||
|
||||
create table t1 (from_ int, to_ int);
|
||||
insert into t1 values (1,2), (1,100), (2,3), (3,4), (4,1);
|
||||
|
||||
WITH RECURSIVE cte (depth, from_, to_) as (
|
||||
SELECT 0,1,1
|
||||
UNION
|
||||
SELECT depth+1, t1.from_, t1.to_
|
||||
FROM t1, cte WHERE t1.from_ = cte.to_
|
||||
) CYCLE from_, to_ RESTRICT
|
||||
select * from cte;
|
||||
|
||||
create view v1 as WITH RECURSIVE cte (depth, from_, to_) as (
|
||||
SELECT 0,1,1
|
||||
UNION
|
||||
SELECT depth+1, t1.from_, t1.to_
|
||||
FROM t1, cte WHERE t1.from_ = cte.to_
|
||||
) CYCLE from_, to_ RESTRICT
|
||||
select * from cte;
|
||||
|
||||
show create view v1;
|
||||
|
||||
select * from v1;
|
||||
|
||||
delete from t1;
|
||||
|
||||
insert into t1 values (1,2), (1,NULL), (NULL,NULL), (NULL, 1);
|
||||
|
||||
select * from v1;
|
||||
|
||||
drop view v1;
|
||||
|
||||
drop table t1;
|
||||
|
||||
|
||||
--echo #
|
||||
--echo # A simple blob case
|
||||
--echo #
|
||||
|
||||
create table t1 (a int, b text);
|
||||
insert into t1 values (1, "a");
|
||||
|
||||
WITH RECURSIVE cte AS (
|
||||
SELECT a, b from t1 UNION ALL
|
||||
SELECT a, b FROM cte WHERE a IS NOT NULL)
|
||||
CYCLE b RESTRICT
|
||||
SELECT * FROM cte;
|
||||
|
||||
drop table t1;
|
||||
|
||||
--echo #
|
||||
--echo # check bit types
|
||||
--echo #
|
||||
|
||||
create table t1 (from_ bit(3), to_ bit(3));
|
||||
insert into t1 values (1,2), (1,7), (2,3), (3,4), (4,1);
|
||||
|
||||
WITH RECURSIVE cte (depth, from_, to_) as (
|
||||
SELECT 0,1,1
|
||||
UNION
|
||||
SELECT depth+1, t1.from_, t1.to_
|
||||
FROM t1, cte WHERE t1.from_ = cte.to_
|
||||
) CYCLE from_, to_ RESTRICT
|
||||
select * from cte;
|
||||
drop table t1;
|
||||
|
||||
--echo #
|
||||
--echo # check bit types with BLOBs (TEXT)
|
||||
--echo #
|
||||
|
||||
create table t1 (from_ bit(3), to_ bit(3), load_ text);
|
||||
insert into t1 values (1,2,"A"), (1,7,"A"), (2,3,"A"), (3,4,"A"), (4,1,"A");
|
||||
|
||||
WITH RECURSIVE cte (depth, from_, to_, load_) as (
|
||||
SELECT 0,1,1,"A"
|
||||
UNION
|
||||
SELECT depth+1, t1.from_, t1.to_, t1.load_
|
||||
FROM t1, cte WHERE t1.from_ = cte.to_
|
||||
) CYCLE from_, to_, load_ RESTRICT
|
||||
select * from cte;
|
||||
insert into t1 values (4,1,"B");
|
||||
WITH RECURSIVE cte (depth, from_, to_, load_) as (
|
||||
SELECT 0,1,1,"A"
|
||||
UNION
|
||||
SELECT depth+1, t1.from_, t1.to_, t1.load_
|
||||
FROM t1, cte WHERE t1.from_ = cte.to_
|
||||
) CYCLE from_, to_, load_ RESTRICT
|
||||
select * from cte;
|
||||
|
||||
drop table t1;
|
||||
|
||||
|
|
@ -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(c) as (select `t1`.`a` AS `c` from `t1` where `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 `t1`.`a` AS `c` from `t1` where `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(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 (select `test`.`t1`.`a` AS `c` from `test`.`t1` where `test`.`t1`.`b` >= 'c') `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 (select `test`.`t1`.`a` AS `c` from `test`.`t1` where `test`.`t1`.`b` >= 'c') `r2`) where `r1`.`c` = `r2`.`c` and `r2`.`c` = 4 latin1 latin1_swedish_ci
|
||||
select * from v4;
|
||||
c d
|
||||
4 4
|
||||
|
|
|
@ -699,7 +699,7 @@ id select_type table type possible_keys key key_len ref rows filtered Extra
|
|||
5 RECURSIVE UNION p ALL NULL NULL NULL NULL 12 100.00 Using where; Using join buffer (flat, BNL join)
|
||||
NULL UNION RESULT <union3,4,5> ALL NULL NULL NULL NULL NULL NULL
|
||||
Warnings:
|
||||
Note 1003 with recursive ancestor_couple_ids(h_id,w_id) as (/* select#2 */ 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#3 */ 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#4 */ 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#5 */ 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#1 */ 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#2 */ 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#3 */ 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#4 */ 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#5 */ 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#1 */ 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)
|
||||
|
@ -3093,7 +3093,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(city,legs) as (/* select#2 */ select `test`.`a`.`arrival` AS `city`,1 AS `legs` from `test`.`flights` `a` where `test`.`a`.`departure` = 'Cairo' union /* select#3 */ 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#4 */ select `destinations`.`city` from `destinations` where trigcond(`test`.`b`.`arrival` = `destinations`.`city` or `destinations`.`city` is null) having trigcond(`destinations`.`city` is null))))/* select#1 */ select `destinations`.`city` AS `city`,`destinations`.`legs` AS `legs` from `destinations`
|
||||
Note 1003 with recursive destinations(`city`,`legs`) as (/* select#2 */ select `test`.`a`.`arrival` AS `city`,1 AS `legs` from `test`.`flights` `a` where `test`.`a`.`departure` = 'Cairo' union /* select#3 */ 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#4 */ select `destinations`.`city` from `destinations` where trigcond(`test`.`b`.`arrival` = `destinations`.`city` or `destinations`.`city` is null) having trigcond(`destinations`.`city` is null))))/* select#1 */ select `destinations`.`city` AS `city`,`destinations`.`legs` AS `legs` from `destinations`
|
||||
set standard_compliant_cte=default;
|
||||
drop table flights;
|
||||
#
|
||||
|
@ -3380,7 +3380,7 @@ id select_type table type possible_keys key key_len ref rows filtered Extra
|
|||
3 RECURSIVE UNION <derived2> ALL NULL NULL NULL NULL 2 100.00 Using where
|
||||
NULL UNION RESULT <union2,3> ALL NULL NULL NULL NULL NULL NULL
|
||||
Warnings:
|
||||
Note 1003 with recursive rcte(a) as (/* select#2 */ select 1 AS `a` union /* select#3 */ select cast(`rcte`.`a` + 1 as unsigned) AS `cast(a+1 as unsigned)` from `rcte` where `rcte`.`a` < 10), cte1 as (/* select#4 */ 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#5 */ 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#1 */ select `cte1`.`c1` AS `c1`,`cte2`.`c2` AS `c2` from `cte1` join `cte2`
|
||||
Note 1003 with recursive rcte(`a`) as (/* select#2 */ select 1 AS `a` union /* select#3 */ select cast(`rcte`.`a` + 1 as unsigned) AS `cast(a+1 as unsigned)` from `rcte` where `rcte`.`a` < 10), cte1 as (/* select#4 */ 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#5 */ 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#1 */ 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),
|
||||
|
|
|
@ -2593,7 +2593,7 @@ ERROR 42S22: Unknown column '2' in 'order clause'
|
|||
create view v1 as with t(a) as (values (2), (1)) select a from t;
|
||||
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(a) as (values (2),(1))select `t`.`a` AS `a` from `t` latin1 latin1_swedish_ci
|
||||
v1 CREATE ALGORITHM=UNDEFINED DEFINER=`root`@`localhost` SQL SECURITY DEFINER VIEW `v1` AS with t(`a`) as (values (2),(1))select `t`.`a` AS `a` from `t` latin1 latin1_swedish_ci
|
||||
select * from v1;
|
||||
a
|
||||
2
|
||||
|
|
|
@ -403,7 +403,7 @@ int Item::save_str_value_in_field(Field *field, String *result)
|
|||
|
||||
Item::Item(THD *thd):
|
||||
is_expensive_cache(-1), rsize(0), name(null_clex_str), orig_name(0),
|
||||
is_autogenerated_name(TRUE)
|
||||
common_flags(IS_AUTO_GENERATED_NAME)
|
||||
{
|
||||
DBUG_ASSERT(thd);
|
||||
marker= 0;
|
||||
|
@ -463,7 +463,7 @@ Item::Item(THD *thd, Item *item):
|
|||
with_param(item->with_param),
|
||||
with_window_func(item->with_window_func),
|
||||
with_field(item->with_field),
|
||||
is_autogenerated_name(item->is_autogenerated_name)
|
||||
common_flags(item->common_flags)
|
||||
{
|
||||
next= thd->free_list; // Put in free list
|
||||
thd->free_list= this;
|
||||
|
@ -1117,7 +1117,7 @@ void Item::set_name(THD *thd, const char *str, size_t length, CHARSET_INFO *cs)
|
|||
str++;
|
||||
}
|
||||
}
|
||||
if (str != str_start && !is_autogenerated_name)
|
||||
if (str != str_start && !is_autogenerated_name())
|
||||
{
|
||||
char buff[SAFE_NAME_LEN];
|
||||
|
||||
|
@ -5099,7 +5099,7 @@ static Item** find_field_in_group_list(Item *find_item, ORDER *group_list)
|
|||
|
||||
/* SELECT list element with explicit alias */
|
||||
if ((*(cur_group->item))->name.str && !table_name.str &&
|
||||
!(*(cur_group->item))->is_autogenerated_name &&
|
||||
!(*(cur_group->item))->is_autogenerated_name() &&
|
||||
!lex_string_cmp(system_charset_info,
|
||||
&(*(cur_group->item))->name, &field_name))
|
||||
{
|
||||
|
|
13
sql/item.h
13
sql/item.h
|
@ -624,6 +624,13 @@ class st_select_lex_unit;
|
|||
class Item_func_not;
|
||||
class Item_splocal;
|
||||
|
||||
/* Item::common_flags */
|
||||
/* Indicates that name of this Item autogenerated or set by user */
|
||||
#define IS_AUTO_GENERATED_NAME 1
|
||||
/* Indicates that this item is in CYCLE clause of WITH */
|
||||
#define IS_IN_WITH_CYCLE 2
|
||||
|
||||
|
||||
/**
|
||||
String_copier that sends Item specific warnings.
|
||||
*/
|
||||
|
@ -931,8 +938,9 @@ public:
|
|||
True if any item except Item_sum contains a field. Set during parsing.
|
||||
*/
|
||||
bool with_field;
|
||||
bool is_autogenerated_name; /* indicate was name of this Item
|
||||
autogenerated or set by user */
|
||||
uint8 common_flags;
|
||||
bool is_autogenerated_name()
|
||||
{ return (common_flags & IS_AUTO_GENERATED_NAME); }
|
||||
// alloc & destruct is done as start of select on THD::mem_root
|
||||
Item(THD *thd);
|
||||
/*
|
||||
|
@ -7028,6 +7036,7 @@ public:
|
|||
name= item->name;
|
||||
Type_std_attributes::set(*attr);
|
||||
maybe_null= maybe_null_arg;
|
||||
common_flags= item->common_flags;
|
||||
}
|
||||
|
||||
const Type_handler *type_handler() const
|
||||
|
|
|
@ -2387,7 +2387,7 @@ static bool has_named_parameters(List<Item> *params)
|
|||
List_iterator<Item> it(*params);
|
||||
while ((param= it++))
|
||||
{
|
||||
if (! param->is_autogenerated_name)
|
||||
if (! param->is_autogenerated_name())
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -2633,7 +2633,7 @@ Create_func_arg1::create_func(THD *thd, LEX_CSTRING *name, List<Item> *item_list
|
|||
|
||||
Item *param_1= item_list->pop();
|
||||
|
||||
if (unlikely(! param_1->is_autogenerated_name))
|
||||
if (unlikely(! param_1->is_autogenerated_name()))
|
||||
{
|
||||
my_error(ER_WRONG_PARAMETERS_TO_NATIVE_FCT, MYF(0), name->str);
|
||||
return NULL;
|
||||
|
@ -2660,8 +2660,8 @@ Create_func_arg2::create_func(THD *thd, LEX_CSTRING *name, List<Item> *item_list
|
|||
Item *param_1= item_list->pop();
|
||||
Item *param_2= item_list->pop();
|
||||
|
||||
if (unlikely(!param_1->is_autogenerated_name ||
|
||||
!param_2->is_autogenerated_name))
|
||||
if (unlikely(!param_1->is_autogenerated_name() ||
|
||||
!param_2->is_autogenerated_name()))
|
||||
{
|
||||
my_error(ER_WRONG_PARAMETERS_TO_NATIVE_FCT, MYF(0), name->str);
|
||||
return NULL;
|
||||
|
@ -2689,9 +2689,9 @@ Create_func_arg3::create_func(THD *thd, LEX_CSTRING *name, List<Item> *item_list
|
|||
Item *param_2= item_list->pop();
|
||||
Item *param_3= item_list->pop();
|
||||
|
||||
if (unlikely(!param_1->is_autogenerated_name ||
|
||||
!param_2->is_autogenerated_name ||
|
||||
!param_3->is_autogenerated_name))
|
||||
if (unlikely(!param_1->is_autogenerated_name() ||
|
||||
!param_2->is_autogenerated_name() ||
|
||||
!param_3->is_autogenerated_name()))
|
||||
{
|
||||
my_error(ER_WRONG_PARAMETERS_TO_NATIVE_FCT, MYF(0), name->str);
|
||||
return NULL;
|
||||
|
|
|
@ -4434,9 +4434,10 @@ void subselect_single_select_engine::print(String *str,
|
|||
enum_query_type query_type)
|
||||
{
|
||||
With_clause* with_clause= select_lex->get_with_clause();
|
||||
THD *thd= get_thd();
|
||||
if (with_clause)
|
||||
with_clause->print(str, query_type);
|
||||
select_lex->print(get_thd(), str, query_type);
|
||||
with_clause->print(thd, str, query_type);
|
||||
select_lex->print(thd, str, query_type);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -5767,7 +5767,7 @@ find_field_in_view(THD *thd, TABLE_LIST *table_list,
|
|||
replace. If the item was aliased by the user, set the alias to
|
||||
the replacing item.
|
||||
*/
|
||||
if (*ref && !(*ref)->is_autogenerated_name)
|
||||
if (*ref && !(*ref)->is_autogenerated_name())
|
||||
item->set_name(thd, (*ref)->name);
|
||||
if (register_tree_change)
|
||||
thd->change_item_tree(ref, item);
|
||||
|
@ -5858,7 +5858,7 @@ find_field_in_natural_join(THD *thd, TABLE_LIST *table_ref, const char *name, si
|
|||
replace. If the item was aliased by the user, set the alias to
|
||||
the replacing item.
|
||||
*/
|
||||
if (*ref && !(*ref)->is_autogenerated_name)
|
||||
if (*ref && !(*ref)->is_autogenerated_name())
|
||||
item->set_name(thd, (*ref)->name);
|
||||
if (register_tree_change && arena)
|
||||
thd->restore_active_arena(arena, &backup);
|
||||
|
|
142
sql/sql_cte.cc
142
sql/sql_cte.cc
|
@ -21,6 +21,7 @@
|
|||
#include "sql_view.h" // for make_valid_column_names
|
||||
#include "sql_parse.h"
|
||||
#include "sql_select.h"
|
||||
#include "sql_show.h" // append_definer, append_identifier
|
||||
|
||||
|
||||
/**
|
||||
|
@ -944,9 +945,9 @@ err:
|
|||
false otherwise
|
||||
*/
|
||||
|
||||
bool
|
||||
With_element::rename_columns_of_derived_unit(THD *thd,
|
||||
st_select_lex_unit *unit)
|
||||
bool
|
||||
With_element::process_columns_of_derived_unit(THD *thd,
|
||||
st_select_lex_unit *unit)
|
||||
{
|
||||
if (unit->columns_are_renamed)
|
||||
return false;
|
||||
|
@ -973,7 +974,7 @@ With_element::rename_columns_of_derived_unit(THD *thd,
|
|||
while ((item= it++, name= nm++))
|
||||
{
|
||||
item->set_name(thd, *name);
|
||||
item->is_autogenerated_name= false;
|
||||
item->common_flags&= ~IS_AUTO_GENERATED_NAME;
|
||||
}
|
||||
|
||||
if (arena)
|
||||
|
@ -982,6 +983,43 @@ With_element::rename_columns_of_derived_unit(THD *thd,
|
|||
else
|
||||
make_valid_column_names(thd, select->item_list);
|
||||
|
||||
if (cycle_list)
|
||||
{
|
||||
List_iterator_fast<Item> it(select->item_list);
|
||||
List_iterator_fast<Lex_ident_sys> nm(*cycle_list);
|
||||
List_iterator_fast<Lex_ident_sys> nm_check(*cycle_list);
|
||||
DBUG_ASSERT(cycle_list->elements != 0);
|
||||
while (LEX_CSTRING *name= nm++)
|
||||
{
|
||||
Item *item;
|
||||
/*
|
||||
Check for uniqueness of each element in the cycle list:
|
||||
It's sufficient to check that there is no duplicate of 'name'
|
||||
among the elements that precede it.
|
||||
*/
|
||||
LEX_CSTRING *check;
|
||||
nm_check.rewind();
|
||||
while ((check= nm_check++) && check != name)
|
||||
{
|
||||
if (check->length == name->length &&
|
||||
strncmp(check->str, name->str, name->length) == 0)
|
||||
{
|
||||
my_error(ER_DUP_FIELDNAME, MYF(0), check->str);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
/* Check that 'name' is the name of a column of the processed CTE */
|
||||
while ((item= it++) &&
|
||||
(item->name.length != name->length ||
|
||||
strncmp(item->name.str, name->str, name->length) != 0));
|
||||
if (item == NULL)
|
||||
{
|
||||
my_error(ER_BAD_FIELD_ERROR, MYF(0), name->str, "CYCLE clause");
|
||||
return true;
|
||||
}
|
||||
item->common_flags|= IS_IN_WITH_CYCLE;
|
||||
}
|
||||
}
|
||||
unit->columns_are_renamed= true;
|
||||
|
||||
return false;
|
||||
|
@ -1018,7 +1056,7 @@ bool With_element::prepare_unreferenced(THD *thd)
|
|||
thd->lex->context_analysis_only|= CONTEXT_ANALYSIS_ONLY_DERIVED;
|
||||
if (!spec->prepared &&
|
||||
(spec->prepare(spec->derived, 0, 0) ||
|
||||
rename_columns_of_derived_unit(thd, spec) ||
|
||||
process_columns_of_derived_unit(thd, spec) ||
|
||||
check_duplicate_names(thd, first_sl->item_list, 1)))
|
||||
rc= true;
|
||||
|
||||
|
@ -1394,16 +1432,17 @@ bool st_select_lex::check_subqueries_with_recursive_references()
|
|||
/**
|
||||
@brief
|
||||
Print this with clause
|
||||
|
||||
@param str Where to print to
|
||||
@param query_type The mode of printing
|
||||
|
||||
@details
|
||||
The method prints a string representation of this clause in the
|
||||
string str. The parameter query_type specifies the mode of printing.
|
||||
*/
|
||||
|
||||
void With_clause::print(String *str, enum_query_type query_type)
|
||||
@param thd Thread handle
|
||||
@param str Where to print to
|
||||
@param query_type The mode of printing
|
||||
|
||||
@details
|
||||
The method prints a string representation of this clause in the
|
||||
string str. The parameter query_type specifies the mode of printing.
|
||||
*/
|
||||
|
||||
void With_clause::print(THD *thd, String *str, enum_query_type query_type)
|
||||
{
|
||||
/*
|
||||
Any with clause contains just definitions of CTE tables.
|
||||
|
@ -1420,7 +1459,22 @@ void With_clause::print(String *str, enum_query_type query_type)
|
|||
{
|
||||
if (with_elem != with_list.first)
|
||||
str->append(", ");
|
||||
with_elem->print(str, query_type);
|
||||
with_elem->print(thd, str, query_type);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void list_strlex_print(THD *thd, String *str, List<Lex_ident_sys> *list)
|
||||
{
|
||||
List_iterator_fast<Lex_ident_sys> li(*list);
|
||||
bool first= TRUE;
|
||||
while(Lex_ident_sys *col_name= li++)
|
||||
{
|
||||
if (first)
|
||||
first= FALSE;
|
||||
else
|
||||
str->append(',');
|
||||
append_identifier(thd, str, col_name);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1428,38 +1482,37 @@ void With_clause::print(String *str, enum_query_type query_type)
|
|||
/**
|
||||
@brief
|
||||
Print this with element
|
||||
|
||||
|
||||
@param thd Thread handle
|
||||
@param str Where to print to
|
||||
@param query_type The mode of printing
|
||||
|
||||
@param query_type The mode of printing
|
||||
|
||||
@details
|
||||
The method prints a string representation of this with element in the
|
||||
The method prints a string representation of this with element in the
|
||||
string str. The parameter query_type specifies the mode of printing.
|
||||
*/
|
||||
|
||||
void With_element::print(String *str, enum_query_type query_type)
|
||||
void With_element::print(THD *thd, String *str, enum_query_type query_type)
|
||||
{
|
||||
str->append(query_name);
|
||||
if (column_list.elements)
|
||||
{
|
||||
List_iterator_fast<Lex_ident_sys> li(column_list);
|
||||
str->append('(');
|
||||
for (LEX_CSTRING *col_name= li++; ; )
|
||||
{
|
||||
str->append(col_name);
|
||||
col_name= li++;
|
||||
if (!col_name)
|
||||
{
|
||||
str->append(')');
|
||||
break;
|
||||
}
|
||||
str->append(',');
|
||||
}
|
||||
list_strlex_print(thd, str, &column_list);
|
||||
str->append(')');
|
||||
}
|
||||
str->append(STRING_WITH_LEN(" as "));
|
||||
str->append('(');
|
||||
str->append(STRING_WITH_LEN(" as ("));
|
||||
spec->print(str, query_type);
|
||||
str->append(')');
|
||||
|
||||
if (cycle_list)
|
||||
{
|
||||
DBUG_ASSERT(cycle_list->elements != 0);
|
||||
str->append(STRING_WITH_LEN(" CYCLE "));
|
||||
list_strlex_print(thd, str, cycle_list);
|
||||
str->append(STRING_WITH_LEN(" RESTRICT "));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -1483,3 +1536,26 @@ bool With_element::instantiate_tmp_tables()
|
|||
return false;
|
||||
}
|
||||
|
||||
void With_element::set_cycle_list(List<Lex_ident_sys> *cycle_list_arg)
|
||||
{
|
||||
cycle_list= cycle_list_arg;
|
||||
|
||||
/*
|
||||
If a CTE table with columns c1,...,cn is defined with a cycle
|
||||
clause CYCLE(ci1,...,cik) then no two rows r1 and r2 from the
|
||||
table shall have r1.ci1=r2.ci1 && ... && r1.cik=r2.cik.
|
||||
|
||||
If a cycle clause is used in the specification of a CTE then
|
||||
each UNION ALL at the top level of the specification is interpreted
|
||||
as a UNION DISTINCT over the cycle columns.
|
||||
*/
|
||||
for (st_select_lex *sl= spec->first_select(); sl; sl= sl->next_select())
|
||||
{
|
||||
spec->union_distinct= sl;
|
||||
if (sl != spec->first_select())
|
||||
{
|
||||
sl->distinct= TRUE;
|
||||
sl->with_all_modifier= FALSE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -112,6 +112,7 @@ public:
|
|||
always empty.
|
||||
*/
|
||||
List <Lex_ident_sys> column_list;
|
||||
List <Lex_ident_sys> *cycle_list;
|
||||
/* The query that specifies the table introduced by this with element */
|
||||
st_select_lex_unit *spec;
|
||||
/*
|
||||
|
@ -169,7 +170,7 @@ public:
|
|||
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),
|
||||
query_name(name), column_list(list), cycle_list(0), spec(unit),
|
||||
is_recursive(false), rec_outer_references(0), with_anchor(false),
|
||||
level(0), rec_result(NULL)
|
||||
{ unit->with_element= this; }
|
||||
|
@ -206,7 +207,7 @@ public:
|
|||
|
||||
void inc_references() { references++; }
|
||||
|
||||
bool rename_columns_of_derived_unit(THD *thd, st_select_lex_unit *unit);
|
||||
bool process_columns_of_derived_unit(THD *thd, st_select_lex_unit *unit);
|
||||
|
||||
bool prepare_unreferenced(THD *thd);
|
||||
|
||||
|
@ -214,7 +215,7 @@ public:
|
|||
table_map &unrestricted,
|
||||
table_map &encountered);
|
||||
|
||||
void print(String *str, enum_query_type query_type);
|
||||
void print(THD *thd, String *str, enum_query_type query_type);
|
||||
|
||||
With_clause *get_owner() { return owner; }
|
||||
|
||||
|
@ -259,6 +260,8 @@ public:
|
|||
|
||||
void prepare_for_next_iteration();
|
||||
|
||||
void set_cycle_list(List<Lex_ident_sys> *cycle_list_arg);
|
||||
|
||||
friend class With_clause;
|
||||
};
|
||||
|
||||
|
@ -353,7 +356,7 @@ public:
|
|||
|
||||
void add_unrestricted(table_map map) { unrestricted|= map; }
|
||||
|
||||
void print(String *str, enum_query_type query_type);
|
||||
void print(THD *thd, String *str, enum_query_type query_type);
|
||||
|
||||
friend class With_element;
|
||||
|
||||
|
|
|
@ -816,8 +816,8 @@ bool mysql_derived_prepare(THD *thd, LEX *lex, TABLE_LIST *derived)
|
|||
if ((res= unit->prepare(derived, derived->derived_result, 0)))
|
||||
goto exit;
|
||||
if (derived->with &&
|
||||
(res= derived->with->rename_columns_of_derived_unit(thd, unit)))
|
||||
goto exit;
|
||||
(res= derived->with->process_columns_of_derived_unit(thd, unit)))
|
||||
goto exit;
|
||||
lex->context_analysis_only&= ~CONTEXT_ANALYSIS_ONLY_DERIVED;
|
||||
if ((res= check_duplicate_names(thd, unit->types, 0)))
|
||||
goto exit;
|
||||
|
|
|
@ -3581,7 +3581,7 @@ void LEX::print(String *str, enum_query_type query_type)
|
|||
void st_select_lex_unit::print(String *str, enum_query_type query_type)
|
||||
{
|
||||
if (with_clause)
|
||||
with_clause->print(str, query_type);
|
||||
with_clause->print(thd, str, query_type);
|
||||
for (SELECT_LEX *sl= first_select(); sl; sl= sl->next_select())
|
||||
{
|
||||
if (sl != first_select())
|
||||
|
|
|
@ -18023,14 +18023,27 @@ class Create_tmp_table: public Data_type_statistics
|
|||
ORDER *m_group;
|
||||
bool m_distinct;
|
||||
bool m_save_sum_fields;
|
||||
bool m_with_cycle;
|
||||
ulonglong m_select_options;
|
||||
ha_rows m_rows_limit;
|
||||
uint m_hidden_field_count; // Remove this eventually
|
||||
uint m_group_null_items;
|
||||
uint m_null_count;
|
||||
uint m_hidden_uneven_bit_length;
|
||||
uint m_hidden_null_count;
|
||||
|
||||
// counter for distinct/other fields
|
||||
uint m_field_count[2];
|
||||
// counter for distinct/other fields which can be NULL
|
||||
uint m_null_count[2];
|
||||
// counter for distinct/other blob fields
|
||||
uint m_blobs_count[2];
|
||||
// counter for "tails" of bit fields which do not fit in a byte
|
||||
uint m_uneven_bit[2];
|
||||
|
||||
public:
|
||||
enum counter {distinct, other};
|
||||
/*
|
||||
shows which field we are processing: distinct/other (set in processing
|
||||
cycles)
|
||||
*/
|
||||
counter current_counter;
|
||||
Create_tmp_table(const TMP_TABLE_PARAM *param,
|
||||
ORDER *group, bool distinct, bool save_sum_fields,
|
||||
ulonglong select_options, ha_rows rows_limit)
|
||||
|
@ -18040,14 +18053,21 @@ public:
|
|||
m_group(group),
|
||||
m_distinct(distinct),
|
||||
m_save_sum_fields(save_sum_fields),
|
||||
m_with_cycle(false),
|
||||
m_select_options(select_options),
|
||||
m_rows_limit(rows_limit),
|
||||
m_hidden_field_count(param->hidden_field_count),
|
||||
m_group_null_items(0),
|
||||
m_null_count(0),
|
||||
m_hidden_uneven_bit_length(0),
|
||||
m_hidden_null_count(0)
|
||||
{ }
|
||||
current_counter(other)
|
||||
{
|
||||
m_field_count[Create_tmp_table::distinct]= 0;
|
||||
m_field_count[Create_tmp_table::other]= 0;
|
||||
m_null_count[Create_tmp_table::distinct]= 0;
|
||||
m_null_count[Create_tmp_table::other]= 0;
|
||||
m_blobs_count[Create_tmp_table::distinct]= 0;
|
||||
m_blobs_count[Create_tmp_table::other]= 0;
|
||||
m_uneven_bit[Create_tmp_table::distinct]= 0;
|
||||
m_uneven_bit[Create_tmp_table::other]= 0;
|
||||
}
|
||||
|
||||
void add_field(TABLE *table, Field *field, uint fieldnr, bool force_not_null_cols);
|
||||
|
||||
|
@ -18080,13 +18100,16 @@ void Create_tmp_table::add_field(TABLE *table, Field *field, uint fieldnr, bool
|
|||
}
|
||||
|
||||
if (!(field->flags & NOT_NULL_FLAG))
|
||||
m_null_count++;
|
||||
m_null_count[current_counter]++;
|
||||
|
||||
table->s->reclength+= field->pack_length();
|
||||
|
||||
// Assign it here, before update_data_type_statistics() changes m_blob_count
|
||||
if (field->flags & BLOB_FLAG)
|
||||
{
|
||||
table->s->blob_field[m_blob_count]= fieldnr;
|
||||
m_blobs_count[current_counter]++;
|
||||
}
|
||||
|
||||
table->field[fieldnr]= field;
|
||||
field->field_index= fieldnr;
|
||||
|
@ -18298,6 +18321,7 @@ bool Create_tmp_table::add_fields(THD *thd,
|
|||
DBUG_ASSERT(table->s->blob_fields == 0);
|
||||
|
||||
const bool not_all_columns= !(m_select_options & TMP_TABLE_ALL_COLUMNS);
|
||||
bool distinct_record_structure= m_distinct;
|
||||
uint fieldnr= 0;
|
||||
TABLE_SHARE *share= table->s;
|
||||
Item **copy_func= param->items_to_copy;
|
||||
|
@ -18308,8 +18332,29 @@ bool Create_tmp_table::add_fields(THD *thd,
|
|||
List_iterator_fast<Item> li(fields);
|
||||
Item *item;
|
||||
Field **tmp_from_field= m_from_field;
|
||||
uint uneven_delta;
|
||||
while (!m_with_cycle && (item= li++))
|
||||
if (item->common_flags & IS_IN_WITH_CYCLE)
|
||||
{
|
||||
m_with_cycle= true;
|
||||
/*
|
||||
Following distinct_record_structure is (m_distinct || m_with_cycle)
|
||||
|
||||
Note: distinct_record_structure can be true even if m_distinct is
|
||||
false, for example for incr_table in recursive CTE
|
||||
(see select_union_recursive::create_result_table)
|
||||
*/
|
||||
distinct_record_structure= true;
|
||||
}
|
||||
li.rewind();
|
||||
while ((item=li++))
|
||||
{
|
||||
current_counter= (((param->hidden_field_count < (fieldnr + 1)) &&
|
||||
distinct_record_structure &&
|
||||
(!m_with_cycle ||
|
||||
(item->common_flags & IS_IN_WITH_CYCLE)))?
|
||||
distinct :
|
||||
other);
|
||||
Item::Type type= item->type();
|
||||
if (type == Item::COPY_STR_ITEM)
|
||||
{
|
||||
|
@ -18334,7 +18379,8 @@ bool Create_tmp_table::add_fields(THD *thd,
|
|||
continue;
|
||||
}
|
||||
}
|
||||
if (item->const_item() && (int) m_hidden_field_count <= 0)
|
||||
if (item->const_item() &&
|
||||
param->hidden_field_count < (fieldnr + 1))
|
||||
continue; // We don't have to store this
|
||||
}
|
||||
if (type == Item::SUM_FUNC_ITEM && !m_group && !m_save_sum_fields)
|
||||
|
@ -18351,7 +18397,7 @@ bool Create_tmp_table::add_fields(THD *thd,
|
|||
create_tmp_field(table, arg, ©_func,
|
||||
tmp_from_field, &m_default_field[fieldnr],
|
||||
m_group != 0, not_all_columns,
|
||||
m_distinct, false);
|
||||
distinct_record_structure , false);
|
||||
if (!new_field)
|
||||
goto err; // Should be OOM
|
||||
tmp_from_field++;
|
||||
|
@ -18363,7 +18409,10 @@ bool Create_tmp_table::add_fields(THD *thd,
|
|||
arg= sum_item->set_arg(i, thd, tmp_item);
|
||||
thd->mem_root= &table->mem_root;
|
||||
|
||||
uneven_delta= m_uneven_bit_length;
|
||||
add_field(table, new_field, fieldnr++, param->force_not_null_cols);
|
||||
uneven_delta= m_uneven_bit_length - uneven_delta;
|
||||
m_field_count[current_counter]++;
|
||||
|
||||
if (!(new_field->flags & NOT_NULL_FLAG))
|
||||
{
|
||||
|
@ -18373,6 +18422,8 @@ bool Create_tmp_table::add_fields(THD *thd,
|
|||
*/
|
||||
arg->maybe_null=1;
|
||||
}
|
||||
if (current_counter == distinct)
|
||||
new_field->flags|= FIELD_PART_OF_TMP_UNIQUE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -18440,36 +18491,23 @@ bool Create_tmp_table::add_fields(THD *thd,
|
|||
}
|
||||
tmp_from_field++;
|
||||
|
||||
uneven_delta= m_uneven_bit_length;
|
||||
add_field(table, new_field, fieldnr++, param->force_not_null_cols);
|
||||
uneven_delta= m_uneven_bit_length - uneven_delta;
|
||||
m_field_count[current_counter]++;
|
||||
|
||||
if (item->marker == 4 && item->maybe_null)
|
||||
{
|
||||
m_group_null_items++;
|
||||
new_field->flags|= GROUP_FLAG;
|
||||
}
|
||||
if (current_counter == distinct)
|
||||
new_field->flags|= FIELD_PART_OF_TMP_UNIQUE;
|
||||
}
|
||||
if (!--m_hidden_field_count)
|
||||
{
|
||||
/*
|
||||
This was the last hidden field; Remember how many hidden fields could
|
||||
have null
|
||||
*/
|
||||
m_hidden_null_count= m_null_count;
|
||||
/*
|
||||
We need to update hidden_field_count as we may have stored group
|
||||
functions with constant arguments
|
||||
*/
|
||||
param->hidden_field_count= fieldnr;
|
||||
m_null_count= 0;
|
||||
/*
|
||||
On last hidden field we store uneven bit length in
|
||||
m_hidden_uneven_bit_length and proceed calculation of
|
||||
uneven bits for visible fields into m_uneven_bit_length.
|
||||
*/
|
||||
m_hidden_uneven_bit_length= m_uneven_bit_length;
|
||||
m_uneven_bit_length= 0;
|
||||
}
|
||||
m_uneven_bit[current_counter]+= uneven_delta;
|
||||
}
|
||||
DBUG_ASSERT(fieldnr == m_field_count[other] + m_field_count[distinct]);
|
||||
DBUG_ASSERT(m_blob_count == m_blobs_count[other] + m_blobs_count[distinct]);
|
||||
share->fields= fieldnr;
|
||||
share->blob_fields= m_blob_count;
|
||||
table->field[fieldnr]= 0; // End marker
|
||||
|
@ -18495,8 +18533,12 @@ bool Create_tmp_table::finalize(THD *thd,
|
|||
DBUG_ENTER("Create_tmp_table::finalize");
|
||||
DBUG_ASSERT(table);
|
||||
|
||||
uint hidden_null_pack_length;
|
||||
uint null_pack_length;
|
||||
uint null_pack_length[2];
|
||||
uint null_pack_base[2];
|
||||
uint null_counter[2]= {0, 0};
|
||||
|
||||
uint whole_null_pack_length;
|
||||
|
||||
bool use_packed_rows= false;
|
||||
uchar *pos;
|
||||
uchar *null_flags;
|
||||
|
@ -18547,16 +18589,21 @@ bool Create_tmp_table::finalize(THD *thd,
|
|||
if (share->blob_fields == 0)
|
||||
{
|
||||
/* We need to ensure that first byte is not 0 for the delete link */
|
||||
if (param->hidden_field_count)
|
||||
m_hidden_null_count++;
|
||||
if (m_field_count[other])
|
||||
m_null_count[other]++;
|
||||
else
|
||||
m_null_count++;
|
||||
m_null_count[distinct]++;
|
||||
}
|
||||
hidden_null_pack_length= (m_hidden_null_count + 7 +
|
||||
m_hidden_uneven_bit_length) / 8;
|
||||
null_pack_length= (hidden_null_pack_length +
|
||||
(m_null_count + m_uneven_bit_length + 7) / 8);
|
||||
share->reclength+= null_pack_length;
|
||||
|
||||
null_pack_length[other]= (m_null_count[other] + 7 +
|
||||
m_uneven_bit[other]) / 8;
|
||||
null_pack_base[other]= 0;
|
||||
null_pack_length[distinct]= (m_null_count[distinct] + 7 +
|
||||
m_uneven_bit[distinct]) / 8;
|
||||
null_pack_base[distinct]= null_pack_length[other];
|
||||
whole_null_pack_length= null_pack_length[other] +
|
||||
null_pack_length[distinct];
|
||||
share->reclength+= whole_null_pack_length;
|
||||
if (!share->reclength)
|
||||
share->reclength= 1; // Dummy select
|
||||
/* Use packed rows if there is blobs or a lot of space to gain */
|
||||
|
@ -18580,43 +18627,53 @@ bool Create_tmp_table::finalize(THD *thd,
|
|||
|
||||
recinfo=param->start_recinfo;
|
||||
null_flags=(uchar*) table->record[0];
|
||||
pos=table->record[0]+ null_pack_length;
|
||||
if (null_pack_length)
|
||||
pos=table->record[0]+ whole_null_pack_length;
|
||||
if (whole_null_pack_length)
|
||||
{
|
||||
bzero((uchar*) recinfo,sizeof(*recinfo));
|
||||
recinfo->type=FIELD_NORMAL;
|
||||
recinfo->length=null_pack_length;
|
||||
recinfo->length= whole_null_pack_length;
|
||||
recinfo++;
|
||||
bfill(null_flags,null_pack_length,255); // Set null fields
|
||||
bfill(null_flags, whole_null_pack_length, 255); // Set null fields
|
||||
|
||||
table->null_flags= (uchar*) table->record[0];
|
||||
share->null_fields= m_null_count + m_hidden_null_count;
|
||||
share->null_bytes= share->null_bytes_for_compare= null_pack_length;
|
||||
share->null_fields= m_null_count[other] + m_null_count[distinct];
|
||||
share->null_bytes= share->null_bytes_for_compare= whole_null_pack_length;
|
||||
}
|
||||
|
||||
if (share->blob_fields == 0)
|
||||
{
|
||||
null_counter[(m_field_count[other] ? other : distinct)]++;
|
||||
}
|
||||
m_null_count= (share->blob_fields == 0) ? 1 : 0;
|
||||
m_hidden_field_count= param->hidden_field_count;
|
||||
for (uint i= 0; i < share->fields; i++, recinfo++)
|
||||
{
|
||||
Field *field= table->field[i];
|
||||
uint length;
|
||||
bzero((uchar*) recinfo,sizeof(*recinfo));
|
||||
|
||||
current_counter= ((field->flags & FIELD_PART_OF_TMP_UNIQUE) ?
|
||||
distinct :
|
||||
other);
|
||||
|
||||
if (!(field->flags & NOT_NULL_FLAG))
|
||||
{
|
||||
recinfo->null_bit= (uint8)1 << (m_null_count & 7);
|
||||
recinfo->null_pos= m_null_count/8;
|
||||
field->move_field(pos, null_flags + m_null_count/8,
|
||||
(uint8)1 << (m_null_count & 7));
|
||||
m_null_count++;
|
||||
|
||||
recinfo->null_bit= (uint8)1 << (null_counter[current_counter] & 7);
|
||||
recinfo->null_pos= (null_pack_base[current_counter] +
|
||||
null_counter[current_counter]/8);
|
||||
field->move_field(pos, null_flags + recinfo->null_pos, recinfo->null_bit);
|
||||
null_counter[current_counter]++;
|
||||
}
|
||||
else
|
||||
field->move_field(pos,(uchar*) 0,0);
|
||||
if (field->type() == MYSQL_TYPE_BIT)
|
||||
{
|
||||
/* We have to reserve place for extra bits among null bits */
|
||||
((Field_bit*) field)->set_bit_ptr(null_flags + m_null_count / 8,
|
||||
m_null_count & 7);
|
||||
m_null_count+= (field->field_length & 7);
|
||||
((Field_bit*) field)->set_bit_ptr(null_flags +
|
||||
null_pack_base[current_counter] +
|
||||
null_counter[current_counter]/8,
|
||||
null_counter[current_counter] & 7);
|
||||
null_counter[current_counter]+= (field->field_length & 7);
|
||||
}
|
||||
field->reset();
|
||||
|
||||
|
@ -18655,8 +18712,6 @@ bool Create_tmp_table::finalize(THD *thd,
|
|||
/* Make entry for create table */
|
||||
recinfo->length=length;
|
||||
recinfo->type= field->tmp_engine_column_type(use_packed_rows);
|
||||
if (!--m_hidden_field_count)
|
||||
m_null_count= (m_null_count + 7) & ~7; // move to next byte
|
||||
|
||||
// fix table name in field entry
|
||||
field->set_table_name(&table->alias);
|
||||
|
@ -18777,7 +18832,8 @@ bool Create_tmp_table::finalize(THD *thd,
|
|||
m_group_buff <= param->group_buff + param->group_length);
|
||||
}
|
||||
|
||||
if (m_distinct && share->fields != param->hidden_field_count)
|
||||
if (m_distinct && (share->fields != param->hidden_field_count ||
|
||||
m_with_cycle))
|
||||
{
|
||||
uint i;
|
||||
Field **reg_field;
|
||||
|
@ -18789,7 +18845,7 @@ bool Create_tmp_table::finalize(THD *thd,
|
|||
*/
|
||||
DBUG_PRINT("info",("hidden_field_count: %d", param->hidden_field_count));
|
||||
|
||||
if (share->blob_fields)
|
||||
if (m_blobs_count[distinct])
|
||||
{
|
||||
/*
|
||||
Special mode for index creation in MyISAM used to support unique
|
||||
|
@ -18798,10 +18854,8 @@ bool Create_tmp_table::finalize(THD *thd,
|
|||
*/
|
||||
share->uniques= 1;
|
||||
}
|
||||
null_pack_length-=hidden_null_pack_length;
|
||||
keyinfo->user_defined_key_parts=
|
||||
((share->fields - param->hidden_field_count)+
|
||||
(share->uniques ? MY_TEST(null_pack_length) : 0));
|
||||
keyinfo->user_defined_key_parts= m_field_count[distinct] +
|
||||
(share->uniques ? MY_TEST(null_pack_length[distinct]) : 0);
|
||||
keyinfo->ext_key_parts= keyinfo->user_defined_key_parts;
|
||||
keyinfo->usable_key_parts= keyinfo->user_defined_key_parts;
|
||||
table->distinct= 1;
|
||||
|
@ -18844,11 +18898,11 @@ bool Create_tmp_table::finalize(THD *thd,
|
|||
blobs can distinguish NULL from 0. This extra field is not needed
|
||||
when we do not use UNIQUE indexes for blobs.
|
||||
*/
|
||||
if (null_pack_length && share->uniques)
|
||||
if (null_pack_length[distinct] && share->uniques)
|
||||
{
|
||||
m_key_part_info->null_bit=0;
|
||||
m_key_part_info->offset=hidden_null_pack_length;
|
||||
m_key_part_info->length=null_pack_length;
|
||||
m_key_part_info->offset= null_pack_base[distinct];
|
||||
m_key_part_info->length= null_pack_length[distinct];
|
||||
m_key_part_info->field= new Field_string(table->record[0],
|
||||
(uint32) m_key_part_info->length,
|
||||
(uchar*) 0,
|
||||
|
@ -18866,8 +18920,10 @@ bool Create_tmp_table::finalize(THD *thd,
|
|||
/* Create a distinct key over the columns we are going to return */
|
||||
for (i= param->hidden_field_count, reg_field= table->field + i ;
|
||||
i < share->fields;
|
||||
i++, reg_field++, m_key_part_info++)
|
||||
i++, reg_field++)
|
||||
{
|
||||
if (!((*reg_field)->flags & FIELD_PART_OF_TMP_UNIQUE))
|
||||
continue;
|
||||
m_key_part_info->field= *reg_field;
|
||||
(*reg_field)->flags |= PART_KEY_FLAG;
|
||||
if (m_key_part_info == keyinfo->key_part)
|
||||
|
@ -18904,6 +18960,8 @@ bool Create_tmp_table::finalize(THD *thd,
|
|||
(ha_base_keytype) m_key_part_info->type == HA_KEYTYPE_VARTEXT1 ||
|
||||
(ha_base_keytype) m_key_part_info->type == HA_KEYTYPE_VARTEXT2) ?
|
||||
0 : FIELDFLAG_BINARY;
|
||||
|
||||
m_key_part_info++;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -27481,7 +27539,7 @@ void st_select_lex::print(THD *thd, String *str, enum_query_type query_type)
|
|||
else
|
||||
str->append(',');
|
||||
|
||||
if (is_subquery_function() && item->is_autogenerated_name)
|
||||
if (is_subquery_function() && item->is_autogenerated_name())
|
||||
{
|
||||
/*
|
||||
Do not print auto-generated aliases in subqueries. It has no purpose
|
||||
|
|
|
@ -257,6 +257,8 @@ int select_union_recursive::send_data(List<Item> &values)
|
|||
write_err != HA_ERR_FOUND_DUPP_UNIQUE)
|
||||
{
|
||||
int err;
|
||||
DBUG_ASSERT(incr_table->s->reclength == table->s->reclength ||
|
||||
incr_table->s->reclength == table->s->reclength - MARIA_UNIQUE_HASH_LENGTH);
|
||||
if ((err= incr_table->file->ha_write_tmp_row(table->record[0])))
|
||||
{
|
||||
bool is_duplicate;
|
||||
|
@ -1561,7 +1563,7 @@ bool st_select_lex_unit::prepare(TABLE_LIST *derived_arg,
|
|||
{
|
||||
if (with_element)
|
||||
{
|
||||
if (with_element->rename_columns_of_derived_unit(thd, this))
|
||||
if (with_element->process_columns_of_derived_unit(thd, this))
|
||||
goto err;
|
||||
if (check_duplicate_names(thd, sl->item_list, 0))
|
||||
goto err;
|
||||
|
|
|
@ -138,7 +138,7 @@ bool check_duplicate_names(THD *thd, List<Item> &item_list, bool gen_unique_view
|
|||
Item *check;
|
||||
/* treat underlying fields like set by user names */
|
||||
if (item->real_item()->type() == Item::FIELD_ITEM)
|
||||
item->is_autogenerated_name= FALSE;
|
||||
item->common_flags&= ~IS_AUTO_GENERATED_NAME;
|
||||
itc.rewind();
|
||||
while ((check= itc++) && check != item)
|
||||
{
|
||||
|
@ -146,9 +146,9 @@ bool check_duplicate_names(THD *thd, List<Item> &item_list, bool gen_unique_view
|
|||
{
|
||||
if (!gen_unique_view_name)
|
||||
goto err;
|
||||
if (item->is_autogenerated_name)
|
||||
if (item->is_autogenerated_name())
|
||||
make_unique_view_field_name(thd, item, item_list, item);
|
||||
else if (check->is_autogenerated_name)
|
||||
else if (check->is_autogenerated_name())
|
||||
make_unique_view_field_name(thd, check, item_list, item);
|
||||
else
|
||||
goto err;
|
||||
|
@ -180,7 +180,7 @@ void make_valid_column_names(THD *thd, List<Item> &item_list)
|
|||
|
||||
for (uint column_no= 1; (item= it++); column_no++)
|
||||
{
|
||||
if (!item->is_autogenerated_name || !check_column_name(item->name.str))
|
||||
if (!item->is_autogenerated_name() || !check_column_name(item->name.str))
|
||||
continue;
|
||||
name_len= my_snprintf(buff, NAME_LEN, "Name_exp_%u", column_no);
|
||||
item->orig_name= item->name.str;
|
||||
|
@ -566,7 +566,7 @@ bool mysql_create_view(THD *thd, TABLE_LIST *views,
|
|||
while ((item= it++, name= nm++))
|
||||
{
|
||||
item->set_name(thd, *name);
|
||||
item->is_autogenerated_name= FALSE;
|
||||
item->common_flags&= ~IS_AUTO_GENERATED_NAME;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1749,6 +1749,7 @@ End SQL_MODE_ORACLE_SPECIFIC */
|
|||
comma_separated_ident_list
|
||||
opt_with_column_list
|
||||
with_column_list
|
||||
opt_cycle
|
||||
|
||||
%type <vers_range_unit> opt_history_unit
|
||||
%type <vers_history_point> history_point
|
||||
|
@ -9076,7 +9077,7 @@ select_item:
|
|||
if (unlikely(Lex->sql_command == SQLCOM_CREATE_VIEW &&
|
||||
check_column_name($4.str)))
|
||||
my_yyabort_error((ER_WRONG_COLUMN_NAME, MYF(0), $4.str));
|
||||
$2->is_autogenerated_name= FALSE;
|
||||
$2->common_flags&= ~IS_AUTO_GENERATED_NAME;
|
||||
$2->set_name(thd, $4);
|
||||
}
|
||||
else if (!$2->name.str || $2->name.str == item_empty_name)
|
||||
|
@ -10638,7 +10639,7 @@ udf_expr:
|
|||
*/
|
||||
if ($4.str)
|
||||
{
|
||||
$2->is_autogenerated_name= FALSE;
|
||||
$2->common_flags&= ~IS_AUTO_GENERATED_NAME;
|
||||
$2->set_name(thd, $4);
|
||||
}
|
||||
/*
|
||||
|
@ -14749,7 +14750,7 @@ with_list:
|
|||
with_list_element:
|
||||
query_name
|
||||
opt_with_column_list
|
||||
AS '(' query_expression ')'
|
||||
AS '(' query_expression ')' opt_cycle
|
||||
{
|
||||
LEX *lex= thd->lex;
|
||||
const char *query_start= lex->sphead ? lex->sphead->m_tmp_query
|
||||
|
@ -14761,9 +14762,30 @@ with_list_element:
|
|||
if (elem->set_unparsed_spec(thd, spec_start, $6.pos(),
|
||||
spec_start - query_start))
|
||||
MYSQL_YYABORT;
|
||||
if ($7)
|
||||
{
|
||||
elem->set_cycle_list($7);
|
||||
}
|
||||
}
|
||||
;
|
||||
|
||||
opt_cycle:
|
||||
/* empty */
|
||||
{ $$= NULL; }
|
||||
|
|
||||
CYCLE_SYM
|
||||
{
|
||||
if (!Lex->curr_with_clause->with_recursive)
|
||||
{
|
||||
thd->parse_error(ER_SYNTAX_ERROR, $1.pos());
|
||||
}
|
||||
}
|
||||
comma_separated_ident_list RESTRICT
|
||||
{
|
||||
$$= $3;
|
||||
}
|
||||
;
|
||||
|
||||
|
||||
opt_with_column_list:
|
||||
/* empty */
|
||||
|
|
Loading…
Add table
Reference in a new issue