revision-id: 71be1731fe2f6546da2e0775904a6a7f9f0d7416 (mariadb-10.5.2-477-g71be1731fe2)
parent(s): b1b756021476a8b2069dee574a1df12e848f9232
author: Sergei Petrunia
committer: Sergei Petrunia
timestamp: 2021-03-15 14:58:04 +0300
message:
tmp
---
mysql-test/main/query_cache.result | 8 ++++----
mysql-test/main/query_cache.test | 14 +++++++-------
2 files changed, 11 insertions(+), 11 deletions(-)
diff --git a/mysql-test/main/query_cache.result b/mysql-test/main/query_cache.result
index cc53791cb14..849e58d7464 100644
--- a/mysql-test/main/query_cache.result
+++ b/mysql-test/main/query_cache.result
@@ -2208,10 +2208,6 @@ Qcache_queries_in_cache 0
DROP FUNCTION foo;
drop table t1;
#
-# MDEV-24858 SIGABRT in DbugExit from my_malloc in Query_cache::init_cache Regression
-#
-set global Query_cache_size=18446744073709547520;
-#
# MDEV-22301 JSON_TABLE: Queries are not inserted into query cache.
#
create table t1 (a text);
@@ -2227,6 +2223,10 @@ SHOW STATUS LIKE 'Qcache_inserts';
Variable_name Value
Qcache_inserts 1
drop table t1;
+#
+# MDEV-24858 SIGABRT in DbugExit from my_malloc in Query_cache::init_cache Regression
+#
+set global Query_cache_size=18446744073709547520;
restore defaults
SET GLOBAL query_cache_type= default;
SET GLOBAL query_cache_size=@save_query_cache_size;
diff --git a/mysql-test/main/query_cache.test b/mysql-test/main/query_cache.test
index 0d747b97acc..49c069da653 100644
--- a/mysql-test/main/query_cache.test
+++ b/mysql-test/main/query_cache.test
@@ -1800,13 +1800,6 @@ show status like "Qcache_queries_in_cache";
DROP FUNCTION foo;
drop table t1;
---echo #
---echo # MDEV-24858 SIGABRT in DbugExit from my_malloc in Query_cache::init_cache Regression
---echo #
---disable_warnings
-set global Query_cache_size=18446744073709547520;
---enable_warnings
-
--echo #
--echo # MDEV-22301 JSON_TABLE: Queries are not inserted into query cache.
--echo #
@@ -1818,6 +1811,13 @@ select * from t1, json_table(t1.a, '$' columns (f varchar(20) path '$.a')) as jt
SHOW STATUS LIKE 'Qcache_inserts';
drop table t1;
+--echo #
+--echo # MDEV-24858 SIGABRT in DbugExit from my_malloc in Query_cache::init_cache Regression
+--echo #
+--disable_warnings
+set global Query_cache_size=18446744073709547520;
+--enable_warnings
+
--echo restore defaults
SET GLOBAL query_cache_type= default;
SET GLOBAL query_cache_size=@save_query_cache_size;
1
0
[Commits] 59a3c46b7b2: MDEV-17399: JSON_TABLE: Fix the problem with cross-nested-join dependency
by psergey 15 Mar '21
by psergey 15 Mar '21
15 Mar '21
revision-id: 59a3c46b7b2dfecc04679b0aaaa5a7ee7fc1de1e (mariadb-10.5.2-432-g59a3c46b7b2)
parent(s): 687b7cc7f7ef1f842a080a2d5d57eb5e24e26f42
author: Sergei Petrunia
committer: Sergei Petrunia
timestamp: 2021-03-15 14:22:21 +0300
message:
MDEV-17399: JSON_TABLE: Fix the problem with cross-nested-join dependency
Fix for for the problem with
- Cross-outer-join dependency
- dead-end join prefix
- join order pruning
See the comments in the patch for detailed description
---
mysql-test/suite/json/r/json_table.result | 31 +++++++-
mysql-test/suite/json/t/json_table.test | 33 ++++++++-
sql/json_table.cc | 114 ++++++++++++++++++++++++++++++
sql/json_table.h | 3 +
sql/sql_select.cc | 3 +
5 files changed, 182 insertions(+), 2 deletions(-)
diff --git a/mysql-test/suite/json/r/json_table.result b/mysql-test/suite/json/r/json_table.result
index 6a8a9fb08ec..ffe88a7721f 100644
--- a/mysql-test/suite/json/r/json_table.result
+++ b/mysql-test/suite/json/r/json_table.result
@@ -457,5 +457,34 @@ seq0 name seq1 color seq2 size seq3 price
2 Y NULL NULL 2 11 NULL NULL
2 Y 1 red NULL NULL NULL NULL
#
-# End of 10.5 tests
+# Test for the problem with
+# - Cross-outer-join dependency
+# - dead-end join prefix
+# - join order pruning
+#
+create table t20 (a int not null);
+create table t21 (a int not null primary key, js varchar(100));
+insert into t20 select seq from seq_1_to_100;
+insert into t21 select a, '{"a":100}' from t20;
+create table t31(a int);
+create table t32(b int);
+insert into t31 values (1);
+insert into t32 values (1);
+explain
+select
+t20.a, jt1.ab
+from
+t20
+left join t21 on t20.a=t21.a
+join
+(t31 left join (t32 join JSON_TABLE(t21.js,'$' COLUMNS (ab INT PATH '$.a')) AS jt1) on t31.a<3);
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t31 system NULL NULL NULL NULL 1
+1 SIMPLE t20 ALL NULL NULL NULL NULL 100
+1 SIMPLE t21 eq_ref PRIMARY PRIMARY 4 test.t20.a 1
+1 SIMPLE t32 ALL NULL NULL NULL NULL 1 Using where
+1 SIMPLE jt1 ALL NULL NULL NULL NULL 40 Table function: json_table
+drop table t20,t21,t31,t32;
+#
+# End of 10.6 tests
#
diff --git a/mysql-test/suite/json/t/json_table.test b/mysql-test/suite/json/t/json_table.test
index 071642fc737..03cc19ad72c 100644
--- a/mysql-test/suite/json/t/json_table.test
+++ b/mysql-test/suite/json/t/json_table.test
@@ -1,3 +1,5 @@
+--source include/have_sequence.inc
+
select * from json_table('[{"a": 1, "b": [11,111]}, {"a": 2, "b": [22,222]}]', '$[*]' COLUMNS( a INT PATH '$.a')) as tt;
select * from JSON_TABLE( '[ {"a": 1, "b": [11,111]}, {"a": 2, "b": [22,222]}, {"a":3}]', '$[*]' COLUMNS( a INT PATH '$.a', NESTED PATH '$.b[*]' COLUMNS (b INT PATH '$'))) as jt;
@@ -342,5 +344,34 @@ json_table(
) as T order by seq0, name;
--echo #
---echo # End of 10.5 tests
+--echo # Test for the problem with
+--echo # - Cross-outer-join dependency
+--echo # - dead-end join prefix
+--echo # - join order pruning
+--echo #
+
+create table t20 (a int not null);
+create table t21 (a int not null primary key, js varchar(100));
+
+insert into t20 select seq from seq_1_to_100;
+insert into t21 select a, '{"a":100}' from t20;
+
+create table t31(a int);
+create table t32(b int);
+insert into t31 values (1);
+insert into t32 values (1);
+
+explain
+select
+ t20.a, jt1.ab
+from
+ t20
+ left join t21 on t20.a=t21.a
+ join
+ (t31 left join (t32 join JSON_TABLE(t21.js,'$' COLUMNS (ab INT PATH '$.a')) AS jt1) on t31.a<3);
+
+drop table t20,t21,t31,t32;
+
+--echo #
+--echo # End of 10.6 tests
--echo #
diff --git a/sql/json_table.cc b/sql/json_table.cc
index 1c44e370195..701aeff5020 100644
--- a/sql/json_table.cc
+++ b/sql/json_table.cc
@@ -1284,3 +1284,117 @@ void Table_function_json_table::fix_after_pullout(TABLE_LIST *sql_table,
}
+/*
+ @brief
+ Recursively make all tables in the join_list also depend on deps.
+*/
+
+static void add_extra_deps(List<TABLE_LIST> *join_list, table_map deps)
+{
+ TABLE_LIST *table;
+ List_iterator<TABLE_LIST> li(*join_list);
+ while ((table= li++))
+ {
+ table->dep_tables |= deps;
+ NESTED_JOIN *nested_join;
+ if ((nested_join= table->nested_join))
+ {
+ // set the deps inside, too
+ add_extra_deps(&nested_join->join_list, deps);
+ }
+ }
+}
+
+
+/*
+ @brief
+ Add extra dependencies implied by table functions so that the join
+ optimizer does not construct "dead-end" join prefixes.
+
+ @detail
+ There are two kinds of limitations on join order:
+ 1A. Outer joins require that inner tables follow outer.
+ 1B. Tables within a join nest must be present in the join order
+ "without interleaving". See check_interleaving_with_nj for details.
+
+ 2. Table function argument may refer to *any* table that precedes the
+ current table in the query text. The table maybe outside of the current
+ nested join and/or inside another nested join.
+
+ @example
+
+ select ...
+ from
+ t20 left join t21 on t20.a=t21.a
+ join
+ (t31 left join (t32 join
+ JSON_TABLE(t21.js,
+ '$' COLUMNS (ab INT PATH '$.a')) AS jt
+ ) on t31.a<3
+ )
+
+ Here, jt's argument refers to t21.
+
+ Table dependencies are:
+ t21 -> t20
+ t32 -> t31
+ jt -> t21 t31 (also indirectly depends on t20 through t21)
+
+ This allows to construct a "dead-end" join prefix, like:
+
+ t31, t32
+
+ Here, "no interleaving" rule requires the next table to be jt, but we
+ can't add it, because it depends on t21 which is not in the join prefix.
+
+ @end example
+
+ Dead-end join prefixes do not work with join prefix pruning done for
+ @@optimizer_prune_level: it is possible that all non-dead-end prefixes are
+ pruned away.
+
+ The solution is as follows: if there is an outer join that contains
+ (directly on indirectly) a table function JT which has a reference JREF
+ outside of the outer join:
+
+ left join ( T_I ... json_table(JREF, ...) as JT ...)
+
+ then make *all* tables T_I also dependent on outside references in JREF.
+ This way, the optimizer will put table T_I into the join prefix only when
+ JT can be put there as well, and "dead-end" prefixes will not be built.
+
+ @param join_list List of tables to process. Initial invocation should
+ supply the JOIN's top-level table list.
+ @param nest_tables Bitmap of all tables in the join list.
+
+ @return Bitmap of all outside references that tables in join_list have
+*/
+
+table_map add_table_function_dependencies(List<TABLE_LIST> *join_list,
+ table_map nest_tables)
+{
+ TABLE_LIST *table;
+ table_map res= 0;
+ List_iterator<TABLE_LIST> li(*join_list);
+
+ // Recursively compute extra dependencies
+ while ((table= li++))
+ {
+ NESTED_JOIN *nested_join;
+ if ((nested_join= table->nested_join))
+ {
+ res |= add_table_function_dependencies(&nested_join->join_list,
+ nested_join->used_tables);
+ }
+ else if (table->table_function)
+ res |= table->dep_tables;
+ }
+ res= res & ~nest_tables & ~PSEUDO_TABLE_BITS;
+ // Then, make all "peers" have them:
+ if (res)
+ add_extra_deps(join_list, res);
+
+ return res;
+}
+
+
diff --git a/sql/json_table.h b/sql/json_table.h
index bab4ef9c185..8988c05354a 100644
--- a/sql/json_table.h
+++ b/sql/json_table.h
@@ -252,5 +252,8 @@ class Table_function_json_table : public Sql_alloc
TABLE *create_table_for_function(THD *thd, TABLE_LIST *sql_table);
+table_map add_table_function_dependencies(List<TABLE_LIST> *join_list,
+ table_map nest_tables);
+
#endif /* JSON_TABLE_INCLUDED */
diff --git a/sql/sql_select.cc b/sql/sql_select.cc
index 2acb6f9443a..e9ae46e1a5e 100644
--- a/sql/sql_select.cc
+++ b/sql/sql_select.cc
@@ -1921,6 +1921,9 @@ JOIN::optimize_inner()
/* Convert all outer joins to inner joins if possible */
conds= simplify_joins(this, join_list, conds, TRUE, FALSE);
+
+ add_table_function_dependencies(join_list, table_map(-1));
+
if (thd->is_error() || select_lex->save_leaf_tables(thd))
{
if (arena)
1
0
[Commits] 6c90c78: MDEV-25112 MIN/MAX aggregation over an indexed column may return wrong result
by IgorBabaev 15 Mar '21
by IgorBabaev 15 Mar '21
15 Mar '21
revision-id: 6c90c78531c0f601ba58f29e82e7cc3e59836966 (mariadb-10.2.31-785-g6c90c78)
parent(s): 90780bb5a949d5e27b39df4adaa1d7b1ad98948e
author: Igor Babaev
committer: Igor Babaev
timestamp: 2021-03-14 23:38:25 -0700
message:
MDEV-25112 MIN/MAX aggregation over an indexed column may return wrong result
If a query with implicit grouping contains in MIN/MAX set function in the
select list over a column that is a part of an index then the query
might be subject to MIN/MAX optimization. With this optimization the
server performs a look-up into an index, fetches a value of the column C
used in the MIN/MAX function and substitute the MIN/MAX expression for this
value. This allows to eliminate the table containing C from further join
processing. In order the optimization to be applied the WHERE condition
must be a conjunction of simple equality/inequality predicates or/and
BETWEEN predicates.
The bug fixed in the patch resulted in fetching a wrong value from the
index used for MIN/MAX optimization. It may happened when a BETWEEN
predicate containg the MIN/MAX value followed a strict inequality.
Approved by dmitry.shulga(a)mariadb.com
---
mysql-test/r/func_group.result | 34 ++++++++++++++++++++++++++++++++++
mysql-test/t/func_group.test | 28 ++++++++++++++++++++++++++++
sql/opt_sum.cc | 3 +++
3 files changed, 65 insertions(+)
diff --git a/mysql-test/r/func_group.result b/mysql-test/r/func_group.result
index 0f9fdef..fb07ba7 100644
--- a/mysql-test/r/func_group.result
+++ b/mysql-test/r/func_group.result
@@ -2458,3 +2458,37 @@ select count(*)+sleep(0) from t1;
count(*)+sleep(0)
2
drop table t1;
+#
+# MDEV-25112: MIN/MAX optimization for query containing BETWEEN in WHERE
+#
+create table t1 (a int) engine=myisam;
+insert into t1 values (267), (273), (287), (303), (308);
+select max(a) from t1 where a < 303 and (a between 267 AND 287);
+max(a)
+287
+explain select max(a) from t1 where a < 303 and (a between 267 AND 287);
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 ALL NULL NULL NULL NULL 5 Using where
+select min(a) from t1 where a > 267 and (a between 273 AND 303);
+min(a)
+273
+explain select min(a) from t1 where a > 267 and (a between 273 AND 303);
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 ALL NULL NULL NULL NULL 5 Using where
+create index idx on t1(a);
+select max(a) from t1 where a < 303 and (a between 267 AND 287);
+max(a)
+287
+explain select max(a) from t1 where a < 303 and (a between 267 AND 287);
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE NULL NULL NULL NULL NULL NULL NULL Select tables optimized away
+select min(a) from t1 where a > 267 and (a between 273 AND 303);
+min(a)
+273
+explain select min(a) from t1 where a > 267 and (a between 273 AND 303);
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE NULL NULL NULL NULL NULL NULL NULL Select tables optimized away
+drop table t1;
+#
+# End of 10.2 tests
+#
diff --git a/mysql-test/t/func_group.test b/mysql-test/t/func_group.test
index 8bbc9e6..d7da9fe 100644
--- a/mysql-test/t/func_group.test
+++ b/mysql-test/t/func_group.test
@@ -1703,4 +1703,32 @@ select count(*)+sleep(0) from t1;
drop table t1;
+--echo #
+--echo # MDEV-25112: MIN/MAX optimization for query containing BETWEEN in WHERE
+--echo #
+
+create table t1 (a int) engine=myisam;
+insert into t1 values (267), (273), (287), (303), (308);
+
+let $q1=
+select max(a) from t1 where a < 303 and (a between 267 AND 287);
+let $q2=
+select min(a) from t1 where a > 267 and (a between 273 AND 303);
+
+eval $q1;
+eval explain $q1;
+eval $q2;
+eval explain $q2;
+
+create index idx on t1(a);
+eval $q1;
+eval explain $q1;
+eval $q2;
+eval explain $q2;
+
+drop table t1;
+
+--echo #
+--echo # End of 10.2 tests
+--echo #
diff --git a/sql/opt_sum.cc b/sql/opt_sum.cc
index 02b95da..868d8b2 100644
--- a/sql/opt_sum.cc
+++ b/sql/opt_sum.cc
@@ -830,7 +830,10 @@ static bool matching_cond(bool max_fl, TABLE_REF *ref, KEY *keyinfo,
if (is_field_part)
{
if (between || eq_type)
+ {
*range_fl&= ~(NO_MAX_RANGE | NO_MIN_RANGE);
+ *range_fl&= ~(max_fl ? NEAR_MAX : NEAR_MIN);
+ }
else
{
*range_fl&= ~(max_fl ? NO_MAX_RANGE : NO_MIN_RANGE);
1
0
[Commits] 9892a3b4fda: JSON_TABLE: undo the changes that are no longer relevant
by psergey 12 Mar '21
by psergey 12 Mar '21
12 Mar '21
revision-id: 9892a3b4fda4c34d35643f5c7e037fbaee775ae6 (mariadb-10.5.2-429-g9892a3b4fda)
parent(s): bbc62d491f523ed84efcfcc73436005d39f77a90
author: Sergei Petrunia
committer: Sergei Petrunia
timestamp: 2021-03-12 21:58:51 +0300
message:
JSON_TABLE: undo the changes that are no longer relevant
---
sql/table.cc | 8 +++-----
1 file changed, 3 insertions(+), 5 deletions(-)
diff --git a/sql/table.cc b/sql/table.cc
index 9c205fc4be6..6f4fd1af3f8 100644
--- a/sql/table.cc
+++ b/sql/table.cc
@@ -6722,8 +6722,6 @@ Item *create_view_field(THD *thd, TABLE_LIST *view, Item **field_ref,
LEX_CSTRING *name)
{
bool save_wrapper= thd->lex->first_select_lex()->no_wrap_view_item;
- bool *wrapper_to_set= thd->lex->current_select ?
- &thd->lex->current_select->no_wrap_view_item : &save_wrapper;
Item *field= *field_ref;
DBUG_ENTER("create_view_field");
@@ -6739,17 +6737,17 @@ Item *create_view_field(THD *thd, TABLE_LIST *view, Item **field_ref,
}
DBUG_ASSERT(field);
- *wrapper_to_set= TRUE;
+ thd->lex->current_select->no_wrap_view_item= TRUE;
if (!field->is_fixed())
{
if (field->fix_fields(thd, field_ref))
{
- *wrapper_to_set= save_wrapper;
+ thd->lex->current_select->no_wrap_view_item= save_wrapper;
DBUG_RETURN(0);
}
field= *field_ref;
}
- *wrapper_to_set= save_wrapper;
+ thd->lex->current_select->no_wrap_view_item= save_wrapper;
if (save_wrapper)
{
DBUG_RETURN(field);
1
0
[Commits] 7ea4872: MDEV-21104 Wrong result (extra rows and wrong values) with incremental BNLH
by IgorBabaev 11 Mar '21
by IgorBabaev 11 Mar '21
11 Mar '21
revision-id: 7ea487207e4c8d582161c7c4dc413f29f9e61552 (mariadb-10.2.31-769-g7ea4872)
parent(s): 259e5243faa88370bbb890342326a324fb648f7d
author: Igor Babaev
committer: Igor Babaev
timestamp: 2021-03-10 17:26:43 -0800
message:
MDEV-21104 Wrong result (extra rows and wrong values) with incremental BNLH
This bug could affect multi-way join queries with embedded outer joins that
contained a conjunctive IS NULL predicate over a non-nullable column from
inner table of an outer join. The predicate could occur in WHERE condition
or in ON condition. Due to this bug a wrong result set could be returned by
the query. The bug manifested itself only when join buffers were employed
for join operations.
The problem appeared because
- a bug in the function JOIN_CACHE::get_match_flag_by_pos that not always
returned proper match flags for embedding outer joins stored together
with table rows put a join buffer.
- bug in the function JOIN_CACHE::join_matching_records that not always
correctly determined that a row from the buffer could be skipped due
to applied 'not_exists' optimization.
Example:
SELECT * FROM t1 LEFT JOIN ((t2 LEFT JOIN t3 ON c = d) JOIN t4) ON b = e
WHERE e IS NULL;
The patch introduces a new function that finds the match flag for a record
from join buffer specifying the buffer where this flag has to be found.
The function is called JOIN_CACHE::get_match_flag_by_pos_from_join_buffer().
Now this function rather than JOIN_CACHE::get_match_flag_by_pos() is used
in JOIN_CACHE::skip_if_matched() to check whether a record from the join
buffer must be ignored when extending the record by null complements.
Also the code of the function JOIN_CACHE::skip_if_not_needed_match() has
been changed. The function checks whether a record from the join buffer
still may produce some useful extensions.
Also some clarifying comments has been added.
Approved by monty(a)mariadb.com.
---
mysql-test/r/join_cache.result | 53 +++++++++++++++++++++++++
mysql-test/t/join_cache.test | 36 +++++++++++++++++
sql/sql_join_cache.cc | 88 +++++++++++++++++++++++++++++++++++++-----
sql/sql_join_cache.h | 11 +++++-
4 files changed, 177 insertions(+), 11 deletions(-)
diff --git a/mysql-test/r/join_cache.result b/mysql-test/r/join_cache.result
index e41c79a..87c4079 100644
--- a/mysql-test/r/join_cache.result
+++ b/mysql-test/r/join_cache.result
@@ -6054,4 +6054,57 @@ select f2 from t2,t1 where f2 = 0;
f2
drop table t1, t2;
set join_buffer_size=@save_join_buffer_size;
+#
+# MDEV-21104: BNLH used for multi-join query with embedded outer join
+# and possible 'not exists' optimization
+#
+set join_cache_level=4;
+CREATE TABLE t1 (a int) ENGINE=MyISAM;
+INSERT INTO t1 VALUES (1),(2);
+CREATE TABLE t2 (b int, c int) ENGINE=MyISAM;
+INSERT INTO t2 VALUES (1,2),(2,4);
+CREATE TABLE t3 (d int, KEY(d)) ENGINE=MyISAM;
+INSERT INTO t3 VALUES (1),(2);
+CREATE TABLE t4 (e int primary key) ENGINE=MyISAM;
+INSERT INTO t4 VALUES (1),(2);
+ANALYZE TABLE t1,t2,t3,t4;
+Table Op Msg_type Msg_text
+test.t1 analyze status OK
+test.t2 analyze status OK
+test.t3 analyze status OK
+test.t4 analyze status OK
+SELECT * FROM t2 LEFT JOIN t3 ON c = d;
+b c d
+1 2 2
+2 4 NULL
+SELECT * FROM (t2 LEFT JOIN t3 ON c = d ) JOIN t4;
+b c d e
+1 2 2 1
+2 4 NULL 1
+1 2 2 2
+2 4 NULL 2
+EXPLAIN SELECT * FROM t1 LEFT JOIN ( ( t2 LEFT JOIN t3 ON c = d ) JOIN t4 ) ON b = e;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 ALL NULL NULL NULL NULL 2
+1 SIMPLE t2 ALL NULL NULL NULL NULL 2 Using where; Using join buffer (flat, BNL join)
+1 SIMPLE t3 hash_index d #hash#d:d 5:5 test.t2.c 2 Using where; Using index; Using join buffer (incremental, BNLH join)
+1 SIMPLE t4 hash_index PRIMARY #hash#PRIMARY:PRIMARY 4:4 test.t2.b 2 Using index; Using join buffer (incremental, BNLH join)
+SELECT * FROM t1 LEFT JOIN ( ( t2 LEFT JOIN t3 ON c = d ) JOIN t4 ) ON b = e;
+a b c d e
+1 1 2 2 1
+2 1 2 2 1
+1 2 4 NULL 2
+2 2 4 NULL 2
+EXPLAIN SELECT * FROM t1 LEFT JOIN ( ( t2 LEFT JOIN t3 ON c = d ) JOIN t4 ) ON b = e
+WHERE e IS NULL;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 ALL NULL NULL NULL NULL 2
+1 SIMPLE t2 ALL NULL NULL NULL NULL 2 Using where; Using join buffer (flat, BNL join)
+1 SIMPLE t3 hash_index d #hash#d:d 5:5 test.t2.c 2 Using where; Using index; Using join buffer (incremental, BNLH join)
+1 SIMPLE t4 hash_index PRIMARY #hash#PRIMARY:PRIMARY 4:4 test.t2.b 2 Using where; Using index; Not exists; Using join buffer (incremental, BNLH join)
+SELECT * FROM t1 LEFT JOIN ( ( t2 LEFT JOIN t3 ON c = d ) JOIN t4 ) ON b = e
+WHERE e IS NULL;
+a b c d e
+DROP TABLE t1,t2,t3,t4;
+set join_cache_level=@save_join_cache_level;
set @@optimizer_switch=@save_optimizer_switch;
diff --git a/mysql-test/t/join_cache.test b/mysql-test/t/join_cache.test
index 9576d59..15cd1e9 100644
--- a/mysql-test/t/join_cache.test
+++ b/mysql-test/t/join_cache.test
@@ -4014,5 +4014,41 @@ select f2 from t2,t1 where f2 = 0;
drop table t1, t2;
set join_buffer_size=@save_join_buffer_size;
+
+--echo #
+--echo # MDEV-21104: BNLH used for multi-join query with embedded outer join
+--echo # and possible 'not exists' optimization
+--echo #
+
+set join_cache_level=4;
+
+CREATE TABLE t1 (a int) ENGINE=MyISAM;
+INSERT INTO t1 VALUES (1),(2);
+CREATE TABLE t2 (b int, c int) ENGINE=MyISAM;
+INSERT INTO t2 VALUES (1,2),(2,4);
+CREATE TABLE t3 (d int, KEY(d)) ENGINE=MyISAM;
+INSERT INTO t3 VALUES (1),(2);
+CREATE TABLE t4 (e int primary key) ENGINE=MyISAM;
+INSERT INTO t4 VALUES (1),(2);
+ANALYZE TABLE t1,t2,t3,t4;
+
+SELECT * FROM t2 LEFT JOIN t3 ON c = d;
+SELECT * FROM (t2 LEFT JOIN t3 ON c = d ) JOIN t4;
+
+let $q1=
+SELECT * FROM t1 LEFT JOIN ( ( t2 LEFT JOIN t3 ON c = d ) JOIN t4 ) ON b = e;
+eval EXPLAIN $q1;
+eval $q1;
+
+let $q2=
+SELECT * FROM t1 LEFT JOIN ( ( t2 LEFT JOIN t3 ON c = d ) JOIN t4 ) ON b = e
+ WHERE e IS NULL;
+eval EXPLAIN $q2;
+eval $q2;
+
+DROP TABLE t1,t2,t3,t4;
+
+set join_cache_level=@save_join_cache_level;
+
# The following command must be the last one in the file
set @@optimizer_switch=@save_optimizer_switch;
diff --git a/sql/sql_join_cache.cc b/sql/sql_join_cache.cc
index 1dfc938..594094a 100644
--- a/sql/sql_join_cache.cc
+++ b/sql/sql_join_cache.cc
@@ -1649,7 +1649,7 @@ void JOIN_CACHE::get_record_by_pos(uchar *rec_ptr)
}
-/*
+/*
Get the match flag from the referenced record: the default implementation
SYNOPSIS
@@ -1661,6 +1661,7 @@ void JOIN_CACHE::get_record_by_pos(uchar *rec_ptr)
get the match flag for the record pointed by the reference at the position
rec_ptr. If the match flag is placed in one of the previous buffers the
function first reaches the linked record fields in this buffer.
+ The function returns the value of the first encountered match flag.
RETURN VALUE
match flag for the record at the position rec_ptr
@@ -1685,6 +1686,39 @@ enum JOIN_CACHE::Match_flag JOIN_CACHE::get_match_flag_by_pos(uchar *rec_ptr)
/*
+ Get the match flag for the referenced record from specified join buffer
+
+ SYNOPSIS
+ get_match_flag_by_pos_from_join_buffer()
+ rec_ptr position of the first field of the record in the join buffer
+ tab join table with join buffer where to look for the match flag
+
+ DESCRIPTION
+ This default implementation of the get_match_flag_by_pos_from_join_buffer
+ method gets the match flag for the record pointed by the reference at the
+ position rec_ptr from the join buffer attached to the join table tab.
+
+ RETURN VALUE
+ match flag for the record at the position rec_ptr from the join
+ buffer attached to the table tab.
+*/
+
+enum JOIN_CACHE::Match_flag
+JOIN_CACHE::get_match_flag_by_pos_from_join_buffer(uchar *rec_ptr,
+ JOIN_TAB *tab)
+{
+ DBUG_ASSERT(tab->cache && tab->cache->with_match_flag);
+ for (JOIN_CACHE *cache= this; ; )
+ {
+ if (cache->join_tab == tab)
+ return (enum Match_flag) rec_ptr[0];
+ cache= cache->prev_cache;
+ rec_ptr= cache->get_rec_ref(rec_ptr);
+ }
+}
+
+
+/*
Calculate the increment of the auxiliary buffer for a record write
SYNOPSIS
@@ -1954,6 +1988,10 @@ bool JOIN_CACHE::read_referenced_field(CACHE_FIELD *copy,
If the record is skipped the value of 'pos' is set to point to the position
right after the record.
+ NOTE
+ Currently this function is called only when generating null complemented
+ records for outer joins (=> only when join_tab->first_unmatched != NULL).
+
RETURN VALUE
TRUE the match flag is set to MATCH_FOUND and the record has been skipped
FALSE otherwise
@@ -1966,7 +2004,9 @@ bool JOIN_CACHE::skip_if_matched()
if (prev_cache)
offset+= prev_cache->get_size_of_rec_offset();
/* Check whether the match flag is MATCH_FOUND */
- if (get_match_flag_by_pos(pos+offset) == MATCH_FOUND)
+ if (get_match_flag_by_pos_from_join_buffer(pos+offset,
+ join_tab->first_unmatched) ==
+ MATCH_FOUND)
{
pos+= size_of_rec_len + get_rec_length(pos);
return TRUE;
@@ -1983,13 +2023,23 @@ bool JOIN_CACHE::skip_if_matched()
DESCRIPTION
This default implementation of the virtual function skip_if_not_needed_match
- skips the next record from the join buffer if its match flag is not
- MATCH_NOT_FOUND, and, either its value is MATCH_FOUND and join_tab is the
- first inner table of an inner join, or, its value is MATCH_IMPOSSIBLE
- and join_tab is the first inner table of an outer join.
+ skips the next record from the join when generating join extensions
+ for the records in the join buffer depending on the value of the match flag.
+ - In the case of a semi-nest the match flag may be in two states
+ {MATCH_NOT_FOUND, MATCH_FOUND}. The record is skipped if the flag is set
+ to MATCH_FOUND.
+ - In the case of a outer join nest when not_exists optimization is applied
+ the match may be in three states {MATCH_NOT_FOUND, MATCH_IMPOSSIBLE,
+ MATCH_FOUND. The record is skipped if the flag is set to MATCH_FOUND or
+ to MATCH_IMPOSSIBLE.
+
If the record is skipped the value of 'pos' is set to point to the position
right after the record.
+ NOTE
+ Currently the function is called only when generating non-null complemented
+ extensions for records in the join buffer.
+
RETURN VALUE
TRUE the record has to be skipped
FALSE otherwise
@@ -2000,11 +2050,19 @@ bool JOIN_CACHE::skip_if_not_needed_match()
DBUG_ASSERT(with_length);
enum Match_flag match_fl;
uint offset= size_of_rec_len;
+ bool skip= FALSE;
if (prev_cache)
offset+= prev_cache->get_size_of_rec_offset();
- if ((match_fl= get_match_flag_by_pos(pos+offset)) != MATCH_NOT_FOUND &&
- (join_tab->check_only_first_match() == (match_fl == MATCH_FOUND)) )
+ if (!join_tab->check_only_first_match())
+ return FALSE;
+
+ match_fl= get_match_flag_by_pos(pos+offset);
+ skip= join_tab->first_sj_inner_tab ?
+ match_fl == MATCH_FOUND : // the case of semi-join
+ match_fl != MATCH_NOT_FOUND; // the case of outer-join
+
+ if (skip)
{
pos+= size_of_rec_len + get_rec_length(pos);
return TRUE;
@@ -2104,7 +2162,14 @@ enum_nested_loop_state JOIN_CACHE::join_records(bool skip_last)
goto finish;
}
join_tab->not_null_compl= FALSE;
- /* Prepare for generation of null complementing extensions */
+ /*
+ Prepare for generation of null complementing extensions.
+ For all inner tables of the outer join operation for which
+ regular matches have been just found the field 'first_unmatched'
+ is set to point the the first inner table. After all null
+ complement rows are generated for this outer join this field
+ is set back to NULL.
+ */
for (tab= join_tab->first_inner; tab <= join_tab->last_inner; tab++)
tab->first_unmatched= join_tab->first_inner;
}
@@ -2221,7 +2286,10 @@ enum_nested_loop_state JOIN_CACHE::join_matching_records(bool skip_last)
int error;
enum_nested_loop_state rc= NESTED_LOOP_OK;
join_tab->table->null_row= 0;
- bool check_only_first_match= join_tab->check_only_first_match();
+ bool check_only_first_match=
+ join_tab->check_only_first_match() &&
+ (!join_tab->first_inner || // semi-join case
+ join_tab->first_inner == join_tab->first_unmatched); // outer join case
bool outer_join_first_inner= join_tab->is_first_inner_for_outer_join();
DBUG_ENTER("JOIN_CACHE::join_matching_records");
diff --git a/sql/sql_join_cache.h b/sql/sql_join_cache.h
index 1cbc6ac..4569910 100644
--- a/sql/sql_join_cache.h
+++ b/sql/sql_join_cache.h
@@ -206,7 +206,9 @@ class JOIN_CACHE :public Sql_alloc
/*
This flag indicates that records written into the join buffer contain
- a match flag field. The flag must be set by the init method.
+ a match flag field. The flag must be set by the init method.
+ Currently any implementation of the virtial init method calls
+ the function JOIN_CACHE::calc_record_fields() to set this flag.
*/
bool with_match_flag;
/*
@@ -646,6 +648,13 @@ class JOIN_CACHE :public Sql_alloc
/* Shall return the value of the match flag for the positioned record */
virtual enum Match_flag get_match_flag_by_pos(uchar *rec_ptr);
+ /*
+ Shall return the value of the match flag for the positioned record
+ from the join buffer attached to the specified table
+ */
+ virtual enum Match_flag
+ get_match_flag_by_pos_from_join_buffer(uchar *rec_ptr, JOIN_TAB *tab);
+
/* Shall return the position of the current record */
virtual uchar *get_curr_rec() { return curr_rec_pos; }
1
0
10 Mar '21
revision-id: df098d08601aa4f40caea1d91364bc030f56188f (mariadb-10.2.31-781-gdf098d0)
parent(s): dc6667805dd4567693fcc01666da7d1277050097
author: Igor Babaev
committer: Igor Babaev
timestamp: 2021-03-09 15:11:44 -0800
message:
MDEV-24597 Explicit column name error in CTE of UNION
This bug manifested itself when executing queries with multiple reference
to a CTE specified by a query expression with union and having its
column names explicitly declared. In this case the server returned a bogus
error message about unknown column name. It happened because while for the
first reference to the CTE the names of the columns returned by the CTE
specification were properly changed to match the CTE definition for the
other references it was not done. This was a consequence of not quite
complete code of the function With_element::clone_parsed_spec() that forgot
to set the reference to the CTE definition for unit structures representing
non-first CTE references.
Approved by dmitry.shulga(a)mariadb.com
---
mysql-test/r/cte_nonrecursive.result | 42 ++++++++++++++++++++++++++++++++++--
mysql-test/t/cte_nonrecursive.test | 31 ++++++++++++++++++++++++++
sql/sql_cte.cc | 1 +
3 files changed, 72 insertions(+), 2 deletions(-)
diff --git a/mysql-test/r/cte_nonrecursive.result b/mysql-test/r/cte_nonrecursive.result
index a47fdcd..da954c1 100644
--- a/mysql-test/r/cte_nonrecursive.result
+++ b/mysql-test/r/cte_nonrecursive.result
@@ -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 (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 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
@@ -1126,7 +1126,7 @@ NULL UNION RESULT <union2,5> ALL NULL NULL NULL NULL NULL NULL
NULL UNION RESULT <union9,12> 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 (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 union select `test`.`t1`.`a` AS `a` from `test`.`t1` where `test`.`t1`.`a` > 4 and `test`.`t
1`.`a` > 1 and `test`.`t1`.`a` < 7) `cte_e2`
+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`
drop table t1;
#
# MDEV-13753: embedded CTE in a VIEW created in prepared statement
@@ -1725,4 +1725,42 @@ drop table db1.t1;
drop database db1;
create database test;
use test;
+#
+# MDEV-24597: CTE with union used multiple times in query
+#
+with cte(a) as
+(select 1 as d union select 2 as d)
+select a from cte as r1
+union
+select a from cte as r2;
+a
+1
+2
+create table t1 (a int, b int) engine=myisam;
+insert into t1 values
+(3,30), (7,70), (1,10), (7,71), (2,20), (7,72), (3,33), (4,44),
+(5,50), (4,40), (3,33), (4,42), (4,43), (5,51);
+with cte(c) as
+(select a from t1 where b < 30 union select a from t1 where b > 40)
+select * from cte as r1, cte as r2 where r1.c = r2.c;
+c c
+1 1
+2 2
+7 7
+4 4
+5 5
+with cte(a,c) as
+(
+select a, count(*) from t1 group by a having count(*) = 1
+union
+select a, count(*) from t1 group by a having count(*) = 3
+)
+select a, c from cte as r1 where a < 3
+union
+select a, c from cte as r2 where a > 4;
+a c
+1 1
+2 1
+7 3
+drop table t1;
# End of 10.2 tests
diff --git a/mysql-test/t/cte_nonrecursive.test b/mysql-test/t/cte_nonrecursive.test
index 0174ddb..c2ebc92 100644
--- a/mysql-test/t/cte_nonrecursive.test
+++ b/mysql-test/t/cte_nonrecursive.test
@@ -1230,4 +1230,35 @@ drop database db1;
create database test;
use test;
+--echo #
+--echo # MDEV-24597: CTE with union used multiple times in query
+--echo #
+
+with cte(a) as
+(select 1 as d union select 2 as d)
+select a from cte as r1
+union
+select a from cte as r2;
+
+create table t1 (a int, b int) engine=myisam;
+insert into t1 values
+(3,30), (7,70), (1,10), (7,71), (2,20), (7,72), (3,33), (4,44),
+(5,50), (4,40), (3,33), (4,42), (4,43), (5,51);
+
+with cte(c) as
+(select a from t1 where b < 30 union select a from t1 where b > 40)
+select * from cte as r1, cte as r2 where r1.c = r2.c;
+
+with cte(a,c) as
+(
+ select a, count(*) from t1 group by a having count(*) = 1
+ union
+ select a, count(*) from t1 group by a having count(*) = 3
+)
+select a, c from cte as r1 where a < 3
+union
+select a, c from cte as r2 where a > 4;
+
+drop table t1;
+
--echo # End of 10.2 tests
diff --git a/sql/sql_cte.cc b/sql/sql_cte.cc
index e07a525..3a2301a 100644
--- a/sql/sql_cte.cc
+++ b/sql/sql_cte.cc
@@ -879,6 +879,7 @@ st_select_lex_unit *With_element::clone_parsed_spec(THD *thd,
with_table->next_global= spec_tables;
}
res= &lex->unit;
+ res->with_element= this;
lex->unit.include_down(with_table->select_lex);
lex->unit.set_slave(with_select);
1
0
09 Mar '21
revision-id: 79ac9485f8fb86243c31d6e7895dfbe3c2f369ca (mariadb-10.2.31-781-g79ac948)
parent(s): dc6667805dd4567693fcc01666da7d1277050097
author: Igor Babaev
committer: Igor Babaev
timestamp: 2021-03-09 14:51:52 -0800
message:
MDEV-24597 Explicit column name error in CTE of UNION
This bug manifested itself when executing queries with multiple reference
to a CTE specified by a query expression with union and having its
column names explicitly declared. In this case the server returned a bogus
error message about unknown column name. It happened because while for the
first reference to the CTE the names of the columns returned by the CTE
specification were properly changed to match the CTE definition for the
other references it was not done. This was a consequence of not quite
complete code of the function TABLE_LIST::set_as_with_table that forgot to
set the reference to the CTE definition for unit structures representing
non-first CTE references.
Approved by dmitry.shulga(a)mariadb.com
---
mysql-test/r/cte_nonrecursive.result | 42 ++++++++++++++++++++++++++++++++++--
mysql-test/t/cte_nonrecursive.test | 31 ++++++++++++++++++++++++++
sql/sql_cte.cc | 1 +
3 files changed, 72 insertions(+), 2 deletions(-)
diff --git a/mysql-test/r/cte_nonrecursive.result b/mysql-test/r/cte_nonrecursive.result
index a47fdcd..da954c1 100644
--- a/mysql-test/r/cte_nonrecursive.result
+++ b/mysql-test/r/cte_nonrecursive.result
@@ -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 (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 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
@@ -1126,7 +1126,7 @@ NULL UNION RESULT <union2,5> ALL NULL NULL NULL NULL NULL NULL
NULL UNION RESULT <union9,12> 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 (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 union select `test`.`t1`.`a` AS `a` from `test`.`t1` where `test`.`t1`.`a` > 4 and `test`.`t
1`.`a` > 1 and `test`.`t1`.`a` < 7) `cte_e2`
+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`
drop table t1;
#
# MDEV-13753: embedded CTE in a VIEW created in prepared statement
@@ -1725,4 +1725,42 @@ drop table db1.t1;
drop database db1;
create database test;
use test;
+#
+# MDEV-24597: CTE with union used multiple times in query
+#
+with cte(a) as
+(select 1 as d union select 2 as d)
+select a from cte as r1
+union
+select a from cte as r2;
+a
+1
+2
+create table t1 (a int, b int) engine=myisam;
+insert into t1 values
+(3,30), (7,70), (1,10), (7,71), (2,20), (7,72), (3,33), (4,44),
+(5,50), (4,40), (3,33), (4,42), (4,43), (5,51);
+with cte(c) as
+(select a from t1 where b < 30 union select a from t1 where b > 40)
+select * from cte as r1, cte as r2 where r1.c = r2.c;
+c c
+1 1
+2 2
+7 7
+4 4
+5 5
+with cte(a,c) as
+(
+select a, count(*) from t1 group by a having count(*) = 1
+union
+select a, count(*) from t1 group by a having count(*) = 3
+)
+select a, c from cte as r1 where a < 3
+union
+select a, c from cte as r2 where a > 4;
+a c
+1 1
+2 1
+7 3
+drop table t1;
# End of 10.2 tests
diff --git a/mysql-test/t/cte_nonrecursive.test b/mysql-test/t/cte_nonrecursive.test
index 0174ddb..c2ebc92 100644
--- a/mysql-test/t/cte_nonrecursive.test
+++ b/mysql-test/t/cte_nonrecursive.test
@@ -1230,4 +1230,35 @@ drop database db1;
create database test;
use test;
+--echo #
+--echo # MDEV-24597: CTE with union used multiple times in query
+--echo #
+
+with cte(a) as
+(select 1 as d union select 2 as d)
+select a from cte as r1
+union
+select a from cte as r2;
+
+create table t1 (a int, b int) engine=myisam;
+insert into t1 values
+(3,30), (7,70), (1,10), (7,71), (2,20), (7,72), (3,33), (4,44),
+(5,50), (4,40), (3,33), (4,42), (4,43), (5,51);
+
+with cte(c) as
+(select a from t1 where b < 30 union select a from t1 where b > 40)
+select * from cte as r1, cte as r2 where r1.c = r2.c;
+
+with cte(a,c) as
+(
+ select a, count(*) from t1 group by a having count(*) = 1
+ union
+ select a, count(*) from t1 group by a having count(*) = 3
+)
+select a, c from cte as r1 where a < 3
+union
+select a, c from cte as r2 where a > 4;
+
+drop table t1;
+
--echo # End of 10.2 tests
diff --git a/sql/sql_cte.cc b/sql/sql_cte.cc
index e07a525..4929c2e 100644
--- a/sql/sql_cte.cc
+++ b/sql/sql_cte.cc
@@ -1124,6 +1124,7 @@ bool TABLE_LIST::set_as_with_table(THD *thd, With_element *with_elem)
{
if(!(derived= with_elem->clone_parsed_spec(thd, this)))
return true;
+ derived->with_element= with_elem;
}
derived->first_select()->linkage= DERIVED_TABLE_TYPE;
with_elem->inc_references();
1
0
06 Mar '21
revision-id: c24bcd5948bd69f336af61214c0f31165d19dc14 (mariadb-10.4.11-556-gc24bcd5)
parent(s): fcc9f8b10cd2f497ff410b592808eedb3ee5f212
author: Igor Babaev
committer: Igor Babaev
timestamp: 2021-03-05 20:18:28 -0800
message:
MDEV-22786 Crashes with nested table value constructors
The bug caused crashes of the server when processing queries with nested
table value constructors (TVC) . It happened because the grammar rules to
parse TVC used the same global lists for both nested TVC and nesting TVC.
As a result invalid select trees were constructed for queries with nested
TVC and this led to crashes at the prepare stage.
This patch provides its own lists structures for each TVC nest level.
Besides the patch fixes a bug in the function wrap_tvc() that missed
inheritance of the SELECT_LEX::exclude_from_table_unique_test for
selects that wrapped TVCs. This inheritance is critical for specifications
of derived tables that employ nested TVCs.
Approved by dmitry.shulga(a)mariadb.com
---
mysql-test/main/table_value_constr.result | 175 ++++++++++++++++++++++++++++++
mysql-test/main/table_value_constr.test | 106 ++++++++++++++++++
sql/sql_lex.cc | 21 +++-
sql/sql_lex.h | 11 +-
sql/sql_tvc.cc | 2 +
5 files changed, 305 insertions(+), 10 deletions(-)
diff --git a/mysql-test/main/table_value_constr.result b/mysql-test/main/table_value_constr.result
index 4525a50..0914645 100644
--- a/mysql-test/main/table_value_constr.result
+++ b/mysql-test/main/table_value_constr.result
@@ -2887,6 +2887,181 @@ drop table t1,t2,t3;
select sum((values(1)));
sum((values(1)))
1
+#
+# MDEV-22786: Nested table values constructors
+#
+values ((values (2)));
+(values (2))
+2
+values ((values (2)), (5), (select 4));
+(values (2)) 5 (select 4)
+2 5 4
+values ((7), (values (2)), (5), (select 4));
+7 (values (2)) 5 (select 4)
+7 2 5 4
+values ((values (2))) union values ((values (3)));
+(values (2))
+2
+3
+values ((values (2))), ((values (3)));
+(values (2))
+2
+3
+values ((values (2))), ((select 4)), ((values (3)));
+(values (2))
+2
+4
+3
+values ((values (4)), (values (5))), ((values (1)), (values (7)));
+(values (4)) (values (5))
+4 5
+1 7
+values ((values (4)), (select 5)), ((select 1), (values (7)));
+(values (4)) (select 5)
+4 5
+1 7
+values ((select 2)) union values ((values (3)));
+(select 2)
+2
+3
+values ((values (2))) union values((select 3));
+(values (2))
+2
+3
+values ((values (2))) union all values ((values (2)));
+(values (2))
+2
+2
+values ((values (4)), (values (5))), ((values (1)), (values (7)))
+union
+values ((values (4)), (select 5)), ((select 2), (values (8)));
+(values (4)) (values (5))
+4 5
+1 7
+2 8
+values ((values (4)), (values (5))), ((values (1)), (values (7)))
+union all
+values ((values (4)), (select 5)), ((select 2), (values (8)));
+(values (4)) (values (5))
+4 5
+1 7
+4 5
+2 8
+values ((values (1) union values (1)));
+(values (1) union values (1))
+1
+values ((values (1) union values (1) union values (1)));
+(values (1) union values (1) union values (1))
+1
+values ((values ((values (4)))));
+(values ((values (4))))
+4
+values ((values ((select 5))));
+(values ((select 5)))
+5
+values ((select (values (4))), (values ((values(5)))));
+(select (values (4))) (values ((values(5))))
+4 5
+values ((select (values (4))), (values ((select 5))));
+(select (values (4))) (values ((select 5)))
+4 5
+values ((select (values (4))), (values ((values(5)))))
+union
+values ((select (values (4))), (values ((select 7))));
+(select (values (4))) (values ((values(5))))
+4 5
+4 7
+values ((values (2))), ((values ((values (4)))));
+(values (2))
+2
+4
+values ((values (2))), ((values ((select 4))));
+(values (2))
+2
+4
+values ((values (2))), ((values ((values (4)))))
+union
+values ((values (8))), ((values ((select 4))));
+(values (2))
+2
+4
+8
+values ((values (2))), ((values ((values (4)))))
+union all
+values ((values (8))), ((values ((select 4))));
+(values (2))
+2
+4
+8
+4
+select * from (values ((values (2)))) dt;
+(values (2))
+2
+select * from (values ((values (2)), (5), (select 4))) dt;
+(values (2)) 5 (select 4)
+2 5 4
+select * from (values ((values (2))) union values ((values (3)))) dt;
+(values (2))
+2
+3
+select * from (values ((values (2))), ((values (3)))) dt;
+(values (2))
+2
+3
+select * from (values ((values (2))), ((values (3)))) dt;
+(values (2))
+2
+3
+select * from (values ((values (2))), ((select 4)), ((values (3)))) dt;
+(values (2))
+2
+4
+3
+create table t1 (a int);
+insert into t1 values (3), (7), (1);
+values ((values ((select a from t1 where a=7))));
+(values ((select a from t1 where a=7)))
+7
+values ((values ((select (values(2)) from t1 where a=8))));
+(values ((select (values(2)) from t1 where a=8)))
+NULL
+values ((values ((select a from t1 where a=7))))
+union
+values ((values ((select (values(2)) from t1 where a=8))));
+(values ((select a from t1 where a=7)))
+7
+NULL
+values ((values ((select a from t1 where a in ((values (7)))))));
+(values ((select a from t1 where a in ((values (7))))))
+7
+values ((values ((select a from t1 where a in ((values (7), (8)))))));
+(values ((select a from t1 where a in ((values (7), (8))))))
+7
+values ((values
+((select a from t1 where a in (values (7) union values (8))))));
+(values
+((select a from t1 where a in (values (7) union values (8)))))
+7
+values ((values ((select (values(2)) from t1 where a=8))));
+(values ((select (values(2)) from t1 where a=8)))
+NULL
+values ((select (values(2)) from t1 where a<7));
+ERROR 21000: Subquery returns more than 1 row
+select * from (values ((values ((select a from t1 where a=7))))) dt;
+(values ((select a from t1 where a=7)))
+7
+select * from (values ((values ((select (values(2)) from t1 where a=8))))) dt;
+(values ((select (values(2)) from t1 where a=8)))
+NULL
+insert into t1(a) values ((values (2))), ((values (3)));
+select * from t1;
+a
+3
+7
+1
+2
+3
+drop table t1;
End of 10.3 tests
#
# MDEV-22610 Crash in INSERT INTO t1 (VALUES (DEFAULT) UNION VALUES (DEFAULT))
diff --git a/mysql-test/main/table_value_constr.test b/mysql-test/main/table_value_constr.test
index 55cf2fc..49e1c7c 100644
--- a/mysql-test/main/table_value_constr.test
+++ b/mysql-test/main/table_value_constr.test
@@ -1522,6 +1522,112 @@ drop table t1,t2,t3;
select sum((values(1)));
+--echo #
+--echo # MDEV-22786: Nested table values constructors
+--echo #
+
+values ((values (2)));
+
+values ((values (2)), (5), (select 4));
+
+values ((7), (values (2)), (5), (select 4));
+
+values ((values (2))) union values ((values (3)));
+
+values ((values (2))), ((values (3)));
+
+values ((values (2))), ((select 4)), ((values (3)));
+
+values ((values (4)), (values (5))), ((values (1)), (values (7)));
+
+values ((values (4)), (select 5)), ((select 1), (values (7)));
+
+values ((select 2)) union values ((values (3)));
+
+values ((values (2))) union values((select 3));
+
+values ((values (2))) union all values ((values (2)));
+
+values ((values (4)), (values (5))), ((values (1)), (values (7)))
+union
+values ((values (4)), (select 5)), ((select 2), (values (8)));
+
+values ((values (4)), (values (5))), ((values (1)), (values (7)))
+union all
+values ((values (4)), (select 5)), ((select 2), (values (8)));
+
+values ((values (1) union values (1)));
+
+values ((values (1) union values (1) union values (1)));
+
+values ((values ((values (4)))));
+
+values ((values ((select 5))));
+
+values ((select (values (4))), (values ((values(5)))));
+
+values ((select (values (4))), (values ((select 5))));
+
+values ((select (values (4))), (values ((values(5)))))
+union
+values ((select (values (4))), (values ((select 7))));
+
+values ((values (2))), ((values ((values (4)))));
+
+values ((values (2))), ((values ((select 4))));
+
+values ((values (2))), ((values ((values (4)))))
+union
+values ((values (8))), ((values ((select 4))));
+
+values ((values (2))), ((values ((values (4)))))
+union all
+values ((values (8))), ((values ((select 4))));
+
+select * from (values ((values (2)))) dt;
+
+select * from (values ((values (2)), (5), (select 4))) dt;
+
+select * from (values ((values (2))) union values ((values (3)))) dt;
+
+select * from (values ((values (2))), ((values (3)))) dt;
+
+select * from (values ((values (2))), ((values (3)))) dt;
+
+select * from (values ((values (2))), ((select 4)), ((values (3)))) dt;
+
+create table t1 (a int);
+insert into t1 values (3), (7), (1);
+
+values ((values ((select a from t1 where a=7))));
+
+values ((values ((select (values(2)) from t1 where a=8))));
+
+values ((values ((select a from t1 where a=7))))
+union
+values ((values ((select (values(2)) from t1 where a=8))));
+
+values ((values ((select a from t1 where a in ((values (7)))))));
+
+values ((values ((select a from t1 where a in ((values (7), (8)))))));
+
+values ((values
+ ((select a from t1 where a in (values (7) union values (8))))));
+
+values ((values ((select (values(2)) from t1 where a=8))));
+
+--error ER_SUBQUERY_NO_1_ROW
+values ((select (values(2)) from t1 where a<7));
+
+select * from (values ((values ((select a from t1 where a=7))))) dt;
+
+select * from (values ((values ((select (values(2)) from t1 where a=8))))) dt;
+
+insert into t1(a) values ((values (2))), ((values (3)));
+select * from t1;
+
+drop table t1;
+
--echo End of 10.3 tests
--echo #
diff --git a/sql/sql_lex.cc b/sql/sql_lex.cc
index 73cbcf5..17186e0 100644
--- a/sql/sql_lex.cc
+++ b/sql/sql_lex.cc
@@ -2487,6 +2487,8 @@ void st_select_lex::init_select()
with_dep= 0;
join= 0;
lock_type= TL_READ_DEFAULT;
+ save_many_values.empty();
+ save_insert_list= 0;
tvc= 0;
in_funcs.empty();
curr_tvc_name= 0;
@@ -8897,7 +8899,6 @@ bool LEX::last_field_generated_always_as_row_end()
VERS_SYS_END_FLAG);
}
-
void st_select_lex_unit::reset_distinct()
{
union_distinct= NULL;
@@ -8913,6 +8914,20 @@ void st_select_lex_unit::reset_distinct()
}
+void LEX::save_values_list_state()
+{
+ current_select->save_many_values= many_values;
+ current_select->save_insert_list= insert_list;
+}
+
+
+void LEX::restore_values_list_state()
+{
+ many_values= current_select->save_many_values;
+ insert_list= current_select->save_insert_list;
+}
+
+
void st_select_lex_unit::fix_distinct()
{
if (union_distinct && this != union_distinct->master_unit())
@@ -9409,6 +9424,7 @@ bool LEX::parsed_insert_select(SELECT_LEX *first_select)
bool LEX::parsed_TVC_start()
{
SELECT_LEX *sel;
+ save_values_list_state();
many_values.empty();
insert_list= 0;
if (!(sel= alloc_select(TRUE)) ||
@@ -9422,14 +9438,13 @@ bool LEX::parsed_TVC_start()
SELECT_LEX *LEX::parsed_TVC_end()
{
-
SELECT_LEX *res= pop_select(); // above TVC select
if (!(res->tvc=
new (thd->mem_root) table_value_constr(many_values,
res,
res->options)))
return NULL;
- many_values.empty();
+ restore_values_list_state();
return res;
}
diff --git a/sql/sql_lex.h b/sql/sql_lex.h
index d3b316d..6ef36a2 100644
--- a/sql/sql_lex.h
+++ b/sql/sql_lex.h
@@ -1287,6 +1287,8 @@ class st_select_lex: public st_select_lex_node
/* it is for correct printing SELECT options */
thr_lock_type lock_type;
+ List<List_item> save_many_values;
+ List<Item> *save_insert_list;
table_value_constr *tvc;
bool in_tvc;
@@ -4409,13 +4411,6 @@ struct LEX: public Query_tables_list
return false;
}
- void tvc_start()
- {
- field_list.empty();
- many_values.empty();
- insert_list= 0;
- }
-
SELECT_LEX_UNIT *alloc_unit();
SELECT_LEX *alloc_select(bool is_select);
SELECT_LEX_UNIT *create_unit(SELECT_LEX*);
@@ -4470,6 +4465,8 @@ struct LEX: public Query_tables_list
bool distinct);
SELECT_LEX *parsed_subselect(SELECT_LEX_UNIT *unit);
bool parsed_insert_select(SELECT_LEX *firs_select);
+ void save_values_list_state();
+ void restore_values_list_state();
bool parsed_TVC_start();
SELECT_LEX *parsed_TVC_end();
TABLE_LIST *parsed_derived_table(SELECT_LEX_UNIT *unit,
diff --git a/sql/sql_tvc.cc b/sql/sql_tvc.cc
index 0957de2..63e9e76 100644
--- a/sql/sql_tvc.cc
+++ b/sql/sql_tvc.cc
@@ -672,6 +672,8 @@ st_select_lex *wrap_tvc(THD *thd, st_select_lex *tvc_sl,
wrapper_sl->nest_level= tvc_sl->nest_level;
wrapper_sl->parsing_place= tvc_sl->parsing_place;
wrapper_sl->set_linkage(tvc_sl->get_linkage());
+ wrapper_sl->exclude_from_table_unique_test=
+ tvc_sl->exclude_from_table_unique_test;
lex->current_select= wrapper_sl;
item= new (thd->mem_root) Item_field(thd, &wrapper_sl->context,
1
0
[Commits] 0ce31b9: MDEV-25002 ON expressions cannot contain outer references
by IgorBabaev 05 Mar '21
by IgorBabaev 05 Mar '21
05 Mar '21
revision-id: 0ce31b93c4b85d17e467c922c8359949c18da7cb (mariadb-10.2.31-777-g0ce31b9)
parent(s): 7759991a06d54630214f19eaa0ec39bd21bf09df
author: Igor Babaev
committer: Igor Babaev
timestamp: 2021-03-04 23:02:47 -0800
message:
MDEV-25002 ON expressions cannot contain outer references
A bogus error message was issued for any outer references occurred in
ON expressions used in subqueries. This prevented execution of queries
containing subqueries as soon as they used outer references in their ON
clauses. This happened because the Name_resolution_context structure
created for any ON expression erroneously had the field outer_context set
to NULL. The fields select_lex of this structure was not set correctly
either.
The idea of the fix was taken from mysql code of the function
push_new_name_resolution_context().
Approved by dmitry.shulga(a)mariadb.com
---
mysql-test/r/subselect.result | 38 +++++++++++++++++++++++++++
mysql-test/r/subselect_no_exists_to_in.result | 38 +++++++++++++++++++++++++++
mysql-test/r/subselect_no_mat.result | 38 +++++++++++++++++++++++++++
mysql-test/r/subselect_no_opts.result | 38 +++++++++++++++++++++++++++
mysql-test/r/subselect_no_scache.result | 38 +++++++++++++++++++++++++++
mysql-test/r/subselect_no_semijoin.result | 38 +++++++++++++++++++++++++++
mysql-test/t/subselect.test | 35 ++++++++++++++++++++++++
sql/sql_parse.cc | 2 ++
8 files changed, 265 insertions(+)
diff --git a/mysql-test/r/subselect.result b/mysql-test/r/subselect.result
index d4d61eb..a03a2cf 100644
--- a/mysql-test/r/subselect.result
+++ b/mysql-test/r/subselect.result
@@ -7331,4 +7331,42 @@ WHERE (t2.i, t2.pk) NOT IN ( SELECT t3.i, t3.i FROM t t3, t t4 ) AND t1.c = 'foo
pk i c pk i c
1 10 foo 1 10 foo
DROP TABLE t;
+#
+# MDEV-25002: Outer reference in ON clause of subselect
+#
+create table t1 (
+pk int primary key,
+a int
+) engine=myisam;
+insert into t1 values (1,1), (2,2);
+create table t2 (
+pk int primary key,
+b int
+) engine=myisam;
+insert into t2 values (1,1), (2,3);
+create table t3 (a int);
+insert into t3 values (1),(2);
+select a,
+(select count(*) from t1, t2
+where t2.pk=t3.a and t1.pk=1) as sq
+from t3;
+a sq
+1 1
+2 1
+select a,
+(select count(*) from t1 join t2 on t2.pk=t3.a
+where t1.pk=1) as sq
+from t3;
+a sq
+1 1
+2 1
+select a from t3
+where a in (select t2.b from t1,t2 where t2.pk=t3.a and t1.pk=1);
+a
+1
+select a from t3
+where a in (select t2.b from t1 join t2 on t2.pk=t3.a where t1.pk=1);
+a
+1
+drop table t1,t2,t3;
# End of 10.2 tests
diff --git a/mysql-test/r/subselect_no_exists_to_in.result b/mysql-test/r/subselect_no_exists_to_in.result
index fa9ab8b..f7da3fd 100644
--- a/mysql-test/r/subselect_no_exists_to_in.result
+++ b/mysql-test/r/subselect_no_exists_to_in.result
@@ -7331,6 +7331,44 @@ WHERE (t2.i, t2.pk) NOT IN ( SELECT t3.i, t3.i FROM t t3, t t4 ) AND t1.c = 'foo
pk i c pk i c
1 10 foo 1 10 foo
DROP TABLE t;
+#
+# MDEV-25002: Outer reference in ON clause of subselect
+#
+create table t1 (
+pk int primary key,
+a int
+) engine=myisam;
+insert into t1 values (1,1), (2,2);
+create table t2 (
+pk int primary key,
+b int
+) engine=myisam;
+insert into t2 values (1,1), (2,3);
+create table t3 (a int);
+insert into t3 values (1),(2);
+select a,
+(select count(*) from t1, t2
+where t2.pk=t3.a and t1.pk=1) as sq
+from t3;
+a sq
+1 1
+2 1
+select a,
+(select count(*) from t1 join t2 on t2.pk=t3.a
+where t1.pk=1) as sq
+from t3;
+a sq
+1 1
+2 1
+select a from t3
+where a in (select t2.b from t1,t2 where t2.pk=t3.a and t1.pk=1);
+a
+1
+select a from t3
+where a in (select t2.b from t1 join t2 on t2.pk=t3.a where t1.pk=1);
+a
+1
+drop table t1,t2,t3;
# End of 10.2 tests
set optimizer_switch=default;
select @@optimizer_switch like '%exists_to_in=off%';
diff --git a/mysql-test/r/subselect_no_mat.result b/mysql-test/r/subselect_no_mat.result
index c4d5b28..6ab304c 100644
--- a/mysql-test/r/subselect_no_mat.result
+++ b/mysql-test/r/subselect_no_mat.result
@@ -7324,6 +7324,44 @@ WHERE (t2.i, t2.pk) NOT IN ( SELECT t3.i, t3.i FROM t t3, t t4 ) AND t1.c = 'foo
pk i c pk i c
1 10 foo 1 10 foo
DROP TABLE t;
+#
+# MDEV-25002: Outer reference in ON clause of subselect
+#
+create table t1 (
+pk int primary key,
+a int
+) engine=myisam;
+insert into t1 values (1,1), (2,2);
+create table t2 (
+pk int primary key,
+b int
+) engine=myisam;
+insert into t2 values (1,1), (2,3);
+create table t3 (a int);
+insert into t3 values (1),(2);
+select a,
+(select count(*) from t1, t2
+where t2.pk=t3.a and t1.pk=1) as sq
+from t3;
+a sq
+1 1
+2 1
+select a,
+(select count(*) from t1 join t2 on t2.pk=t3.a
+where t1.pk=1) as sq
+from t3;
+a sq
+1 1
+2 1
+select a from t3
+where a in (select t2.b from t1,t2 where t2.pk=t3.a and t1.pk=1);
+a
+1
+select a from t3
+where a in (select t2.b from t1 join t2 on t2.pk=t3.a where t1.pk=1);
+a
+1
+drop table t1,t2,t3;
# End of 10.2 tests
set optimizer_switch=default;
select @@optimizer_switch like '%materialization=on%';
diff --git a/mysql-test/r/subselect_no_opts.result b/mysql-test/r/subselect_no_opts.result
index fd1c527..338ddd5 100644
--- a/mysql-test/r/subselect_no_opts.result
+++ b/mysql-test/r/subselect_no_opts.result
@@ -7322,5 +7322,43 @@ WHERE (t2.i, t2.pk) NOT IN ( SELECT t3.i, t3.i FROM t t3, t t4 ) AND t1.c = 'foo
pk i c pk i c
1 10 foo 1 10 foo
DROP TABLE t;
+#
+# MDEV-25002: Outer reference in ON clause of subselect
+#
+create table t1 (
+pk int primary key,
+a int
+) engine=myisam;
+insert into t1 values (1,1), (2,2);
+create table t2 (
+pk int primary key,
+b int
+) engine=myisam;
+insert into t2 values (1,1), (2,3);
+create table t3 (a int);
+insert into t3 values (1),(2);
+select a,
+(select count(*) from t1, t2
+where t2.pk=t3.a and t1.pk=1) as sq
+from t3;
+a sq
+1 1
+2 1
+select a,
+(select count(*) from t1 join t2 on t2.pk=t3.a
+where t1.pk=1) as sq
+from t3;
+a sq
+1 1
+2 1
+select a from t3
+where a in (select t2.b from t1,t2 where t2.pk=t3.a and t1.pk=1);
+a
+1
+select a from t3
+where a in (select t2.b from t1 join t2 on t2.pk=t3.a where t1.pk=1);
+a
+1
+drop table t1,t2,t3;
# End of 10.2 tests
set @optimizer_switch_for_subselect_test=null;
diff --git a/mysql-test/r/subselect_no_scache.result b/mysql-test/r/subselect_no_scache.result
index 48d5bc9..741b070 100644
--- a/mysql-test/r/subselect_no_scache.result
+++ b/mysql-test/r/subselect_no_scache.result
@@ -7337,6 +7337,44 @@ WHERE (t2.i, t2.pk) NOT IN ( SELECT t3.i, t3.i FROM t t3, t t4 ) AND t1.c = 'foo
pk i c pk i c
1 10 foo 1 10 foo
DROP TABLE t;
+#
+# MDEV-25002: Outer reference in ON clause of subselect
+#
+create table t1 (
+pk int primary key,
+a int
+) engine=myisam;
+insert into t1 values (1,1), (2,2);
+create table t2 (
+pk int primary key,
+b int
+) engine=myisam;
+insert into t2 values (1,1), (2,3);
+create table t3 (a int);
+insert into t3 values (1),(2);
+select a,
+(select count(*) from t1, t2
+where t2.pk=t3.a and t1.pk=1) as sq
+from t3;
+a sq
+1 1
+2 1
+select a,
+(select count(*) from t1 join t2 on t2.pk=t3.a
+where t1.pk=1) as sq
+from t3;
+a sq
+1 1
+2 1
+select a from t3
+where a in (select t2.b from t1,t2 where t2.pk=t3.a and t1.pk=1);
+a
+1
+select a from t3
+where a in (select t2.b from t1 join t2 on t2.pk=t3.a where t1.pk=1);
+a
+1
+drop table t1,t2,t3;
# End of 10.2 tests
set optimizer_switch=default;
select @@optimizer_switch like '%subquery_cache=on%';
diff --git a/mysql-test/r/subselect_no_semijoin.result b/mysql-test/r/subselect_no_semijoin.result
index 2df8073..ebabafb 100644
--- a/mysql-test/r/subselect_no_semijoin.result
+++ b/mysql-test/r/subselect_no_semijoin.result
@@ -7322,6 +7322,44 @@ WHERE (t2.i, t2.pk) NOT IN ( SELECT t3.i, t3.i FROM t t3, t t4 ) AND t1.c = 'foo
pk i c pk i c
1 10 foo 1 10 foo
DROP TABLE t;
+#
+# MDEV-25002: Outer reference in ON clause of subselect
+#
+create table t1 (
+pk int primary key,
+a int
+) engine=myisam;
+insert into t1 values (1,1), (2,2);
+create table t2 (
+pk int primary key,
+b int
+) engine=myisam;
+insert into t2 values (1,1), (2,3);
+create table t3 (a int);
+insert into t3 values (1),(2);
+select a,
+(select count(*) from t1, t2
+where t2.pk=t3.a and t1.pk=1) as sq
+from t3;
+a sq
+1 1
+2 1
+select a,
+(select count(*) from t1 join t2 on t2.pk=t3.a
+where t1.pk=1) as sq
+from t3;
+a sq
+1 1
+2 1
+select a from t3
+where a in (select t2.b from t1,t2 where t2.pk=t3.a and t1.pk=1);
+a
+1
+select a from t3
+where a in (select t2.b from t1 join t2 on t2.pk=t3.a where t1.pk=1);
+a
+1
+drop table t1,t2,t3;
# End of 10.2 tests
#
# MDEV-19714: JOIN::pseudo_bits_cond is not visible in EXPLAIN FORMAT=JSON
diff --git a/mysql-test/t/subselect.test b/mysql-test/t/subselect.test
index 7e16321..57ac431 100644
--- a/mysql-test/t/subselect.test
+++ b/mysql-test/t/subselect.test
@@ -6188,4 +6188,39 @@ SELECT * FROM t t1 RIGHT JOIN t t2 ON (t2.pk = t1.pk)
DROP TABLE t;
+--echo #
+--echo # MDEV-25002: Outer reference in ON clause of subselect
+--echo #
+
+create table t1 (
+ pk int primary key,
+ a int
+) engine=myisam;
+insert into t1 values (1,1), (2,2);
+
+create table t2 (
+ pk int primary key,
+ b int
+) engine=myisam;
+insert into t2 values (1,1), (2,3);
+
+create table t3 (a int);
+insert into t3 values (1),(2);
+
+select a,
+ (select count(*) from t1, t2
+ where t2.pk=t3.a and t1.pk=1) as sq
+from t3;
+select a,
+ (select count(*) from t1 join t2 on t2.pk=t3.a
+ where t1.pk=1) as sq
+from t3;
+
+select a from t3
+ where a in (select t2.b from t1,t2 where t2.pk=t3.a and t1.pk=1);
+select a from t3
+ where a in (select t2.b from t1 join t2 on t2.pk=t3.a where t1.pk=1);
+
+drop table t1,t2,t3;
+
--echo # End of 10.2 tests
diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc
index 7f75624..e8ab9a2 100644
--- a/sql/sql_parse.cc
+++ b/sql/sql_parse.cc
@@ -8774,6 +8774,8 @@ push_new_name_resolution_context(THD *thd,
left_op->first_leaf_for_name_resolution();
on_context->last_name_resolution_table=
right_op->last_leaf_for_name_resolution();
+ on_context->select_lex = thd->lex->current_select;
+ on_context->outer_context = thd->lex->current_context()->outer_context;
return thd->lex->push_context(on_context, thd->mem_root);
}
1
0
revision-id: 9554d6f4a3419a080c4cbf393bbcde25119b13da (mariadb-10.5.2-426-g9554d6f4a34)
parent(s): 079179eacbd74f5c73c93d8947aaf4838556393e
author: Sergei Petrunia
committer: Sergei Petrunia
timestamp: 2021-03-04 12:58:05 +0300
message:
MDEV-17399: JSON_TABLE, code cleanup
In class Json_table_nested_path:
- Make most members private.
- Provide the interfaces:
= "Builder" interface for use from the parser.
= Scanning/reading the value interface for use from the executor.
In class Table_function_json_table:
- Make most members private.
- Provide the interfaces:
= "Builder" for use from the parser. There are still a few public
member vars that are used as parser context.
= Functions to get/set various properties from the optimizer.
- Remove unused members: m_depth, m_cur_depth.
In class ha_json_table:
- Mark override functions with "override".
- Remove unnecessary function implementations.
- Factor out common code from rnd_next() and rnd_pos() into
fill_column_values().
In class Create_json_table:
- Remove force_not_null_cols argument support.
---
sql/sql_yacc.yy | 9 +-
sql/table_function.cc | 337 ++++++++++++++++++++++++++------------------------
sql/table_function.h | 149 +++++++++++++---------
3 files changed, 271 insertions(+), 224 deletions(-)
diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy
index eb36e7f3b78..166abfc55b9 100644
--- a/sql/sql_yacc.yy
+++ b/sql/sql_yacc.yy
@@ -11535,7 +11535,7 @@ json_table_column:
lex->json_table->m_cur_json_table_column=
new (thd->mem_root) Json_table_column(f,
- lex->json_table->m_sql_nest);
+ lex->json_table->get_cur_nested_path());
if (unlikely(!f ||
!lex->json_table->m_cur_json_table_column))
@@ -11556,14 +11556,14 @@ json_table_column:
{
LEX *lex=Lex;
Json_table_nested_path *np= new (thd->mem_root)
- Json_table_nested_path(lex->json_table->m_sql_nest);
+ Json_table_nested_path();
np->set_path(thd, $3);
- lex->json_table->add_nested(np);
+ lex->json_table->start_nested_path(np);
}
json_table_columns_clause
{
LEX *lex=Lex;
- lex->json_table->leave_nested();
+ lex->json_table->end_nested_path();
}
;
@@ -11659,7 +11659,6 @@ table_function:
if (unlikely(!jt))
MYSQL_YYABORT;
Lex->json_table= jt;
- jt->m_sql_nest= &jt->m_nested_path;
}
json_text_literal json_table_columns_clause ')' opt_as ident_table_alias
{
diff --git a/sql/table_function.cc b/sql/table_function.cc
index a11e12101d3..8c92df94895 100644
--- a/sql/table_function.cc
+++ b/sql/table_function.cc
@@ -46,10 +46,13 @@ static table_function_handlerton table_function_hton;
class ha_json_table: public handler
{
-protected:
Table_function_json_table *m_jt;
- String m_tmps;
- String *m_js;
+
+ String *m_js; // The JSON document we're reading
+ String m_tmps; // Buffer for the above
+
+ int fill_column_values(uchar * buf, uchar *pos);
+
public:
ha_json_table(TABLE_SHARE *share_arg, Table_function_json_table *jt):
handler(&table_function_hton.m_hton, share_arg), m_jt(jt)
@@ -62,44 +65,42 @@ class ha_json_table: public handler
that call makes no sence for ha_json_table.
*/
mark_trx_read_write_done= 1;
+
+ /* See ha_json_table::position for format definition */
ref_length= m_jt->m_columns.elements * 4;
}
~ha_json_table() {}
- handler *clone(const char *name, MEM_ROOT *mem_root) { return NULL; }
- const char *index_type(uint inx) { return "NONE"; }
+ handler *clone(const char *name, MEM_ROOT *mem_root) override { return NULL; }
/* Rows also use a fixed-size format */
- enum row_type get_row_type() const { return ROW_TYPE_FIXED; }
- ulonglong table_flags() const
+ enum row_type get_row_type() const override { return ROW_TYPE_FIXED; }
+ ulonglong table_flags() const override
{
return (HA_FAST_KEY_READ | /*HA_NO_BLOBS |*/ HA_NULL_IN_KEY |
HA_CAN_SQL_HANDLER |
HA_REC_NOT_IN_SEQ | HA_NO_TRANSACTIONS |
- HA_HAS_RECORDS | HA_CAN_HASH_KEYS);
+ HA_HAS_RECORDS);
}
- ulong index_flags(uint inx, uint part, bool all_parts) const
+ ulong index_flags(uint inx, uint part, bool all_parts) const override
{
+ DBUG_ASSERT(0);
return HA_ONLY_WHOLE_INDEX | HA_KEY_SCAN_NOT_ROR;
}
- ha_rows records() { return HA_POS_ERROR; }
- uint max_supported_keys() const { return 1; }
- uint max_supported_key_part_length() const { return MAX_KEY_LENGTH; }
-
- int open(const char *name, int mode, uint test_if_locked);
- int close(void) { return 0; }
- int rnd_init(bool scan);
- int rnd_next(uchar *buf);
- int rnd_pos(uchar * buf, uchar *pos);
- void position(const uchar *record);
- int can_continue_handler_scan() { return 1; }
- int info(uint);
- int extra(enum ha_extra_function operation);
+ ha_rows records() override { return HA_POS_ERROR; }
+
+ int open(const char *name, int mode, uint test_if_locked) override
+ { return 0; }
+ int close(void) override { return 0; }
+ int rnd_init(bool scan) override;
+ int rnd_next(uchar *buf) override;
+ int rnd_pos(uchar * buf, uchar *pos) override;
+ void position(const uchar *record) override;
+ int info(uint) override;
+ int extra(enum ha_extra_function operation) override { return 0; }
THR_LOCK_DATA **store_lock(THD *thd, THR_LOCK_DATA **to,
- enum thr_lock_type lock_type)
+ enum thr_lock_type lock_type) override
{ return NULL; }
int create(const char *name, TABLE *form, HA_CREATE_INFO *create_info)
- { return 1; }
-private:
- void update_key_stats();
+ override { return 1; }
};
@@ -123,7 +124,7 @@ class Create_json_table: public Data_type_statistics
m_null_count(0)
{ }
- void add_field(TABLE *table, Field *field, uint fieldnr, bool force_not_null_cols);
+ void add_field(TABLE *table, Field *field, uint fieldnr);
TABLE *start(THD *thd,
TMP_TABLE_PARAM *param,
@@ -143,14 +144,16 @@ class Create_json_table: public Data_type_statistics
@detail
Note: non-root nested paths are set to scan one JSON node (that is, a
- "subdocument")
+ "subdocument").
+ The [str ... end] typically includes content beyond the end of the
+ subdocoument.
*/
void Json_table_nested_path::scan_start(CHARSET_INFO *i_cs,
const uchar *str, const uchar *end)
{
json_get_path_start(&m_engine, i_cs, str, end, &m_cur_path);
- m_cur_nested= 0;
+ m_cur_nested= NULL;
m_null= false;
m_ordinality_counter= 0;
}
@@ -202,18 +205,6 @@ int Json_table_nested_path::scan_next()
}
-int ha_json_table::open(const char *name, int mode, uint test_if_locked)
-{
- return 0;
-}
-
-
-int ha_json_table::extra(enum ha_extra_function operation)
-{
- return 0;
-}
-
-
int ha_json_table::rnd_init(bool scan)
{
Json_table_nested_path &p= m_jt->m_nested_path;
@@ -229,6 +220,12 @@ int ha_json_table::rnd_init(bool scan)
}
+/*
+ @brief
+ Store JSON value in an SQL field, doing necessary special conversions
+ for JSON's null, true, and false.
+*/
+
static int store_json_in_field(Field *f, const json_engine_t *je)
{
switch (je->value_type)
@@ -253,22 +250,58 @@ static int store_json_in_field(Field *f, const json_engine_t *je)
}
+/*
+ Add the first child to this nested path, connecting it with all necessary
+ links.
+*/
+void Json_table_nested_path::add_first_child(Json_table_nested_path *np)
+{
+ np->m_parent= this;
+ DBUG_ASSERT(!m_nested);
+ m_nested= np;
+}
+
+
+/*
+ Add the next sibling to this nested path, connecting it with all necessary
+ links.
+*/
+
+void Json_table_nested_path::add_sibling(Json_table_nested_path *np)
+{
+ np->m_parent= m_parent;
+ DBUG_ASSERT(!m_next_nested);
+ m_next_nested= np;
+}
+
+
+bool Json_table_nested_path::check_error(const char *str)
+{
+ if (m_engine.s.error)
+ {
+ report_json_error_ex(str, &m_engine, "JSON_TABLE", 0,
+ Sql_condition::WARN_LEVEL_ERROR);
+ return true; // Error
+ }
+ return false; // Ok
+}
+
+
int ha_json_table::rnd_next(uchar *buf)
{
- Field **f= table->field;
- Json_table_column *jc;
enum_check_fields cf_orig;
if (!m_js)
return HA_ERR_END_OF_FILE;
+ /*
+ Step 1: Move the root nested path to the next record (this implies moving
+ its child nested paths accordingly)
+ */
if (m_jt->m_nested_path.scan_next())
{
- if (m_jt->m_nested_path.m_engine.s.error)
+ if (m_jt->m_nested_path.check_error(m_js->ptr()))
{
- report_json_error_ex(m_js->ptr(), &m_jt->m_nested_path.m_engine,
- "JSON_TABLE", 0, Sql_condition::WARN_LEVEL_ERROR);
-
/*
We already reported an error, so returning an
error code that just doesn't produce extra
@@ -278,93 +311,33 @@ int ha_json_table::rnd_next(uchar *buf)
}
return HA_ERR_END_OF_FILE;
}
-
- cf_orig= table->in_use->count_cuted_fields;
- table->in_use->count_cuted_fields= CHECK_FIELD_EXPRESSION;
+
/*
- Get the values for each field of the table
+ Step 2: Read values for all columns (the columns refer to nested paths
+ they are in).
*/
- List_iterator_fast<Json_table_column> jc_i(m_jt->m_columns);
- my_ptrdiff_t ptrdiff= buf - table->record[0];
- while ((jc= jc_i++))
- {
- if (!bitmap_is_set(table->read_set, (*f)->field_index))
- goto cont_loop;
-
- if (ptrdiff)
- (*f)->move_field_offset(ptrdiff);
- if (jc->m_nest->m_null)
- {
- (*f)->set_null();
- }
- else
- {
- (*f)->set_notnull();
- switch (jc->m_column_type)
- {
- case Json_table_column::FOR_ORDINALITY:
- (*f)->store(jc->m_nest->m_ordinality_counter, TRUE);
- break;
- case Json_table_column::PATH:
- case Json_table_column::EXISTS_PATH:
- {
- json_engine_t je;
- json_engine_t &nest_je= jc->m_nest->m_engine;
- json_path_step_t *cur_step;
- uint array_counters[JSON_DEPTH_LIMIT];
- int not_found;
-
- json_scan_start(&je, nest_je.s.cs,
- nest_je.value_begin, nest_je.s.str_end);
-
- cur_step= jc->m_path.steps;
- not_found= json_find_path(&je, &jc->m_path, &cur_step, array_counters) ||
- json_read_value(&je);
+ cf_orig= table->in_use->count_cuted_fields;
+ table->in_use->count_cuted_fields= CHECK_FIELD_EXPRESSION;
- if (jc->m_column_type == Json_table_column::EXISTS_PATH)
- {
- (*f)->store(!not_found);
- }
- else /*PATH*/
- {
- if (not_found)
- jc->m_on_empty.respond(jc, *f);
- else
- {
- if (!json_value_scalar(&je) ||
- store_json_in_field(*f, &je))
- jc->m_on_error.respond(jc, *f);
- else
- {
- /*
- If the path contains wildcards, check if there are
- more matches for it in json and report an error if so.
- */
- if (jc->m_path.types_used &
- (JSON_PATH_WILD | JSON_PATH_DOUBLE_WILD) &&
- (json_scan_next(&je) ||
- !json_find_path(&je, &jc->m_path, &cur_step,
- array_counters)))
- jc->m_on_error.respond(jc, *f);
- }
+ fill_column_values(buf, NULL);
- }
- }
- break;
- }
- };
- }
- if (ptrdiff)
- (*f)->move_field_offset(-ptrdiff);
-cont_loop:
- f++;
- }
table->in_use->count_cuted_fields= cf_orig;
return 0;
}
-int ha_json_table::rnd_pos(uchar * buf, uchar *pos)
+/*
+ @brief
+ Fill values of table columns, taking data either from Json_nested_path
+ objects, or from the rowid value
+
+ @param pos NULL means the data should be read from Json_nested_path
+ objects.
+ Non-null value is a pointer to previously saved rowid (see
+ ha_json_table::position() for description)
+*/
+
+int ha_json_table::fill_column_values(uchar * buf, uchar *pos)
{
Field **f= table->field;
Json_table_column *jc;
@@ -373,7 +346,8 @@ int ha_json_table::rnd_pos(uchar * buf, uchar *pos)
while ((jc= jc_i++))
{
- uint f_pos;
+ bool is_null_value;
+ uint int_pos;
if (!bitmap_is_set(table->read_set, (*f)->field_index))
goto cont_loop;
@@ -381,9 +355,17 @@ int ha_json_table::rnd_pos(uchar * buf, uchar *pos)
if (ptrdiff)
(*f)->move_field_offset(ptrdiff);
- f_pos= uint4korr(pos);
+ /*
+ Read the NULL flag:
+ - if we are reading from a rowid value, 0 means SQL NULL.
+ - if scanning json document, read it from the nested path
+ */
+ if (pos)
+ is_null_value= !(int_pos= uint4korr(pos));
+ else
+ is_null_value= jc->m_nest->m_null;
- if (f_pos == 0)
+ if (is_null_value)
{
(*f)->set_null();
}
@@ -393,8 +375,16 @@ int ha_json_table::rnd_pos(uchar * buf, uchar *pos)
switch (jc->m_column_type)
{
case Json_table_column::FOR_ORDINALITY:
- (*f)->store(f_pos, TRUE);
+ {
+ /*
+ Read the cardinality counter:
+ - read it from nested path when scanning the json document
+ - or, read it from rowid when in rnd_pos() call
+ */
+ longlong counter= pos? int_pos: jc->m_nest->m_ordinality_counter;
+ (*f)->store(counter, TRUE);
break;
+ }
case Json_table_column::PATH:
case Json_table_column::EXISTS_PATH:
{
@@ -402,11 +392,27 @@ int ha_json_table::rnd_pos(uchar * buf, uchar *pos)
json_path_step_t *cur_step;
uint array_counters[JSON_DEPTH_LIMIT];
int not_found;
+ const uchar* node_start;
+ const uchar* node_end;
+
+ /*
+ Get the JSON context node that we will need to evaluate PATH or
+ EXISTS against:
+ - when scanning the json document, read it from nested path
+ - when in rnd_pos call, the rowid has the start offset.
+ */
+ if (pos)
+ {
+ node_start= (const uchar *) (m_js->ptr() + (int_pos-1));
+ node_end= (const uchar *) m_js->end();
+ }
+ else
+ {
+ node_start= jc->m_nest->get_value();
+ node_end= jc->m_nest->get_value_end();
+ }
- json_scan_start(&je, m_js->charset(),
- (const uchar *) (m_js->ptr() + (f_pos-1)),
- (const uchar *) m_js->end());
-
+ json_scan_start(&je, m_js->charset(), node_start, node_end);
cur_step= jc->m_path.steps;
not_found= json_find_path(&je, &jc->m_path, &cur_step, array_counters) ||
json_read_value(&je);
@@ -447,12 +453,19 @@ int ha_json_table::rnd_pos(uchar * buf, uchar *pos)
(*f)->move_field_offset(-ptrdiff);
cont_loop:
f++;
- pos+= 4;
+ if (pos)
+ pos+= 4;
}
return 0;
}
+int ha_json_table::rnd_pos(uchar * buf, uchar *pos)
+{
+ return fill_column_values(buf, pos);
+}
+
+
/*
The reference has 4 bytes for every column of the JSON_TABLE.
There it keeps 0 for the NULL values, ordinality index for
@@ -481,7 +494,7 @@ void ha_json_table::position(const uchar *record)
case Json_table_column::PATH:
case Json_table_column::EXISTS_PATH:
{
- size_t pos= jc->m_nest->m_engine.value_begin -
+ size_t pos= jc->m_nest->get_value() -
(const uchar *) m_js->ptr() + 1;
int4store(c_ref, pos);
break;
@@ -506,18 +519,11 @@ int ha_json_table::info(uint)
}
-void Create_json_table::add_field(TABLE *table, Field *field,
- uint fieldnr, bool force_not_null_cols)
+void Create_json_table::add_field(TABLE *table, Field *field, uint fieldnr)
{
DBUG_ASSERT(!field->field_name.str ||
strlen(field->field_name.str) == field->field_name.length);
- if (force_not_null_cols)
- {
- field->flags|= NOT_NULL_FLAG;
- field->null_ptr= NULL;
- }
-
if (!(field->flags & NOT_NULL_FLAG))
m_null_count++;
@@ -858,7 +864,7 @@ bool Create_json_table::add_json_table_fields(THD *thd, TABLE *table,
if (!f)
goto err_exit;
f->init(table);
- add_field(table, f, fieldnr++, FALSE);
+ add_field(table, f, fieldnr++);
}
share->fields= fieldnr;
@@ -1090,23 +1096,31 @@ int Json_table_column::On_response::print(const char *name, String *str) const
}
-void Table_function_json_table::add_nested(Json_table_nested_path *np)
-{
- *m_sql_nest->m_nested_hook= np;
- m_sql_nest->m_nested_hook= &np->m_next_nested;
- m_sql_nest= np;
- if (++m_cur_depth > m_depth)
- m_depth= m_cur_depth;
+void Table_function_json_table::start_nested_path(Json_table_nested_path *np)
+{
+ if (cur_last_sibling)
+ {
+ cur_last_sibling->add_sibling(np);
+ cur_last_sibling= np;
+ }
+ else
+ {
+ cur_parent->add_first_child(np);
+ cur_last_sibling= np;
+ }
+
+ // Make the newly added path the parent
+ cur_parent= cur_last_sibling;
+ cur_last_sibling= NULL;
}
-void Table_function_json_table::leave_nested()
-{
- m_sql_nest= m_sql_nest->m_parent;
- --m_cur_depth;
+void Table_function_json_table::end_nested_path()
+{
+ cur_last_sibling= cur_parent;
+ cur_parent= cur_parent->get_parent();
}
-
/*
@brief
Perform name-resolution phase tasks
@@ -1174,8 +1188,10 @@ int Table_function_json_table::setup(THD *thd, TABLE_LIST *sql_table,
return FALSE;
}
+
void Table_function_json_table::get_estimates(ha_rows *out_rows,
- double *scan_time, double *startup_cost)
+ double *scan_time,
+ double *startup_cost)
{
*out_rows= 40;
*scan_time= 0.0;
@@ -1282,13 +1298,12 @@ int Table_function_json_table::print(THD *thd, TABLE_LIST *sql_table,
void Table_function_json_table::fix_after_pullout(TABLE_LIST *sql_table,
st_select_lex *new_parent, bool merge)
{
- if (m_dep_tables)
- sql_table->dep_tables&= ~m_dep_tables;
+ sql_table->dep_tables&= ~m_dep_tables;
+
m_json->fix_after_pullout(new_parent, &m_json, merge);
m_dep_tables= m_json->used_tables();
- if (m_dep_tables)
- sql_table->dep_tables|= m_dep_tables;
+ sql_table->dep_tables|= m_dep_tables;
}
diff --git a/sql/table_function.h b/sql/table_function.h
index 822af71c83d..14cae72c8e2 100644
--- a/sql/table_function.h
+++ b/sql/table_function.h
@@ -36,32 +36,54 @@ class Json_table_column;
COLUMNS( a INT PATH '$.a' ,
NESTED PATH '$.b[*]' COLUMNS (b INT PATH '$',
NESTED PATH '$.c[*]' COLUMNS(x INT PATH '$')),
- NESTED PATH '$.n[*]' COLUMNS (z INT PAHT '$'))
+ NESTED PATH '$.n[*]' COLUMNS (z INT PATH '$'))
results in 4 'nested_path' created:
root nested_b nested_c nested_n
m_path '$[*]' '$.b[*]' '$.c[*]' '$.n[*]
m_nested &nested_b &nested_c NULL NULL
n_next_nested NULL &nested_n NULL NULL
-and 4 columns created:
+ and 4 columns created:
a b x z
m_nest &root &nested_b &nested_c &nested_n
*/
-
class Json_table_nested_path : public Sql_alloc
{
public:
- bool m_null; // TRUE <=> produce SQL NULL.
+ json_path_t m_path; /* The JSON Path to get the rows from */
- json_path_t m_path;
- json_engine_t m_engine;
- json_path_t m_cur_path;
+ /*** Construction interface ***/
+ Json_table_nested_path():
+ m_null(TRUE), m_parent(NULL), m_nested(NULL), m_next_nested(NULL)
+ {}
+
+ int set_path(THD *thd, const LEX_CSTRING &path);
+
+ void add_first_child(Json_table_nested_path *np);
+ void add_sibling(Json_table_nested_path *np);
+ Json_table_nested_path *get_parent() { return m_parent; }
+
+ /*** Methods for performing a scan ***/
+ void scan_start(CHARSET_INFO *i_cs, const uchar *str, const uchar *end);
+ int scan_next();
+ bool check_error(const char *str);
- /* Counts the rows produced. Value is set to the FOR ORDINALITY coluns */
+ /*** Members for getting the values we've scanned to ***/
+ const uchar *get_value() { return m_engine.value_begin; }
+ const uchar *get_value_end() { return m_engine.s.str_end; }
+
+ bool m_null; // TRUE <=> producing a NULL-complemented row.
+
+ /* Counts the rows produced. Used by FOR ORDINALITY columns */
longlong m_ordinality_counter;
- /* the Json_table_nested_path that nests this. */
+ int print(THD *thd, Field ***f, String *str,
+ List_iterator_fast<Json_table_column> &it,
+ Json_table_column **last_column);
+private:
+ /*** Members describing NESTED PATH structure ***/
+ /* Parent nested path. The "root" path has this NULL */
Json_table_nested_path *m_parent;
/* The head of the list of nested NESTED PATH statements. */
@@ -70,30 +92,28 @@ class Json_table_nested_path : public Sql_alloc
/* in the above list items are linked with the */
Json_table_nested_path *m_next_nested;
- /*
- The pointer to the 'm_next_nested' member of the
- last item of the above list. So we can add new item to
- the list doing *m_next_nexted_hook= new_item_ptr
- */
- Json_table_nested_path **m_nested_hook;
+ /*** Members describing current JSON Path scan state ***/
+ /* The JSON Parser and JSON Path evaluator */
+ json_engine_t m_engine;
- /*
- The NESTED PATH that is currently scanned in rnd_next.
- */
+ /* The path the parser is currently pointing to */
+ json_path_t m_cur_path;
+
+ /* The child NESTED PATH we're currently scanning */
Json_table_nested_path *m_cur_nested;
- Json_table_nested_path(Json_table_nested_path *parent_nest):
- m_null(TRUE), m_parent(parent_nest), m_nested(0), m_next_nested(0),
- m_nested_hook(&m_nested) {}
- int set_path(THD *thd, const LEX_CSTRING &path);
- void scan_start(CHARSET_INFO *i_cs, const uchar *str, const uchar *end);
- int scan_next();
- int print(THD *thd, Field ***f, String *str,
- List_iterator_fast<Json_table_column> &it,
- Json_table_column **last_column);
};
+/*
+ @brief
+ Describes the column definition in JSON_TABLE(...) syntax.
+
+ @detail
+ Has methods for printing/handling errors but otherwise it's a static
+ object.
+*/
+
class Json_table_column : public Sql_alloc
{
public:
@@ -166,57 +186,70 @@ class Json_table_column : public Sql_alloc
Then the ha_json_table instance is created based on it in
the create_table_for_function().
*/
+
class Table_function_json_table : public Sql_alloc
{
public:
+ /*** Basic properties of the original JSON_TABLE(...) ***/
+
Item *m_json; /* The JSON value to be parsed. */
/* The COLUMNS(...) part representation. */
Json_table_nested_path m_nested_path;
+
/* The list of table column definitions. */
List<Json_table_column> m_columns;
+ /*** Name resolution functions ***/
+ int setup(THD *thd, TABLE_LIST *sql_table, SELECT_LEX *s_lex);
+
+ /*** Functions for interaction with the Query Optimizer ***/
+ void fix_after_pullout(TABLE_LIST *sql_table,
+ st_select_lex *new_parent, bool merge);
+ void update_used_tables() { m_json->update_used_tables(); }
+
+ table_map used_tables() const { return m_dep_tables; }
+ bool join_cache_allowed() const { return !m_dep_tables; }
+ void get_estimates(ha_rows *out_rows,
+ double *scan_time, double *startup_cost);
+
+ int print(THD *thd, TABLE_LIST *sql_table,
+ String *str, enum_query_type query_type);
+
+ /*** Construction interface to be used from the parser ***/
+ Table_function_json_table(Item *json):
+ m_json(json)
+ {
+ cur_parent= &m_nested_path;
+ cur_last_sibling= NULL;
+ }
+
+ void start_nested_path(Json_table_nested_path *np);
+ void end_nested_path();
+ Json_table_nested_path *get_cur_nested_path() { return cur_parent; }
+
+ /* SQL Parser: current column in JSON_TABLE (...) syntax */
+ Json_table_column *m_cur_json_table_column;
+
+ /* SQL Parser: charset of the current text literal */
+ CHARSET_INFO *m_text_literal_cs;
+
+private:
/*
the JSON argument can be taken from other tables.
We have to mark these tables as dependent so the
mask of these dependent tables is calculated in ::setup().
*/
table_map m_dep_tables;
-
- /*
- The 'depth' of NESTED PATH statements nesting.
- Needed to calculate the reference length.
- m_cur_depth is used in parser.
- */
- uint m_depth, m_cur_depth;
-
- /* used in parser. */
- Json_table_column *m_cur_json_table_column;
- CHARSET_INFO *m_text_literal_cs;
-
- Table_function_json_table(Item *json):
- m_json(json), m_nested_path(0), m_depth(0), m_cur_depth(0) {}
+ /* Current NESTED PATH level being parsed */
+ Json_table_nested_path *cur_parent;
/*
- Used in sql_yacc.yy.
- Represents the current NESTED PATH level being parsed.
+ The last sibling in the current level, if there is any. We need this
+ to call add_sibling() for it
*/
- Json_table_nested_path *m_sql_nest;
- void add_nested(Json_table_nested_path *np);
- void leave_nested();
+ Json_table_nested_path *cur_last_sibling;
- int setup(THD *thd, TABLE_LIST *sql_table, SELECT_LEX *s_lex);
- /* if the table is ready to be used in Item_field::fix_fieds */
- bool join_cache_allowed() const { return !m_dep_tables; }
- table_map used_tables() const { return m_dep_tables; }
- void get_estimates(ha_rows *out_rows,
- double *scan_time, double *startup_cost);
- int print(THD *thd, TABLE_LIST *sql_table,
- String *str, enum_query_type query_type);
-
- void fix_after_pullout(TABLE_LIST *sql_table,
- st_select_lex *new_parent, bool merge);
- void update_used_tables() { m_json->update_used_tables(); }
};
1
0