MDEV-6111 Optimizer Trace
This task involves the implementation for the optimizer trace. This feature produces a trace for any SELECT/UPDATE/DELETE/, which contains information about decisions taken by the optimizer during the optimization phase (choice of table access method, various costs, transformations, etc). This feature would help to tell why some decisions were taken by the optimizer and why some were rejected. Trace is session-local, controlled by the @@optimizer_trace variable. To enable optimizer trace we need to write: set @@optimizer_trace variable= 'enabled=on'; To display the trace one can run: SELECT trace FROM INFORMATION_SCHEMA.OPTIMIZER_TRACE; This task also involves: MDEV-18489: Limit the memory used by the optimizer trace introduces a switch optimizer_trace_max_mem_size which limits the memory used by the optimizer trace. This was implemented by Sergei Petrunia.
This commit is contained in:
parent
6b979416e0
commit
be8709eb7b
@ -122,6 +122,7 @@ SET(SQL_EMBEDDED_SOURCES emb_qcache.cc libmysqld.c lib_sql.cc
|
||||
../sql/sql_tvc.cc ../sql/sql_tvc.h
|
||||
../sql/opt_split.cc
|
||||
../sql/item_vers.cc
|
||||
../sql/opt_trace.cc
|
||||
${GEN_SOURCES}
|
||||
${MYSYS_LIBWRAP_SOURCE}
|
||||
)
|
||||
|
@ -67,6 +67,7 @@ GLOBAL_VARIABLES
|
||||
INDEX_STATISTICS
|
||||
KEY_CACHES
|
||||
KEY_COLUMN_USAGE
|
||||
OPTIMIZER_TRACE
|
||||
PARAMETERS
|
||||
PARTITIONS
|
||||
PLUGINS
|
||||
@ -864,6 +865,8 @@ information_schema COLUMNS COLUMN_DEFAULT
|
||||
information_schema COLUMNS COLUMN_TYPE
|
||||
information_schema COLUMNS GENERATION_EXPRESSION
|
||||
information_schema EVENTS EVENT_DEFINITION
|
||||
information_schema OPTIMIZER_TRACE QUERY
|
||||
information_schema OPTIMIZER_TRACE TRACE
|
||||
information_schema PARAMETERS DTD_IDENTIFIER
|
||||
information_schema PARTITIONS PARTITION_EXPRESSION
|
||||
information_schema PARTITIONS SUBPARTITION_EXPRESSION
|
||||
|
@ -43,6 +43,7 @@ INNODB_TABLESPACES_SCRUBBING
|
||||
INNODB_TRX
|
||||
KEY_CACHES
|
||||
KEY_COLUMN_USAGE
|
||||
OPTIMIZER_TRACE
|
||||
PARAMETERS
|
||||
PARTITIONS
|
||||
PLUGINS
|
||||
@ -123,6 +124,7 @@ INNODB_TABLESPACES_SCRUBBING SPACE
|
||||
INNODB_TRX trx_id
|
||||
KEY_CACHES KEY_CACHE_NAME
|
||||
KEY_COLUMN_USAGE CONSTRAINT_SCHEMA
|
||||
OPTIMIZER_TRACE QUERY
|
||||
PARAMETERS SPECIFIC_SCHEMA
|
||||
PARTITIONS TABLE_SCHEMA
|
||||
PLUGINS PLUGIN_NAME
|
||||
@ -203,6 +205,7 @@ INNODB_TABLESPACES_SCRUBBING SPACE
|
||||
INNODB_TRX trx_id
|
||||
KEY_CACHES KEY_CACHE_NAME
|
||||
KEY_COLUMN_USAGE CONSTRAINT_SCHEMA
|
||||
OPTIMIZER_TRACE QUERY
|
||||
PARAMETERS SPECIFIC_SCHEMA
|
||||
PARTITIONS TABLE_SCHEMA
|
||||
PLUGINS PLUGIN_NAME
|
||||
@ -288,6 +291,7 @@ INNODB_TABLESPACES_SCRUBBING information_schema.INNODB_TABLESPACES_SCRUBBING 1
|
||||
INNODB_TRX information_schema.INNODB_TRX 1
|
||||
KEY_CACHES information_schema.KEY_CACHES 1
|
||||
KEY_COLUMN_USAGE information_schema.KEY_COLUMN_USAGE 1
|
||||
OPTIMIZER_TRACE information_schema.OPTIMIZER_TRACE 1
|
||||
PARAMETERS information_schema.PARAMETERS 1
|
||||
PARTITIONS information_schema.PARTITIONS 1
|
||||
PLUGINS information_schema.PLUGINS 1
|
||||
@ -358,6 +362,7 @@ Database: information_schema
|
||||
| INNODB_TRX |
|
||||
| KEY_CACHES |
|
||||
| KEY_COLUMN_USAGE |
|
||||
| OPTIMIZER_TRACE |
|
||||
| PARAMETERS |
|
||||
| PARTITIONS |
|
||||
| PLUGINS |
|
||||
@ -428,6 +433,7 @@ Database: INFORMATION_SCHEMA
|
||||
| INNODB_TRX |
|
||||
| KEY_CACHES |
|
||||
| KEY_COLUMN_USAGE |
|
||||
| OPTIMIZER_TRACE |
|
||||
| PARAMETERS |
|
||||
| PARTITIONS |
|
||||
| PLUGINS |
|
||||
@ -459,5 +465,5 @@ Wildcard: inf_rmation_schema
|
||||
| information_schema |
|
||||
SELECT table_schema, count(*) FROM information_schema.TABLES WHERE table_schema IN ('mysql', 'INFORMATION_SCHEMA', 'test', 'mysqltest') GROUP BY TABLE_SCHEMA;
|
||||
table_schema count(*)
|
||||
information_schema 65
|
||||
information_schema 66
|
||||
mysql 31
|
||||
|
@ -693,6 +693,12 @@ The following specify which files/extra groups are read (specified before remain
|
||||
extended_keys, exists_to_in, orderby_uses_equalities,
|
||||
condition_pushdown_for_derived, split_materialized,
|
||||
condition_pushdown_for_subquery
|
||||
--optimizer-trace=name
|
||||
Controls tracing of the Optimizer:
|
||||
optimizer_trace=option=val[,option=val...], where option
|
||||
is one of {enabled} and val is one of {on, off, default}
|
||||
--optimizer-trace-max-mem-size=#
|
||||
Maximum allowed size of an optimizer trace
|
||||
--optimizer-use-condition-selectivity=#
|
||||
Controls selectivity of which conditions the optimizer
|
||||
takes into account to calculate cardinality of a partial
|
||||
@ -1563,6 +1569,8 @@ optimizer-prune-level 1
|
||||
optimizer-search-depth 62
|
||||
optimizer-selectivity-sampling-limit 100
|
||||
optimizer-switch index_merge=on,index_merge_union=on,index_merge_sort_union=on,index_merge_intersection=on,index_merge_sort_intersection=off,engine_condition_pushdown=off,index_condition_pushdown=on,derived_merge=on,derived_with_keys=on,firstmatch=on,loosescan=on,materialization=on,in_to_exists=on,semijoin=on,partial_match_rowid_merge=on,partial_match_table_scan=on,subquery_cache=on,mrr=off,mrr_cost_based=off,mrr_sort_keys=off,outer_join_with_cache=on,semijoin_with_cache=on,join_cache_incremental=on,join_cache_hashed=on,join_cache_bka=on,optimize_join_buffer_size=off,table_elimination=on,extended_keys=on,exists_to_in=on,orderby_uses_equalities=on,condition_pushdown_for_derived=on,split_materialized=on,condition_pushdown_for_subquery=on
|
||||
optimizer-trace
|
||||
optimizer-trace-max-mem-size 1048576
|
||||
optimizer-use-condition-selectivity 4
|
||||
performance-schema FALSE
|
||||
performance-schema-accounts-size -1
|
||||
|
3499
mysql-test/main/opt_trace.result
Normal file
3499
mysql-test/main/opt_trace.result
Normal file
File diff suppressed because it is too large
Load Diff
335
mysql-test/main/opt_trace.test
Normal file
335
mysql-test/main/opt_trace.test
Normal file
@ -0,0 +1,335 @@
|
||||
--source include/not_embedded.inc
|
||||
SELECT table_name, column_name FROM information_schema.columns where table_name="OPTIMIZER_TRACE";
|
||||
show variables like 'optimizer_trace';
|
||||
set optimizer_trace="enabled=on";
|
||||
show variables like 'optimizer_trace';
|
||||
set optimizer_trace="enabled=off";
|
||||
create table t1 (a int, b int);
|
||||
insert into t1 values (1,2),(2,3);
|
||||
|
||||
create table t2 (b int);
|
||||
insert into t2 values (1),(2);
|
||||
|
||||
analyze table t1;
|
||||
analyze table t2;
|
||||
create function f1 (a int) returns INT
|
||||
return 1;
|
||||
|
||||
create view v1 as select * from t1 where t1.a=1;
|
||||
create view v2 as select * from t1 where t1.a=1 group by t1.b;
|
||||
set optimizer_trace="enabled=on";
|
||||
|
||||
--echo # Mergeable views/derived tables
|
||||
select * from v1;
|
||||
select * from information_schema.OPTIMIZER_TRACE;
|
||||
select * from (select * from t1 where t1.a=1)q;
|
||||
select * from information_schema.OPTIMIZER_TRACE;
|
||||
|
||||
--echo # Non-Mergeable views
|
||||
select * from v2;
|
||||
select * from information_schema.OPTIMIZER_TRACE;
|
||||
|
||||
drop table t1,t2;
|
||||
drop view v1,v2;
|
||||
drop function f1;
|
||||
|
||||
create table t1(a int, b int);
|
||||
insert into t1 values (0,0),(1,1),(2,1),(3,2),(4,3),
|
||||
(5,3),(6,3),(7,3),(8,3),(9,3);
|
||||
create table t2(a int, b int);
|
||||
insert into t2 values (0,0),(1,1),(2,1),(3,2),(4,3),
|
||||
(5,3),(6,3),(7,3),(8,3),(9,3);
|
||||
|
||||
ANALYZE TABLE t1;
|
||||
ANALYZE TABLE t2;
|
||||
|
||||
create view v1 as select a from t1 group by b;
|
||||
create view v2 as select a from t2;
|
||||
|
||||
--echo # Mergeable view
|
||||
explain select * from v2 ;
|
||||
select * from information_schema.OPTIMIZER_TRACE;
|
||||
|
||||
--echo # Non-Mergeable view
|
||||
explain select * from v1 ;
|
||||
select * from information_schema.OPTIMIZER_TRACE;
|
||||
drop table t1,t2;
|
||||
drop view v1,v2;
|
||||
|
||||
--echo #
|
||||
--echo # print ref-keyues array
|
||||
--echo #
|
||||
|
||||
create table t0 (a int);
|
||||
INSERT INTO t0 VALUES (0),(1),(2),(3),(4),(5),(6),(7),(8),(9);
|
||||
|
||||
create table t1 (a int, b int, c int, key(a));
|
||||
insert into t1 select A.a*10 + B.a, A.a*10 + B.a, A.a*10 + B.a from t0 A, t0 B;
|
||||
|
||||
create table t2(a int, b int, c int , key(a));
|
||||
insert into t2 select A.a*10 + B.a, A.a*10 + B.a, A.a*10 + B.a from t0 A, t0 B;
|
||||
|
||||
analyze table t1;
|
||||
analyze table t2;
|
||||
|
||||
explain select * from t1,t2 where t1.a=t2.b+2 and t2.a= t1.b;
|
||||
select * from information_schema.OPTIMIZER_TRACE;
|
||||
drop table t1,t2,t0;
|
||||
|
||||
--echo #
|
||||
--echo # group_by min max optimization
|
||||
--echo #
|
||||
CREATE TABLE t1 (id INT NOT NULL AUTO_INCREMENT PRIMARY KEY, a INT NOT NULL, KEY(a));
|
||||
--disable_query_log
|
||||
INSERT INTO t1(a) VALUES (1), (2), (3), (4);
|
||||
INSERT INTO t1(a) SELECT a FROM t1;
|
||||
INSERT INTO t1(a) SELECT a FROM t1;
|
||||
INSERT INTO t1(a) SELECT a FROM t1;
|
||||
INSERT INTO t1(a) SELECT a FROM t1;
|
||||
INSERT INTO t1(a) SELECT a FROM t1;
|
||||
INSERT INTO t1(a) SELECT a FROM t1;
|
||||
INSERT INTO t1(a) SELECT a FROM t1;
|
||||
INSERT INTO t1(a) SELECT a FROM t1;
|
||||
INSERT INTO t1(a) SELECT a FROM t1;
|
||||
INSERT INTO t1(a) SELECT a FROM t1;
|
||||
INSERT INTO t1(a) SELECT a FROM t1;
|
||||
INSERT INTO t1(a) SELECT a FROM t1;
|
||||
INSERT INTO t1(a) SELECT a FROM t1;
|
||||
INSERT INTO t1(a) SELECT a FROM t1;
|
||||
--enable_query_log
|
||||
|
||||
analyze table t1;
|
||||
EXPLAIN SELECT DISTINCT a FROM t1;
|
||||
select * from information_schema.OPTIMIZER_TRACE;
|
||||
drop table t1;
|
||||
|
||||
--echo #
|
||||
--echo # With group by , where clause and MIN/MAX function
|
||||
--echo #
|
||||
CREATE TABLE t1 (a INT, b INT, c int, d int, KEY(a,b,c,d));
|
||||
INSERT INTO t1 VALUES (1,1,1,1), (2,2,2,2), (3,3,3,3), (4,4,4,4), (1,0,1,1), (3,2,3,3), (4,5,4,4);
|
||||
ANALYZE TABLE t1;
|
||||
EXPLAIN SELECT MIN(d) FROM t1 where b=2 and c=3 group by a;
|
||||
select * from information_schema.OPTIMIZER_TRACE;
|
||||
DROP TABLE t1;
|
||||
|
||||
CREATE TABLE t1 (id INT NOT NULL, a DATE, KEY(id,a));
|
||||
INSERT INTO t1 values (1,'2001-01-01'),(1,'2001-01-02'),
|
||||
(1,'2001-01-03'),(1,'2001-01-04'),
|
||||
(2,'2001-01-01'),(2,'2001-01-02'),
|
||||
(2,'2001-01-03'),(2,'2001-01-04'),
|
||||
(3,'2001-01-01'),(3,'2001-01-02'),
|
||||
(3,'2001-01-03'),(3,'2001-01-04'),
|
||||
(4,'2001-01-01'),(4,'2001-01-02'),
|
||||
(4,'2001-01-03'),(4,'2001-01-04');
|
||||
set optimizer_trace='enabled=on';
|
||||
EXPLAIN SELECT id,MIN(a),MAX(a) FROM t1 WHERE a>=20010104e0 GROUP BY id;
|
||||
select * from INFORMATION_SCHEMA.OPTIMIZER_TRACE;
|
||||
EXPLAIN SELECT * FROM t1 WHERE a = 20010104e0 GROUP BY id;
|
||||
select * from INFORMATION_SCHEMA.OPTIMIZER_TRACE;
|
||||
drop table t1;
|
||||
|
||||
--echo #
|
||||
--echo # Late ORDER BY optimization
|
||||
--echo #
|
||||
|
||||
create table ten(a int);
|
||||
insert into ten values (0),(1),(2),(3),(4),(5),(6),(7),(8),(9);
|
||||
create table one_k(a int primary key);
|
||||
insert into one_k select A.a + B.a* 10 + C.a * 100 from ten A, ten B, ten C;
|
||||
create table t1 (
|
||||
pk int not null,
|
||||
a int,
|
||||
b int,
|
||||
c int,
|
||||
filler char(100),
|
||||
KEY a_a(c),
|
||||
KEY a_c(a,c),
|
||||
KEY a_b(a,b)
|
||||
);
|
||||
|
||||
insert into t1
|
||||
select a, a,a,a, 'filler-dataaa' from test.one_k;
|
||||
update t1 set a=1 where pk between 0 and 180;
|
||||
update t1 set b=2 where pk between 0 and 20;
|
||||
analyze table t1;
|
||||
set optimizer_trace='enabled=on';
|
||||
explain select * from t1 where a=1 and b=2 order by c limit 1;
|
||||
select * from INFORMATION_SCHEMA.OPTIMIZER_TRACE;
|
||||
drop table t1,ten,one_k;
|
||||
|
||||
--echo #
|
||||
--echo # TABLE ELIMINATION
|
||||
--echo #
|
||||
|
||||
create table t1 (a int);
|
||||
insert into t1 values (0),(1),(2),(3);
|
||||
create table t0 as select * from t1;
|
||||
|
||||
create table t2 (a int primary key, b int)
|
||||
as select a, a as b from t1 where a in (1,2);
|
||||
|
||||
create table t3 (a int primary key, b int)
|
||||
as select a, a as b from t1 where a in (1,3);
|
||||
|
||||
set optimizer_trace='enabled=on';
|
||||
|
||||
analyze table t1;
|
||||
analyze table t2;
|
||||
analyze table t3;
|
||||
|
||||
--echo # table t2 should be eliminated
|
||||
explain
|
||||
select t1.a from t1 left join t2 on t1.a=t2.a;
|
||||
select * from INFORMATION_SCHEMA.OPTIMIZER_TRACE;
|
||||
|
||||
--echo # no tables should be eliminated
|
||||
explain select * from t1 left join t2 on t2.a=t1.a;
|
||||
select * from INFORMATION_SCHEMA.OPTIMIZER_TRACE;
|
||||
|
||||
--echo # multiple tables are eliminated
|
||||
explain select t1.a from t1 left join (t2 join t3 on t2.b=t3.b) on t2.a=t1.a and t3.a=t1.a;
|
||||
select * from INFORMATION_SCHEMA.OPTIMIZER_TRACE;
|
||||
drop table t0, t1, t2, t3;
|
||||
|
||||
--echo #
|
||||
--echo # IN subquery to sem-join is traced
|
||||
--echo #
|
||||
|
||||
create table t0 (a int);
|
||||
insert into t0 values (0),(1),(2),(3),(4),(5),(6),(7),(8),(9);
|
||||
|
||||
create table t1(a int, b int);
|
||||
insert into t1 values (0,0),(1,1),(2,2);
|
||||
create table t2 as select * from t1;
|
||||
|
||||
create table t11(a int, b int);
|
||||
|
||||
create table t10 (pk int, a int);
|
||||
insert into t10 select a,a from t0;
|
||||
create table t12 like t10;
|
||||
insert into t12 select * from t10;
|
||||
|
||||
analyze table t1,t10;
|
||||
|
||||
set optimizer_trace='enabled=on';
|
||||
explain extended select * from t1 where a in (select pk from t10);
|
||||
select * from INFORMATION_SCHEMA.OPTIMIZER_TRACE;
|
||||
drop table t0,t1,t11,t10,t12,t2;
|
||||
|
||||
--echo #
|
||||
--echo # Selectivities for columns and indexes.
|
||||
--echo #
|
||||
|
||||
create table t0 (a int);
|
||||
insert into t0 values (0),(1),(2),(3),(4),(5),(6),(7),(8),(9);
|
||||
|
||||
create table t1 (
|
||||
pk int,
|
||||
a int,
|
||||
b int,
|
||||
key pk(pk),
|
||||
key pk_a(pk,a),
|
||||
key pk_a_b(pk,a,b));
|
||||
insert into t1 select a,a,a from t0;
|
||||
|
||||
ANALYZE TABLE t1 PERSISTENT FOR COLUMNS (a,b) INDEXES ();
|
||||
set @save_optimizer_use_condition_selectivity=@@optimizer_use_condition_selectivity;
|
||||
set @save_use_stat_tables= @@use_stat_tables;
|
||||
set @@optimizer_use_condition_selectivity=4;
|
||||
set @@use_stat_tables= PREFERABLY;
|
||||
set optimizer_trace='enabled=on';
|
||||
explain select * from t1 where pk = 2 and a=5 and b=1;
|
||||
select * from INFORMATION_SCHEMA.OPTIMIZER_TRACE;
|
||||
set @@optimizer_use_condition_selectivity=@save_optimizer_use_condition_selectivity;
|
||||
set @@use_stat_tables= @save_use_stat_tables;
|
||||
drop table t0,t1;
|
||||
set optimizer_trace="enabled=off";
|
||||
|
||||
--echo #
|
||||
--echo # Tests added to show that sub-statements are not traced
|
||||
--echo #
|
||||
|
||||
create table t1(a int);
|
||||
insert into t1 values (1),(2),(3),(4);
|
||||
create table t2(a int);
|
||||
insert into t2 values (1),(2),(3),(4);
|
||||
delimiter |;
|
||||
create function f1(a int) returns int
|
||||
begin
|
||||
declare a int default 0;
|
||||
set a= a+ (select count(*) from t2);
|
||||
return a;
|
||||
end|
|
||||
|
||||
create function f2(a int) returns int
|
||||
begin
|
||||
declare a int default 0;
|
||||
select count(*) from t2 into a;
|
||||
return a;
|
||||
end|
|
||||
|
||||
delimiter ;|
|
||||
set optimizer_trace='enabled=on';
|
||||
select f1(a) from t1;
|
||||
select * from INFORMATION_SCHEMA.OPTIMIZER_TRACE;
|
||||
select f2(a) from t1;
|
||||
select * from INFORMATION_SCHEMA.OPTIMIZER_TRACE;
|
||||
drop table t1,t2;
|
||||
drop function f1;
|
||||
drop function f2;
|
||||
set optimizer_trace='enabled=off';
|
||||
|
||||
--echo #
|
||||
--echo # MDEV-18489: Limit the memory used by the optimizer trace
|
||||
--echo #
|
||||
|
||||
create table t1 (a int);
|
||||
insert into t1 values (1),(2);
|
||||
|
||||
set optimizer_trace='enabled=on';
|
||||
set @save_optimizer_trace_max_mem_size= @@optimizer_trace_max_mem_size;
|
||||
select * from t1;
|
||||
select length(trace) from INFORMATION_SCHEMA.OPTIMIZER_TRACE;
|
||||
|
||||
set optimizer_trace_max_mem_size=100;
|
||||
select * from t1;
|
||||
select * from INFORMATION_SCHEMA.OPTIMIZER_TRACE;
|
||||
|
||||
set optimizer_trace_max_mem_size=0;
|
||||
select * from t1;
|
||||
select * from INFORMATION_SCHEMA.OPTIMIZER_TRACE;
|
||||
drop table t1;
|
||||
set optimizer_trace='enabled=off';
|
||||
set @@optimizer_trace_max_mem_size= @save_optimizer_trace_max_mem_size;
|
||||
|
||||
--echo #
|
||||
--echo # MDEV-18527: Optimizer trace for DELETE query shows table:null
|
||||
--echo #
|
||||
|
||||
create table ten(a int);
|
||||
insert into ten values (0),(1),(2),(3),(4),(5),(6),(7),(8),(9);
|
||||
create table t0 (a int, b int);
|
||||
insert into t0 select a,a from ten;
|
||||
alter table t0 add key(a);
|
||||
|
||||
set optimizer_trace=1;
|
||||
explain delete from t0 where t0.a<3;
|
||||
select * from information_schema.optimizer_trace;
|
||||
drop table ten,t0;
|
||||
set optimizer_trace='enabled=off';
|
||||
|
||||
--echo #
|
||||
--echo # MDEV-18528: Optimizer trace support for multi-table UPDATE and DELETE
|
||||
--echo #
|
||||
|
||||
create table ten(a int);
|
||||
insert into ten values (0),(1),(2),(3),(4),(5),(6),(7),(8),(9);
|
||||
create table t0 (a int, b int);
|
||||
insert into t0 select a,a from ten;
|
||||
alter table t0 add key(a);
|
||||
create table t1 like t0;
|
||||
insert into t1 select * from t0;
|
||||
explain delete t0,t1 from t0, t1 where t0.a=t1.a and t1.a<3;
|
||||
select * from information_schema.optimizer_trace;
|
||||
drop table ten,t0,t1;
|
249
mysql-test/main/opt_trace_index_merge.result
Normal file
249
mysql-test/main/opt_trace_index_merge.result
Normal file
@ -0,0 +1,249 @@
|
||||
set @tmp_opt_switch= @@optimizer_switch;
|
||||
set optimizer_switch='index_merge_sort_intersection=on';
|
||||
set optimizer_trace='enabled=on';
|
||||
create table t0 (a int);
|
||||
insert into t0 values (0),(1),(2),(3),(4),(5),(6),(7),(8),(9);
|
||||
create table t1 (a int, b int, c int, filler char(100),
|
||||
key(a), key(b), key(c));
|
||||
insert into t1 select
|
||||
A.a * B.a*10 + C.a*100,
|
||||
A.a * B.a*10 + C.a*100,
|
||||
A.a,
|
||||
'filler'
|
||||
from t0 A, t0 B, t0 C;
|
||||
This should use union:
|
||||
explain select * from t1 where a=1 or b=1;
|
||||
id select_type table type possible_keys key key_len ref rows Extra
|
||||
1 SIMPLE t1 index_merge a,b a,b 5,5 NULL 2 Using union(a,b); Using where
|
||||
select * from information_schema.OPTIMIZER_TRACE;
|
||||
QUERY TRACE MISSING_BYTES_BEYOND_MAX_MEM_SIZE INSUFFICIENT_PRIVILEGES
|
||||
explain select * from t1 where a=1 or b=1 {
|
||||
"steps": [
|
||||
{
|
||||
"join_preparation": {
|
||||
"select_id": 1,
|
||||
"steps": [
|
||||
{
|
||||
"expanded_query": "select `t1`.`a` AS `a`,`t1`.`b` AS `b`,`t1`.`c` AS `c`,`t1`.`filler` AS `filler` from `t1` where `t1`.`a` = 1 or `t1`.`b` = 1"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"join_optimization": {
|
||||
"select_id": 1,
|
||||
"steps": [
|
||||
{
|
||||
"condition_processing": {
|
||||
"condition": "WHERE",
|
||||
"original_condition": "t1.a = 1 or t1.b = 1",
|
||||
"steps": [
|
||||
{
|
||||
"transformation": "equality_propagation",
|
||||
"resulting_condition": "multiple equal(1, t1.a) or multiple equal(1, t1.b)"
|
||||
},
|
||||
{
|
||||
"transformation": "constant_propagation",
|
||||
"resulting_condition": "multiple equal(1, t1.a) or multiple equal(1, t1.b)"
|
||||
},
|
||||
{
|
||||
"transformation": "trivial_condition_removal",
|
||||
"resulting_condition": "multiple equal(1, t1.a) or multiple equal(1, t1.b)"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"table_dependencies": [
|
||||
{
|
||||
"table": "t1",
|
||||
"row_may_be_null": false,
|
||||
"map_bit": 0,
|
||||
"depends_on_map_bits": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"ref_optimizer_key_uses": []
|
||||
},
|
||||
{
|
||||
"rows_estimation": [
|
||||
{
|
||||
"table": "t1",
|
||||
"range_analysis": {
|
||||
"table_scan": {
|
||||
"rows": 1000,
|
||||
"cost": 231.69
|
||||
},
|
||||
"potential_range_indexes": [
|
||||
{
|
||||
"index": "a",
|
||||
"usable": true,
|
||||
"key_parts": ["a"]
|
||||
},
|
||||
{
|
||||
"index": "b",
|
||||
"usable": true,
|
||||
"key_parts": ["b"]
|
||||
},
|
||||
{
|
||||
"index": "c",
|
||||
"usable": false,
|
||||
"cause": "not applicable"
|
||||
}
|
||||
],
|
||||
"setup_range_conditions": [],
|
||||
"group_index_range": {
|
||||
"chosen": false,
|
||||
"cause": "no group by or distinct"
|
||||
},
|
||||
"analyzing_range_alternatives": {
|
||||
"range_scan_alternatives": [],
|
||||
"analyzing_roworder_intersect": {
|
||||
"cause": "too few roworder scans"
|
||||
},
|
||||
"analyzing_sort_intersect": {},
|
||||
"analyzing_index_merge_union": [
|
||||
{
|
||||
"indexes_to_merge": [
|
||||
{
|
||||
"range_scan_alternatives": [
|
||||
{
|
||||
"index": "a",
|
||||
"ranges": ["1 <= a <= 1"],
|
||||
"rowid_ordered": true,
|
||||
"using_mrr": false,
|
||||
"index_only": true,
|
||||
"rows": 1,
|
||||
"cost": 2.21,
|
||||
"chosen": true
|
||||
}
|
||||
],
|
||||
"index_to_merge": "a",
|
||||
"cumulated_cost": 2.21
|
||||
},
|
||||
{
|
||||
"range_scan_alternatives": [
|
||||
{
|
||||
"index": "b",
|
||||
"ranges": ["1 <= b <= 1"],
|
||||
"rowid_ordered": true,
|
||||
"using_mrr": false,
|
||||
"index_only": true,
|
||||
"rows": 1,
|
||||
"cost": 2.21,
|
||||
"chosen": true
|
||||
}
|
||||
],
|
||||
"index_to_merge": "b",
|
||||
"cumulated_cost": 4.42
|
||||
}
|
||||
],
|
||||
"cost_of_reading_ranges": 4.42,
|
||||
"use_roworder_union": true,
|
||||
"cause": "always cheaper than non roworder retrieval",
|
||||
"analyzing_roworder_scans": [
|
||||
{
|
||||
"type": "range_scan",
|
||||
"index": "a",
|
||||
"rows": 1,
|
||||
"ranges": ["1 <= a <= 1"],
|
||||
"analyzing_roworder_intersect": {
|
||||
"cause": "too few roworder scans"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "range_scan",
|
||||
"index": "b",
|
||||
"rows": 1,
|
||||
"ranges": ["1 <= b <= 1"],
|
||||
"analyzing_roworder_intersect": {
|
||||
"cause": "too few roworder scans"
|
||||
}
|
||||
}
|
||||
],
|
||||
"index_roworder_union_cost": 6.2137,
|
||||
"members": 2,
|
||||
"chosen": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"chosen_range_access_summary": {
|
||||
"range_access_plan": {
|
||||
"type": "index_roworder_union",
|
||||
"union_of": [
|
||||
{
|
||||
"type": "range_scan",
|
||||
"index": "a",
|
||||
"rows": 1,
|
||||
"ranges": ["1 <= a <= 1"]
|
||||
},
|
||||
{
|
||||
"type": "range_scan",
|
||||
"index": "b",
|
||||
"rows": 1,
|
||||
"ranges": ["1 <= b <= 1"]
|
||||
}
|
||||
]
|
||||
},
|
||||
"rows_for_plan": 2,
|
||||
"cost_for_plan": 6.2137,
|
||||
"chosen": true
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"selectivity_for_indexes": [],
|
||||
"selectivity_for_columns": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"execution_plan_for_potential_materialization": {
|
||||
"steps": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"considered_execution_plans": [
|
||||
{
|
||||
"plan_prefix": [],
|
||||
"table": "t1",
|
||||
"best_access_path": {
|
||||
"considered_access_paths": [
|
||||
{
|
||||
"access_type": "range",
|
||||
"resulting_rows": 2,
|
||||
"cost": 6.2137,
|
||||
"chosen": true
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"attaching_conditions_to_tables": {
|
||||
"original_condition": "t1.a = 1 or t1.b = 1",
|
||||
"attached_conditions_computation": [],
|
||||
"attached_conditions_summary": [
|
||||
{
|
||||
"table": "t1",
|
||||
"attached": "t1.a = 1 or t1.b = 1"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"join_execution": {
|
||||
"select_id": 1,
|
||||
"steps": []
|
||||
}
|
||||
}
|
||||
]
|
||||
} 0 0
|
||||
drop table t0,t1;
|
||||
set optimizer_trace="enabled=off";
|
||||
set @@optimizer_switch= @tmp_opt_switch;
|
21
mysql-test/main/opt_trace_index_merge.test
Normal file
21
mysql-test/main/opt_trace_index_merge.test
Normal file
@ -0,0 +1,21 @@
|
||||
--source include/not_embedded.inc
|
||||
set @tmp_opt_switch= @@optimizer_switch;
|
||||
set optimizer_switch='index_merge_sort_intersection=on';
|
||||
set optimizer_trace='enabled=on';
|
||||
create table t0 (a int);
|
||||
insert into t0 values (0),(1),(2),(3),(4),(5),(6),(7),(8),(9);
|
||||
create table t1 (a int, b int, c int, filler char(100),
|
||||
key(a), key(b), key(c));
|
||||
insert into t1 select
|
||||
A.a * B.a*10 + C.a*100,
|
||||
A.a * B.a*10 + C.a*100,
|
||||
A.a,
|
||||
'filler'
|
||||
from t0 A, t0 B, t0 C;
|
||||
|
||||
--echo This should use union:
|
||||
explain select * from t1 where a=1 or b=1;
|
||||
select * from information_schema.OPTIMIZER_TRACE;
|
||||
drop table t0,t1;
|
||||
set optimizer_trace="enabled=off";
|
||||
set @@optimizer_switch= @tmp_opt_switch;
|
242
mysql-test/main/opt_trace_index_merge_innodb.result
Normal file
242
mysql-test/main/opt_trace_index_merge_innodb.result
Normal file
@ -0,0 +1,242 @@
|
||||
create table t1
|
||||
(
|
||||
pk1 int not null,
|
||||
pk2 int not null,
|
||||
key1 int not null,
|
||||
key2 int not null,
|
||||
key (key1),
|
||||
key (key2),
|
||||
primary key (pk1, pk2)
|
||||
)engine=Innodb;
|
||||
analyze table t1;
|
||||
Table Op Msg_type Msg_text
|
||||
test.t1 analyze status Engine-independent statistics collected
|
||||
test.t1 analyze status OK
|
||||
set optimizer_trace="enabled=on";
|
||||
set @tmp_index_merge_ror_cpk=@@optimizer_switch;
|
||||
set optimizer_switch='extended_keys=off';
|
||||
explain select * from t1 where pk1 != 0 and key1 = 1;
|
||||
id select_type table type possible_keys key key_len ref rows Extra
|
||||
1 SIMPLE t1 ref PRIMARY,key1 key1 4 const 1 Using index condition
|
||||
select * from information_schema.OPTIMIZER_TRACE;
|
||||
QUERY TRACE MISSING_BYTES_BEYOND_MAX_MEM_SIZE INSUFFICIENT_PRIVILEGES
|
||||
explain select * from t1 where pk1 != 0 and key1 = 1 {
|
||||
"steps": [
|
||||
{
|
||||
"join_preparation": {
|
||||
"select_id": 1,
|
||||
"steps": [
|
||||
{
|
||||
"expanded_query": "select `t1`.`pk1` AS `pk1`,`t1`.`pk2` AS `pk2`,`t1`.`key1` AS `key1`,`t1`.`key2` AS `key2` from `t1` where `t1`.`pk1` <> 0 and `t1`.`key1` = 1"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"join_optimization": {
|
||||
"select_id": 1,
|
||||
"steps": [
|
||||
{
|
||||
"condition_processing": {
|
||||
"condition": "WHERE",
|
||||
"original_condition": "t1.pk1 <> 0 and t1.key1 = 1",
|
||||
"steps": [
|
||||
{
|
||||
"transformation": "equality_propagation",
|
||||
"resulting_condition": "t1.pk1 <> 0 and multiple equal(1, t1.key1)"
|
||||
},
|
||||
{
|
||||
"transformation": "constant_propagation",
|
||||
"resulting_condition": "t1.pk1 <> 0 and multiple equal(1, t1.key1)"
|
||||
},
|
||||
{
|
||||
"transformation": "trivial_condition_removal",
|
||||
"resulting_condition": "t1.pk1 <> 0 and multiple equal(1, t1.key1)"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"table_dependencies": [
|
||||
{
|
||||
"table": "t1",
|
||||
"row_may_be_null": false,
|
||||
"map_bit": 0,
|
||||
"depends_on_map_bits": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"ref_optimizer_key_uses": [
|
||||
{
|
||||
"table": "t1",
|
||||
"field": "key1",
|
||||
"equals": "1",
|
||||
"null_rejecting": false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"rows_estimation": [
|
||||
{
|
||||
"table": "t1",
|
||||
"range_analysis": {
|
||||
"table_scan": {
|
||||
"rows": 1000,
|
||||
"cost": 206.1
|
||||
},
|
||||
"potential_range_indexes": [
|
||||
{
|
||||
"index": "PRIMARY",
|
||||
"usable": true,
|
||||
"key_parts": ["pk1", "pk2"]
|
||||
},
|
||||
{
|
||||
"index": "key1",
|
||||
"usable": true,
|
||||
"key_parts": ["key1"]
|
||||
},
|
||||
{
|
||||
"index": "key2",
|
||||
"usable": false,
|
||||
"cause": "not applicable"
|
||||
}
|
||||
],
|
||||
"setup_range_conditions": [],
|
||||
"group_index_range": {
|
||||
"chosen": false,
|
||||
"cause": "no group by or distinct"
|
||||
},
|
||||
"analyzing_range_alternatives": {
|
||||
"range_scan_alternatives": [
|
||||
{
|
||||
"index": "PRIMARY",
|
||||
"ranges": ["pk1 < 0", "0 < pk1"],
|
||||
"rowid_ordered": true,
|
||||
"using_mrr": false,
|
||||
"index_only": false,
|
||||
"rows": 1001,
|
||||
"cost": 203.59,
|
||||
"chosen": true
|
||||
},
|
||||
{
|
||||
"index": "key1",
|
||||
"ranges": ["1 <= key1 <= 1"],
|
||||
"rowid_ordered": true,
|
||||
"using_mrr": false,
|
||||
"index_only": false,
|
||||
"rows": 1,
|
||||
"cost": 2.21,
|
||||
"chosen": true
|
||||
}
|
||||
],
|
||||
"analyzing_roworder_intersect": {
|
||||
"intersecting_indexes": [
|
||||
{
|
||||
"index": "key1",
|
||||
"index_scan_cost": 1,
|
||||
"cumulateed_index_scan_cost": 1,
|
||||
"disk_sweep_cost": 1.0014,
|
||||
"cumulative_total_cost": 2.0014,
|
||||
"usable": true,
|
||||
"matching_rows_now": 1,
|
||||
"intersect_covering_with_this_index": false,
|
||||
"chosen": true
|
||||
}
|
||||
],
|
||||
"clustered_pk": {
|
||||
"index_scan_cost": 0.002,
|
||||
"cumulateed_index_scan_cost": 1.002,
|
||||
"disk_sweep_cost": 1.0014,
|
||||
"clustered_pk_added_to_intersect": false,
|
||||
"cause": "cost"
|
||||
},
|
||||
"chosen": false,
|
||||
"cause": "too few indexes to merge"
|
||||
},
|
||||
"analyzing_index_merge_union": []
|
||||
},
|
||||
"chosen_range_access_summary": {
|
||||
"range_access_plan": {
|
||||
"type": "range_scan",
|
||||
"index": "key1",
|
||||
"rows": 1,
|
||||
"ranges": ["1 <= key1 <= 1"]
|
||||
},
|
||||
"rows_for_plan": 1,
|
||||
"cost_for_plan": 2.21,
|
||||
"chosen": true
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"selectivity_for_indexes": [
|
||||
{
|
||||
"index_name": "PRIMARY",
|
||||
"selectivity_from_index": 1.001
|
||||
},
|
||||
{
|
||||
"index_name": "key1",
|
||||
"selectivity_from_index": 0.001
|
||||
}
|
||||
],
|
||||
"selectivity_for_columns": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"execution_plan_for_potential_materialization": {
|
||||
"steps": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"considered_execution_plans": [
|
||||
{
|
||||
"plan_prefix": [],
|
||||
"table": "t1",
|
||||
"best_access_path": {
|
||||
"considered_access_paths": [
|
||||
{
|
||||
"access_type": "ref",
|
||||
"index": "key1",
|
||||
"used_range_estimates": true,
|
||||
"rows": 1,
|
||||
"cost": 2,
|
||||
"chosen": true
|
||||
},
|
||||
{
|
||||
"type": "scan",
|
||||
"chosen": false,
|
||||
"cause": "cost"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"attaching_conditions_to_tables": {
|
||||
"original_condition": "t1.key1 = 1 and t1.pk1 <> 0",
|
||||
"attached_conditions_computation": [],
|
||||
"attached_conditions_summary": [
|
||||
{
|
||||
"table": "t1",
|
||||
"attached": "t1.pk1 <> 0"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"join_execution": {
|
||||
"select_id": 1,
|
||||
"steps": []
|
||||
}
|
||||
}
|
||||
]
|
||||
} 0 0
|
||||
drop table t1;
|
||||
set @@optimizer_switch= @tmp_index_merge_ror_cpk;
|
||||
set optimizer_trace="enabled=off";
|
31
mysql-test/main/opt_trace_index_merge_innodb.test
Normal file
31
mysql-test/main/opt_trace_index_merge_innodb.test
Normal file
@ -0,0 +1,31 @@
|
||||
--source include/not_embedded.inc
|
||||
--source include/have_innodb.inc
|
||||
create table t1
|
||||
(
|
||||
pk1 int not null,
|
||||
pk2 int not null,
|
||||
key1 int not null,
|
||||
key2 int not null,
|
||||
key (key1),
|
||||
key (key2),
|
||||
primary key (pk1, pk2)
|
||||
)engine=Innodb;
|
||||
|
||||
--disable_query_log
|
||||
let $1=1000;
|
||||
while ($1)
|
||||
{
|
||||
eval insert into t1 values (1+$1/10,$1 mod 100,$1,$1/100);
|
||||
dec $1;
|
||||
}
|
||||
--enable_query_log
|
||||
analyze table t1;
|
||||
|
||||
set optimizer_trace="enabled=on";
|
||||
set @tmp_index_merge_ror_cpk=@@optimizer_switch;
|
||||
set optimizer_switch='extended_keys=off';
|
||||
explain select * from t1 where pk1 != 0 and key1 = 1;
|
||||
select * from information_schema.OPTIMIZER_TRACE;
|
||||
drop table t1;
|
||||
set @@optimizer_switch= @tmp_index_merge_ror_cpk;
|
||||
set optimizer_trace="enabled=off";
|
396
mysql-test/main/opt_trace_security.result
Normal file
396
mysql-test/main/opt_trace_security.result
Normal file
@ -0,0 +1,396 @@
|
||||
create database db1;
|
||||
use db1;
|
||||
create table t1(a int);
|
||||
insert into t1 values (1),(2),(3);
|
||||
create table t2(a int);
|
||||
CREATE USER 'foo'@'%';
|
||||
CREATE USER 'bar'@'%';
|
||||
create definer=foo SQL SECURITY definer view db1.v1 as select * from db1.t1;
|
||||
create definer=foo function f1 (a int) returns INT SQL SECURITY DEFINER
|
||||
BEGIN
|
||||
insert into t2 select * from t1;
|
||||
return a+1;
|
||||
END|
|
||||
set optimizer_trace="enabled=on";
|
||||
select * from db1.t1;
|
||||
ERROR 42000: SELECT command denied to user 'foo'@'localhost' for table 't1'
|
||||
select * from information_schema.OPTIMIZER_TRACE;
|
||||
QUERY TRACE MISSING_BYTES_BEYOND_MAX_MEM_SIZE INSUFFICIENT_PRIVILEGES
|
||||
0 1
|
||||
set optimizer_trace="enabled=off";
|
||||
grant select(a) on db1.t1 to 'foo'@'%';
|
||||
set optimizer_trace="enabled=on";
|
||||
select * from db1.t1;
|
||||
a
|
||||
1
|
||||
2
|
||||
3
|
||||
# INSUFFICIENT PRIVILEGES should be set to 1
|
||||
# Trace and Query should be empty
|
||||
# We need SELECT privilege on the table db1.t1;
|
||||
select * from information_schema.OPTIMIZER_TRACE;
|
||||
QUERY TRACE MISSING_BYTES_BEYOND_MAX_MEM_SIZE INSUFFICIENT_PRIVILEGES
|
||||
0 1
|
||||
set optimizer_trace="enabled=off";
|
||||
select * from information_schema.OPTIMIZER_TRACE;
|
||||
QUERY TRACE MISSING_BYTES_BEYOND_MAX_MEM_SIZE INSUFFICIENT_PRIVILEGES
|
||||
grant select on db1.t1 to 'foo'@'%';
|
||||
grant select on db1.t2 to 'foo'@'%';
|
||||
set optimizer_trace="enabled=on";
|
||||
#
|
||||
# SELECT privilege on the table db1.t1
|
||||
# The trace would be present.
|
||||
#
|
||||
select * from db1.t1;
|
||||
a
|
||||
1
|
||||
2
|
||||
3
|
||||
select * from information_schema.OPTIMIZER_TRACE;
|
||||
QUERY TRACE MISSING_BYTES_BEYOND_MAX_MEM_SIZE INSUFFICIENT_PRIVILEGES
|
||||
select * from db1.t1 {
|
||||
"steps": [
|
||||
{
|
||||
"join_preparation": {
|
||||
"select_id": 1,
|
||||
"steps": [
|
||||
{
|
||||
"expanded_query": "select `db1`.`t1`.`a` AS `a` from `t1`"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"join_optimization": {
|
||||
"select_id": 1,
|
||||
"steps": [
|
||||
{
|
||||
"table_dependencies": [
|
||||
{
|
||||
"table": "t1",
|
||||
"row_may_be_null": false,
|
||||
"map_bit": 0,
|
||||
"depends_on_map_bits": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"rows_estimation": [
|
||||
{
|
||||
"table": "t1",
|
||||
"table_scan": {
|
||||
"rows": 3,
|
||||
"cost": 2.0051
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"execution_plan_for_potential_materialization": {
|
||||
"steps": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"considered_execution_plans": [
|
||||
{
|
||||
"plan_prefix": [],
|
||||
"table": "t1",
|
||||
"best_access_path": {
|
||||
"considered_access_paths": [
|
||||
{
|
||||
"access_type": "scan",
|
||||
"resulting_rows": 3,
|
||||
"cost": 2.0051,
|
||||
"chosen": true
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"attaching_conditions_to_tables": {
|
||||
"original_condition": null,
|
||||
"attached_conditions_computation": [],
|
||||
"attached_conditions_summary": [
|
||||
{
|
||||
"table": "t1",
|
||||
"attached": null
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"join_execution": {
|
||||
"select_id": 1,
|
||||
"steps": []
|
||||
}
|
||||
}
|
||||
]
|
||||
} 0 0
|
||||
set optimizer_trace="enabled=off";
|
||||
grant select on db1.v1 to 'foo'@'%';
|
||||
grant show view on db1.v1 to 'foo'@'%';
|
||||
grant select on db1.v1 to 'bar'@'%';
|
||||
grant show view on db1.v1 to 'bar'@'%';
|
||||
select current_user();
|
||||
current_user()
|
||||
foo@%
|
||||
set optimizer_trace="enabled=on";
|
||||
select * from db1.v1;
|
||||
a
|
||||
1
|
||||
2
|
||||
3
|
||||
select * from information_schema.OPTIMIZER_TRACE;
|
||||
QUERY TRACE MISSING_BYTES_BEYOND_MAX_MEM_SIZE INSUFFICIENT_PRIVILEGES
|
||||
select * from db1.v1 {
|
||||
"steps": [
|
||||
{
|
||||
"join_preparation": {
|
||||
"select_id": 1,
|
||||
"steps": [
|
||||
{
|
||||
"view": {
|
||||
"table": "v1",
|
||||
"select_id": 2,
|
||||
"merged": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"join_preparation": {
|
||||
"select_id": 2,
|
||||
"steps": [
|
||||
{
|
||||
"expanded_query": "/* select#2 */ select `db1`.`t1`.`a` AS `a` from `t1`"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"expanded_query": "/* select#1 */ select `db1`.`t1`.`a` AS `a` from `v1`"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"join_optimization": {
|
||||
"select_id": 1,
|
||||
"steps": [
|
||||
{
|
||||
"table_dependencies": [
|
||||
{
|
||||
"table": "t1",
|
||||
"row_may_be_null": false,
|
||||
"map_bit": 0,
|
||||
"depends_on_map_bits": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"rows_estimation": [
|
||||
{
|
||||
"table": "t1",
|
||||
"table_scan": {
|
||||
"rows": 3,
|
||||
"cost": 2.0051
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"execution_plan_for_potential_materialization": {
|
||||
"steps": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"considered_execution_plans": [
|
||||
{
|
||||
"plan_prefix": [],
|
||||
"table": "t1",
|
||||
"best_access_path": {
|
||||
"considered_access_paths": [
|
||||
{
|
||||
"access_type": "scan",
|
||||
"resulting_rows": 3,
|
||||
"cost": 2.0051,
|
||||
"chosen": true
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"attaching_conditions_to_tables": {
|
||||
"original_condition": null,
|
||||
"attached_conditions_computation": [],
|
||||
"attached_conditions_summary": [
|
||||
{
|
||||
"table": "t1",
|
||||
"attached": null
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"join_execution": {
|
||||
"select_id": 1,
|
||||
"steps": []
|
||||
}
|
||||
}
|
||||
]
|
||||
} 0 0
|
||||
set optimizer_trace="enabled=off";
|
||||
select current_user();
|
||||
current_user()
|
||||
bar@%
|
||||
set optimizer_trace="enabled=on";
|
||||
select * from db1.v1;
|
||||
a
|
||||
1
|
||||
2
|
||||
3
|
||||
#
|
||||
# INSUFFICIENT PRIVILEGES should be set to 1
|
||||
# Trace and Query should be empty
|
||||
# Privileges for the underlying tables of the
|
||||
# view should also be present for the current user
|
||||
#
|
||||
select * from information_schema.OPTIMIZER_TRACE;
|
||||
QUERY TRACE MISSING_BYTES_BEYOND_MAX_MEM_SIZE INSUFFICIENT_PRIVILEGES
|
||||
0 1
|
||||
set optimizer_trace="enabled=off";
|
||||
grant execute on function db1.f1 to 'foo'@'%';
|
||||
grant execute on function db1.f1 to 'bar'@'%';
|
||||
grant select on db1.t1 to 'bar'@'%';
|
||||
grant insert on db1.t2 to 'foo'@'%';
|
||||
select current_user();
|
||||
current_user()
|
||||
foo@%
|
||||
set optimizer_trace="enabled=on";
|
||||
select db1.f1(a) from db1.t1;
|
||||
db1.f1(a)
|
||||
2
|
||||
3
|
||||
4
|
||||
select INSUFFICIENT_PRIVILEGES from information_schema.OPTIMIZER_TRACE;
|
||||
INSUFFICIENT_PRIVILEGES
|
||||
0
|
||||
set optimizer_trace="enabled=off";
|
||||
select current_user();
|
||||
current_user()
|
||||
bar@%
|
||||
set optimizer_trace="enabled=on";
|
||||
#
|
||||
# The trace should be empty, because the current user
|
||||
# does not have INSERT privilege for table t2 which is
|
||||
# used in the function f1
|
||||
#
|
||||
select db1.f1(a) from db1.t1;
|
||||
db1.f1(a)
|
||||
2
|
||||
3
|
||||
4
|
||||
select * from information_schema.OPTIMIZER_TRACE;
|
||||
QUERY TRACE MISSING_BYTES_BEYOND_MAX_MEM_SIZE INSUFFICIENT_PRIVILEGES
|
||||
0 1
|
||||
set optimizer_trace="enabled=off";
|
||||
select current_user();
|
||||
current_user()
|
||||
root@localhost
|
||||
REVOKE ALL PRIVILEGES, GRANT OPTION FROM foo;
|
||||
drop user if exists foo;
|
||||
drop user if exists bar;
|
||||
drop table db1.t1, db1.t2;
|
||||
drop database db1;
|
||||
#
|
||||
# Privilege checking for optimizer trace across connections
|
||||
#
|
||||
connection default;
|
||||
create database db1;
|
||||
use db1;
|
||||
create table t1(a int);
|
||||
insert into t1 values (1),(2),(3);
|
||||
create table t2(a int);
|
||||
CREATE USER 'foo'@'localhost';
|
||||
CREATE USER 'bar'@'localhost';
|
||||
grant all on *.* to foo@localhost with grant option;
|
||||
grant all on *.* to bar@localhost with grant option;
|
||||
connect con_foo,localhost, foo,, db1;
|
||||
connection default;
|
||||
connect con_bar,localhost, bar,, db1;
|
||||
connection default;
|
||||
create definer=foo@localhost SQL SECURITY definer view db1.v1 as select * from db1.t1;
|
||||
create function f1 (a int) returns INT SQL SECURITY DEFINER
|
||||
BEGIN
|
||||
insert into t2 select * from t1;
|
||||
return a+1;
|
||||
END|
|
||||
grant execute on function f1 to bar@localhost;
|
||||
connection con_foo;
|
||||
set optimizer_trace='enabled=on';
|
||||
select * from db1.t1;
|
||||
a
|
||||
1
|
||||
2
|
||||
3
|
||||
#
|
||||
# Test that security context changes are allowed when, and only
|
||||
# when, invoker has all global privileges.
|
||||
#
|
||||
select query, INSUFFICIENT_PRIVILEGES from information_schema.OPTIMIZER_TRACE;
|
||||
query INSUFFICIENT_PRIVILEGES
|
||||
select * from db1.t1 0
|
||||
set optimizer_trace='enabled=off';
|
||||
connection con_bar;
|
||||
set optimizer_trace='enabled=on';
|
||||
select f1(a) from db1.t1;
|
||||
f1(a)
|
||||
2
|
||||
3
|
||||
4
|
||||
select query, INSUFFICIENT_PRIVILEGES from information_schema.OPTIMIZER_TRACE;
|
||||
query INSUFFICIENT_PRIVILEGES
|
||||
select f1(a) from db1.t1 0
|
||||
set optimizer_trace='enabled=off';
|
||||
connection default;
|
||||
revoke shutdown on *.* from foo@localhost;
|
||||
disconnect con_foo;
|
||||
connect con_foo, localhost, foo,, db1;
|
||||
connection con_foo;
|
||||
set optimizer_trace='enabled=on';
|
||||
select f1(a) from db1.t1;
|
||||
f1(a)
|
||||
2
|
||||
3
|
||||
4
|
||||
#
|
||||
# Test to check if invoker has all global privileges or not, only then
|
||||
# the security context changes are allowed. The user has been revoked
|
||||
# shutdown privilege so INSUFFICIENT PRIVILEGES should be set to 1.
|
||||
#
|
||||
select query, INSUFFICIENT_PRIVILEGES from information_schema.OPTIMIZER_TRACE;
|
||||
query INSUFFICIENT_PRIVILEGES
|
||||
1
|
||||
set optimizer_trace='enabled=off';
|
||||
connection default;
|
||||
select current_user();
|
||||
current_user()
|
||||
root@localhost
|
||||
select * from db1.v1;
|
||||
a
|
||||
1
|
||||
2
|
||||
3
|
||||
drop user foo@localhost, bar@localhost;
|
||||
drop view db1.v1;
|
||||
drop table db1.t1;
|
||||
drop database db1;
|
||||
set optimizer_trace="enabled=off";
|
197
mysql-test/main/opt_trace_security.test
Normal file
197
mysql-test/main/opt_trace_security.test
Normal file
@ -0,0 +1,197 @@
|
||||
--source include/not_embedded.inc
|
||||
create database db1;
|
||||
use db1;
|
||||
create table t1(a int);
|
||||
insert into t1 values (1),(2),(3);
|
||||
create table t2(a int);
|
||||
|
||||
CREATE USER 'foo'@'%';
|
||||
CREATE USER 'bar'@'%';
|
||||
|
||||
create definer=foo SQL SECURITY definer view db1.v1 as select * from db1.t1;
|
||||
|
||||
delimiter |;
|
||||
create definer=foo function f1 (a int) returns INT SQL SECURITY DEFINER
|
||||
BEGIN
|
||||
insert into t2 select * from t1;
|
||||
return a+1;
|
||||
END|
|
||||
delimiter ;|
|
||||
|
||||
--change_user foo
|
||||
set optimizer_trace="enabled=on";
|
||||
--error 1142
|
||||
select * from db1.t1;
|
||||
select * from information_schema.OPTIMIZER_TRACE;
|
||||
set optimizer_trace="enabled=off";
|
||||
|
||||
--change_user root
|
||||
grant select(a) on db1.t1 to 'foo'@'%';
|
||||
|
||||
--change_user foo
|
||||
set optimizer_trace="enabled=on";
|
||||
select * from db1.t1;
|
||||
|
||||
--echo # INSUFFICIENT PRIVILEGES should be set to 1
|
||||
--echo # Trace and Query should be empty
|
||||
--echo # We need SELECT privilege on the table db1.t1;
|
||||
|
||||
select * from information_schema.OPTIMIZER_TRACE;
|
||||
set optimizer_trace="enabled=off";
|
||||
|
||||
--change_user root
|
||||
select * from information_schema.OPTIMIZER_TRACE;
|
||||
grant select on db1.t1 to 'foo'@'%';
|
||||
grant select on db1.t2 to 'foo'@'%';
|
||||
|
||||
--change_user foo
|
||||
set optimizer_trace="enabled=on";
|
||||
|
||||
--echo #
|
||||
--echo # SELECT privilege on the table db1.t1
|
||||
--echo # The trace would be present.
|
||||
--echo #
|
||||
select * from db1.t1;
|
||||
select * from information_schema.OPTIMIZER_TRACE;
|
||||
set optimizer_trace="enabled=off";
|
||||
|
||||
--change_user root
|
||||
|
||||
grant select on db1.v1 to 'foo'@'%';
|
||||
grant show view on db1.v1 to 'foo'@'%';
|
||||
|
||||
grant select on db1.v1 to 'bar'@'%';
|
||||
grant show view on db1.v1 to 'bar'@'%';
|
||||
|
||||
--change_user foo
|
||||
select current_user();
|
||||
set optimizer_trace="enabled=on";
|
||||
select * from db1.v1;
|
||||
select * from information_schema.OPTIMIZER_TRACE;
|
||||
set optimizer_trace="enabled=off";
|
||||
|
||||
--change_user bar
|
||||
select current_user();
|
||||
set optimizer_trace="enabled=on";
|
||||
select * from db1.v1;
|
||||
--echo #
|
||||
--echo # INSUFFICIENT PRIVILEGES should be set to 1
|
||||
--echo # Trace and Query should be empty
|
||||
--echo # Privileges for the underlying tables of the
|
||||
--echo # view should also be present for the current user
|
||||
--echo #
|
||||
select * from information_schema.OPTIMIZER_TRACE;
|
||||
set optimizer_trace="enabled=off";
|
||||
|
||||
--change_user root
|
||||
grant execute on function db1.f1 to 'foo'@'%';
|
||||
grant execute on function db1.f1 to 'bar'@'%';
|
||||
|
||||
grant select on db1.t1 to 'bar'@'%';
|
||||
grant insert on db1.t2 to 'foo'@'%';
|
||||
|
||||
--change_user foo
|
||||
select current_user();
|
||||
set optimizer_trace="enabled=on";
|
||||
|
||||
select db1.f1(a) from db1.t1;
|
||||
select INSUFFICIENT_PRIVILEGES from information_schema.OPTIMIZER_TRACE;
|
||||
set optimizer_trace="enabled=off";
|
||||
|
||||
--change_user bar
|
||||
select current_user();
|
||||
set optimizer_trace="enabled=on";
|
||||
--echo #
|
||||
--echo # The trace should be empty, because the current user
|
||||
--echo # does not have INSERT privilege for table t2 which is
|
||||
--echo # used in the function f1
|
||||
--echo #
|
||||
select db1.f1(a) from db1.t1;
|
||||
select * from information_schema.OPTIMIZER_TRACE;
|
||||
set optimizer_trace="enabled=off";
|
||||
|
||||
--change_user root
|
||||
select current_user();
|
||||
REVOKE ALL PRIVILEGES, GRANT OPTION FROM foo;
|
||||
|
||||
--change_user root
|
||||
drop user if exists foo;
|
||||
drop user if exists bar;
|
||||
drop table db1.t1, db1.t2;
|
||||
drop database db1;
|
||||
|
||||
|
||||
--echo #
|
||||
--echo # Privilege checking for optimizer trace across connections
|
||||
--echo #
|
||||
|
||||
connection default;
|
||||
create database db1;
|
||||
use db1;
|
||||
create table t1(a int);
|
||||
insert into t1 values (1),(2),(3);
|
||||
create table t2(a int);
|
||||
|
||||
CREATE USER 'foo'@'localhost';
|
||||
CREATE USER 'bar'@'localhost';
|
||||
grant all on *.* to foo@localhost with grant option;
|
||||
grant all on *.* to bar@localhost with grant option;
|
||||
#grant select on db1.t1 to bar@localhost;
|
||||
#grant insert on db1.t2 to bar@localhost;
|
||||
|
||||
connect (con_foo,localhost, foo,, db1);
|
||||
connection default;
|
||||
connect (con_bar,localhost, bar,, db1);
|
||||
connection default;
|
||||
create definer=foo@localhost SQL SECURITY definer view db1.v1 as select * from db1.t1;
|
||||
|
||||
delimiter |;
|
||||
create function f1 (a int) returns INT SQL SECURITY DEFINER
|
||||
BEGIN
|
||||
insert into t2 select * from t1;
|
||||
return a+1;
|
||||
END|
|
||||
delimiter ;|
|
||||
|
||||
grant execute on function f1 to bar@localhost;
|
||||
|
||||
connection con_foo;
|
||||
set optimizer_trace='enabled=on';
|
||||
select * from db1.t1;
|
||||
--echo #
|
||||
--echo # Test that security context changes are allowed when, and only
|
||||
--echo # when, invoker has all global privileges.
|
||||
--echo #
|
||||
select query, INSUFFICIENT_PRIVILEGES from information_schema.OPTIMIZER_TRACE;
|
||||
set optimizer_trace='enabled=off';
|
||||
|
||||
connection con_bar;
|
||||
set optimizer_trace='enabled=on';
|
||||
select f1(a) from db1.t1;
|
||||
select query, INSUFFICIENT_PRIVILEGES from information_schema.OPTIMIZER_TRACE;
|
||||
set optimizer_trace='enabled=off';
|
||||
|
||||
connection default;
|
||||
revoke shutdown on *.* from foo@localhost;
|
||||
disconnect con_foo;
|
||||
connect (con_foo, localhost, foo,, db1);
|
||||
|
||||
connection con_foo;
|
||||
set optimizer_trace='enabled=on';
|
||||
select f1(a) from db1.t1;
|
||||
--echo #
|
||||
--echo # Test to check if invoker has all global privileges or not, only then
|
||||
--echo # the security context changes are allowed. The user has been revoked
|
||||
--echo # shutdown privilege so INSUFFICIENT PRIVILEGES should be set to 1.
|
||||
--echo #
|
||||
select query, INSUFFICIENT_PRIVILEGES from information_schema.OPTIMIZER_TRACE;
|
||||
set optimizer_trace='enabled=off';
|
||||
|
||||
connection default;
|
||||
select current_user();
|
||||
select * from db1.v1;
|
||||
drop user foo@localhost, bar@localhost;
|
||||
drop view db1.v1;
|
||||
drop table db1.t1;
|
||||
drop database db1;
|
||||
set optimizer_trace="enabled=off";
|
@ -205,6 +205,10 @@ def information_schema KEY_COLUMN_USAGE REFERENCED_TABLE_SCHEMA 10 NULL YES varc
|
||||
def information_schema KEY_COLUMN_USAGE TABLE_CATALOG 4 '' NO varchar 512 1536 NULL NULL NULL utf8 utf8_general_ci varchar(512) select NEVER NULL
|
||||
def information_schema KEY_COLUMN_USAGE TABLE_NAME 6 '' NO varchar 64 192 NULL NULL NULL utf8 utf8_general_ci varchar(64) select NEVER NULL
|
||||
def information_schema KEY_COLUMN_USAGE TABLE_SCHEMA 5 '' NO varchar 64 192 NULL NULL NULL utf8 utf8_general_ci varchar(64) select NEVER NULL
|
||||
def information_schema OPTIMIZER_TRACE INSUFFICIENT_PRIVILEGES 4 0 NO tinyint NULL NULL 3 0 NULL NULL NULL tinyint(1) select NEVER NULL
|
||||
def information_schema OPTIMIZER_TRACE MISSING_BYTES_BEYOND_MAX_MEM_SIZE 3 0 NO int NULL NULL 10 0 NULL NULL NULL int(20) select NEVER NULL
|
||||
def information_schema OPTIMIZER_TRACE QUERY 1 '' NO longtext 4294967295 4294967295 NULL NULL NULL utf8 utf8_general_ci longtext select NEVER NULL
|
||||
def information_schema OPTIMIZER_TRACE TRACE 2 '' NO longtext 4294967295 4294967295 NULL NULL NULL utf8 utf8_general_ci longtext select NEVER NULL
|
||||
def information_schema PARAMETERS CHARACTER_MAXIMUM_LENGTH 8 NULL YES int NULL NULL 10 0 NULL NULL NULL int(21) select NEVER NULL
|
||||
def information_schema PARAMETERS CHARACTER_OCTET_LENGTH 9 NULL YES int NULL NULL 10 0 NULL NULL NULL int(21) select NEVER NULL
|
||||
def information_schema PARAMETERS CHARACTER_SET_NAME 13 NULL YES varchar 64 192 NULL NULL NULL utf8 utf8_general_ci varchar(64) select NEVER NULL
|
||||
@ -743,6 +747,10 @@ NULL information_schema KEY_COLUMN_USAGE POSITION_IN_UNIQUE_CONSTRAINT bigint NU
|
||||
3.0000 information_schema KEY_COLUMN_USAGE REFERENCED_TABLE_SCHEMA varchar 64 192 utf8 utf8_general_ci varchar(64)
|
||||
3.0000 information_schema KEY_COLUMN_USAGE REFERENCED_TABLE_NAME varchar 64 192 utf8 utf8_general_ci varchar(64)
|
||||
3.0000 information_schema KEY_COLUMN_USAGE REFERENCED_COLUMN_NAME varchar 64 192 utf8 utf8_general_ci varchar(64)
|
||||
1.0000 information_schema OPTIMIZER_TRACE QUERY longtext 4294967295 4294967295 utf8 utf8_general_ci longtext
|
||||
1.0000 information_schema OPTIMIZER_TRACE TRACE longtext 4294967295 4294967295 utf8 utf8_general_ci longtext
|
||||
NULL information_schema OPTIMIZER_TRACE MISSING_BYTES_BEYOND_MAX_MEM_SIZE int NULL NULL NULL NULL int(20)
|
||||
NULL information_schema OPTIMIZER_TRACE INSUFFICIENT_PRIVILEGES tinyint NULL NULL NULL NULL tinyint(1)
|
||||
3.0000 information_schema PARAMETERS SPECIFIC_CATALOG varchar 512 1536 utf8 utf8_general_ci varchar(512)
|
||||
3.0000 information_schema PARAMETERS SPECIFIC_SCHEMA varchar 64 192 utf8 utf8_general_ci varchar(64)
|
||||
3.0000 information_schema PARAMETERS SPECIFIC_NAME varchar 64 192 utf8 utf8_general_ci varchar(64)
|
||||
|
@ -205,6 +205,10 @@ def information_schema KEY_COLUMN_USAGE REFERENCED_TABLE_SCHEMA 10 NULL YES varc
|
||||
def information_schema KEY_COLUMN_USAGE TABLE_CATALOG 4 '' NO varchar 512 1536 NULL NULL NULL utf8 utf8_general_ci varchar(512) NEVER NULL
|
||||
def information_schema KEY_COLUMN_USAGE TABLE_NAME 6 '' NO varchar 64 192 NULL NULL NULL utf8 utf8_general_ci varchar(64) NEVER NULL
|
||||
def information_schema KEY_COLUMN_USAGE TABLE_SCHEMA 5 '' NO varchar 64 192 NULL NULL NULL utf8 utf8_general_ci varchar(64) NEVER NULL
|
||||
def information_schema OPTIMIZER_TRACE INSUFFICIENT_PRIVILEGES 4 0 NO tinyint NULL NULL 3 0 NULL NULL NULL tinyint(1) NEVER NULL
|
||||
def information_schema OPTIMIZER_TRACE MISSING_BYTES_BEYOND_MAX_MEM_SIZE 3 0 NO int NULL NULL 10 0 NULL NULL NULL int(20) NEVER NULL
|
||||
def information_schema OPTIMIZER_TRACE QUERY 1 '' NO longtext 4294967295 4294967295 NULL NULL NULL utf8 utf8_general_ci longtext NEVER NULL
|
||||
def information_schema OPTIMIZER_TRACE TRACE 2 '' NO longtext 4294967295 4294967295 NULL NULL NULL utf8 utf8_general_ci longtext NEVER NULL
|
||||
def information_schema PARAMETERS CHARACTER_MAXIMUM_LENGTH 8 NULL YES int NULL NULL 10 0 NULL NULL NULL int(21) NEVER NULL
|
||||
def information_schema PARAMETERS CHARACTER_OCTET_LENGTH 9 NULL YES int NULL NULL 10 0 NULL NULL NULL int(21) NEVER NULL
|
||||
def information_schema PARAMETERS CHARACTER_SET_NAME 13 NULL YES varchar 64 192 NULL NULL NULL utf8 utf8_general_ci varchar(64) NEVER NULL
|
||||
@ -743,6 +747,10 @@ NULL information_schema KEY_COLUMN_USAGE POSITION_IN_UNIQUE_CONSTRAINT bigint NU
|
||||
3.0000 information_schema KEY_COLUMN_USAGE REFERENCED_TABLE_SCHEMA varchar 64 192 utf8 utf8_general_ci varchar(64)
|
||||
3.0000 information_schema KEY_COLUMN_USAGE REFERENCED_TABLE_NAME varchar 64 192 utf8 utf8_general_ci varchar(64)
|
||||
3.0000 information_schema KEY_COLUMN_USAGE REFERENCED_COLUMN_NAME varchar 64 192 utf8 utf8_general_ci varchar(64)
|
||||
1.0000 information_schema OPTIMIZER_TRACE QUERY longtext 4294967295 4294967295 utf8 utf8_general_ci longtext
|
||||
1.0000 information_schema OPTIMIZER_TRACE TRACE longtext 4294967295 4294967295 utf8 utf8_general_ci longtext
|
||||
NULL information_schema OPTIMIZER_TRACE MISSING_BYTES_BEYOND_MAX_MEM_SIZE int NULL NULL NULL NULL int(20)
|
||||
NULL information_schema OPTIMIZER_TRACE INSUFFICIENT_PRIVILEGES tinyint NULL NULL NULL NULL tinyint(1)
|
||||
3.0000 information_schema PARAMETERS SPECIFIC_CATALOG varchar 512 1536 utf8 utf8_general_ci varchar(512)
|
||||
3.0000 information_schema PARAMETERS SPECIFIC_SCHEMA varchar 64 192 utf8 utf8_general_ci varchar(64)
|
||||
3.0000 information_schema PARAMETERS SPECIFIC_NAME varchar 64 192 utf8 utf8_general_ci varchar(64)
|
||||
|
@ -489,6 +489,31 @@ user_comment
|
||||
Separator -----------------------------------------------------
|
||||
TABLE_CATALOG def
|
||||
TABLE_SCHEMA information_schema
|
||||
TABLE_NAME OPTIMIZER_TRACE
|
||||
TABLE_TYPE SYSTEM VIEW
|
||||
ENGINE MYISAM_OR_MARIA
|
||||
VERSION 11
|
||||
ROW_FORMAT DYNAMIC_OR_PAGE
|
||||
TABLE_ROWS #TBLR#
|
||||
AVG_ROW_LENGTH #ARL#
|
||||
DATA_LENGTH #DL#
|
||||
MAX_DATA_LENGTH #MDL#
|
||||
INDEX_LENGTH #IL#
|
||||
DATA_FREE #DF#
|
||||
AUTO_INCREMENT NULL
|
||||
CREATE_TIME #CRT#
|
||||
UPDATE_TIME #UT#
|
||||
CHECK_TIME #CT#
|
||||
TABLE_COLLATION utf8_general_ci
|
||||
CHECKSUM NULL
|
||||
CREATE_OPTIONS #CO#
|
||||
TABLE_COMMENT #TC#
|
||||
MAX_INDEX_LENGTH #MIL#
|
||||
TEMPORARY Y
|
||||
user_comment
|
||||
Separator -----------------------------------------------------
|
||||
TABLE_CATALOG def
|
||||
TABLE_SCHEMA information_schema
|
||||
TABLE_NAME PARAMETERS
|
||||
TABLE_TYPE SYSTEM VIEW
|
||||
ENGINE MYISAM_OR_MARIA
|
||||
@ -1530,6 +1555,31 @@ user_comment
|
||||
Separator -----------------------------------------------------
|
||||
TABLE_CATALOG def
|
||||
TABLE_SCHEMA information_schema
|
||||
TABLE_NAME OPTIMIZER_TRACE
|
||||
TABLE_TYPE SYSTEM VIEW
|
||||
ENGINE MYISAM_OR_MARIA
|
||||
VERSION 11
|
||||
ROW_FORMAT DYNAMIC_OR_PAGE
|
||||
TABLE_ROWS #TBLR#
|
||||
AVG_ROW_LENGTH #ARL#
|
||||
DATA_LENGTH #DL#
|
||||
MAX_DATA_LENGTH #MDL#
|
||||
INDEX_LENGTH #IL#
|
||||
DATA_FREE #DF#
|
||||
AUTO_INCREMENT NULL
|
||||
CREATE_TIME #CRT#
|
||||
UPDATE_TIME #UT#
|
||||
CHECK_TIME #CT#
|
||||
TABLE_COLLATION utf8_general_ci
|
||||
CHECKSUM NULL
|
||||
CREATE_OPTIONS #CO#
|
||||
TABLE_COMMENT #TC#
|
||||
MAX_INDEX_LENGTH #MIL#
|
||||
TEMPORARY Y
|
||||
user_comment
|
||||
Separator -----------------------------------------------------
|
||||
TABLE_CATALOG def
|
||||
TABLE_SCHEMA information_schema
|
||||
TABLE_NAME PARAMETERS
|
||||
TABLE_TYPE SYSTEM VIEW
|
||||
ENGINE MYISAM_OR_MARIA
|
||||
|
@ -489,6 +489,31 @@ user_comment
|
||||
Separator -----------------------------------------------------
|
||||
TABLE_CATALOG def
|
||||
TABLE_SCHEMA information_schema
|
||||
TABLE_NAME OPTIMIZER_TRACE
|
||||
TABLE_TYPE SYSTEM VIEW
|
||||
ENGINE MYISAM_OR_MARIA
|
||||
VERSION 11
|
||||
ROW_FORMAT DYNAMIC_OR_PAGE
|
||||
TABLE_ROWS #TBLR#
|
||||
AVG_ROW_LENGTH #ARL#
|
||||
DATA_LENGTH #DL#
|
||||
MAX_DATA_LENGTH #MDL#
|
||||
INDEX_LENGTH #IL#
|
||||
DATA_FREE #DF#
|
||||
AUTO_INCREMENT NULL
|
||||
CREATE_TIME #CRT#
|
||||
UPDATE_TIME #UT#
|
||||
CHECK_TIME #CT#
|
||||
TABLE_COLLATION utf8_general_ci
|
||||
CHECKSUM NULL
|
||||
CREATE_OPTIONS #CO#
|
||||
TABLE_COMMENT #TC#
|
||||
MAX_INDEX_LENGTH #MIL#
|
||||
TEMPORARY Y
|
||||
user_comment
|
||||
Separator -----------------------------------------------------
|
||||
TABLE_CATALOG def
|
||||
TABLE_SCHEMA information_schema
|
||||
TABLE_NAME PARAMETERS
|
||||
TABLE_TYPE SYSTEM VIEW
|
||||
ENGINE MYISAM_OR_MARIA
|
||||
@ -1530,6 +1555,31 @@ user_comment
|
||||
Separator -----------------------------------------------------
|
||||
TABLE_CATALOG def
|
||||
TABLE_SCHEMA information_schema
|
||||
TABLE_NAME OPTIMIZER_TRACE
|
||||
TABLE_TYPE SYSTEM VIEW
|
||||
ENGINE MYISAM_OR_MARIA
|
||||
VERSION 11
|
||||
ROW_FORMAT DYNAMIC_OR_PAGE
|
||||
TABLE_ROWS #TBLR#
|
||||
AVG_ROW_LENGTH #ARL#
|
||||
DATA_LENGTH #DL#
|
||||
MAX_DATA_LENGTH #MDL#
|
||||
INDEX_LENGTH #IL#
|
||||
DATA_FREE #DF#
|
||||
AUTO_INCREMENT NULL
|
||||
CREATE_TIME #CRT#
|
||||
UPDATE_TIME #UT#
|
||||
CHECK_TIME #CT#
|
||||
TABLE_COLLATION utf8_general_ci
|
||||
CHECKSUM NULL
|
||||
CREATE_OPTIONS #CO#
|
||||
TABLE_COMMENT #TC#
|
||||
MAX_INDEX_LENGTH #MIL#
|
||||
TEMPORARY Y
|
||||
user_comment
|
||||
Separator -----------------------------------------------------
|
||||
TABLE_CATALOG def
|
||||
TABLE_SCHEMA information_schema
|
||||
TABLE_NAME PARAMETERS
|
||||
TABLE_TYPE SYSTEM VIEW
|
||||
ENGINE MYISAM_OR_MARIA
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -2742,6 +2742,34 @@ NUMERIC_BLOCK_SIZE NULL
|
||||
ENUM_VALUE_LIST index_merge,index_merge_union,index_merge_sort_union,index_merge_intersection,index_merge_sort_intersection,engine_condition_pushdown,index_condition_pushdown,derived_merge,derived_with_keys,firstmatch,loosescan,materialization,in_to_exists,semijoin,partial_match_rowid_merge,partial_match_table_scan,subquery_cache,mrr,mrr_cost_based,mrr_sort_keys,outer_join_with_cache,semijoin_with_cache,join_cache_incremental,join_cache_hashed,join_cache_bka,optimize_join_buffer_size,table_elimination,extended_keys,exists_to_in,orderby_uses_equalities,condition_pushdown_for_derived,split_materialized,condition_pushdown_for_subquery,default
|
||||
READ_ONLY NO
|
||||
COMMAND_LINE_ARGUMENT REQUIRED
|
||||
VARIABLE_NAME OPTIMIZER_TRACE
|
||||
SESSION_VALUE enabled=off
|
||||
GLOBAL_VALUE enabled=off
|
||||
GLOBAL_VALUE_ORIGIN COMPILE-TIME
|
||||
DEFAULT_VALUE enabled=off
|
||||
VARIABLE_SCOPE SESSION
|
||||
VARIABLE_TYPE FLAGSET
|
||||
VARIABLE_COMMENT Controls tracing of the Optimizer: optimizer_trace=option=val[,option=val...], where option is one of {enabled} and val is one of {on, off, default}
|
||||
NUMERIC_MIN_VALUE NULL
|
||||
NUMERIC_MAX_VALUE NULL
|
||||
NUMERIC_BLOCK_SIZE NULL
|
||||
ENUM_VALUE_LIST enabled,default
|
||||
READ_ONLY NO
|
||||
COMMAND_LINE_ARGUMENT REQUIRED
|
||||
VARIABLE_NAME OPTIMIZER_TRACE_MAX_MEM_SIZE
|
||||
SESSION_VALUE 1048576
|
||||
GLOBAL_VALUE 1048576
|
||||
GLOBAL_VALUE_ORIGIN COMPILE-TIME
|
||||
DEFAULT_VALUE 1048576
|
||||
VARIABLE_SCOPE SESSION
|
||||
VARIABLE_TYPE BIGINT UNSIGNED
|
||||
VARIABLE_COMMENT Maximum allowed size of an optimizer trace
|
||||
NUMERIC_MIN_VALUE 0
|
||||
NUMERIC_MAX_VALUE 18446744073709551615
|
||||
NUMERIC_BLOCK_SIZE 1
|
||||
ENUM_VALUE_LIST NULL
|
||||
READ_ONLY NO
|
||||
COMMAND_LINE_ARGUMENT REQUIRED
|
||||
VARIABLE_NAME OPTIMIZER_USE_CONDITION_SELECTIVITY
|
||||
SESSION_VALUE 4
|
||||
GLOBAL_VALUE 4
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -2966,6 +2966,34 @@ NUMERIC_BLOCK_SIZE NULL
|
||||
ENUM_VALUE_LIST index_merge,index_merge_union,index_merge_sort_union,index_merge_intersection,index_merge_sort_intersection,engine_condition_pushdown,index_condition_pushdown,derived_merge,derived_with_keys,firstmatch,loosescan,materialization,in_to_exists,semijoin,partial_match_rowid_merge,partial_match_table_scan,subquery_cache,mrr,mrr_cost_based,mrr_sort_keys,outer_join_with_cache,semijoin_with_cache,join_cache_incremental,join_cache_hashed,join_cache_bka,optimize_join_buffer_size,table_elimination,extended_keys,exists_to_in,orderby_uses_equalities,condition_pushdown_for_derived,split_materialized,condition_pushdown_for_subquery,default
|
||||
READ_ONLY NO
|
||||
COMMAND_LINE_ARGUMENT REQUIRED
|
||||
VARIABLE_NAME OPTIMIZER_TRACE
|
||||
SESSION_VALUE enabled=off
|
||||
GLOBAL_VALUE enabled=off
|
||||
GLOBAL_VALUE_ORIGIN COMPILE-TIME
|
||||
DEFAULT_VALUE enabled=off
|
||||
VARIABLE_SCOPE SESSION
|
||||
VARIABLE_TYPE FLAGSET
|
||||
VARIABLE_COMMENT Controls tracing of the Optimizer: optimizer_trace=option=val[,option=val...], where option is one of {enabled} and val is one of {on, off, default}
|
||||
NUMERIC_MIN_VALUE NULL
|
||||
NUMERIC_MAX_VALUE NULL
|
||||
NUMERIC_BLOCK_SIZE NULL
|
||||
ENUM_VALUE_LIST enabled,default
|
||||
READ_ONLY NO
|
||||
COMMAND_LINE_ARGUMENT REQUIRED
|
||||
VARIABLE_NAME OPTIMIZER_TRACE_MAX_MEM_SIZE
|
||||
SESSION_VALUE 1048576
|
||||
GLOBAL_VALUE 1048576
|
||||
GLOBAL_VALUE_ORIGIN COMPILE-TIME
|
||||
DEFAULT_VALUE 1048576
|
||||
VARIABLE_SCOPE SESSION
|
||||
VARIABLE_TYPE BIGINT UNSIGNED
|
||||
VARIABLE_COMMENT Maximum allowed size of an optimizer trace
|
||||
NUMERIC_MIN_VALUE 0
|
||||
NUMERIC_MAX_VALUE 18446744073709551615
|
||||
NUMERIC_BLOCK_SIZE 1
|
||||
ENUM_VALUE_LIST NULL
|
||||
READ_ONLY NO
|
||||
COMMAND_LINE_ARGUMENT REQUIRED
|
||||
VARIABLE_NAME OPTIMIZER_USE_CONDITION_SELECTIVITY
|
||||
SESSION_VALUE 4
|
||||
GLOBAL_VALUE 4
|
||||
|
@ -139,6 +139,7 @@ SET (SQL_SOURCE
|
||||
sql_sequence.cc sql_sequence.h ha_sequence.h
|
||||
sql_tvc.cc sql_tvc.h
|
||||
opt_split.cc
|
||||
opt_trace.cc
|
||||
${WSREP_SOURCES}
|
||||
table_cache.cc encryption.cc temporary_tables.cc
|
||||
proxy_protocol.cc backup.cc
|
||||
|
@ -993,6 +993,7 @@ enum enum_schema_tables
|
||||
SCH_KEY_CACHES,
|
||||
SCH_KEY_COLUMN_USAGE,
|
||||
SCH_OPEN_TABLES,
|
||||
SCH_OPT_TRACE,
|
||||
SCH_PARAMETERS,
|
||||
SCH_PARTITIONS,
|
||||
SCH_PLUGINS,
|
||||
|
@ -219,19 +219,20 @@ void Json_writer::add_str(const String &str)
|
||||
add_str(str.ptr(), str.length());
|
||||
}
|
||||
|
||||
Json_writer_object::Json_writer_object(Json_writer *writer):Json_writer_struct(writer)
|
||||
Json_writer_object::Json_writer_object(THD *thd) :
|
||||
Json_writer_struct(thd)
|
||||
{
|
||||
if (my_writer)
|
||||
my_writer->start_object();
|
||||
}
|
||||
|
||||
Json_writer_object::Json_writer_object(Json_writer *writer, const char *str)
|
||||
:Json_writer_struct(writer)
|
||||
Json_writer_object::Json_writer_object(THD* thd, const char *str)
|
||||
: Json_writer_struct(thd)
|
||||
{
|
||||
if (my_writer)
|
||||
my_writer->add_member(str).start_object();
|
||||
|
||||
}
|
||||
|
||||
Json_writer_object::~Json_writer_object()
|
||||
{
|
||||
if (!closed && my_writer)
|
||||
@ -239,14 +240,15 @@ Json_writer_object::~Json_writer_object()
|
||||
closed= TRUE;
|
||||
}
|
||||
|
||||
Json_writer_array::Json_writer_array(Json_writer *writer):Json_writer_struct(writer)
|
||||
Json_writer_array::Json_writer_array(THD *thd) :
|
||||
Json_writer_struct(thd)
|
||||
{
|
||||
if (my_writer)
|
||||
my_writer->start_array();
|
||||
}
|
||||
|
||||
Json_writer_array::Json_writer_array(Json_writer *writer, const char *str)
|
||||
:Json_writer_struct(writer)
|
||||
Json_writer_array::Json_writer_array(THD *thd, const char *str)
|
||||
:Json_writer_struct(thd)
|
||||
{
|
||||
if (my_writer)
|
||||
my_writer->add_member(str).start_array();
|
||||
|
@ -100,6 +100,80 @@ public:
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
Something that looks like class String, but has an internal limit of
|
||||
how many bytes one can append to it.
|
||||
|
||||
Bytes that were truncated due to the size limitation are counted.
|
||||
*/
|
||||
|
||||
class String_with_limit
|
||||
{
|
||||
public:
|
||||
|
||||
String_with_limit() : size_limit(SIZE_MAX), truncated_len(0)
|
||||
{
|
||||
str.length(0);
|
||||
}
|
||||
|
||||
size_t get_truncated_bytes() const { return truncated_len; }
|
||||
size_t get_size_limit() { return size_limit; }
|
||||
|
||||
void set_size_limit(size_t limit_arg)
|
||||
{
|
||||
// Setting size limit to be shorter than length will not have the desired
|
||||
// effect
|
||||
DBUG_ASSERT(str.length() < size_limit);
|
||||
size_limit= limit_arg;
|
||||
}
|
||||
|
||||
void append(const char *s, size_t size)
|
||||
{
|
||||
if (str.length() + size <= size_limit)
|
||||
{
|
||||
// Whole string can be added, just do it
|
||||
str.append(s, size);
|
||||
}
|
||||
else
|
||||
{
|
||||
// We cannot add the whole string
|
||||
if (str.length() < size_limit)
|
||||
{
|
||||
// But we can still add something
|
||||
size_t bytes_to_add = size_limit - str.length();
|
||||
str.append(s, bytes_to_add);
|
||||
truncated_len += size - bytes_to_add;
|
||||
}
|
||||
else
|
||||
truncated_len += size;
|
||||
}
|
||||
}
|
||||
|
||||
void append(const char *s)
|
||||
{
|
||||
append(s, strlen(s));
|
||||
}
|
||||
|
||||
void append(char c)
|
||||
{
|
||||
if (str.length() + 1 > size_limit)
|
||||
truncated_len++;
|
||||
else
|
||||
str.append(c);
|
||||
}
|
||||
|
||||
const String *get_string() { return &str; }
|
||||
size_t length() { return str.length(); }
|
||||
private:
|
||||
String str;
|
||||
|
||||
// str must not get longer than this many bytes.
|
||||
size_t size_limit;
|
||||
|
||||
// How many bytes were truncated from the string
|
||||
size_t truncated_len;
|
||||
};
|
||||
|
||||
/*
|
||||
A class to write well-formed JSON documents. The documents are also formatted
|
||||
for human readability.
|
||||
@ -116,7 +190,8 @@ public:
|
||||
void add_str(const char* val, size_t num_bytes);
|
||||
void add_str(const String &str);
|
||||
void add_str(Item *item);
|
||||
void add_table_name(JOIN_TAB *tab);
|
||||
void add_table_name(const JOIN_TAB *tab);
|
||||
void add_table_name(const TABLE* table);
|
||||
|
||||
void add_ll(longlong val);
|
||||
void add_size(longlong val);
|
||||
@ -134,9 +209,18 @@ public:
|
||||
void end_object();
|
||||
void end_array();
|
||||
|
||||
/*
|
||||
One can set a limit of how large a JSON document should be.
|
||||
Writes beyond that size will be counted, but will not be collected.
|
||||
*/
|
||||
void set_size_limit(size_t mem_size) { output.set_size_limit(mem_size); }
|
||||
|
||||
// psergey: return how many bytes would be required to store everything
|
||||
size_t get_truncated_bytes() { return output.get_truncated_bytes(); }
|
||||
|
||||
Json_writer() :
|
||||
indent_level(0), document_start(true), element_started(false),
|
||||
first_child(true)
|
||||
first_child(true), allowed_mem_size(0)
|
||||
{
|
||||
fmt_helper.init(this);
|
||||
}
|
||||
@ -151,22 +235,28 @@ private:
|
||||
bool element_started;
|
||||
bool first_child;
|
||||
|
||||
/*
|
||||
True when we are using the optimizer trace
|
||||
FALSE otherwise
|
||||
*/
|
||||
size_t allowed_mem_size;
|
||||
|
||||
Single_line_formatting_helper fmt_helper;
|
||||
|
||||
void append_indent();
|
||||
void start_element();
|
||||
void start_sub_element();
|
||||
|
||||
//const char *new_member_name;
|
||||
public:
|
||||
String output;
|
||||
String_with_limit output;
|
||||
};
|
||||
|
||||
/* A class to add values to Json_writer_object and Json_writer_array */
|
||||
class Json_value_context
|
||||
class Json_value_helper
|
||||
{
|
||||
Json_writer* writer;
|
||||
public:
|
||||
|
||||
public:
|
||||
void init(Json_writer *my_writer) { writer= my_writer; }
|
||||
void add_str(const char* val)
|
||||
{
|
||||
@ -219,11 +309,16 @@ class Json_value_context
|
||||
if (writer)
|
||||
writer->add_null();
|
||||
}
|
||||
void add_table_name(JOIN_TAB *tab)
|
||||
void add_table_name(const JOIN_TAB *tab)
|
||||
{
|
||||
if (writer)
|
||||
writer->add_table_name(tab);
|
||||
}
|
||||
void add_table_name(const TABLE* table)
|
||||
{
|
||||
if (writer)
|
||||
writer->add_table_name(table);
|
||||
}
|
||||
};
|
||||
|
||||
/* A common base for Json_writer_object and Json_writer_array */
|
||||
@ -231,17 +326,17 @@ class Json_writer_struct
|
||||
{
|
||||
protected:
|
||||
Json_writer* my_writer;
|
||||
Json_value_context context;
|
||||
Json_value_helper context;
|
||||
/*
|
||||
Tells when a json_writer_struct has been closed or not
|
||||
*/
|
||||
bool closed;
|
||||
|
||||
public:
|
||||
Json_writer_struct(Json_writer* writer)
|
||||
explicit Json_writer_struct(THD *thd)
|
||||
{
|
||||
my_writer= writer;
|
||||
context.init(writer);
|
||||
my_writer= thd->opt_trace.get_current_json();
|
||||
context.init(my_writer);
|
||||
closed= false;
|
||||
}
|
||||
};
|
||||
@ -249,9 +344,13 @@ public:
|
||||
|
||||
/*
|
||||
RAII-based class to start/end writing a JSON object into the JSON document
|
||||
|
||||
There is "ignore mode": one can initialize Json_writer_object with a NULL
|
||||
Json_writer argument, and then all its calls will do nothing. This is used
|
||||
by optimizer trace which can be enabled or disabled.
|
||||
*/
|
||||
|
||||
class Json_writer_object:public Json_writer_struct
|
||||
class Json_writer_object : public Json_writer_struct
|
||||
{
|
||||
private:
|
||||
void add_member(const char *name)
|
||||
@ -260,46 +359,49 @@ private:
|
||||
my_writer->add_member(name);
|
||||
}
|
||||
public:
|
||||
Json_writer_object(Json_writer *w);
|
||||
Json_writer_object(Json_writer *w, const char *str);
|
||||
explicit Json_writer_object(THD *thd);
|
||||
explicit Json_writer_object(THD *thd, const char *str);
|
||||
|
||||
Json_writer_object& add(const char *name, bool value)
|
||||
{
|
||||
DBUG_ASSERT(!closed);
|
||||
add_member(name);
|
||||
context.add_bool(value);
|
||||
return *this;
|
||||
}
|
||||
Json_writer_object& add(const char* name, uint value)
|
||||
Json_writer_object& add(const char *name, ulonglong value)
|
||||
{
|
||||
DBUG_ASSERT(!closed);
|
||||
add_member(name);
|
||||
context.add_ll(value);
|
||||
return *this;
|
||||
}
|
||||
Json_writer_object& add(const char* name, ha_rows value)
|
||||
{
|
||||
add_member(name);
|
||||
context.add_ll(value);
|
||||
context.add_ll(static_cast<longlong>(value));
|
||||
return *this;
|
||||
}
|
||||
Json_writer_object& add(const char *name, longlong value)
|
||||
{
|
||||
DBUG_ASSERT(!closed);
|
||||
add_member(name);
|
||||
context.add_ll(value);
|
||||
return *this;
|
||||
}
|
||||
Json_writer_object& add(const char *name, double value)
|
||||
{
|
||||
DBUG_ASSERT(!closed);
|
||||
add_member(name);
|
||||
context.add_double(value);
|
||||
return *this;
|
||||
}
|
||||
#ifndef _WIN64
|
||||
Json_writer_object& add(const char *name, size_t value)
|
||||
{
|
||||
DBUG_ASSERT(!closed);
|
||||
add_member(name);
|
||||
context.add_ll(value);
|
||||
context.add_ll(static_cast<longlong>(value));
|
||||
return *this;
|
||||
}
|
||||
#endif
|
||||
Json_writer_object& add(const char *name, const char *value)
|
||||
{
|
||||
DBUG_ASSERT(!closed);
|
||||
add_member(name);
|
||||
context.add_str(value);
|
||||
return *this;
|
||||
@ -310,38 +412,54 @@ public:
|
||||
context.add_str(value, num_bytes);
|
||||
return *this;
|
||||
}
|
||||
Json_writer_object& add(const char *name, const String &value)
|
||||
{
|
||||
add_member(name);
|
||||
context.add_str(value);
|
||||
return *this;
|
||||
}
|
||||
Json_writer_object& add(const char *name, LEX_CSTRING value)
|
||||
{
|
||||
DBUG_ASSERT(!closed);
|
||||
add_member(name);
|
||||
context.add_str(value.str);
|
||||
return *this;
|
||||
}
|
||||
Json_writer_object& add(const char *name, Item *value)
|
||||
{
|
||||
DBUG_ASSERT(!closed);
|
||||
add_member(name);
|
||||
context.add_str(value);
|
||||
return *this;
|
||||
}
|
||||
Json_writer_object& add_null(const char*name)
|
||||
{
|
||||
DBUG_ASSERT(!closed);
|
||||
add_member(name);
|
||||
context.add_null();
|
||||
return *this;
|
||||
}
|
||||
Json_writer_object& add_table_name(JOIN_TAB *tab)
|
||||
Json_writer_object& add_table_name(const JOIN_TAB *tab)
|
||||
{
|
||||
DBUG_ASSERT(!closed);
|
||||
add_member("table");
|
||||
context.add_table_name(tab);
|
||||
return *this;
|
||||
}
|
||||
Json_writer_object& add_table_name(const TABLE *table)
|
||||
{
|
||||
DBUG_ASSERT(!closed);
|
||||
add_member("table");
|
||||
context.add_table_name(table);
|
||||
return *this;
|
||||
}
|
||||
Json_writer_object& add_select_number(uint select_number)
|
||||
{
|
||||
DBUG_ASSERT(!closed);
|
||||
add_member("select_id");
|
||||
if (unlikely(select_number >= INT_MAX))
|
||||
context.add_str("fake");
|
||||
else
|
||||
context.add_ll(static_cast<longlong>(select_number));
|
||||
return *this;
|
||||
}
|
||||
void end()
|
||||
{
|
||||
DBUG_ASSERT(!closed);
|
||||
if (my_writer)
|
||||
my_writer->end_object();
|
||||
closed= TRUE;
|
||||
@ -352,83 +470,99 @@ public:
|
||||
|
||||
/*
|
||||
RAII-based class to start/end writing a JSON array into the JSON document
|
||||
|
||||
There is "ignore mode": one can initialize Json_writer_array with a NULL
|
||||
Json_writer argument, and then all its calls will do nothing. This is used
|
||||
by optimizer trace which can be enabled or disabled.
|
||||
*/
|
||||
class Json_writer_array:public Json_writer_struct
|
||||
|
||||
class Json_writer_array : public Json_writer_struct
|
||||
{
|
||||
public:
|
||||
Json_writer_array(Json_writer *w);
|
||||
Json_writer_array(Json_writer *w, const char *str);
|
||||
Json_writer_array(THD *thd);
|
||||
Json_writer_array(THD *thd, const char *str);
|
||||
void end()
|
||||
{
|
||||
DBUG_ASSERT(!closed);
|
||||
if (my_writer)
|
||||
my_writer->end_array();
|
||||
closed= TRUE;
|
||||
}
|
||||
|
||||
Json_writer_array& add(bool value)
|
||||
{
|
||||
DBUG_ASSERT(!closed);
|
||||
context.add_bool(value);
|
||||
return *this;
|
||||
}
|
||||
Json_writer_array& add(uint value)
|
||||
Json_writer_array& add(ulonglong value)
|
||||
{
|
||||
context.add_ll(value);
|
||||
return *this;
|
||||
}
|
||||
Json_writer_array& add(ha_rows value)
|
||||
{
|
||||
context.add_ll(value);
|
||||
DBUG_ASSERT(!closed);
|
||||
context.add_ll(static_cast<longlong>(value));
|
||||
return *this;
|
||||
}
|
||||
Json_writer_array& add(longlong value)
|
||||
{
|
||||
DBUG_ASSERT(!closed);
|
||||
context.add_ll(value);
|
||||
return *this;
|
||||
}
|
||||
Json_writer_array& add(double value)
|
||||
{
|
||||
DBUG_ASSERT(!closed);
|
||||
context.add_double(value);
|
||||
return *this;
|
||||
}
|
||||
#ifndef _WIN64
|
||||
Json_writer_array& add(size_t value)
|
||||
{
|
||||
context.add_ll(value);
|
||||
DBUG_ASSERT(!closed);
|
||||
context.add_ll(static_cast<longlong>(value));
|
||||
return *this;
|
||||
}
|
||||
#endif
|
||||
Json_writer_array& add(const char *value)
|
||||
{
|
||||
DBUG_ASSERT(!closed);
|
||||
context.add_str(value);
|
||||
return *this;
|
||||
}
|
||||
Json_writer_array& add(const char *value, size_t num_bytes)
|
||||
{
|
||||
DBUG_ASSERT(!closed);
|
||||
context.add_str(value, num_bytes);
|
||||
return *this;
|
||||
}
|
||||
Json_writer_array& add(const String &value)
|
||||
{
|
||||
context.add_str(value);
|
||||
return *this;
|
||||
}
|
||||
Json_writer_array& add(LEX_CSTRING value)
|
||||
{
|
||||
DBUG_ASSERT(!closed);
|
||||
context.add_str(value.str);
|
||||
return *this;
|
||||
}
|
||||
Json_writer_array& add(Item *value)
|
||||
{
|
||||
DBUG_ASSERT(!closed);
|
||||
context.add_str(value);
|
||||
return *this;
|
||||
}
|
||||
Json_writer_array& add_null()
|
||||
{
|
||||
DBUG_ASSERT(!closed);
|
||||
context.add_null();
|
||||
return *this;
|
||||
}
|
||||
Json_writer_array& add_table_name(JOIN_TAB *tab)
|
||||
Json_writer_array& add_table_name(const JOIN_TAB *tab)
|
||||
{
|
||||
DBUG_ASSERT(!closed);
|
||||
context.add_table_name(tab);
|
||||
return *this;
|
||||
}
|
||||
Json_writer_array& add_table_name(const TABLE *table)
|
||||
{
|
||||
DBUG_ASSERT(!closed);
|
||||
context.add_table_name(table);
|
||||
return *this;
|
||||
}
|
||||
~Json_writer_array();
|
||||
};
|
||||
|
||||
|
845
sql/opt_range.cc
845
sql/opt_range.cc
File diff suppressed because it is too large
Load Diff
@ -566,7 +566,7 @@ public:
|
||||
FALSE Otherwise
|
||||
*/
|
||||
|
||||
bool is_singlepoint()
|
||||
bool is_singlepoint() const
|
||||
{
|
||||
/*
|
||||
Check for NEAR_MIN ("strictly less") and NO_MIN_RANGE (-inf < field)
|
||||
|
@ -33,6 +33,7 @@
|
||||
#include "opt_subselect.h"
|
||||
#include "sql_test.h"
|
||||
#include <my_bit.h>
|
||||
#include "opt_trace.h"
|
||||
|
||||
/*
|
||||
This file contains optimizations for semi-join subqueries.
|
||||
@ -437,7 +438,7 @@ Currently, solution #2 is implemented.
|
||||
LEX_CSTRING weedout_key= {STRING_WITH_LEN("weedout_key")};
|
||||
|
||||
static
|
||||
bool subquery_types_allow_materialization(Item_in_subselect *in_subs);
|
||||
bool subquery_types_allow_materialization(THD *thd, Item_in_subselect *in_subs);
|
||||
static bool replace_where_subcondition(JOIN *, Item **, Item *, Item *, bool);
|
||||
static int subq_sj_candidate_cmp(Item_in_subselect* el1, Item_in_subselect* el2,
|
||||
void *arg);
|
||||
@ -521,7 +522,7 @@ bool is_materialization_applicable(THD *thd, Item_in_subselect *in_subs,
|
||||
parent_unit->first_select()->leaf_tables.elements && // 2
|
||||
child_select->outer_select() &&
|
||||
child_select->outer_select()->leaf_tables.elements && // 2A
|
||||
subquery_types_allow_materialization(in_subs) &&
|
||||
subquery_types_allow_materialization(thd, in_subs) &&
|
||||
(in_subs->is_top_level_item() || //3
|
||||
optimizer_flag(thd,
|
||||
OPTIMIZER_SWITCH_PARTIAL_MATCH_ROWID_MERGE) || //3
|
||||
@ -682,7 +683,7 @@ int check_and_do_in_subquery_rewrites(JOIN *join)
|
||||
{
|
||||
DBUG_PRINT("info", ("Subquery is semi-join conversion candidate"));
|
||||
|
||||
(void)subquery_types_allow_materialization(in_subs);
|
||||
(void)subquery_types_allow_materialization(thd, in_subs);
|
||||
|
||||
in_subs->is_flattenable_semijoin= TRUE;
|
||||
|
||||
@ -696,6 +697,9 @@ int check_and_do_in_subquery_rewrites(JOIN *join)
|
||||
if (arena)
|
||||
thd->restore_active_arena(arena, &backup);
|
||||
in_subs->is_registered_semijoin= TRUE;
|
||||
OPT_TRACE_TRANSFORM(thd, oto0, oto1, select_lex->select_number,
|
||||
"IN (SELECT)", "semijoin");
|
||||
oto1.add("chosen", true);
|
||||
}
|
||||
}
|
||||
else
|
||||
@ -823,7 +827,7 @@ int check_and_do_in_subquery_rewrites(JOIN *join)
|
||||
*/
|
||||
|
||||
static
|
||||
bool subquery_types_allow_materialization(Item_in_subselect *in_subs)
|
||||
bool subquery_types_allow_materialization(THD* thd, Item_in_subselect *in_subs)
|
||||
{
|
||||
DBUG_ENTER("subquery_types_allow_materialization");
|
||||
|
||||
@ -831,9 +835,14 @@ bool subquery_types_allow_materialization(Item_in_subselect *in_subs)
|
||||
|
||||
List_iterator<Item> it(in_subs->unit->first_select()->item_list);
|
||||
uint elements= in_subs->unit->first_select()->item_list.elements;
|
||||
const char* cause= NULL;
|
||||
|
||||
in_subs->types_allow_materialization= FALSE; // Assign default values
|
||||
in_subs->sjm_scan_allowed= FALSE;
|
||||
|
||||
OPT_TRACE_TRANSFORM(thd, oto0, oto1,
|
||||
in_subs->get_select_lex()->select_number,
|
||||
"IN (SELECT)", "materialization");
|
||||
|
||||
bool all_are_fields= TRUE;
|
||||
uint32 total_key_length = 0;
|
||||
@ -846,7 +855,11 @@ bool subquery_types_allow_materialization(Item_in_subselect *in_subs)
|
||||
total_key_length += inner->max_length;
|
||||
if (!inner->type_handler()->subquery_type_allows_materialization(inner,
|
||||
outer))
|
||||
{
|
||||
oto1.add("possible", false);
|
||||
oto1.add("cause", "types mismatch");
|
||||
DBUG_RETURN(FALSE);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
@ -856,14 +869,23 @@ bool subquery_types_allow_materialization(Item_in_subselect *in_subs)
|
||||
Make sure that the length of the key for the temp_table is atleast
|
||||
greater than 0.
|
||||
*/
|
||||
if (!total_key_length || total_key_length > tmp_table_max_key_length() ||
|
||||
elements > tmp_table_max_key_parts())
|
||||
DBUG_RETURN(FALSE);
|
||||
|
||||
in_subs->types_allow_materialization= TRUE;
|
||||
in_subs->sjm_scan_allowed= all_are_fields;
|
||||
DBUG_PRINT("info",("subquery_types_allow_materialization: ok, allowed"));
|
||||
DBUG_RETURN(TRUE);
|
||||
if (!total_key_length)
|
||||
cause= "zero length key for materialized table";
|
||||
else if (total_key_length > tmp_table_max_key_length())
|
||||
cause= "length of key greater than allowed key length for materialized tables";
|
||||
else if (elements > tmp_table_max_key_parts())
|
||||
cause= "#keyparts greater than allowed key parts for materialized tables";
|
||||
else
|
||||
{
|
||||
in_subs->types_allow_materialization= TRUE;
|
||||
in_subs->sjm_scan_allowed= all_are_fields;
|
||||
oto1.add("sjm_scan_allowed", all_are_fields)
|
||||
.add("possible", true);
|
||||
DBUG_PRINT("info",("subquery_types_allow_materialization: ok, allowed"));
|
||||
DBUG_RETURN(TRUE);
|
||||
}
|
||||
oto1.add("possible", false).add("cause", cause);
|
||||
DBUG_RETURN(FALSE);
|
||||
}
|
||||
|
||||
|
||||
@ -1213,15 +1235,30 @@ bool convert_join_subqueries_to_semijoins(JOIN *join)
|
||||
|
||||
/* Stop processing if we've reached a subquery that's attached to the ON clause */
|
||||
if (in_subq->do_not_convert_to_sj)
|
||||
{
|
||||
OPT_TRACE_TRANSFORM(thd, oto0, oto1,
|
||||
in_subq->get_select_lex()->select_number,
|
||||
"IN (SELECT)", "semijoin");
|
||||
oto1.add("converted_to_semi_join", false)
|
||||
.add("cause", "subquery attached to the ON clause");
|
||||
break;
|
||||
}
|
||||
|
||||
if (in_subq->is_flattenable_semijoin)
|
||||
{
|
||||
OPT_TRACE_TRANSFORM(thd, oto0, oto1,
|
||||
in_subq->get_select_lex()->select_number,
|
||||
"IN (SELECT)", "semijoin");
|
||||
if (join->table_count +
|
||||
in_subq->unit->first_select()->join->table_count >= MAX_TABLES)
|
||||
{
|
||||
oto1.add("converted_to_semi_join", false);
|
||||
oto1.add("cause", "table in parent join now exceeds MAX_TABLES");
|
||||
break;
|
||||
}
|
||||
if (convert_subq_to_sj(join, in_subq))
|
||||
goto restore_arena_and_fail;
|
||||
oto1.add("converted_to_semi_join", true);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -2340,8 +2377,13 @@ int pull_out_semijoin_tables(JOIN *join)
|
||||
bool optimize_semijoin_nests(JOIN *join, table_map all_table_map)
|
||||
{
|
||||
DBUG_ENTER("optimize_semijoin_nests");
|
||||
THD *thd= join->thd;
|
||||
List_iterator<TABLE_LIST> sj_list_it(join->select_lex->sj_nests);
|
||||
TABLE_LIST *sj_nest;
|
||||
Json_writer_object wrapper(thd);
|
||||
Json_writer_object trace_semijoin_nest(thd,
|
||||
"execution_plan_for_potential_materialization");
|
||||
Json_writer_array trace_steps_array(thd, "steps");
|
||||
while ((sj_nest= sj_list_it++))
|
||||
{
|
||||
/* semi-join nests with only constant tables are not valid */
|
||||
|
@ -31,6 +31,8 @@
|
||||
#include "mariadb.h"
|
||||
#include "my_bit.h"
|
||||
#include "sql_select.h"
|
||||
#include "opt_trace.h"
|
||||
#include "my_json_writer.h"
|
||||
|
||||
/*
|
||||
OVERVIEW
|
||||
@ -522,7 +524,8 @@ eliminate_tables_for_list(JOIN *join,
|
||||
List<TABLE_LIST> *join_list,
|
||||
table_map tables_in_list,
|
||||
Item *on_expr,
|
||||
table_map tables_used_elsewhere);
|
||||
table_map tables_used_elsewhere,
|
||||
Json_writer_array* eliminate_tables);
|
||||
static
|
||||
bool check_func_dependency(JOIN *join,
|
||||
table_map dep_tables,
|
||||
@ -541,7 +544,8 @@ static
|
||||
Dep_module_expr *merge_eq_mods(Dep_module_expr *start,
|
||||
Dep_module_expr *new_fields,
|
||||
Dep_module_expr *end, uint and_level);
|
||||
static void mark_as_eliminated(JOIN *join, TABLE_LIST *tbl);
|
||||
static void mark_as_eliminated(JOIN *join, TABLE_LIST *tbl,
|
||||
Json_writer_array* eliminate_tables);
|
||||
static
|
||||
void add_module_expr(Dep_analysis_context *dac, Dep_module_expr **eq_mod,
|
||||
uint and_level, Dep_value_field *field_val, Item *right,
|
||||
@ -608,6 +612,8 @@ void eliminate_tables(JOIN *join)
|
||||
if (!optimizer_flag(thd, OPTIMIZER_SWITCH_TABLE_ELIMINATION))
|
||||
DBUG_VOID_RETURN; /* purecov: inspected */
|
||||
|
||||
Json_writer_object trace_wrapper(thd);
|
||||
|
||||
/* Find the tables that are referred to from WHERE/HAVING */
|
||||
used_tables= (join->conds? join->conds->used_tables() : 0) |
|
||||
(join->having? join->having->used_tables() : 0);
|
||||
@ -663,13 +669,14 @@ void eliminate_tables(JOIN *join)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
table_map all_tables= join->all_tables_map();
|
||||
Json_writer_array eliminated_tables(thd,"eliminated_tables");
|
||||
if (all_tables & ~used_tables)
|
||||
{
|
||||
/* There are some tables that we probably could eliminate. Try it. */
|
||||
eliminate_tables_for_list(join, join->join_list, all_tables, NULL,
|
||||
used_tables);
|
||||
used_tables, &eliminated_tables);
|
||||
}
|
||||
DBUG_VOID_RETURN;
|
||||
}
|
||||
@ -712,7 +719,8 @@ void eliminate_tables(JOIN *join)
|
||||
static bool
|
||||
eliminate_tables_for_list(JOIN *join, List<TABLE_LIST> *join_list,
|
||||
table_map list_tables, Item *on_expr,
|
||||
table_map tables_used_elsewhere)
|
||||
table_map tables_used_elsewhere,
|
||||
Json_writer_array *eliminate_tables)
|
||||
{
|
||||
TABLE_LIST *tbl;
|
||||
List_iterator<TABLE_LIST> it(*join_list);
|
||||
@ -734,9 +742,9 @@ eliminate_tables_for_list(JOIN *join, List<TABLE_LIST> *join_list,
|
||||
&tbl->nested_join->join_list,
|
||||
tbl->nested_join->used_tables,
|
||||
tbl->on_expr,
|
||||
outside_used_tables))
|
||||
outside_used_tables, eliminate_tables))
|
||||
{
|
||||
mark_as_eliminated(join, tbl);
|
||||
mark_as_eliminated(join, tbl, eliminate_tables);
|
||||
}
|
||||
else
|
||||
all_eliminated= FALSE;
|
||||
@ -748,7 +756,7 @@ eliminate_tables_for_list(JOIN *join, List<TABLE_LIST> *join_list,
|
||||
check_func_dependency(join, tbl->table->map, NULL, tbl,
|
||||
tbl->on_expr))
|
||||
{
|
||||
mark_as_eliminated(join, tbl);
|
||||
mark_as_eliminated(join, tbl, eliminate_tables);
|
||||
}
|
||||
else
|
||||
all_eliminated= FALSE;
|
||||
@ -1788,7 +1796,8 @@ Dep_module* Dep_value_field::get_next_unbound_module(Dep_analysis_context *dac,
|
||||
Mark one table or the whole join nest as eliminated.
|
||||
*/
|
||||
|
||||
static void mark_as_eliminated(JOIN *join, TABLE_LIST *tbl)
|
||||
static void mark_as_eliminated(JOIN *join, TABLE_LIST *tbl,
|
||||
Json_writer_array* eliminate_tables)
|
||||
{
|
||||
TABLE *table;
|
||||
/*
|
||||
@ -1801,7 +1810,7 @@ static void mark_as_eliminated(JOIN *join, TABLE_LIST *tbl)
|
||||
TABLE_LIST *child;
|
||||
List_iterator<TABLE_LIST> it(tbl->nested_join->join_list);
|
||||
while ((child= it++))
|
||||
mark_as_eliminated(join, child);
|
||||
mark_as_eliminated(join, child, eliminate_tables);
|
||||
}
|
||||
else if ((table= tbl->table))
|
||||
{
|
||||
@ -1812,6 +1821,7 @@ static void mark_as_eliminated(JOIN *join, TABLE_LIST *tbl)
|
||||
tab->type= JT_CONST;
|
||||
tab->table->const_table= 1;
|
||||
join->eliminated_tables |= table->map;
|
||||
eliminate_tables->add(table->alias.c_ptr_safe());
|
||||
join->const_table_map|= table->map;
|
||||
set_position(join, join->const_tables++, tab, (KEYUSE*)0);
|
||||
}
|
||||
|
722
sql/opt_trace.cc
Normal file
722
sql/opt_trace.cc
Normal file
@ -0,0 +1,722 @@
|
||||
/* This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; version 2 of the License.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */
|
||||
|
||||
#include "mariadb.h"
|
||||
#include "sql_array.h"
|
||||
#include "sql_string.h"
|
||||
#include "sql_class.h"
|
||||
#include "sql_show.h"
|
||||
#include "field.h"
|
||||
#include "table.h"
|
||||
#include "opt_trace.h"
|
||||
#include "sql_parse.h"
|
||||
#include "set_var.h"
|
||||
#include "my_json_writer.h"
|
||||
#include "sp_head.h"
|
||||
|
||||
const char I_S_table_name[] = "OPTIMIZER_TRACE";
|
||||
|
||||
/**
|
||||
Whether a list of tables contains information_schema.OPTIMIZER_TRACE.
|
||||
@param tbl list of tables
|
||||
|
||||
Can we do better than this here??
|
||||
@note this does not catch that a stored routine or view accesses
|
||||
the OPTIMIZER_TRACE table. So using a stored routine or view to read
|
||||
OPTIMIZER_TRACE will overwrite OPTIMIZER_TRACE as it runs and provide
|
||||
uninteresting info.
|
||||
*/
|
||||
bool list_has_optimizer_trace_table(const TABLE_LIST *tbl)
|
||||
{
|
||||
for (; tbl; tbl = tbl->next_global)
|
||||
{
|
||||
if (tbl->schema_table &&
|
||||
0 == strcmp(tbl->schema_table->table_name, I_S_table_name))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
Returns if a query has a set command with optimizer_trace being switched on/off.
|
||||
True: Don't trace the query(uninteresting)
|
||||
*/
|
||||
|
||||
bool sets_var_optimizer_trace(enum enum_sql_command sql_command,
|
||||
List<set_var_base> *set_vars)
|
||||
{
|
||||
if (sql_command == SQLCOM_SET_OPTION)
|
||||
{
|
||||
List_iterator_fast<set_var_base> it(*set_vars);
|
||||
const set_var_base *var;
|
||||
while ((var = it++))
|
||||
if (var->is_var_optimizer_trace()) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
ST_FIELD_INFO optimizer_trace_info[] = {
|
||||
/* name, length, type, value, maybe_null, old_name, open_method */
|
||||
{"QUERY", 65535, MYSQL_TYPE_STRING, 0, false, NULL, SKIP_OPEN_TABLE},
|
||||
{"TRACE", 65535, MYSQL_TYPE_STRING, 0, false, NULL, SKIP_OPEN_TABLE},
|
||||
{"MISSING_BYTES_BEYOND_MAX_MEM_SIZE", 20, MYSQL_TYPE_LONG, 0, false, NULL,
|
||||
SKIP_OPEN_TABLE},
|
||||
{"INSUFFICIENT_PRIVILEGES", 1, MYSQL_TYPE_TINY, 0, false, NULL,
|
||||
SKIP_OPEN_TABLE},
|
||||
{NULL, 0, MYSQL_TYPE_STRING, 0, true, NULL, 0}};
|
||||
|
||||
/*
|
||||
TODO: one-line needs to be implemented seperately
|
||||
*/
|
||||
const char *Opt_trace_context::flag_names[] = {"enabled", "default",
|
||||
NullS};
|
||||
|
||||
/*
|
||||
Returns if a particular command will be traced or not
|
||||
*/
|
||||
|
||||
inline bool sql_command_can_be_traced(enum enum_sql_command sql_command)
|
||||
{
|
||||
/*
|
||||
For first iteration we are only allowing select queries.
|
||||
TODO: change to allow other queries.
|
||||
*/
|
||||
return sql_command == SQLCOM_SELECT ||
|
||||
sql_command == SQLCOM_UPDATE ||
|
||||
sql_command == SQLCOM_DELETE ||
|
||||
sql_command == SQLCOM_DELETE_MULTI ||
|
||||
sql_command == SQLCOM_UPDATE_MULTI;
|
||||
}
|
||||
|
||||
void opt_trace_print_expanded_query(THD *thd, SELECT_LEX *select_lex,
|
||||
Json_writer_object *writer)
|
||||
|
||||
{
|
||||
if (!thd->trace_started())
|
||||
return;
|
||||
char buff[1024];
|
||||
String str(buff, sizeof(buff), system_charset_info);
|
||||
str.length(0);
|
||||
select_lex->print(thd, &str,
|
||||
enum_query_type(QT_TO_SYSTEM_CHARSET |
|
||||
QT_SHOW_SELECT_NUMBER |
|
||||
QT_ITEM_IDENT_SKIP_DB_NAMES |
|
||||
QT_VIEW_INTERNAL
|
||||
));
|
||||
/*
|
||||
The output is not very pretty lots of back-ticks, the output
|
||||
is as the one in explain extended , lets try to improved it here.
|
||||
*/
|
||||
writer->add("expanded_query", str.c_ptr_safe(), str.length());
|
||||
}
|
||||
|
||||
void opt_trace_disable_if_no_security_context_access(THD *thd)
|
||||
{
|
||||
if (likely(!(thd->variables.optimizer_trace &
|
||||
Opt_trace_context::FLAG_ENABLED)) || // (1)
|
||||
thd->system_thread) // (2)
|
||||
{
|
||||
/*
|
||||
(1) We know that the routine's execution starts with "enabled=off".
|
||||
If it stays so until the routine ends, we needn't do security checks on
|
||||
the routine.
|
||||
If it does not stay so, it means the definer sets it to "on" somewhere
|
||||
in the routine's body. Then it is his conscious decision to generate
|
||||
traces, thus it is still correct to skip the security check.
|
||||
|
||||
(2) Threads of the Events Scheduler have an unusual security context
|
||||
(thd->m_main_security_ctx.priv_user==NULL, see comment in
|
||||
Security_context::change_security_context()).
|
||||
*/
|
||||
return;
|
||||
}
|
||||
Opt_trace_context *const trace = &thd->opt_trace;
|
||||
if (!thd->trace_started())
|
||||
{
|
||||
/*
|
||||
@@optimizer_trace has "enabled=on" but trace is not started.
|
||||
Either Opt_trace_start ctor was not called for our statement (3), or it
|
||||
was called but at that time, the variable had "enabled=off" (4).
|
||||
|
||||
There are no known cases of (3).
|
||||
|
||||
(4) suggests that the user managed to change the variable during
|
||||
execution of the statement, and this statement is using
|
||||
view/routine (note that we have not been able to provoke this, maybe
|
||||
this is impossible). If it happens it is suspicious.
|
||||
|
||||
We disable I_S output. And we cannot do otherwise: we have no place to
|
||||
store a possible "missing privilege" information (no Opt_trace_stmt, as
|
||||
is_started() is false), so cannot do security checks, so cannot safely
|
||||
do tracing, so have to disable I_S output. And even then, we don't know
|
||||
when to re-enable I_S output, as we have no place to store the
|
||||
information "re-enable tracing at the end of this statement", and we
|
||||
don't even have a notion of statement here (statements in the optimizer
|
||||
trace world mean an Opt_trace_stmt object, and there is none here). So
|
||||
we must disable for the session's life.
|
||||
|
||||
COM_FIELD_LIST opens views, thus used to be a case of (3). To avoid
|
||||
disabling I_S output for the session's life when this command is issued
|
||||
(like in: "SET OPTIMIZER_TRACE='ENABLED=ON';USE somedb;" in the 'mysql'
|
||||
command-line client), we have decided to create a Opt_trace_start for
|
||||
this command. The command itself is not traced though
|
||||
(SQLCOM_SHOW_FIELDS does not have CF_OPTIMIZER_TRACE).
|
||||
*/
|
||||
return;
|
||||
}
|
||||
/*
|
||||
Note that thd->main_security_ctx.master_access is probably invariant
|
||||
accross the life of THD: GRANT/REVOKE don't affect global privileges of an
|
||||
existing connection, per the manual.
|
||||
*/
|
||||
if (!(thd->main_security_ctx.check_access(GLOBAL_ACLS & ~GRANT_ACL)) &&
|
||||
(0 != strcmp(thd->main_security_ctx.priv_user,
|
||||
thd->security_context()->priv_user) ||
|
||||
0 != my_strcasecmp(system_charset_info,
|
||||
thd->main_security_ctx.priv_host,
|
||||
thd->security_context()->priv_host)))
|
||||
trace->missing_privilege();
|
||||
return;
|
||||
}
|
||||
|
||||
void opt_trace_disable_if_no_stored_proc_func_access(THD *thd, sp_head *sp)
|
||||
{
|
||||
if (likely(!(thd->variables.optimizer_trace &
|
||||
Opt_trace_context::FLAG_ENABLED)) ||
|
||||
thd->system_thread)
|
||||
return;
|
||||
|
||||
Opt_trace_context *const trace = &thd->opt_trace;
|
||||
if (!thd->trace_started())
|
||||
return;
|
||||
bool full_access;
|
||||
Security_context *const backup_thd_sctx = thd->security_context();
|
||||
thd->set_security_context(&thd->main_security_ctx);
|
||||
const bool rc = check_show_routine_access(thd, sp, &full_access) || !full_access;
|
||||
thd->set_security_context(backup_thd_sctx);
|
||||
if (rc)
|
||||
trace->missing_privilege();
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
If tracing is on, checks additional privileges on a list of tables/views,
|
||||
to make sure that the user has the right to do SHOW CREATE TABLE/VIEW and
|
||||
"SELECT *". For that:
|
||||
- this functions checks table-level SELECT
|
||||
- which is sufficient for SHOW CREATE TABLE and "SELECT *", if a base table
|
||||
- if a view, if the view has not been identified as such then
|
||||
opt_trace_disable_if_no_view_access() will be later called and check SHOW
|
||||
VIEW; other we check SHOW VIEW here; SHOW VIEW + SELECT is sufficient for
|
||||
SHOW CREATE VIEW.
|
||||
If a privilege is missing, notifies the trace system.
|
||||
|
||||
@param thd
|
||||
@param tbl list of tables to check
|
||||
*/
|
||||
|
||||
void opt_trace_disable_if_no_tables_access(THD *thd, TABLE_LIST *tbl)
|
||||
{
|
||||
if (likely(!(thd->variables.optimizer_trace &
|
||||
Opt_trace_context::FLAG_ENABLED)) || thd->system_thread)
|
||||
return;
|
||||
Opt_trace_context *const trace = &thd->opt_trace;
|
||||
|
||||
if (!thd->trace_started())
|
||||
return;
|
||||
|
||||
Security_context *const backup_thd_sctx = thd->security_context();
|
||||
thd->set_security_context(&thd->main_security_ctx);
|
||||
const TABLE_LIST *const first_not_own_table = thd->lex->first_not_own_table();
|
||||
for (TABLE_LIST *t = tbl; t != NULL && t != first_not_own_table;
|
||||
t = t->next_global)
|
||||
{
|
||||
/*
|
||||
Anonymous derived tables (as in
|
||||
"SELECT ... FROM (SELECT ...)") don't have their grant.privilege set.
|
||||
*/
|
||||
if (!t->is_anonymous_derived_table())
|
||||
{
|
||||
const GRANT_INFO backup_grant_info = t->grant;
|
||||
Security_context *const backup_table_sctx = t->security_ctx;
|
||||
t->security_ctx = NULL;
|
||||
/*
|
||||
(1) check_table_access() fills t->grant.privilege.
|
||||
(2) Because SELECT privileges can be column-based,
|
||||
check_table_access() will return 'false' as long as there is SELECT
|
||||
privilege on one column. But we want a table-level privilege.
|
||||
*/
|
||||
|
||||
bool rc =
|
||||
check_table_access(thd, SELECT_ACL, t, false, 1, true) || // (1)
|
||||
((t->grant.privilege & SELECT_ACL) == 0); // (2)
|
||||
if (t->is_view())
|
||||
{
|
||||
/*
|
||||
It's a view which has already been opened: we are executing a
|
||||
prepared statement. The view has been unfolded in the global list of
|
||||
tables. So underlying tables will be automatically checked in the
|
||||
present function, but we need an explicit check of SHOW VIEW:
|
||||
*/
|
||||
rc |= check_table_access(thd, SHOW_VIEW_ACL, t, false, 1, true);
|
||||
}
|
||||
t->security_ctx = backup_table_sctx;
|
||||
t->grant = backup_grant_info;
|
||||
if (rc)
|
||||
{
|
||||
trace->missing_privilege();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
thd->set_security_context(backup_thd_sctx);
|
||||
return;
|
||||
}
|
||||
|
||||
void opt_trace_disable_if_no_view_access(THD *thd, TABLE_LIST *view,
|
||||
TABLE_LIST *underlying_tables)
|
||||
{
|
||||
|
||||
if (likely(!(thd->variables.optimizer_trace &
|
||||
Opt_trace_context::FLAG_ENABLED)) ||
|
||||
thd->system_thread)
|
||||
return;
|
||||
Opt_trace_context *const trace = &thd->opt_trace;
|
||||
if (!thd->trace_started())
|
||||
return;
|
||||
|
||||
Security_context *const backup_table_sctx = view->security_ctx;
|
||||
Security_context *const backup_thd_sctx = thd->security_context();
|
||||
const GRANT_INFO backup_grant_info = view->grant;
|
||||
|
||||
view->security_ctx = NULL; // no SUID context for view
|
||||
// no SUID context for THD
|
||||
thd->set_security_context(&thd->main_security_ctx);
|
||||
const int rc = check_table_access(thd, SHOW_VIEW_ACL, view, false, 1, true);
|
||||
|
||||
view->security_ctx = backup_table_sctx;
|
||||
thd->set_security_context(backup_thd_sctx);
|
||||
view->grant = backup_grant_info;
|
||||
|
||||
if (rc)
|
||||
{
|
||||
trace->missing_privilege();
|
||||
return;
|
||||
}
|
||||
/*
|
||||
We needn't check SELECT privilege on this view. Some
|
||||
opt_trace_disable_if_no_tables_access() call has or will check it.
|
||||
|
||||
Now we check underlying tables/views of our view:
|
||||
*/
|
||||
opt_trace_disable_if_no_tables_access(thd, underlying_tables);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
@class Opt_trace_stmt
|
||||
|
||||
The trace of one statement.
|
||||
*/
|
||||
|
||||
class Opt_trace_stmt {
|
||||
public:
|
||||
/**
|
||||
Constructor, starts a trace for information_schema and dbug.
|
||||
@param ctx_arg context
|
||||
*/
|
||||
Opt_trace_stmt(Opt_trace_context *ctx_arg)
|
||||
{
|
||||
ctx= ctx_arg;
|
||||
current_json= new Json_writer();
|
||||
missing_priv= false;
|
||||
I_S_disabled= 0;
|
||||
}
|
||||
~Opt_trace_stmt()
|
||||
{
|
||||
delete current_json;
|
||||
missing_priv= false;
|
||||
ctx= NULL;
|
||||
I_S_disabled= 0;
|
||||
}
|
||||
void set_query(const char *query_ptr, size_t length, const CHARSET_INFO *charset);
|
||||
void open_struct(const char *key, char opening_bracket);
|
||||
void close_struct(const char *saved_key, char closing_bracket);
|
||||
void fill_info(Opt_trace_info* info);
|
||||
void add(const char *key, char *opening_bracket, size_t val_length);
|
||||
Json_writer* get_current_json(){return current_json;}
|
||||
void missing_privilege();
|
||||
void disable_tracing_for_children();
|
||||
void enable_tracing_for_children();
|
||||
bool is_enabled();
|
||||
|
||||
void set_allowed_mem_size(size_t mem_size);
|
||||
size_t get_length() { return current_json->output.length(); }
|
||||
size_t get_truncated_bytes() { return current_json->get_truncated_bytes(); }
|
||||
bool get_missing_priv() { return missing_priv; }
|
||||
|
||||
private:
|
||||
Opt_trace_context *ctx;
|
||||
String query; // store the query sent by the user
|
||||
Json_writer *current_json; // stores the trace
|
||||
bool missing_priv; ///< whether user lacks privilege to see this trace
|
||||
uint I_S_disabled;
|
||||
};
|
||||
|
||||
void Opt_trace_stmt::set_query(const char *query_ptr, size_t length,
|
||||
const CHARSET_INFO *charset)
|
||||
{
|
||||
query.append(query_ptr, length, charset);
|
||||
}
|
||||
|
||||
Json_writer* Opt_trace_context::get_current_json()
|
||||
{
|
||||
if (!is_started())
|
||||
return NULL;
|
||||
return current_trace->get_current_json();
|
||||
}
|
||||
|
||||
void Opt_trace_context::missing_privilege()
|
||||
{
|
||||
if (current_trace)
|
||||
current_trace->missing_privilege();
|
||||
}
|
||||
|
||||
void Opt_trace_context::set_allowed_mem_size(size_t mem_size)
|
||||
{
|
||||
current_trace->set_allowed_mem_size(mem_size);
|
||||
}
|
||||
|
||||
/*
|
||||
TODO: In future when we would be saving multiple trace,
|
||||
this function would return
|
||||
max_mem_size - memory_occupied_by_the_saved_traces
|
||||
*/
|
||||
|
||||
size_t Opt_trace_context::remaining_mem_size()
|
||||
{
|
||||
return max_mem_size;
|
||||
}
|
||||
|
||||
bool Opt_trace_context::disable_tracing_if_required()
|
||||
{
|
||||
if (current_trace)
|
||||
{
|
||||
current_trace->disable_tracing_for_children();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Opt_trace_context::enable_tracing_if_required()
|
||||
{
|
||||
if (current_trace)
|
||||
{
|
||||
current_trace->enable_tracing_for_children();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Opt_trace_context::is_enabled()
|
||||
{
|
||||
if (current_trace)
|
||||
return current_trace->is_enabled();
|
||||
return false;
|
||||
}
|
||||
|
||||
Opt_trace_context::Opt_trace_context()
|
||||
{
|
||||
current_trace= NULL;
|
||||
inited= FALSE;
|
||||
traces= NULL;
|
||||
max_mem_size= 0;
|
||||
}
|
||||
Opt_trace_context::~Opt_trace_context()
|
||||
{
|
||||
inited= FALSE;
|
||||
/*
|
||||
would be nice to move this to a function
|
||||
*/
|
||||
if (traces)
|
||||
{
|
||||
while (traces->elements())
|
||||
{
|
||||
Opt_trace_stmt *prev= traces->at(0);
|
||||
delete prev;
|
||||
traces->del(0);
|
||||
}
|
||||
delete traces;
|
||||
traces= NULL;
|
||||
}
|
||||
max_mem_size= 0;
|
||||
}
|
||||
|
||||
void Opt_trace_context::set_query(const char *query, size_t length, const CHARSET_INFO *charset)
|
||||
{
|
||||
current_trace->set_query(query, length, charset);
|
||||
}
|
||||
|
||||
void Opt_trace_context::start(THD *thd, TABLE_LIST *tbl,
|
||||
enum enum_sql_command sql_command,
|
||||
const char *query,
|
||||
size_t query_length,
|
||||
const CHARSET_INFO *query_charset,
|
||||
ulong max_mem_size_arg)
|
||||
{
|
||||
/*
|
||||
This is done currently because we don't want to have multiple
|
||||
traces open at the same time, so as soon as a new trace is created
|
||||
we forcefully end the previous one, if it has not ended by itself.
|
||||
This would mostly happen with stored functions or procedures.
|
||||
|
||||
TODO: handle multiple traces
|
||||
*/
|
||||
DBUG_ASSERT(!current_trace);
|
||||
current_trace= new Opt_trace_stmt(this);
|
||||
max_mem_size= max_mem_size_arg;
|
||||
if (!inited)
|
||||
{
|
||||
traces= new Dynamic_array<Opt_trace_stmt*>();
|
||||
inited= TRUE;
|
||||
}
|
||||
set_allowed_mem_size(remaining_mem_size());
|
||||
}
|
||||
|
||||
void Opt_trace_context::end()
|
||||
{
|
||||
if (current_trace)
|
||||
traces->push(current_trace);
|
||||
|
||||
if (!traces->elements())
|
||||
return;
|
||||
if (traces->elements() > 1)
|
||||
{
|
||||
Opt_trace_stmt *prev= traces->at(0);
|
||||
delete prev;
|
||||
traces->del(0);
|
||||
}
|
||||
current_trace= NULL;
|
||||
}
|
||||
|
||||
Opt_trace_start::Opt_trace_start(THD *thd, TABLE_LIST *tbl,
|
||||
enum enum_sql_command sql_command,
|
||||
List<set_var_base> *set_vars,
|
||||
const char *query,
|
||||
size_t query_length,
|
||||
const CHARSET_INFO *query_charset):ctx(&thd->opt_trace)
|
||||
{
|
||||
/*
|
||||
if optimizer trace is enabled and the statment we have is traceable,
|
||||
then we start the context.
|
||||
*/
|
||||
const ulonglong var = thd->variables.optimizer_trace;
|
||||
traceable= FALSE;
|
||||
if (unlikely(var & Opt_trace_context::FLAG_ENABLED) &&
|
||||
sql_command_can_be_traced(sql_command) &&
|
||||
!list_has_optimizer_trace_table(tbl) &&
|
||||
!sets_var_optimizer_trace(sql_command, set_vars) &&
|
||||
!thd->system_thread &&
|
||||
!ctx->disable_tracing_if_required())
|
||||
{
|
||||
ctx->start(thd, tbl, sql_command, query, query_length, query_charset,
|
||||
thd->variables.optimizer_trace_max_mem_size);
|
||||
ctx->set_query(query, query_length, query_charset);
|
||||
traceable= TRUE;
|
||||
opt_trace_disable_if_no_tables_access(thd, tbl);
|
||||
}
|
||||
}
|
||||
|
||||
Opt_trace_start::~Opt_trace_start()
|
||||
{
|
||||
if (traceable)
|
||||
{
|
||||
ctx->end();
|
||||
traceable= FALSE;
|
||||
}
|
||||
else
|
||||
{
|
||||
ctx->enable_tracing_if_required();
|
||||
}
|
||||
}
|
||||
|
||||
void Opt_trace_stmt::fill_info(Opt_trace_info* info)
|
||||
{
|
||||
if (unlikely(info->missing_priv = get_missing_priv()))
|
||||
{
|
||||
info->trace_ptr = info->query_ptr = "";
|
||||
info->trace_length = info->query_length = 0;
|
||||
info->query_charset = &my_charset_bin;
|
||||
info->missing_bytes = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
info->trace_ptr = current_json->output.get_string()->ptr();
|
||||
info->trace_length = get_length();
|
||||
info->query_ptr = query.ptr();
|
||||
info->query_length = query.length();
|
||||
info->query_charset = query.charset();
|
||||
info->missing_bytes = get_truncated_bytes();
|
||||
info->missing_priv= get_missing_priv();
|
||||
}
|
||||
}
|
||||
|
||||
void Opt_trace_stmt::missing_privilege()
|
||||
{
|
||||
missing_priv= true;
|
||||
}
|
||||
|
||||
void Opt_trace_stmt::disable_tracing_for_children()
|
||||
{
|
||||
++I_S_disabled;
|
||||
}
|
||||
|
||||
void Opt_trace_stmt::enable_tracing_for_children()
|
||||
{
|
||||
if (I_S_disabled)
|
||||
--I_S_disabled;
|
||||
}
|
||||
|
||||
bool Opt_trace_stmt::is_enabled()
|
||||
{
|
||||
return I_S_disabled == 0;
|
||||
}
|
||||
|
||||
void Opt_trace_stmt::set_allowed_mem_size(size_t mem_size)
|
||||
{
|
||||
current_json->set_size_limit(mem_size);
|
||||
}
|
||||
|
||||
/*
|
||||
Prefer this when you are iterating over JOIN_TABs
|
||||
*/
|
||||
|
||||
void Json_writer::add_table_name(const JOIN_TAB *tab)
|
||||
{
|
||||
if (tab != NULL)
|
||||
{
|
||||
char table_name_buffer[SAFE_NAME_LEN];
|
||||
if (tab->table && tab->table->derived_select_number)
|
||||
{
|
||||
/* Derived table name generation */
|
||||
size_t len= my_snprintf(table_name_buffer, sizeof(table_name_buffer)-1,
|
||||
"<derived%u>",
|
||||
tab->table->derived_select_number);
|
||||
add_str(table_name_buffer, len);
|
||||
}
|
||||
else if (tab->bush_children)
|
||||
{
|
||||
JOIN_TAB *ctab= tab->bush_children->start;
|
||||
size_t len= my_snprintf(table_name_buffer,
|
||||
sizeof(table_name_buffer)-1,
|
||||
"<subquery%d>",
|
||||
ctab->emb_sj_nest->sj_subq_pred->get_identifier());
|
||||
add_str(table_name_buffer, len);
|
||||
}
|
||||
else
|
||||
{
|
||||
TABLE_LIST *real_table= tab->table->pos_in_table_list;
|
||||
add_str(real_table->alias.str, real_table->alias.length);
|
||||
}
|
||||
}
|
||||
else
|
||||
DBUG_ASSERT(0);
|
||||
}
|
||||
|
||||
void Json_writer::add_table_name(const TABLE *table)
|
||||
{
|
||||
add_str(table->pos_in_table_list->alias.str);
|
||||
}
|
||||
|
||||
|
||||
void add_table_scan_values_to_trace(THD *thd, JOIN_TAB *tab)
|
||||
{
|
||||
Json_writer_object table_records(thd);
|
||||
table_records.add_table_name(tab);
|
||||
Json_writer_object table_rec(thd, "table_scan");
|
||||
table_rec.add("rows", tab->found_records)
|
||||
.add("cost", tab->read_time);
|
||||
}
|
||||
/*
|
||||
Introduce enum_query_type flags parameter, maybe also allow
|
||||
EXPLAIN also use this function.
|
||||
*/
|
||||
|
||||
void Json_writer::add_str(Item *item)
|
||||
{
|
||||
if (item)
|
||||
{
|
||||
THD *thd= current_thd;
|
||||
char buff[256];
|
||||
String str(buff, sizeof(buff), system_charset_info);
|
||||
str.length(0);
|
||||
|
||||
ulonglong save_option_bits= thd->variables.option_bits;
|
||||
thd->variables.option_bits &= ~OPTION_QUOTE_SHOW_CREATE;
|
||||
item->print(&str,
|
||||
enum_query_type(QT_TO_SYSTEM_CHARSET | QT_SHOW_SELECT_NUMBER
|
||||
| QT_ITEM_IDENT_SKIP_DB_NAMES));
|
||||
thd->variables.option_bits= save_option_bits;
|
||||
add_str(str.c_ptr_safe());
|
||||
}
|
||||
else
|
||||
add_null();
|
||||
}
|
||||
|
||||
void Opt_trace_context::flush_optimizer_trace()
|
||||
{
|
||||
inited= false;
|
||||
if (traces)
|
||||
{
|
||||
while (traces->elements())
|
||||
{
|
||||
Opt_trace_stmt *prev= traces->at(0);
|
||||
delete prev;
|
||||
traces->del(0);
|
||||
}
|
||||
delete traces;
|
||||
traces= NULL;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
int fill_optimizer_trace_info(THD *thd, TABLE_LIST *tables, Item *)
|
||||
{
|
||||
TABLE *table = tables->table;
|
||||
Opt_trace_info info;
|
||||
|
||||
/* get_values of trace, query , missing bytes and missing_priv
|
||||
|
||||
@todo: Need an iterator here to walk over all the traces
|
||||
*/
|
||||
Opt_trace_context* ctx= &thd->opt_trace;
|
||||
|
||||
if (thd->opt_trace.empty())
|
||||
{
|
||||
Opt_trace_stmt *stmt= ctx->get_top_trace();
|
||||
stmt->fill_info(&info);
|
||||
|
||||
table->field[0]->store(info.query_ptr, static_cast<uint>(info.query_length),
|
||||
info.query_charset);
|
||||
table->field[1]->store(info.trace_ptr, static_cast<uint>(info.trace_length),
|
||||
system_charset_info);
|
||||
table->field[2]->store(info.missing_bytes, true);
|
||||
table->field[3]->store(info.missing_priv, true);
|
||||
// Store in IS
|
||||
if (schema_table_store_record(thd, table))
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
201
sql/opt_trace.h
Normal file
201
sql/opt_trace.h
Normal file
@ -0,0 +1,201 @@
|
||||
#ifndef OPT_TRACE_INCLUDED
|
||||
#define OPT_TRACE_INCLUDED
|
||||
/* This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; version 2 of the License.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */
|
||||
|
||||
#include "opt_trace_context.h" // Opt_trace_context
|
||||
#include "sql_lex.h"
|
||||
#include "my_json_writer.h"
|
||||
#include "sql_select.h"
|
||||
class Item;
|
||||
class THD;
|
||||
struct TABLE_LIST;
|
||||
|
||||
class Opt_trace_stmt;
|
||||
|
||||
/*
|
||||
User-visible information about a trace.
|
||||
*/
|
||||
|
||||
struct Opt_trace_info
|
||||
{
|
||||
/**
|
||||
String containing trace.
|
||||
If trace has been end()ed, this is 0-terminated, which is only to aid
|
||||
debugging or unit testing; this property is not relied upon in normal
|
||||
server usage.
|
||||
If trace has not been ended, this is not 0-terminated. That rare case can
|
||||
happen when a substatement reads OPTIMIZER_TRACE (at that stage, the top
|
||||
statement is still executing so its trace is not ended yet, but may still
|
||||
be read by the sub-statement).
|
||||
*/
|
||||
const char *trace_ptr;
|
||||
size_t trace_length;
|
||||
//// String containing original query.
|
||||
const char *query_ptr;
|
||||
size_t query_length;
|
||||
const CHARSET_INFO *query_charset; ///< charset of query string
|
||||
/**
|
||||
How many bytes this trace is missing (for traces which were truncated
|
||||
because of @@@@optimizer-trace-max-mem-size).
|
||||
The trace is not extended beyond trace-max-mem-size.
|
||||
*/
|
||||
size_t missing_bytes;
|
||||
/*
|
||||
Whether user lacks privilege to see this trace.
|
||||
If this is set to TRUE, then we return an empty trace
|
||||
*/
|
||||
bool missing_priv;
|
||||
};
|
||||
|
||||
/**
|
||||
Instantiate this class to start tracing a THD's actions (generally at a
|
||||
statement's start), and to set the "original" query (not transformed, as
|
||||
sent by client) for the new trace. Destructor will end the trace.
|
||||
|
||||
@param thd the THD
|
||||
@param tbl list of tables read/written by the statement.
|
||||
@param sql_command SQL command being prepared or executed
|
||||
@param set_vars what variables are set by this command (only used if
|
||||
sql_command is SQLCOM_SET_OPTION)
|
||||
@param query query
|
||||
@param length query's length
|
||||
@param charset charset which was used to encode this query
|
||||
*/
|
||||
|
||||
|
||||
class Opt_trace_start {
|
||||
public:
|
||||
Opt_trace_start(THD *thd_arg, TABLE_LIST *tbl,
|
||||
enum enum_sql_command sql_command,
|
||||
List<set_var_base> *set_vars,
|
||||
const char *query,
|
||||
size_t query_length,
|
||||
const CHARSET_INFO *query_charset);
|
||||
~Opt_trace_start();
|
||||
|
||||
private:
|
||||
Opt_trace_context *const ctx;
|
||||
/*
|
||||
True: the query will be traced
|
||||
False: otherwise
|
||||
*/
|
||||
bool traceable;
|
||||
};
|
||||
|
||||
/**
|
||||
Prints SELECT query to optimizer trace. It is not the original query (as in
|
||||
@c Opt_trace_context::set_query()) but a printout of the parse tree
|
||||
(Item-s).
|
||||
@param thd the THD
|
||||
@param select_lex query's parse tree
|
||||
@param trace_object Json_writer object to which the query will be added
|
||||
*/
|
||||
void opt_trace_print_expanded_query(THD *thd, SELECT_LEX *select_lex,
|
||||
Json_writer_object *trace_object);
|
||||
|
||||
void add_table_scan_values_to_trace(THD *thd, JOIN_TAB *tab);
|
||||
|
||||
/*
|
||||
Security related (need to add a proper comment here)
|
||||
*/
|
||||
|
||||
/**
|
||||
If the security context is not that of the connected user, inform the trace
|
||||
system that a privilege is missing. With one exception: see below.
|
||||
|
||||
@param thd
|
||||
|
||||
This serves to eliminate the following issue.
|
||||
Any information readable by a SELECT may theoretically end up in
|
||||
the trace. And a SELECT may read information from other places than tables:
|
||||
- from views (reading their bodies)
|
||||
- from stored routines (reading their bodies)
|
||||
- from files (reading their content), with LOAD_FILE()
|
||||
- from the list of connections (reading their queries...), with
|
||||
I_S.PROCESSLIST.
|
||||
If the connected user has EXECUTE privilege on a routine which does a
|
||||
security context change, the routine can retrieve information internally
|
||||
(if allowed by the SUID context's privileges), and present only a portion
|
||||
of it to the connected user. But with tracing on, all information is
|
||||
possibly in the trace. So the connected user receives more information than
|
||||
the routine's definer intended to provide. Fixing this issue would require
|
||||
adding, near many privilege checks in the server, a new
|
||||
optimizer-trace-specific check done against the connected user's context,
|
||||
to verify that the connected user has the right to see the retrieved
|
||||
information.
|
||||
|
||||
Instead, our chosen simpler solution is that if we see a security context
|
||||
change where SUID user is not the connected user, we disable tracing. With
|
||||
only one safe exception: if the connected user has all global privileges
|
||||
(because then she/he can find any information anyway). By "all global
|
||||
privileges" we mean everything but WITH GRANT OPTION (that latter one isn't
|
||||
related to information gathering).
|
||||
|
||||
Read access to I_S.OPTIMIZER_TRACE by another user than the connected user
|
||||
is restricted: @see fill_optimizer_trace_info().
|
||||
*/
|
||||
void opt_trace_disable_if_no_security_context_access(THD *thd);
|
||||
|
||||
void opt_trace_disable_if_no_tables_access(THD *thd, TABLE_LIST *tbl);
|
||||
|
||||
/**
|
||||
If tracing is on, checks additional privileges for a view, to make sure
|
||||
that the user has the right to do SHOW CREATE VIEW. For that:
|
||||
- this function checks SHOW VIEW
|
||||
- SELECT is tested in opt_trace_disable_if_no_tables_access()
|
||||
- SELECT + SHOW VIEW is sufficient for SHOW CREATE VIEW.
|
||||
We also check underlying tables.
|
||||
If a privilege is missing, notifies the trace system.
|
||||
This function should be called when the view's underlying tables have not
|
||||
yet been merged.
|
||||
|
||||
@param thd THD context
|
||||
@param view view to check
|
||||
@param underlying_tables underlying tables/views of 'view'
|
||||
*/
|
||||
|
||||
void opt_trace_disable_if_no_view_access(THD *thd, TABLE_LIST *view,
|
||||
TABLE_LIST *underlying_tables);
|
||||
|
||||
/**
|
||||
If tracing is on, checks additional privileges on a stored routine, to make
|
||||
sure that the user has the right to do SHOW CREATE PROCEDURE/FUNCTION. For
|
||||
that, we use the same checks as in those SHOW commands.
|
||||
If a privilege is missing, notifies the trace system.
|
||||
|
||||
This function is not redundant with
|
||||
opt_trace_disable_if_no_security_context_access().
|
||||
Indeed, for a SQL SECURITY INVOKER routine, there is no context change, but
|
||||
we must still verify that the invoker can do SHOW CREATE.
|
||||
|
||||
For triggers, see note in sp_head::execute_trigger().
|
||||
|
||||
@param thd
|
||||
@param sp routine to check
|
||||
*/
|
||||
void opt_trace_disable_if_no_stored_proc_func_access(THD *thd, sp_head *sp);
|
||||
|
||||
/**
|
||||
Fills information_schema.OPTIMIZER_TRACE with rows (one per trace)
|
||||
@retval 0 ok
|
||||
@retval 1 error
|
||||
*/
|
||||
int fill_optimizer_trace_info(THD *thd, TABLE_LIST *tables, Item *);
|
||||
|
||||
#define OPT_TRACE_TRANSFORM(writer, object_level0, object_level1, \
|
||||
select_number, from, to) \
|
||||
Json_writer_object object_level0(writer); \
|
||||
Json_writer_object object_level1(writer, "transformation"); \
|
||||
object_level1.add_select_number(select_number).add("from", from).add("to", to);
|
||||
#endif
|
92
sql/opt_trace_context.h
Normal file
92
sql/opt_trace_context.h
Normal file
@ -0,0 +1,92 @@
|
||||
#ifndef OPT_TRACE_CONTEXT_INCLUDED
|
||||
#define OPT_TRACE_CONTEXT_INCLUDED
|
||||
|
||||
#include "sql_array.h"
|
||||
|
||||
class Opt_trace_stmt;
|
||||
|
||||
class Opt_trace_context
|
||||
{
|
||||
public:
|
||||
Opt_trace_context();
|
||||
~Opt_trace_context();
|
||||
|
||||
void start(THD *thd, TABLE_LIST *tbl,
|
||||
enum enum_sql_command sql_command,
|
||||
const char *query,
|
||||
size_t query_length,
|
||||
const CHARSET_INFO *query_charset,
|
||||
ulong max_mem_size_arg);
|
||||
void end();
|
||||
void set_query(const char *query, size_t length, const CHARSET_INFO *charset);
|
||||
void flush_optimizer_trace();
|
||||
void set_allowed_mem_size(size_t mem_size);
|
||||
size_t remaining_mem_size();
|
||||
|
||||
private:
|
||||
Opt_trace_stmt* top_trace()
|
||||
{
|
||||
return *(traces->front());
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
/*
|
||||
This returns the top trace from the list of traces. This function
|
||||
is used when we want to see the contents of the INFORMATION_SCHEMA.OPTIMIZER_TRACE
|
||||
table.
|
||||
*/
|
||||
|
||||
Opt_trace_stmt* get_top_trace()
|
||||
{
|
||||
if (!traces || !traces->elements())
|
||||
return NULL;
|
||||
return top_trace();
|
||||
}
|
||||
|
||||
/*
|
||||
This returns the current trace, to which we are still writing and has not been finished
|
||||
*/
|
||||
|
||||
Json_writer* get_current_json();
|
||||
|
||||
bool empty()
|
||||
{
|
||||
return traces && (static_cast<uint>(traces->elements()) != 0);
|
||||
}
|
||||
|
||||
bool is_started()
|
||||
{
|
||||
return current_trace && is_enabled();
|
||||
}
|
||||
|
||||
bool disable_tracing_if_required();
|
||||
|
||||
bool enable_tracing_if_required();
|
||||
|
||||
bool is_enabled();
|
||||
|
||||
void missing_privilege();
|
||||
|
||||
static const char *flag_names[];
|
||||
enum
|
||||
{
|
||||
FLAG_DEFAULT = 0,
|
||||
FLAG_ENABLED = 1 << 0
|
||||
};
|
||||
|
||||
private:
|
||||
/*
|
||||
List of traces (currently it stores only 1 trace)
|
||||
*/
|
||||
Dynamic_array<Opt_trace_stmt*> *traces;
|
||||
Opt_trace_stmt *current_trace;
|
||||
/*
|
||||
TRUE: if we allocate memory for list of traces
|
||||
FALSE: otherwise
|
||||
*/
|
||||
bool inited;
|
||||
size_t max_mem_size;
|
||||
};
|
||||
|
||||
#endif /* OPT_TRACE_CONTEXT_INCLUDED */
|
@ -275,6 +275,10 @@ public:
|
||||
virtual int update(THD *thd)=0; /* To set the value */
|
||||
virtual int light_check(THD *thd) { return check(thd); } /* for PS */
|
||||
virtual bool is_system() { return FALSE; }
|
||||
/**
|
||||
@returns whether this variable is @@@@optimizer_trace.
|
||||
*/
|
||||
virtual bool is_var_optimizer_trace() const { return false; }
|
||||
};
|
||||
|
||||
|
||||
@ -306,6 +310,11 @@ public:
|
||||
int check(THD *thd);
|
||||
int update(THD *thd);
|
||||
int light_check(THD *thd);
|
||||
virtual bool is_var_optimizer_trace() const
|
||||
{
|
||||
extern sys_var *Sys_optimizer_trace_ptr;
|
||||
return var == Sys_optimizer_trace_ptr;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
@ -29,6 +29,8 @@
|
||||
#include "sql_derived.h" // mysql_handle_derived
|
||||
#include "sql_cte.h"
|
||||
#include "sql_select.h" // Virtual_tmp_table
|
||||
#include "opt_trace.h"
|
||||
#include "my_json_writer.h"
|
||||
|
||||
#ifdef USE_PRAGMA_IMPLEMENTATION
|
||||
#pragma implementation
|
||||
@ -1146,6 +1148,8 @@ sp_head::execute(THD *thd, bool merge_da_on_success)
|
||||
if (check_stack_overrun(thd, 7 * STACK_MIN_SIZE, (uchar*)&old_packet))
|
||||
DBUG_RETURN(TRUE);
|
||||
|
||||
opt_trace_disable_if_no_security_context_access(thd);
|
||||
|
||||
/* init per-instruction memroot */
|
||||
init_sql_alloc(&execute_mem_root, "per_instruction_memroot",
|
||||
MEM_ROOT_BLOCK_SIZE, 0, MYF(0));
|
||||
@ -1982,6 +1986,7 @@ sp_head::execute_function(THD *thd, Item **argp, uint argcount,
|
||||
thd->variables.option_bits&= ~OPTION_BIN_LOG;
|
||||
}
|
||||
|
||||
opt_trace_disable_if_no_stored_proc_func_access(thd, this);
|
||||
/*
|
||||
Switch to call arena/mem_root so objects like sp_cursor or
|
||||
Item_cache holders for case expressions can be allocated on it.
|
||||
@ -2272,6 +2277,7 @@ sp_head::execute_procedure(THD *thd, List<Item> *args)
|
||||
err_status= set_routine_security_ctx(thd, this, &save_security_ctx);
|
||||
#endif
|
||||
|
||||
opt_trace_disable_if_no_stored_proc_func_access(thd, this);
|
||||
if (!err_status)
|
||||
{
|
||||
err_status= execute(thd, TRUE);
|
||||
@ -3297,6 +3303,13 @@ sp_lex_keeper::reset_lex_and_exec_core(THD *thd, uint *nextp,
|
||||
thd->lex->safe_to_cache_query= 0;
|
||||
#endif
|
||||
|
||||
Opt_trace_start ots(thd, m_lex->query_tables,
|
||||
SQLCOM_SELECT, &m_lex->var_list,
|
||||
NULL, 0,
|
||||
thd->variables.character_set_client);
|
||||
|
||||
Json_writer_object trace_command(thd);
|
||||
Json_writer_array trace_command_steps(thd, "steps");
|
||||
if (open_tables)
|
||||
res= check_dependencies_in_with_clauses(m_lex->with_clauses_list) ||
|
||||
instr->exec_open_and_lock_tables(thd, m_lex->query_tables);
|
||||
|
@ -2024,6 +2024,7 @@ private:
|
||||
|
||||
}; // class sp_instr_set_case_expr : public sp_instr_opt_meta
|
||||
|
||||
bool check_show_routine_access(THD *thd, sp_head *sp, bool *full_access);
|
||||
|
||||
#ifndef NO_EMBEDDED_ACCESS_CHECKS
|
||||
bool
|
||||
|
@ -71,6 +71,7 @@
|
||||
#include "wsrep_thd.h"
|
||||
#include "wsrep_trans_observer.h"
|
||||
#endif /* WITH_WSREP */
|
||||
#include "opt_trace.h"
|
||||
|
||||
#ifdef HAVE_SYS_SYSCALL_H
|
||||
#include <sys/syscall.h>
|
||||
@ -1410,6 +1411,7 @@ void THD::change_user(void)
|
||||
sp_cache_clear(&sp_func_cache);
|
||||
sp_cache_clear(&sp_package_spec_cache);
|
||||
sp_cache_clear(&sp_package_body_cache);
|
||||
opt_trace.flush_optimizer_trace();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -2187,6 +2189,11 @@ void THD::reset_globals()
|
||||
net.thd= 0;
|
||||
}
|
||||
|
||||
bool THD::trace_started()
|
||||
{
|
||||
return opt_trace.is_started();
|
||||
}
|
||||
|
||||
/*
|
||||
Cleanup after query.
|
||||
|
||||
@ -4337,6 +4344,13 @@ bool Security_context::set_user(char *user_arg)
|
||||
return user == 0;
|
||||
}
|
||||
|
||||
bool Security_context::check_access(ulong want_access, bool match_any)
|
||||
{
|
||||
DBUG_ENTER("Security_context::check_access");
|
||||
DBUG_RETURN((match_any ? (master_access & want_access)
|
||||
: ((master_access & want_access) == want_access)));
|
||||
}
|
||||
|
||||
#ifndef NO_EMBEDDED_ACCESS_CHECKS
|
||||
/**
|
||||
Initialize this security context from the passed in credentials
|
||||
|
@ -28,6 +28,7 @@
|
||||
#include "rpl_tblmap.h"
|
||||
#include "mdl.h"
|
||||
#include "field.h" // Create_field
|
||||
#include "opt_trace_context.h"
|
||||
#include "probes_mysql.h"
|
||||
#include "sql_locale.h" /* my_locale_st */
|
||||
#include "sql_profile.h" /* PROFILING */
|
||||
@ -569,6 +570,8 @@ typedef struct system_variables
|
||||
ulonglong long_query_time;
|
||||
ulonglong max_statement_time;
|
||||
ulonglong optimizer_switch;
|
||||
ulonglong optimizer_trace;
|
||||
ulong optimizer_trace_max_mem_size;
|
||||
sql_mode_t sql_mode; ///< which non-standard SQL behaviour should be enabled
|
||||
sql_mode_t old_behavior; ///< which old SQL behaviour should be enabled
|
||||
ulonglong option_bits; ///< OPTION_xxx constants, e.g. OPTION_PROFILING
|
||||
@ -1350,6 +1353,14 @@ public:
|
||||
restore_security_context(THD *thd, Security_context *backup);
|
||||
#endif
|
||||
bool user_matches(Security_context *);
|
||||
/**
|
||||
Check global access
|
||||
@param want_access The required privileges
|
||||
@param match_any if the security context must match all or any of the req.
|
||||
* privileges.
|
||||
@return True if the security context fulfills the access requirements.
|
||||
*/
|
||||
bool check_access(ulong want_access, bool match_any = false);
|
||||
};
|
||||
|
||||
|
||||
@ -2306,6 +2317,8 @@ public:
|
||||
|
||||
Security_context main_security_ctx;
|
||||
Security_context *security_ctx;
|
||||
Security_context *security_context() const { return security_ctx; }
|
||||
void set_security_context(Security_context *sctx) { security_ctx = sctx; }
|
||||
|
||||
/*
|
||||
Points to info-string that we show in SHOW PROCESSLIST
|
||||
@ -2989,6 +3002,7 @@ public:
|
||||
ulonglong bytes_sent_old;
|
||||
ulonglong affected_rows; /* Number of changed rows */
|
||||
|
||||
Opt_trace_context opt_trace;
|
||||
pthread_t real_id; /* For debugging */
|
||||
my_thread_id thread_id, thread_dbug_id;
|
||||
uint32 os_thread_id;
|
||||
@ -3297,6 +3311,7 @@ public:
|
||||
void reset_for_reuse();
|
||||
bool store_globals();
|
||||
void reset_globals();
|
||||
bool trace_started();
|
||||
#ifdef SIGNAL_WITH_VIO_CLOSE
|
||||
inline void set_active_vio(Vio* vio)
|
||||
{
|
||||
|
@ -32,6 +32,7 @@
|
||||
#include "sql_acl.h" // SELECT_ACL
|
||||
#include "sql_class.h"
|
||||
#include "sql_cte.h"
|
||||
#include "my_json_writer.h"
|
||||
|
||||
typedef bool (*dt_processor)(THD *thd, LEX *lex, TABLE_LIST *derived);
|
||||
|
||||
@ -198,6 +199,7 @@ mysql_handle_single_derived(LEX *lex, TABLE_LIST *derived, uint phases)
|
||||
if ((res= (*processors[phase])(lex->thd, lex, derived)))
|
||||
break;
|
||||
}
|
||||
|
||||
lex->thd->derived_tables_processing= FALSE;
|
||||
DBUG_RETURN(res);
|
||||
}
|
||||
@ -368,6 +370,7 @@ bool mysql_derived_merge(THD *thd, LEX *lex, TABLE_LIST *derived)
|
||||
DBUG_PRINT("enter", ("Alias: '%s' Unit: %p",
|
||||
(derived->alias.str ? derived->alias.str : "<NULL>"),
|
||||
derived->get_unit()));
|
||||
const char *cause= NULL;
|
||||
|
||||
if (derived->merged)
|
||||
{
|
||||
@ -379,6 +382,7 @@ bool mysql_derived_merge(THD *thd, LEX *lex, TABLE_LIST *derived)
|
||||
if (dt_select->uncacheable & UNCACHEABLE_RAND)
|
||||
{
|
||||
/* There is random function => fall back to materialization. */
|
||||
cause= "Random function in the select";
|
||||
derived->change_refs_to_fields();
|
||||
derived->set_materialized_derived();
|
||||
DBUG_RETURN(FALSE);
|
||||
@ -401,15 +405,11 @@ bool mysql_derived_merge(THD *thd, LEX *lex, TABLE_LIST *derived)
|
||||
and small subqueries, and the bigger one can't be merged it wouldn't
|
||||
block the smaller one.
|
||||
*/
|
||||
if (parent_lex->get_free_table_map(&map, &tablenr))
|
||||
{
|
||||
/* There is no enough table bits, fall back to materialization. */
|
||||
goto unconditional_materialization;
|
||||
}
|
||||
|
||||
if (dt_select->leaf_tables.elements + tablenr > MAX_TABLES)
|
||||
if (parent_lex->get_free_table_map(&map, &tablenr) ||
|
||||
dt_select->leaf_tables.elements + tablenr > MAX_TABLES)
|
||||
{
|
||||
/* There is no enough table bits, fall back to materialization. */
|
||||
cause= "Not enough table bits to merge subquery";
|
||||
goto unconditional_materialization;
|
||||
}
|
||||
|
||||
@ -486,6 +486,24 @@ exit_merge:
|
||||
DBUG_RETURN(res);
|
||||
|
||||
unconditional_materialization:
|
||||
|
||||
if (unlikely(thd->trace_started()))
|
||||
{
|
||||
/*
|
||||
Add to the optimizer trace the change in choice for merged
|
||||
derived tables/views to materialised ones.
|
||||
*/
|
||||
Json_writer_object trace_wrapper(thd);
|
||||
Json_writer_object trace_derived(thd, derived->is_derived() ?
|
||||
"derived" : "view");
|
||||
trace_derived.add("table", derived->alias.str ? derived->alias.str : "<NULL>")
|
||||
.add_select_number(derived->get_unit()->
|
||||
first_select()->select_number)
|
||||
.add("initial_choice", "merged")
|
||||
.add("final_choice", "materialized")
|
||||
.add("cause", cause);
|
||||
}
|
||||
|
||||
derived->change_refs_to_fields();
|
||||
derived->set_materialized_derived();
|
||||
if (!derived->table || !derived->table->is_created())
|
||||
@ -654,7 +672,6 @@ bool mysql_derived_prepare(THD *thd, LEX *lex, TABLE_LIST *derived)
|
||||
DBUG_ENTER("mysql_derived_prepare");
|
||||
DBUG_PRINT("enter", ("unit: %p table_list: %p alias: '%s'",
|
||||
unit, derived, derived->alias.str));
|
||||
|
||||
if (!unit)
|
||||
DBUG_RETURN(FALSE);
|
||||
|
||||
@ -747,6 +764,22 @@ bool mysql_derived_prepare(THD *thd, LEX *lex, TABLE_LIST *derived)
|
||||
}
|
||||
}
|
||||
|
||||
if (unlikely(thd->trace_started()))
|
||||
{
|
||||
/*
|
||||
Add to optimizer trace whether a derived table/view
|
||||
is merged into the parent select or not.
|
||||
*/
|
||||
Json_writer_object trace_wrapper(thd);
|
||||
Json_writer_object trace_derived(thd, derived->is_derived() ?
|
||||
"derived" : "view");
|
||||
trace_derived.add("table", derived->alias.str ? derived->alias.str : "<NULL>")
|
||||
.add_select_number(derived->get_unit()->first_select()->select_number);
|
||||
if (derived->is_materialized_derived())
|
||||
trace_derived.add("materialized", true);
|
||||
if (derived->is_merged_derived())
|
||||
trace_derived.add("merged", true);
|
||||
}
|
||||
/*
|
||||
Above cascade call of prepare is important for PS protocol, but after it
|
||||
is called we can check if we really need prepare for this derived
|
||||
|
@ -233,7 +233,7 @@ void Explain_query::print_explain_json(select_result_sink *output,
|
||||
|
||||
CHARSET_INFO *cs= system_charset_info;
|
||||
List<Item> item_list;
|
||||
String *buf= &writer.output;
|
||||
const String *buf= writer.output.get_string();
|
||||
item_list.push_back(new (thd->mem_root)
|
||||
Item_string(thd, buf->ptr(), buf->length(), cs),
|
||||
thd->mem_root);
|
||||
|
@ -100,6 +100,7 @@
|
||||
#include "set_var.h"
|
||||
#include "sql_bootstrap.h"
|
||||
#include "sql_sequence.h"
|
||||
#include "opt_trace.h"
|
||||
|
||||
#include "my_json_writer.h"
|
||||
|
||||
@ -3412,6 +3413,13 @@ mysql_execute_command(THD *thd)
|
||||
#ifdef HAVE_REPLICATION
|
||||
} /* endif unlikely slave */
|
||||
#endif
|
||||
Opt_trace_start ots(thd, all_tables, lex->sql_command, &lex->var_list,
|
||||
thd->query(), thd->query_length(),
|
||||
thd->variables.character_set_client);
|
||||
|
||||
Json_writer_object trace_command(thd);
|
||||
Json_writer_array trace_command_steps(thd, "steps");
|
||||
|
||||
#ifdef WITH_WSREP
|
||||
if (WSREP(thd))
|
||||
{
|
||||
|
@ -112,6 +112,7 @@ When one supplies long data for a placeholder:
|
||||
#include "sp_cache.h"
|
||||
#include "sql_handler.h" // mysql_ha_rm_tables
|
||||
#include "probes_mysql.h"
|
||||
#include "opt_trace.h"
|
||||
#ifdef EMBEDDED_LIBRARY
|
||||
/* include MYSQL_BIND headers */
|
||||
#include <mysql.h>
|
||||
@ -2273,6 +2274,17 @@ static bool check_prepared_statement(Prepared_statement *stmt)
|
||||
lex->first_select_lex()->context.resolve_in_table_list_only(select_lex->
|
||||
get_table_list());
|
||||
|
||||
/*
|
||||
For the optimizer trace, this is the symmetric, for statement preparation,
|
||||
of what is done at statement execution (in mysql_execute_command()).
|
||||
*/
|
||||
Opt_trace_start ots(thd, tables, lex->sql_command, &lex->var_list,
|
||||
thd->query(), thd->query_length(),
|
||||
thd->variables.character_set_client);
|
||||
|
||||
Json_writer_object trace_command(thd);
|
||||
Json_writer_array trace_command_steps(thd, "steps");
|
||||
|
||||
/* Reset warning count for each query that uses tables */
|
||||
if (tables)
|
||||
thd->get_stmt_da()->opt_clear_warning_info(thd->query_id);
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -63,6 +63,7 @@
|
||||
#include "ha_partition.h"
|
||||
#endif
|
||||
#include "transaction.h"
|
||||
#include "opt_trace.h"
|
||||
|
||||
enum enum_i_s_events_fields
|
||||
{
|
||||
@ -9764,6 +9765,10 @@ ST_FIELD_INFO check_constraints_fields_info[]=
|
||||
OPEN_FULL_TABLE},
|
||||
{0, 0, MYSQL_TYPE_STRING, 0, 0, 0, SKIP_OPEN_TABLE}
|
||||
};
|
||||
|
||||
/** For creating fields of information_schema.OPTIMIZER_TRACE */
|
||||
extern ST_FIELD_INFO optimizer_trace_info[];
|
||||
|
||||
/*
|
||||
Description of ST_FIELD_INFO in table.h
|
||||
|
||||
@ -9816,6 +9821,8 @@ ST_SCHEMA_TABLE schema_tables[]=
|
||||
OPTIMIZE_I_S_TABLE|OPEN_TABLE_ONLY},
|
||||
{"OPEN_TABLES", open_tables_fields_info, 0,
|
||||
fill_open_tables, make_old_format, 0, -1, -1, 1, 0},
|
||||
{"OPTIMIZER_TRACE", optimizer_trace_info, 0,
|
||||
fill_optimizer_trace_info, NULL, NULL, -1, -1, false, 0},
|
||||
{"PARAMETERS", parameters_fields_info, 0,
|
||||
fill_schema_proc, 0, 0, -1, -1, 0, 0},
|
||||
{"PARTITIONS", partitions_fields_info, 0,
|
||||
|
@ -24,6 +24,7 @@
|
||||
#include "sql_show.h" // calc_sum_of_all_status
|
||||
#include "sql_select.h"
|
||||
#include "keycaches.h"
|
||||
#include "my_json_writer.h"
|
||||
#include <hash.h>
|
||||
#include <thr_alarm.h>
|
||||
#if defined(HAVE_MALLINFO) && defined(HAVE_MALLOC_H)
|
||||
@ -36,6 +37,8 @@
|
||||
#include "events.h"
|
||||
#endif
|
||||
|
||||
#define FT_KEYPART (MAX_FIELDS+10)
|
||||
|
||||
static const char *lock_descriptions[] =
|
||||
{
|
||||
/* TL_UNLOCK */ "No lock",
|
||||
@ -225,8 +228,6 @@ TEST_join(JOIN *join)
|
||||
}
|
||||
|
||||
|
||||
#define FT_KEYPART (MAX_FIELDS+10)
|
||||
|
||||
static void print_keyuse(KEYUSE *keyuse)
|
||||
{
|
||||
char buff[256];
|
||||
@ -263,7 +264,6 @@ void print_keyuse_array(DYNAMIC_ARRAY *keyuse_array)
|
||||
DBUG_UNLOCK_FILE;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
Print the current state during query optimization.
|
||||
|
||||
@ -655,3 +655,24 @@ Memory allocated by threads: %s\n",
|
||||
puts("");
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
void print_keyuse_array_for_trace(THD *thd, DYNAMIC_ARRAY *keyuse_array)
|
||||
{
|
||||
Json_writer_object wrapper(thd);
|
||||
Json_writer_array trace_key_uses(thd, "ref_optimizer_key_uses");
|
||||
for(uint i=0; i < keyuse_array->elements; i++)
|
||||
{
|
||||
KEYUSE *keyuse= (KEYUSE*)dynamic_array_ptr(keyuse_array, i);
|
||||
Json_writer_object keyuse_elem(thd);
|
||||
keyuse_elem.add_table_name(keyuse->table->reginfo.join_tab);
|
||||
keyuse_elem.add("field", (keyuse->keypart == FT_KEYPART) ? "<fulltext>"
|
||||
: (keyuse->is_for_hash_join()
|
||||
? keyuse->table->field[keyuse->keypart]
|
||||
->field_name.str
|
||||
: keyuse->table->key_info[keyuse->key]
|
||||
.key_part[keyuse->keypart]
|
||||
.field->field_name.str));
|
||||
keyuse_elem.add("equals",keyuse->val);
|
||||
keyuse_elem.add("null_rejecting",keyuse->null_rejecting);
|
||||
}
|
||||
}
|
||||
|
@ -17,6 +17,7 @@
|
||||
#define SQL_TEST_INCLUDED
|
||||
|
||||
#include "mysqld.h"
|
||||
#include "opt_trace_context.h"
|
||||
|
||||
class JOIN;
|
||||
struct TABLE_LIST;
|
||||
@ -34,6 +35,7 @@ void print_keyuse_array(DYNAMIC_ARRAY *keyuse_array);
|
||||
void print_sjm(SJ_MATERIALIZATION_INFO *sjm);
|
||||
void dump_TABLE_LIST_graph(SELECT_LEX *select_lex, TABLE_LIST* tl);
|
||||
#endif
|
||||
void print_keyuse_array_for_trace(THD *thd, DYNAMIC_ARRAY *keyuse_array);
|
||||
void mysql_print_status();
|
||||
|
||||
#endif /* SQL_TEST_INCLUDED */
|
||||
|
@ -36,6 +36,7 @@
|
||||
#include "datadict.h" // dd_frm_is_view()
|
||||
#include "sql_derived.h"
|
||||
#include "sql_cte.h" // check_dependencies_in_with_clauses()
|
||||
#include "opt_trace.h"
|
||||
|
||||
#define MD5_BUFF_LENGTH 33
|
||||
|
||||
@ -1419,6 +1420,15 @@ bool mysql_make_view(THD *thd, TABLE_SHARE *share, TABLE_LIST *table,
|
||||
if (check_dependencies_in_with_clauses(thd->lex->with_clauses_list))
|
||||
goto err;
|
||||
|
||||
/*
|
||||
Check rights to run commands which show underlying tables.
|
||||
In the optimizer trace we would not like to show trace for
|
||||
cases when the current user does not have rights for the
|
||||
underlying tables.
|
||||
*/
|
||||
if (!table->prelocking_placeholder)
|
||||
opt_trace_disable_if_no_view_access(thd, table, view_tables);
|
||||
|
||||
/*
|
||||
Check rights to run commands (ANALYZE SELECT, EXPLAIN SELECT &
|
||||
SHOW CREATE) which show underlying tables.
|
||||
|
@ -53,6 +53,7 @@
|
||||
#include <myisam.h>
|
||||
#include "debug_sync.h" // DEBUG_SYNC
|
||||
#include "sql_show.h"
|
||||
#include "opt_trace_context.h"
|
||||
|
||||
#include "log_event.h"
|
||||
#ifdef WITH_PERFSCHEMA_STORAGE_ENGINE
|
||||
@ -2542,6 +2543,23 @@ static Sys_var_flagset Sys_optimizer_switch(
|
||||
NO_MUTEX_GUARD, NOT_IN_BINLOG, ON_CHECK(0),
|
||||
ON_UPDATE(fix_optimizer_switch));
|
||||
|
||||
static Sys_var_flagset Sys_optimizer_trace(
|
||||
"optimizer_trace",
|
||||
"Controls tracing of the Optimizer:"
|
||||
" optimizer_trace=option=val[,option=val...], where option is one of"
|
||||
" {enabled}"
|
||||
" and val is one of {on, off, default}",
|
||||
SESSION_VAR(optimizer_trace), CMD_LINE(REQUIRED_ARG),
|
||||
Opt_trace_context::flag_names, DEFAULT(Opt_trace_context::FLAG_DEFAULT));
|
||||
// @see set_var::is_var_optimizer_trace()
|
||||
export sys_var *Sys_optimizer_trace_ptr = &Sys_optimizer_trace;
|
||||
|
||||
static Sys_var_ulong Sys_optimizer_trace_max_mem_size(
|
||||
"optimizer_trace_max_mem_size",
|
||||
"Maximum allowed size of an optimizer trace",
|
||||
SESSION_VAR(optimizer_trace_max_mem_size), CMD_LINE(REQUIRED_ARG),
|
||||
VALID_RANGE(0, ULONG_MAX), DEFAULT(1024 * 1024), BLOCK_SIZE(1));
|
||||
|
||||
static Sys_var_charptr Sys_pid_file(
|
||||
"pid_file", "Pid file used by safe_mysqld",
|
||||
READ_ONLY GLOBAL_VAR(pidfile_name_ptr), CMD_LINE(REQUIRED_ARG),
|
||||
|
@ -44,6 +44,7 @@
|
||||
#include "sql_cte.h"
|
||||
#include "ha_sequence.h"
|
||||
#include "sql_show.h"
|
||||
#include "opt_trace.h"
|
||||
|
||||
/* For MySQL 5.7 virtual fields */
|
||||
#define MYSQL57_GENERATED_FIELD 128
|
||||
@ -5714,6 +5715,7 @@ bool TABLE_LIST::prepare_security(THD *thd)
|
||||
if (prepare_view_security_context(thd))
|
||||
DBUG_RETURN(TRUE);
|
||||
thd->security_ctx= find_view_security_context(thd);
|
||||
opt_trace_disable_if_no_security_context_access(thd);
|
||||
while ((tbl= tb++))
|
||||
{
|
||||
DBUG_ASSERT(tbl->referencing_view);
|
||||
|
Loading…
x
Reference in New Issue
Block a user