From 974cd461b0c8622061ca2071c266be56beeb16d0 Mon Sep 17 00:00:00 2001 From: "wangxiong.wx" Date: Thu, 28 May 2020 09:43:30 +0800 Subject: [PATCH] Issue#28046405: Improve the sql digest so that performance schema does a correct summary Also filed as community bug #99121 Performance schema does a meaningless summary according to digest. Currently, the digest doesn't consider ORDER/GROUP by a constant as a different statement. MySQL treats the constant following ORDER/GROUP BY as the same as other constant e.g 'WHERE columnx = 2'. Actually, it's incorrect because the constant after ORDER/GROUP BY stands for a column. Performance schema will do a summary according to the digest. Obviously, the summary is incorrect and meaningless. This patch fixes such a problem. It makes the digest treat the constant after ORDER/GROUP BY as a normal column. --- .../suite/query_rewrite_plugins/r/special.result | 47 ++- .../suite/query_rewrite_plugins/r/update.result | 4 +- .../suite/query_rewrite_plugins/t/special.test | 18 +- .../suite/query_rewrite_plugins/t/update.test | 2 +- .../suite/rds/r/feature_outline_issue76913.result | 16 +- .../rds/r/feature_outline_issue76913.result-pq | 16 +- mysql-test/suite/spm/r/digest_basic.result | 106 +++++++ mysql-test/suite/spm/t/digest_basic.test | 40 +++ sql/gen_lex_token.cc | 28 +- sql/sql_digest.cc | 320 ++++++++++++++++++--- sql/sql_digest.h | 10 +- sql/sql_digest_stream.h | 7 + storage/perfschema/table_events_statements.cc | 2 +- storage/perfschema/table_events_statements.h | 2 +- 14 files changed, 511 insertions(+), 107 deletions(-) create mode 100644 mysql-test/suite/spm/r/digest_basic.result create mode 100644 mysql-test/suite/spm/t/digest_basic.test diff --git a/mysql-test/suite/query_rewrite_plugins/r/special.result b/mysql-test/suite/query_rewrite_plugins/r/special.result index 5a3e9f5..adcf928 100644 --- a/mysql-test/suite/query_rewrite_plugins/r/special.result +++ b/mysql-test/suite/query_rewrite_plugins/r/special.result @@ -11,8 +11,8 @@ INSERT INTO t1 VALUES ( 1, 'd', 'a' ), ( 7, 'e', 'b' ); # Query rewrite plugin was installed. INSERT INTO query_rewrite.rewrite_rules ( pattern, replacement ) -VALUES ( 'SELECT c1, c2 FROM test.t1 ORDER BY ?', -'SELECT c1, c2 FROM test.t1 ORDER BY 2, ? DESC' ); +VALUES ( 'SELECT c1, c2 FROM test.t1 ORDER BY 1', +'SELECT c1, c2 FROM test.t1 ORDER BY 2, 1 DESC' ); CALL query_rewrite.flush_rewrite_rules(); # Should be rewritten. SELECT c1, c2 FROM test.t1 ORDER BY 1; @@ -131,8 +131,8 @@ c1 + 3 8 9 INSERT INTO query_rewrite.rewrite_rules ( pattern, replacement ) -VALUES ( 'SELECT COUNT( c1 ), c2, c3 FROM test.t1 GROUP BY ?, ?', -'SELECT SUM( c1 ), c2, c3 FROM test.t1 GROUP BY ?, ?' ); +VALUES ( 'SELECT COUNT( c1 ), c2, c3 FROM test.t1 GROUP BY 2, 3', +'SELECT SUM( c1 ), c2, c3 FROM test.t1 GROUP BY 2, 3' ); CALL query_rewrite.flush_rewrite_rules(); # Should be rewritten. SELECT COUNT( c1 ), c2, c3 FROM test.t1 GROUP BY 2, 3; @@ -146,18 +146,16 @@ SUM( c1 ) c2 c3 9 d a Note 1105 Query 'SELECT COUNT( c1 ), c2, c3 FROM test.t1 GROUP BY 2, 3' rewritten to 'SELECT SUM( c1 ), c2, c3 FROM test.t1 GROUP BY 2, 3' by a query rewrite plugin Warnings: -# Should be rewritten. +# Should not be rewritten. SELECT COUNT( c1 ), c2, c3 FROM test.t1 GROUP BY 3, 2; -SUM( c1 ) c2 c3 -2 b c -3 c b -4 a a -5 f c -6 d b -7 e b -9 d a -Note 1105 Query 'SELECT COUNT( c1 ), c2, c3 FROM test.t1 GROUP BY 3, 2' rewritten to 'SELECT SUM( c1 ), c2, c3 FROM test.t1 GROUP BY 3, 2' by a query rewrite plugin -Warnings: +COUNT( c1 ) c2 c3 +1 a a +1 b c +1 c b +1 d b +1 e b +1 f c +2 d a # Should not be rewritten. SELECT COUNT( c1 ), c2, c3 FROM test.t1 GROUP BY c2, c3; COUNT( c1 ) c2 c3 @@ -169,8 +167,8 @@ COUNT( c1 ) c2 c3 1 f c 2 d a INSERT INTO query_rewrite.rewrite_rules ( pattern, replacement ) -VALUES ( 'SELECT COUNT(c1) as c, c2, c3 FROM test.t1 GROUP BY ?, ? HAVING c > ?', -'SELECT COUNT(c1) as c, c2, c3 FROM test.t1 GROUP BY ?, ? HAVING c < ?' +VALUES ( 'SELECT COUNT(c1) as c, c2, c3 FROM test.t1 GROUP BY 2, 3 HAVING c > ?', +'SELECT COUNT(c1) as c, c2, c3 FROM test.t1 GROUP BY 2, 3 HAVING c < ?' ); CALL query_rewrite.flush_rewrite_rules(); SELECT COUNT( c1 ) as c, c2, c3 FROM test.t1 GROUP BY 2, 3 HAVING c > 2; @@ -185,15 +183,6 @@ Note 1105 Query 'SELECT COUNT( c1 ) as c, c2, c3 FROM test.t1 GROUP BY 2, 3 HAVI Warnings: SELECT COUNT( c1 ) as c, c2, c3 FROM test.t1 GROUP BY 3, 2 HAVING c > 3; c c2 c3 -1 a a -1 b c -1 c b -1 d b -1 e b -1 f c -2 d a -Note 1105 Query 'SELECT COUNT( c1 ) as c, c2, c3 FROM test.t1 GROUP BY 3, 2 HAVING c > 3' rewritten to 'SELECT COUNT(c1) as c, c2, c3 FROM test.t1 GROUP BY 3, 2 HAVING c < 3' by a query rewrite plugin -Warnings: CREATE TABLE t2 ( c1 INTEGER, c2 CHAR ( 1 ), c3 CHAR ( 1 ) ); INSERT INTO t2 VALUES ( 1, 'd', 'a' ), ( 2, 'b', 'c' ), @@ -228,8 +217,8 @@ SET sql_mode = 'PIPES_AS_CONCAT'; INSERT INTO query_rewrite.rewrite_rules ( pattern, replacement ) VALUES ( 'SELECT c1,c2,c3 FROM test.t1 WHERE c1=? ' || 'UNION ' || -'SELECT c1 FROM test.t1 WHERE c1=? ORDER BY ?', -'SELECT c1,c2, c3 FROM test.t1 WHERE c1 IN ( ?,? ) ORDER BY ?' ); +'SELECT c1 FROM test.t1 WHERE c1=? ORDER BY 2', +'SELECT c1,c2, c3 FROM test.t1 WHERE c1 IN ( ?,? ) ORDER BY 2' ); CALL query_rewrite.flush_rewrite_rules(); # That's right, query does not compile unless rewritten. SELECT c1, c2, c3 FROM test.t1 WHERE c1 = 1 @@ -284,7 +273,7 @@ SHOW STATUS LIKE 'Rewriter%'; Variable_name Value Rewriter_number_loaded_rules 11 Rewriter_number_reloads 12 -Rewriter_number_rewritten_queries 15 +Rewriter_number_rewritten_queries 13 Rewriter_reload_error OFF DROP TABLE t1, t2; SET sql_mode = DEFAULT; diff --git a/mysql-test/suite/query_rewrite_plugins/r/update.result b/mysql-test/suite/query_rewrite_plugins/r/update.result index eae4cc1..f280198 100644 --- a/mysql-test/suite/query_rewrite_plugins/r/update.result +++ b/mysql-test/suite/query_rewrite_plugins/r/update.result @@ -8,7 +8,7 @@ INSERT INTO t1 VALUES (1, 10), (2, 20), (3, 30); INSERT INTO t2 SELECT a FROM t1; # Query rewrite plugin was installed. INSERT INTO query_rewrite.rewrite_rules ( pattern, pattern_database, replacement ) -VALUES ( 'UPDATE test.t1 SET a = ? WHERE b = ? ORDER BY ? LIMIT ?', 'test', +VALUES ( 'UPDATE test.t1 SET a = ? WHERE b = ? ORDER BY 3 LIMIT ?', 'test', 'UPDATE test.t1 SET b = ? WHERE b = ? ORDER BY a LIMIT 4'), ( 'UPDATE test.t1, test.t2 SET t1.a = ?, t2.a = ? WHERE t1.b = ?', 'test', 'UPDATE test.t1, test.t2 SET t1.a = ?, t1.b = ? WHERE t1.a = ?' ), @@ -17,7 +17,7 @@ VALUES ( 'UPDATE test.t1 SET a = ? WHERE b = ? ORDER BY ? LIMIT ?', 'test', CALL query_rewrite.flush_rewrite_rules(); SELECT * FROM query_rewrite.rewrite_rules; id pattern pattern_database replacement enabled message -1 UPDATE test.t1 SET a = ? WHERE b = ? ORDER BY ? LIMIT ? test UPDATE test.t1 SET b = ? WHERE b = ? ORDER BY a LIMIT 4 YES NULL +1 UPDATE test.t1 SET a = ? WHERE b = ? ORDER BY 3 LIMIT ? test UPDATE test.t1 SET b = ? WHERE b = ? ORDER BY a LIMIT 4 YES NULL 2 UPDATE test.t1, test.t2 SET t1.a = ?, t2.a = ? WHERE t1.b = ? test UPDATE test.t1, test.t2 SET t1.a = ?, t1.b = ? WHERE t1.a = ? YES NULL 3 UPDATE t1 LEFT JOIN t2 ON t1.a = ? SET t1.b = 20 WHERE t1.b > 25 test UPDATE t1 LEFT JOIN t2 ON t2.a = ? SET t1.b = 20 WHERE t1.b > 25 YES NULL UPDATE test.t1 SET a = 0 WHERE b = 2 ORDER BY 3 LIMIT 2; diff --git a/mysql-test/suite/query_rewrite_plugins/t/special.test b/mysql-test/suite/query_rewrite_plugins/t/special.test index dec7d76..3b54d49 100644 --- a/mysql-test/suite/query_rewrite_plugins/t/special.test +++ b/mysql-test/suite/query_rewrite_plugins/t/special.test @@ -17,8 +17,8 @@ INSERT INTO t1 VALUES ( 1, 'd', 'a' ), --source suite/query_rewrite_plugins/include/install_rewriter.inc INSERT INTO query_rewrite.rewrite_rules ( pattern, replacement ) -VALUES ( 'SELECT c1, c2 FROM test.t1 ORDER BY ?', - 'SELECT c1, c2 FROM test.t1 ORDER BY 2, ? DESC' ); +VALUES ( 'SELECT c1, c2 FROM test.t1 ORDER BY 1', + 'SELECT c1, c2 FROM test.t1 ORDER BY 2, 1 DESC' ); CALL query_rewrite.flush_rewrite_rules(); @@ -76,8 +76,8 @@ SELECT c1 * 3 FROM test.t1; SELECT c1 + 3 FROM test.t1; INSERT INTO query_rewrite.rewrite_rules ( pattern, replacement ) -VALUES ( 'SELECT COUNT( c1 ), c2, c3 FROM test.t1 GROUP BY ?, ?', - 'SELECT SUM( c1 ), c2, c3 FROM test.t1 GROUP BY ?, ?' ); +VALUES ( 'SELECT COUNT( c1 ), c2, c3 FROM test.t1 GROUP BY 2, 3', + 'SELECT SUM( c1 ), c2, c3 FROM test.t1 GROUP BY 2, 3' ); CALL query_rewrite.flush_rewrite_rules(); @@ -85,7 +85,7 @@ CALL query_rewrite.flush_rewrite_rules(); --sorted_result SELECT COUNT( c1 ), c2, c3 FROM test.t1 GROUP BY 2, 3; ---echo # Should be rewritten. +--echo # Should not be rewritten. --sorted_result SELECT COUNT( c1 ), c2, c3 FROM test.t1 GROUP BY 3, 2; @@ -94,8 +94,8 @@ SELECT COUNT( c1 ), c2, c3 FROM test.t1 GROUP BY 3, 2; SELECT COUNT( c1 ), c2, c3 FROM test.t1 GROUP BY c2, c3; INSERT INTO query_rewrite.rewrite_rules ( pattern, replacement ) -VALUES ( 'SELECT COUNT(c1) as c, c2, c3 FROM test.t1 GROUP BY ?, ? HAVING c > ?', - 'SELECT COUNT(c1) as c, c2, c3 FROM test.t1 GROUP BY ?, ? HAVING c < ?' +VALUES ( 'SELECT COUNT(c1) as c, c2, c3 FROM test.t1 GROUP BY 2, 3 HAVING c > ?', + 'SELECT COUNT(c1) as c, c2, c3 FROM test.t1 GROUP BY 2, 3 HAVING c < ?' ); CALL query_rewrite.flush_rewrite_rules(); @@ -135,8 +135,8 @@ SET sql_mode = 'PIPES_AS_CONCAT'; INSERT INTO query_rewrite.rewrite_rules ( pattern, replacement ) VALUES ( 'SELECT c1,c2,c3 FROM test.t1 WHERE c1=? ' || 'UNION ' || - 'SELECT c1 FROM test.t1 WHERE c1=? ORDER BY ?', - 'SELECT c1,c2, c3 FROM test.t1 WHERE c1 IN ( ?,? ) ORDER BY ?' ); + 'SELECT c1 FROM test.t1 WHERE c1=? ORDER BY 2', + 'SELECT c1,c2, c3 FROM test.t1 WHERE c1 IN ( ?,? ) ORDER BY 2' ); CALL query_rewrite.flush_rewrite_rules(); diff --git a/mysql-test/suite/query_rewrite_plugins/t/update.test b/mysql-test/suite/query_rewrite_plugins/t/update.test index f12c5d7..f69e2ac 100644 --- a/mysql-test/suite/query_rewrite_plugins/t/update.test +++ b/mysql-test/suite/query_rewrite_plugins/t/update.test @@ -12,7 +12,7 @@ INSERT INTO t2 SELECT a FROM t1; --source suite/query_rewrite_plugins/include/install_rewriter.inc INSERT INTO query_rewrite.rewrite_rules ( pattern, pattern_database, replacement ) -VALUES ( 'UPDATE test.t1 SET a = ? WHERE b = ? ORDER BY ? LIMIT ?', 'test', +VALUES ( 'UPDATE test.t1 SET a = ? WHERE b = ? ORDER BY 3 LIMIT ?', 'test', 'UPDATE test.t1 SET b = ? WHERE b = ? ORDER BY a LIMIT 4'), ( 'UPDATE test.t1, test.t2 SET t1.a = ?, t2.a = ? WHERE t1.b = ?', 'test', 'UPDATE test.t1, test.t2 SET t1.a = ?, t1.b = ? WHERE t1.a = ?' ), diff --git a/mysql-test/suite/rds/r/feature_outline_issue76913.result b/mysql-test/suite/rds/r/feature_outline_issue76913.result index 9e86e79..baf9826 100644 --- a/mysql-test/suite/rds/r/feature_outline_issue76913.result +++ b/mysql-test/suite/rds/r/feature_outline_issue76913.result @@ -142,7 +142,7 @@ Warnings: Note 1003 /* select#1 */ select `outline_db`.`t1`.`id` AS `id`,`outline_db`.`t1`.`col1` AS `col1`,`outline_db`.`t1`.`col2` AS `col2` from `outline_db`.`t1` USE INDEX FOR ORDER BY (`ind_1`) where ((`outline_db`.`t1`.`col2` = 'xpchild') and (`outline_db`.`t1`.`col1` = 2)) order by `outline_db`.`t1`.`col1` call dbms_outln.preview_outline('outline_db', "select * from t1 where col1 = 2 and col2 ='xpchild' order by col1"); SCHEMA DIGEST BLOCK_TYPE BLOCK_NAME BLOCK HINT -outline_db 768ee14da49ea48e9a666810ec97173a71ce5f5bc4d29f14ea43665d6518e890 TABLE t1 1 USE INDEX FOR ORDER BY (`ind_1`) +outline_db 6011618702833bd1d0727954d80ae2550ea21928c5f1276740f2fbfa5aaef3cc TABLE t1 1 USE INDEX FOR ORDER BY (`ind_1`) delete from mysql.outline; commit; call dbms_outln.flush_outline(); @@ -156,7 +156,7 @@ Warnings: Note 1003 /* select#1 */ select `outline_db`.`t1`.`id` AS `id`,`outline_db`.`t1`.`col1` AS `col1`,`outline_db`.`t1`.`col2` AS `col2` from `outline_db`.`t1` USE INDEX (`ind_2`) where ((`outline_db`.`t1`.`col2` = 'xpchild') and (`outline_db`.`t1`.`col1` = 2)) order by `outline_db`.`t1`.`col1` call dbms_outln.preview_outline('outline_db', "select * from t1 where col1 = 2 and col2 ='xpchild' order by col1"); SCHEMA DIGEST BLOCK_TYPE BLOCK_NAME BLOCK HINT -outline_db 768ee14da49ea48e9a666810ec97173a71ce5f5bc4d29f14ea43665d6518e890 TABLE t1 1 USE INDEX (`ind_2`) +outline_db 6011618702833bd1d0727954d80ae2550ea21928c5f1276740f2fbfa5aaef3cc TABLE t1 1 USE INDEX (`ind_2`) call dbms_outln.add_index_outline('outline_db', '', 1, 'USE INDEX', 'ind_1', 'FOR ORDER BY', "select * from t1 where col1 = 2 and col2 = 'xpchild' order by col1"); use outline_db; @@ -167,8 +167,8 @@ Warnings: Note 1003 /* select#1 */ select `outline_db`.`t1`.`id` AS `id`,`outline_db`.`t1`.`col1` AS `col1`,`outline_db`.`t1`.`col2` AS `col2` from `outline_db`.`t1` USE INDEX (`ind_2`) USE INDEX FOR ORDER BY (`ind_1`) where ((`outline_db`.`t1`.`col2` = 'xpchild') and (`outline_db`.`t1`.`col1` = 2)) order by `outline_db`.`t1`.`col1` call dbms_outln.preview_outline('outline_db', "select * from t1 where col1 = 2 and col2 ='xpchild' order by col1"); SCHEMA DIGEST BLOCK_TYPE BLOCK_NAME BLOCK HINT -outline_db 768ee14da49ea48e9a666810ec97173a71ce5f5bc4d29f14ea43665d6518e890 TABLE t1 1 USE INDEX (`ind_2`) -outline_db 768ee14da49ea48e9a666810ec97173a71ce5f5bc4d29f14ea43665d6518e890 TABLE t1 1 USE INDEX FOR ORDER BY (`ind_1`) +outline_db 6011618702833bd1d0727954d80ae2550ea21928c5f1276740f2fbfa5aaef3cc TABLE t1 1 USE INDEX (`ind_2`) +outline_db 6011618702833bd1d0727954d80ae2550ea21928c5f1276740f2fbfa5aaef3cc TABLE t1 1 USE INDEX FOR ORDER BY (`ind_1`) call dbms_outln.add_index_outline('outline_db', '', 1, 'USE INDEX', 'ind_1 ,ind_2', 'FOR ORDER BY', "select * from t1 where col1 = 2 and col2 = 'xpchild' order by col1"); use outline_db; @@ -179,10 +179,10 @@ Warnings: Note 1003 /* select#1 */ select `outline_db`.`t1`.`id` AS `id`,`outline_db`.`t1`.`col1` AS `col1`,`outline_db`.`t1`.`col2` AS `col2` from `outline_db`.`t1` USE INDEX (`ind_2`) USE INDEX FOR ORDER BY (`ind_1`) USE INDEX FOR ORDER BY (`ind_1`) USE INDEX FOR ORDER BY (`ind_2`) where ((`outline_db`.`t1`.`col2` = 'xpchild') and (`outline_db`.`t1`.`col1` = 2)) order by `outline_db`.`t1`.`col1` call dbms_outln.preview_outline('outline_db', "select * from t1 where col1 = 2 and col2 ='xpchild' order by col1"); SCHEMA DIGEST BLOCK_TYPE BLOCK_NAME BLOCK HINT -outline_db 768ee14da49ea48e9a666810ec97173a71ce5f5bc4d29f14ea43665d6518e890 TABLE t1 1 USE INDEX (`ind_2`) -outline_db 768ee14da49ea48e9a666810ec97173a71ce5f5bc4d29f14ea43665d6518e890 TABLE t1 1 USE INDEX FOR ORDER BY (`ind_1`) -outline_db 768ee14da49ea48e9a666810ec97173a71ce5f5bc4d29f14ea43665d6518e890 TABLE t1 1 USE INDEX FOR ORDER BY (`ind_1`) -outline_db 768ee14da49ea48e9a666810ec97173a71ce5f5bc4d29f14ea43665d6518e890 TABLE t1 1 USE INDEX FOR ORDER BY (`ind_2`) +outline_db 6011618702833bd1d0727954d80ae2550ea21928c5f1276740f2fbfa5aaef3cc TABLE t1 1 USE INDEX (`ind_2`) +outline_db 6011618702833bd1d0727954d80ae2550ea21928c5f1276740f2fbfa5aaef3cc TABLE t1 1 USE INDEX FOR ORDER BY (`ind_1`) +outline_db 6011618702833bd1d0727954d80ae2550ea21928c5f1276740f2fbfa5aaef3cc TABLE t1 1 USE INDEX FOR ORDER BY (`ind_1`) +outline_db 6011618702833bd1d0727954d80ae2550ea21928c5f1276740f2fbfa5aaef3cc TABLE t1 1 USE INDEX FOR ORDER BY (`ind_2`) delete from mysql.outline; commit; call dbms_outln.flush_outline(); diff --git a/mysql-test/suite/rds/r/feature_outline_issue76913.result-pq b/mysql-test/suite/rds/r/feature_outline_issue76913.result-pq index a3b73f7..22373f7 100644 --- a/mysql-test/suite/rds/r/feature_outline_issue76913.result-pq +++ b/mysql-test/suite/rds/r/feature_outline_issue76913.result-pq @@ -143,7 +143,7 @@ Warnings: Note 1003 /* select#1 */ select `outline_db`.`t1`.`id` AS `id`,`outline_db`.`t1`.`col1` AS `col1`,`outline_db`.`t1`.`col2` AS `col2` from `outline_db`.`t1` USE INDEX FOR ORDER BY (`ind_1`) where ((`outline_db`.`t1`.`col2` = 'xpchild') and (`outline_db`.`t1`.`col1` = 2)) order by `outline_db`.`t1`.`col1` call dbms_outln.preview_outline('outline_db', "select * from t1 where col1 = 2 and col2 ='xpchild' order by col1"); SCHEMA DIGEST BLOCK_TYPE BLOCK_NAME BLOCK HINT -outline_db 768ee14da49ea48e9a666810ec97173a71ce5f5bc4d29f14ea43665d6518e890 TABLE t1 1 USE INDEX FOR ORDER BY (`ind_1`) +outline_db 6011618702833bd1d0727954d80ae2550ea21928c5f1276740f2fbfa5aaef3cc TABLE t1 1 USE INDEX FOR ORDER BY (`ind_1`) delete from mysql.outline; commit; call dbms_outln.flush_outline(); @@ -158,7 +158,7 @@ Warnings: Note 1003 /* select#1 */ select `outline_db`.`t1`.`id` AS `id`,`outline_db`.`t1`.`col1` AS `col1`,`outline_db`.`t1`.`col2` AS `col2` from `outline_db`.`t1` USE INDEX (`ind_2`) where ((`outline_db`.`t1`.`col2` = 'xpchild') and (`outline_db`.`t1`.`col1` = 2)) order by `outline_db`.`t1`.`col1` call dbms_outln.preview_outline('outline_db', "select * from t1 where col1 = 2 and col2 ='xpchild' order by col1"); SCHEMA DIGEST BLOCK_TYPE BLOCK_NAME BLOCK HINT -outline_db 768ee14da49ea48e9a666810ec97173a71ce5f5bc4d29f14ea43665d6518e890 TABLE t1 1 USE INDEX (`ind_2`) +outline_db 6011618702833bd1d0727954d80ae2550ea21928c5f1276740f2fbfa5aaef3cc TABLE t1 1 USE INDEX (`ind_2`) call dbms_outln.add_index_outline('outline_db', '', 1, 'USE INDEX', 'ind_1', 'FOR ORDER BY', "select * from t1 where col1 = 2 and col2 = 'xpchild' order by col1"); use outline_db; @@ -170,8 +170,8 @@ Warnings: Note 1003 /* select#1 */ select `outline_db`.`t1`.`id` AS `id`,`outline_db`.`t1`.`col1` AS `col1`,`outline_db`.`t1`.`col2` AS `col2` from `outline_db`.`t1` USE INDEX (`ind_2`) USE INDEX FOR ORDER BY (`ind_1`) where ((`outline_db`.`t1`.`col2` = 'xpchild') and (`outline_db`.`t1`.`col1` = 2)) order by `outline_db`.`t1`.`col1` call dbms_outln.preview_outline('outline_db', "select * from t1 where col1 = 2 and col2 ='xpchild' order by col1"); SCHEMA DIGEST BLOCK_TYPE BLOCK_NAME BLOCK HINT -outline_db 768ee14da49ea48e9a666810ec97173a71ce5f5bc4d29f14ea43665d6518e890 TABLE t1 1 USE INDEX (`ind_2`) -outline_db 768ee14da49ea48e9a666810ec97173a71ce5f5bc4d29f14ea43665d6518e890 TABLE t1 1 USE INDEX FOR ORDER BY (`ind_1`) +outline_db 6011618702833bd1d0727954d80ae2550ea21928c5f1276740f2fbfa5aaef3cc TABLE t1 1 USE INDEX (`ind_2`) +outline_db 6011618702833bd1d0727954d80ae2550ea21928c5f1276740f2fbfa5aaef3cc TABLE t1 1 USE INDEX FOR ORDER BY (`ind_1`) call dbms_outln.add_index_outline('outline_db', '', 1, 'USE INDEX', 'ind_1 ,ind_2', 'FOR ORDER BY', "select * from t1 where col1 = 2 and col2 = 'xpchild' order by col1"); use outline_db; @@ -183,10 +183,10 @@ Warnings: Note 1003 /* select#1 */ select `outline_db`.`t1`.`id` AS `id`,`outline_db`.`t1`.`col1` AS `col1`,`outline_db`.`t1`.`col2` AS `col2` from `outline_db`.`t1` USE INDEX (`ind_2`) USE INDEX FOR ORDER BY (`ind_1`) USE INDEX FOR ORDER BY (`ind_1`) USE INDEX FOR ORDER BY (`ind_2`) where ((`outline_db`.`t1`.`col2` = 'xpchild') and (`outline_db`.`t1`.`col1` = 2)) order by `outline_db`.`t1`.`col1` call dbms_outln.preview_outline('outline_db', "select * from t1 where col1 = 2 and col2 ='xpchild' order by col1"); SCHEMA DIGEST BLOCK_TYPE BLOCK_NAME BLOCK HINT -outline_db 768ee14da49ea48e9a666810ec97173a71ce5f5bc4d29f14ea43665d6518e890 TABLE t1 1 USE INDEX (`ind_2`) -outline_db 768ee14da49ea48e9a666810ec97173a71ce5f5bc4d29f14ea43665d6518e890 TABLE t1 1 USE INDEX FOR ORDER BY (`ind_1`) -outline_db 768ee14da49ea48e9a666810ec97173a71ce5f5bc4d29f14ea43665d6518e890 TABLE t1 1 USE INDEX FOR ORDER BY (`ind_1`) -outline_db 768ee14da49ea48e9a666810ec97173a71ce5f5bc4d29f14ea43665d6518e890 TABLE t1 1 USE INDEX FOR ORDER BY (`ind_2`) +outline_db 6011618702833bd1d0727954d80ae2550ea21928c5f1276740f2fbfa5aaef3cc TABLE t1 1 USE INDEX (`ind_2`) +outline_db 6011618702833bd1d0727954d80ae2550ea21928c5f1276740f2fbfa5aaef3cc TABLE t1 1 USE INDEX FOR ORDER BY (`ind_1`) +outline_db 6011618702833bd1d0727954d80ae2550ea21928c5f1276740f2fbfa5aaef3cc TABLE t1 1 USE INDEX FOR ORDER BY (`ind_1`) +outline_db 6011618702833bd1d0727954d80ae2550ea21928c5f1276740f2fbfa5aaef3cc TABLE t1 1 USE INDEX FOR ORDER BY (`ind_2`) delete from mysql.outline; commit; call dbms_outln.flush_outline(); diff --git a/mysql-test/suite/spm/r/digest_basic.result b/mysql-test/suite/spm/r/digest_basic.result new file mode 100644 index 0000000..46c29ae --- /dev/null +++ b/mysql-test/suite/spm/r/digest_basic.result @@ -0,0 +1,106 @@ +# +# bug#99121 Performance schema does a meaningless summary +# according to digest +# +SELECT statement_digest_text("SELECT * FROM t ORDER BY 1, 2"); +statement_digest_text("SELECT * FROM t ORDER BY 1, 2") +SELECT * FROM `t` ORDER BY 1 , 2 +SELECT statement_digest_text("SELECT * FROM t GROUP BY 1, 2"); +statement_digest_text("SELECT * FROM t GROUP BY 1, 2") +SELECT * FROM `t` GROUP BY 1 , 2 +SELECT statement_digest_text("SELECT * FROM t ORDER BY 1, '2'"); +statement_digest_text("SELECT * FROM t ORDER BY 1, '2'") +SELECT * FROM `t` ORDER BY 1 , '2' +SELECT statement_digest_text("SELECT a FROM t GROUP BY 1, '2'"); +statement_digest_text("SELECT a FROM t GROUP BY 1, '2'") +SELECT `a` FROM `t` GROUP BY 1 , '2' +SELECT statement_digest_text("SELECT * FROM t ORDER BY '1', '2'"); +statement_digest_text("SELECT * FROM t ORDER BY '1', '2'") +SELECT * FROM `t` ORDER BY '1' , '2' +SELECT statement_digest_text("SELECT 1 FROM t GROUP BY '1', '2'"); +statement_digest_text("SELECT 1 FROM t GROUP BY '1', '2'") +SELECT ? FROM `t` GROUP BY '1' , '2' +SELECT statement_digest_text("SELECT * FROM t ORDER BY 1, 2, a"); +statement_digest_text("SELECT * FROM t ORDER BY 1, 2, a") +SELECT * FROM `t` ORDER BY 1 , 2 , `a` +SELECT statement_digest_text("SELECT * FROM t GROUP BY 1, 2, a"); +statement_digest_text("SELECT * FROM t GROUP BY 1, 2, a") +SELECT * FROM `t` GROUP BY 1 , 2 , `a` +SELECT statement_digest_text("SELECT * FROM t ORDER BY 1, 2, 'a'"); +statement_digest_text("SELECT * FROM t ORDER BY 1, 2, 'a'") +SELECT * FROM `t` ORDER BY 1 , 2 , 'a' +SELECT statement_digest_text("SELECT * FROM t GROUP BY 1, 2, 'a'"); +statement_digest_text("SELECT * FROM t GROUP BY 1, 2, 'a'") +SELECT * FROM `t` GROUP BY 1 , 2 , 'a' +SELECT statement_digest_text("SELECT * FROM t ORDER BY 1, 2, 'a'"); +statement_digest_text("SELECT * FROM t ORDER BY 1, 2, 'a'") +SELECT * FROM `t` ORDER BY 1 , 2 , 'a' +SELECT statement_digest_text("SELECT * FROM t GROUP BY 1, 2, 'a'"); +statement_digest_text("SELECT * FROM t GROUP BY 1, 2, 'a'") +SELECT * FROM `t` GROUP BY 1 , 2 , 'a' +SELECT statement_digest_text("SELECT * FROM t ORDER BY a, 1, 2, 'a'"); +statement_digest_text("SELECT * FROM t ORDER BY a, 1, 2, 'a'") +SELECT * FROM `t` ORDER BY `a` , 1 , 2 , 'a' +SELECT statement_digest_text("SELECT * FROM t GROUP BY a, 1, 2, 'a'"); +statement_digest_text("SELECT * FROM t GROUP BY a, 1, 2, 'a'") +SELECT * FROM `t` GROUP BY `a` , 1 , 2 , 'a' +SELECT statement_digest_text("SELECT * FROM t ORDER BY (a), 1, 2, 'a'"); +statement_digest_text("SELECT * FROM t ORDER BY (a), 1, 2, 'a'") +SELECT * FROM `t` ORDER BY ( `a` ) , 1 , 2 , 'a' +SELECT statement_digest_text("SELECT * FROM t GROUP BY (a), 1, 2, 'a'"); +statement_digest_text("SELECT * FROM t GROUP BY (a), 1, 2, 'a'") +SELECT * FROM `t` GROUP BY ( `a` ) , 1 , 2 , 'a' +SELECT statement_digest_text("SELECT * FROM t ORDER BY a+1, 2, 'a'"); +statement_digest_text("SELECT * FROM t ORDER BY a+1, 2, 'a'") +SELECT * FROM `t` ORDER BY `a` + 1 , 2 , 'a' +SELECT statement_digest_text("SELECT a+1, b FROM t GROUP BY a+1, 2, 'a'"); +statement_digest_text("SELECT a+1, b FROM t GROUP BY a+1, 2, 'a'") +SELECT `a` + ? , `b` FROM `t` GROUP BY `a` + 1 , 2 , 'a' +SELECT statement_digest_text("SELECT * FROM t ORDER BY (a+1), 2, 'a'"); +statement_digest_text("SELECT * FROM t ORDER BY (a+1), 2, 'a'") +SELECT * FROM `t` ORDER BY ( `a` + 1 ) , 2 , 'a' +SELECT statement_digest_text("SELECT a+1, b FROM t GROUP BY (a+1), 2, 'a'"); +statement_digest_text("SELECT a+1, b FROM t GROUP BY (a+1), 2, 'a'") +SELECT `a` + ? , `b` FROM `t` GROUP BY ( `a` + 1 ) , 2 , 'a' +SELECT statement_digest_text("SELECT * FROM t ORDER BY (1)"); +statement_digest_text("SELECT * FROM t ORDER BY (1)") +SELECT * FROM `t` ORDER BY ( 1 ) +SELECT statement_digest_text("SELECT a FROM t GROUP BY (1)"); +statement_digest_text("SELECT a FROM t GROUP BY (1)") +SELECT `a` FROM `t` GROUP BY ( 1 ) +SELECT statement_digest_text("SELECT * FROM t ORDER BY NULL"); +statement_digest_text("SELECT * FROM t ORDER BY NULL") +SELECT * FROM `t` ORDER BY NULL +SELECT statement_digest_text("SELECT 1 FROM t GROUP BY NULL"); +statement_digest_text("SELECT 1 FROM t GROUP BY NULL") +SELECT ? FROM `t` GROUP BY NULL +SELECT statement_digest_text("SELECT * FROM t ORDER BY NULL, 1"); +statement_digest_text("SELECT * FROM t ORDER BY NULL, 1") +SELECT * FROM `t` ORDER BY NULL , 1 +SELECT statement_digest_text("SELECT 1 FROM t GROUP BY NULL, 1"); +statement_digest_text("SELECT 1 FROM t GROUP BY NULL, 1") +SELECT ? FROM `t` GROUP BY NULL , 1 +SELECT statement_digest_text("SELECT * FROM t ORDER BY 'a', NULL, a"); +statement_digest_text("SELECT * FROM t ORDER BY 'a', NULL, a") +SELECT * FROM `t` ORDER BY 'a' , NULL , `a` +SELECT statement_digest_text("SELECT a FROM t GROUP BY 'a', NULL, a"); +statement_digest_text("SELECT a FROM t GROUP BY 'a', NULL, a") +SELECT `a` FROM `t` GROUP BY 'a' , NULL , `a` +SELECT statement_digest_text("SELECT * FROM t ORDER BY 1 << 1"); +statement_digest_text("SELECT * FROM t ORDER BY 1 << 1") +SELECT * FROM `t` ORDER BY 1 << 1 +SELECT statement_digest_text("SELECT 1 FROM t GROUP BY 1 << 1"); +statement_digest_text("SELECT 1 FROM t GROUP BY 1 << 1") +SELECT ? FROM `t` GROUP BY 1 << 1 +SELECT statement_digest_text("SELECT * FROM t ORDER BY 1 >> 1"); +statement_digest_text("SELECT * FROM t ORDER BY 1 >> 1") +SELECT * FROM `t` ORDER BY 1 >> 1 +SELECT statement_digest_text("SELECT 1 FROM t GROUP BY 1 >> 1"); +statement_digest_text("SELECT 1 FROM t GROUP BY 1 >> 1") +SELECT ? FROM `t` GROUP BY 1 >> 1 +SELECT statement_digest_text("SELECT * FROM t ORDER BY 2 div 1"); +statement_digest_text("SELECT * FROM t ORDER BY 2 div 1") +SELECT * FROM `t` ORDER BY 2 DIV 1 +SELECT statement_digest_text("SELECT 1 FROM t GROUP BY 2 div 1"); +statement_digest_text("SELECT 1 FROM t GROUP BY 2 div 1") +SELECT ? FROM `t` GROUP BY 2 DIV 1 diff --git a/mysql-test/suite/spm/t/digest_basic.test b/mysql-test/suite/spm/t/digest_basic.test new file mode 100644 index 0000000..65bc0cc --- /dev/null +++ b/mysql-test/suite/spm/t/digest_basic.test @@ -0,0 +1,40 @@ +--echo # +--echo # bug#99121 Performance schema does a meaningless summary +--echo # according to digest +--echo # + +SELECT statement_digest_text("SELECT * FROM t ORDER BY 1, 2"); +SELECT statement_digest_text("SELECT * FROM t GROUP BY 1, 2"); +SELECT statement_digest_text("SELECT * FROM t ORDER BY 1, '2'"); +SELECT statement_digest_text("SELECT a FROM t GROUP BY 1, '2'"); +SELECT statement_digest_text("SELECT * FROM t ORDER BY '1', '2'"); +SELECT statement_digest_text("SELECT 1 FROM t GROUP BY '1', '2'"); +SELECT statement_digest_text("SELECT * FROM t ORDER BY 1, 2, a"); +SELECT statement_digest_text("SELECT * FROM t GROUP BY 1, 2, a"); +SELECT statement_digest_text("SELECT * FROM t ORDER BY 1, 2, 'a'"); +SELECT statement_digest_text("SELECT * FROM t GROUP BY 1, 2, 'a'"); +SELECT statement_digest_text("SELECT * FROM t ORDER BY 1, 2, 'a'"); +SELECT statement_digest_text("SELECT * FROM t GROUP BY 1, 2, 'a'"); +SELECT statement_digest_text("SELECT * FROM t ORDER BY a, 1, 2, 'a'"); +SELECT statement_digest_text("SELECT * FROM t GROUP BY a, 1, 2, 'a'"); +SELECT statement_digest_text("SELECT * FROM t ORDER BY (a), 1, 2, 'a'"); +SELECT statement_digest_text("SELECT * FROM t GROUP BY (a), 1, 2, 'a'"); +SELECT statement_digest_text("SELECT * FROM t ORDER BY a+1, 2, 'a'"); +SELECT statement_digest_text("SELECT a+1, b FROM t GROUP BY a+1, 2, 'a'"); +SELECT statement_digest_text("SELECT * FROM t ORDER BY (a+1), 2, 'a'"); +SELECT statement_digest_text("SELECT a+1, b FROM t GROUP BY (a+1), 2, 'a'"); +SELECT statement_digest_text("SELECT * FROM t ORDER BY (1)"); +SELECT statement_digest_text("SELECT a FROM t GROUP BY (1)"); +SELECT statement_digest_text("SELECT * FROM t ORDER BY NULL"); +SELECT statement_digest_text("SELECT 1 FROM t GROUP BY NULL"); +SELECT statement_digest_text("SELECT * FROM t ORDER BY NULL, 1"); +SELECT statement_digest_text("SELECT 1 FROM t GROUP BY NULL, 1"); +SELECT statement_digest_text("SELECT * FROM t ORDER BY 'a', NULL, a"); +SELECT statement_digest_text("SELECT a FROM t GROUP BY 'a', NULL, a"); +SELECT statement_digest_text("SELECT * FROM t ORDER BY 1 << 1"); +SELECT statement_digest_text("SELECT 1 FROM t GROUP BY 1 << 1"); +SELECT statement_digest_text("SELECT * FROM t ORDER BY 1 >> 1"); +SELECT statement_digest_text("SELECT 1 FROM t GROUP BY 1 >> 1"); +SELECT statement_digest_text("SELECT * FROM t ORDER BY 2 div 1"); +SELECT statement_digest_text("SELECT 1 FROM t GROUP BY 2 div 1"); + diff --git a/sql/gen_lex_token.cc b/sql/gen_lex_token.cc index 0eec240..d2d0835 100644 --- a/sql/gen_lex_token.cc +++ b/sql/gen_lex_token.cc @@ -106,6 +106,7 @@ struct gen_lex_token_string { int m_token_length; bool m_append_space; bool m_start_expr; + bool m_start_by_expr; }; gen_lex_token_string compiled_token_array[MY_MAX_TOKEN]; @@ -129,6 +130,7 @@ int tok_hint_comment_open = int tok_hint_comment_close = 0; ///< Fake token value for "*/" of hint comments. int tok_unused = 0; +int tok_by_expr = 0; /** Adjustment value to translate hint parser's internal token values to generally @@ -156,12 +158,21 @@ static void set_token(int tok, const char *str) { compiled_token_array[tok].m_token_length = strlen(str); compiled_token_array[tok].m_append_space = true; compiled_token_array[tok].m_start_expr = false; + compiled_token_array[tok].m_start_by_expr = false; } static void set_start_expr_token(int tok) { compiled_token_array[tok].m_start_expr = true; } +/* + This function will mark start of (ORDER/GROUP) BY expression when BY token is + seen. +*/ +static void set_start_by_expr_token(int tok) { + compiled_token_array[tok].m_start_by_expr = true; +} + static void compute_tokens() { int tok; unsigned int i; @@ -332,6 +343,9 @@ static void compute_tokens() { tok_in_generic_value_expression = max_token_seen++; set_token(tok_in_generic_value_expression, "IN (...)"); + tok_by_expr = max_token_seen++; + set_token(tok_by_expr, "(by_expr)"); + /* Add new digest tokens here */ tok_unused = max_token_seen++; @@ -401,6 +415,8 @@ static void compute_tokens() { set_start_expr_token(DIV_SYM); set_start_expr_token(MOD_SYM); set_start_expr_token('^'); + + set_start_by_expr_token(BY); } static void print_tokens() { @@ -412,19 +428,21 @@ static void print_tokens() { printf("/* PART 1: character tokens. */\n"); for (tok = 0; tok < 256; tok++) { - printf("/* %03d */ { \"\\x%02x\", 1, %s, %s},\n", tok, tok, + printf("/* %03d */ { \"\\x%02x\", 1, %s, %s, %s},\n", tok, tok, compiled_token_array[tok].m_append_space ? "true" : "false", - compiled_token_array[tok].m_start_expr ? "true" : "false"); + compiled_token_array[tok].m_start_expr ? "true" : "false", + compiled_token_array[tok].m_start_by_expr ? "true" : "false"); } printf("/* PART 2: named tokens from sql/sql_yacc.yy. */\n"); for (tok = 256; tok <= max_token_seen_in_sql_yacc; tok++) { - printf("/* %03d */ { \"%s\", %d, %s, %s},\n", tok, + printf("/* %03d */ { \"%s\", %d, %s, %s, %s},\n", tok, compiled_token_array[tok].m_token_string, compiled_token_array[tok].m_token_length, compiled_token_array[tok].m_append_space ? "true" : "false", - compiled_token_array[tok].m_start_expr ? "true" : "false"); + compiled_token_array[tok].m_start_expr ? "true" : "false", + compiled_token_array[tok].m_start_by_expr ? "true" : "false"); } printf("/* PART 3: padding reserved for sql/sql_yacc.yy extensions. */\n"); @@ -488,6 +506,7 @@ static void print_tokens() { tok_in_generic_value_expression); printf("#define TOK_HINT_ADJUST(x) ((x) + %d)\n", tok_hint_adjust); printf("#define TOK_UNUSED %d\n", tok_unused); + printf("#define TOK_BY_EXPR %d\n", tok_by_expr); } /* @@ -525,6 +544,7 @@ int main(int, char **) { printf(" int m_token_length;\n"); printf(" bool m_append_space;\n"); printf(" bool m_start_expr;\n"); + printf(" bool m_start_by_expr;\n"); printf("};\n"); printf("typedef struct lex_token_string lex_token_string;\n"); diff --git a/sql/sql_digest.cc b/sql/sql_digest.cc index 715c8d5..47e08b0 100644 --- a/sql/sql_digest.cc +++ b/sql/sql_digest.cc @@ -33,7 +33,8 @@ #include "my_macros.h" #include "my_sys.h" #include "mysql_com.h" -#include "sha2.h" // SHA256 +#include "sha2.h" // SHA256 +#include "sql/common/component.h" #include "sql/sql_digest_stream.h" // sql_digest_state #include "sql/sql_lex.h" // LEX_YYSTYPE #include "sql/sql_yacc.h" // Generated code. @@ -131,6 +132,13 @@ inline uint read_identifier(const sql_digest_storage *digest_storage, return MAX_DIGEST_STORAGE_SIZE + 1; } +bool is_token_appended_to_by_expr(uint token) { + if (token < 256 || token == NULL_SYM || token == SHIFT_LEFT || + token == SHIFT_RIGHT || token == DIV_SYM) + return true; + return false; +} + /** Store an identifier in token array. */ @@ -162,6 +170,80 @@ inline void store_token_identifier(sql_digest_storage *digest_storage, } } +/** + Append the token list to BY token. In order to keep ORDER/GROUP BY list not to + be replaced by constant marker (?), this function will append the token list + in its own format (token + val) to the BY token. + + @param [in|out] digest_storage the pointer of digest +*/ +static void digest_append_str_to_by_expr(sql_digest_storage *digest_storage, + sql_digest_state *state, uint token, + const char *id_name, + size_t id_length) { + uint bytes_needed = 2 * SIZE_OF_A_TOKEN + id_length; + + /* WRITE: ok to assert, storing a token is race free. */ + DBUG_ASSERT(digest_storage->m_byte_count <= + digest_storage->m_token_array_length); + + if (digest_storage->m_byte_count + bytes_needed <= + (unsigned int)digest_storage->m_token_array_length) { + unsigned char *dest = + &digest_storage->m_token_array[digest_storage->m_byte_count]; + unsigned char *src = &digest_storage->m_token_array[state->m_last_id_index]; + /* Write the token */ + dest[0] = token & 0xff; + dest[1] = (token >> 8) & 0xff; + dest[2] = id_length & 0xff; + dest[3] = (id_length >> 8) & 0xff; + /* Write the string data */ + if (id_length > 0) { + memcpy((char *)dest + 4, id_name, id_length); + } + + // Modify BY list length + size_t org_length = src[2] | (src[3] << 8); + org_length += bytes_needed; + /* Write the string length */ + src[2] = org_length & 0xff; + src[3] = (org_length >> 8) & 0xff; + digest_storage->m_byte_count += bytes_needed; + } else { + digest_storage->m_full = true; + } +} + +static void digest_append_by_expr_token(sql_digest_storage *digest_storage, + sql_digest_state *state, uint tok, + LEX_YYSTYPE yylval) { + size_t id_length = yylval->lex_str.length; + const char *id_name = yylval->lex_str.str; + digest_append_str_to_by_expr(digest_storage, state, tok, id_name, id_length); +} + +static void digest_store_str_to_by_expr(sql_digest_storage *digest_storage, + sql_digest_state *state, uint token, + const char *id_name, size_t id_length) { + /* Update the index of last identifier found. */ + state->m_last_id_index = digest_storage->m_byte_count; + + store_token_identifier(digest_storage, TOK_BY_EXPR, 0, ""); + digest_append_str_to_by_expr(digest_storage, state, token, id_name, + id_length); + + state->m_cont_by_expr = true; +} + +static void digest_store_by_expr_token(sql_digest_storage *digest_storage, + sql_digest_state *state, uint tok, + LEX_YYSTYPE yylval) { + YYSTYPE *lex_token = yylval; + char *yytext = lex_token->lex_str.str; + size_t yylen = lex_token->lex_str.length; + digest_store_str_to_by_expr(digest_storage, state, tok, yytext, yylen); +} + void compute_digest_hash(const sql_digest_storage *digest_storage, unsigned char *hash) { static_assert(DIGEST_HASH_SIZE == SHA256_DIGEST_LENGTH, @@ -170,10 +252,104 @@ void compute_digest_hash(const sql_digest_storage *digest_storage, SHA_EVP256(digest_storage->m_token_array, digest_storage->m_byte_count, hash); } +static void read_token_from_by_expr(char *str, size_t length, + std::string *by_expr_str, + const CHARSET_INFO *from_cs, + const CHARSET_INFO *to_cs) { + size_t cursor = 0; + bool add_quote = false; + bool add_single_quote = false; + bool add_space = false; + char id_buffer[NAME_LEN + 1] = {'\0'}; + bool convert_text = !my_charset_same(from_cs, to_cs); + + while (cursor < length) { + unsigned char *token_array = reinterpret_cast(str); + uint tok = token_array[cursor] | (token_array[cursor + 1] << 8); + uint id_len = token_array[cursor + 2] | (token_array[cursor + 3] << 8); + const char *id_ptr = &str[cursor + 4]; + const char *id_string; + size_t id_length; + uint err_cs = 0; + + switch (tok) { + /* All identifiers are printed with their name. */ + case IDENT: + case IDENT_QUOTED: + case TOK_IDENT: + case TOK_IDENT_AT: + case LEX_HOSTNAME: + add_single_quote = false; + add_quote = true; + break; + case TEXT_STRING: + case NCHAR_STRING: + case PARAM_MARKER: + add_single_quote = true; + add_quote = false; + break; + default: + add_single_quote = false; + add_quote = false; + break; + } + + if (convert_text) { + /* Verify that the converted text will fit. */ + if (to_cs->mbmaxlen * id_len > NAME_LEN) { + if (add_space) { + by_expr_str->append(" ", 1); + add_space = false; + } + + by_expr_str->append("...", 3); + break; + } + /* Convert identifier string into the storage character set. */ + id_length = my_convert(id_buffer, NAME_LEN, to_cs, id_ptr, id_len, + from_cs, &err_cs); + id_string = id_buffer; + } else { + id_string = id_ptr; + id_length = id_len; + } + + if (add_space) { + by_expr_str->append(" ", 1); + add_space = false; + } + + if (id_length == 0 || err_cs != 0) { + break; + } + /* Copy the converted identifier into the digest string. */ + if (add_quote) { + by_expr_str->append("`", 1); + } + if (add_single_quote) { + by_expr_str->append("'", 1); + } + + if (id_length > 0) { + by_expr_str->append(id_string, id_length); + } + + if (add_single_quote) { + by_expr_str->append("'", 1); + } + if (add_quote) { + by_expr_str->append("`", 1); + } + add_space = true; + + cursor += id_len + 2 * SIZE_OF_A_TOKEN; + } +} + /* Iterate token array and updates digest_text. */ -void compute_digest_text(const sql_digest_storage *digest_storage, +void compute_digest_text(sql_digest_storage *digest_storage, String *digest_text) { DBUG_ASSERT(digest_storage != NULL); uint byte_count = digest_storage->m_byte_count; @@ -196,6 +372,7 @@ void compute_digest_text(const sql_digest_storage *digest_storage, if (byte_count > digest_storage->m_token_array_length) { digest_output->append("\0", 1); + digest_storage->sql_digest_truncated = true; return; } @@ -210,6 +387,7 @@ void compute_digest_text(const sql_digest_storage *digest_storage, which can be written to in another thread. */ digest_output->append("\0", 1); + digest_storage->sql_digest_truncated = true; return; } @@ -223,6 +401,7 @@ void compute_digest_text(const sql_digest_storage *digest_storage, if (tok <= 0 || tok >= array_elements(lex_token_array) || current_byte > max_digest_length) { + digest_storage->sql_digest_truncated = true; return; } @@ -233,10 +412,13 @@ void compute_digest_text(const sql_digest_storage *digest_storage, case IDENT: case IDENT_QUOTED: case TOK_IDENT: - case TOK_IDENT_AT: { + case TOK_IDENT_AT: + case TOK_BY_EXPR: { char *id_ptr = NULL; int id_len = 0; uint err_cs = 0; + bool add_quote = true; + std::string by_expr_str; /* Get the next identifier from the storage buffer. */ current_byte = @@ -246,6 +428,15 @@ void compute_digest_text(const sql_digest_storage *digest_storage, return; } + if (tok == TOK_BY_EXPR) { + read_token_from_by_expr(id_ptr, id_len, &by_expr_str, from_cs, to_cs); + id_ptr = const_cast(by_expr_str.c_str()); + id_len = strlen(id_ptr); + add_quote = false; + // read_token_from_by_expr will take care charset transform. + convert_text = 0; + } + if (convert_text) { /* Verify that the converted text will fit. */ if (to_cs->mbmaxlen * id_len > NAME_LEN) { @@ -276,7 +467,7 @@ void compute_digest_text(const sql_digest_storage *digest_storage, } /* Copy the converted identifier into the digest string. */ - digest_output->append("`", 1); + if (add_quote) digest_output->append("`", 1); if (id_length > 0) { digest_output->append(id_string, id_length); } @@ -284,9 +475,11 @@ void compute_digest_text(const sql_digest_storage *digest_storage, { digest_output->append("`", 1); } else { - digest_output->append("`", 1); + if (add_quote) digest_output->append("`", 1); add_space = true; } + + if (tok == TOK_BY_EXPR) convert_text = !my_charset_same(from_cs, to_cs); } break; /* Everything else is printed as is. */ @@ -305,6 +498,7 @@ void compute_digest_text(const sql_digest_storage *digest_storage, break; } } + digest_storage->sql_digest_truncated = digest_storage->m_full; } static inline uint peek_token(const sql_digest_storage *digest, uint index) { @@ -388,6 +582,7 @@ sql_digest_state *digest_add_token(sql_digest_state *state, uint token, sql_digest_storage *digest_storage = NULL; digest_storage = &state->m_digest_storage; + size_t max_tok = array_elements(lex_token_array); /* Stop collecting further tokens if digest storage is full or @@ -465,37 +660,52 @@ sql_digest_state *digest_add_token(sql_digest_state *state, uint token, case TEXT_STRING: case NCHAR_STRING: case PARAM_MARKER: { - /* - REDUCE: - TOK_GENERIC_VALUE := BIN_NUM | DECIMAL_NUM | ... | ULONGLONG_NUM - */ - token = TOK_GENERIC_VALUE; - peek_last_two_tokens(digest_storage, state->m_last_id_index, &last_token, &last_token2); - if ((last_token2 == TOK_GENERIC_VALUE || - last_token2 == TOK_GENERIC_VALUE_LIST) && - (last_token == ',')) { + /* Add this token and identifier string to digest storage. */ + if (last_token < max_tok && lex_token_array[last_token].m_start_by_expr) { + digest_store_by_expr_token(digest_storage, state, token, yylval); + } else if (state->m_cont_by_expr) { + /* Append this token and identifier to "BY" expression list. */ + digest_append_by_expr_token(digest_storage, state, token, yylval); + } else { /* REDUCE: - TOK_GENERIC_VALUE_LIST := + TOK_GENERIC_VALUE := BIN_NUM | DECIMAL_NUM | ... | ULONGLONG_NUM + */ + token = TOK_GENERIC_VALUE; + + if ((last_token2 == TOK_GENERIC_VALUE || + last_token2 == TOK_GENERIC_VALUE_LIST) && + (last_token == ',')) { + /* + REDUCE: + TOK_GENERIC_VALUE_LIST := TOK_GENERIC_VALUE ',' TOK_GENERIC_VALUE - REDUCE: - TOK_GENERIC_VALUE_LIST := + REDUCE: + TOK_GENERIC_VALUE_LIST := TOK_GENERIC_VALUE_LIST ',' TOK_GENERIC_VALUE + */ + digest_storage->m_byte_count -= 2 * SIZE_OF_A_TOKEN; + token = TOK_GENERIC_VALUE_LIST; + } + /* + Add this token or the resulting reduce to digest storage. */ - digest_storage->m_byte_count -= 2 * SIZE_OF_A_TOKEN; - token = TOK_GENERIC_VALUE_LIST; + store_token(digest_storage, token); } - /* - Add this token or the resulting reduce to digest storage. - */ - store_token(digest_storage, token); break; } case ')': { + if (state->m_cont_by_expr) { + /* Append this token and identifier to "BY" expression list. */ + digest_append_str_to_by_expr(digest_storage, state, token, + lex_token_array[token].m_token_string, + lex_token_array[token].m_token_length); + break; + } peek_last_two_tokens(digest_storage, state->m_last_id_index, &last_token, &last_token2); @@ -581,26 +791,35 @@ sql_digest_state *digest_add_token(sql_digest_state *state, uint token, case IDENT: case IDENT_QUOTED: case TOK_IDENT_AT: { - YYSTYPE *lex_token = yylval; - char *yytext = lex_token->lex_str.str; - size_t yylen = lex_token->lex_str.length; + peek_last_two_tokens(digest_storage, state->m_last_id_index, &last_token, + &last_token2); + /* Add this token and identifier string to digest storage. */ + if (last_token < max_tok && lex_token_array[last_token].m_start_by_expr) { + digest_store_by_expr_token(digest_storage, state, token, yylval); + } else if (state->m_cont_by_expr) { + /* Append this token and identifier to "BY" expression list. */ + digest_append_by_expr_token(digest_storage, state, token, yylval); + } else { + YYSTYPE *lex_token = yylval; + char *yytext = lex_token->lex_str.str; + size_t yylen = lex_token->lex_str.length; - /* - REDUCE: + /* + REDUCE: TOK_IDENT := IDENT | IDENT_QUOTED - The parser gives IDENT or IDENT_TOKEN for the same text, - depending on the character set used. - We unify both to always print the same digest text, - and always have the same digest hash. - */ - if (token != TOK_IDENT_AT) { - token = TOK_IDENT; - } - /* Add this token and identifier string to digest storage. */ - store_token_identifier(digest_storage, token, yylen, yytext); + The parser gives IDENT or IDENT_TOKEN for the same text, + depending on the character set used. + We unify both to always print the same digest text, + and always have the same digest hash. + */ + if (token != TOK_IDENT_AT) { + token = TOK_IDENT; + } + store_token_identifier(digest_storage, token, yylen, yytext); - /* Update the index of last identifier found. */ - state->m_last_id_index = digest_storage->m_byte_count; + /* Update the index of last identifier found. */ + state->m_last_id_index = digest_storage->m_byte_count; + } break; } case 0: { @@ -616,8 +835,22 @@ sql_digest_state *digest_add_token(sql_digest_state *state, uint token, break; } default: { - /* Add this token to digest storage. */ - store_token(digest_storage, token); + peek_last_two_tokens(digest_storage, state->m_last_id_index, &last_token, + &last_token2); + /* Add this token and identifier string to digest storage. */ + if (last_token < max_tok && lex_token_array[last_token].m_start_by_expr) { + digest_store_str_to_by_expr(digest_storage, state, token, + lex_token_array[token].m_token_string, + lex_token_array[token].m_token_length); + } else if (is_token_appended_to_by_expr(token) && state->m_cont_by_expr) { + /* Append this token and identifier to "BY" expression list. */ + digest_append_str_to_by_expr(digest_storage, state, token, + lex_token_array[token].m_token_string, + lex_token_array[token].m_token_length); + } else { /* Add this token to digest storage. */ + state->m_cont_by_expr = false; + store_token(digest_storage, token); + } break; } } @@ -638,6 +871,9 @@ sql_digest_state *digest_reduce_token(sql_digest_state *state, uint token_left, return NULL; } + // For BY expression, ignore + if (state->m_cont_by_expr) return state; + uint last_token; uint last_token2; uint last_token3; diff --git a/sql/sql_digest.h b/sql/sql_digest.h index c05f237..c5ac6a9 100644 --- a/sql/sql_digest.h +++ b/sql/sql_digest.h @@ -115,6 +115,11 @@ struct sql_digest_storage { unsigned char *m_token_array; /* Length of the token array to be considered for DIGEST_TEXT calculation. */ size_t m_token_array_length; + /** + It's used to track whether there is any error occurs during generate digest. + Or whether the digest is truncated. + */ + bool sql_digest_truncated; sql_digest_storage() { reset(NULL, 0); } @@ -129,6 +134,7 @@ struct sql_digest_storage { m_byte_count = 0; m_charset_number = 0; memset(m_hash, 0, DIGEST_HASH_SIZE); + sql_digest_truncated = false; } inline bool is_empty() { return (m_byte_count == 0); } @@ -176,9 +182,9 @@ void compute_digest_hash(const sql_digest_storage *digest_storage, - literal values are replaced with a special '?' marker, - lists of values are collapsed using a shorter notation @param digest_storage The digest - @param [out] digest_text The digest text + @param [in/out] digest_text The digest text */ -void compute_digest_text(const sql_digest_storage *digest_storage, +void compute_digest_text(sql_digest_storage *digest_storage, String *digest_text); #endif diff --git a/sql/sql_digest_stream.h b/sql/sql_digest_stream.h index 8fd286c..3b3e480 100644 --- a/sql/sql_digest_stream.h +++ b/sql/sql_digest_stream.h @@ -38,10 +38,17 @@ struct sql_digest_state { @sa digest_add_token */ int m_last_id_index; + /** + True means the following tokens should be appended to BY expression. + Currently BY token means a BY expression starts. Whether the following + tokens belong to this expression or not, is marked by this variable. + */ + bool m_cont_by_expr; sql_digest_storage m_digest_storage; inline void reset(unsigned char *token_array, uint length) { m_last_id_index = 0; + m_cont_by_expr = false; m_digest_storage.reset(token_array, length); } diff --git a/storage/perfschema/table_events_statements.cc b/storage/perfschema/table_events_statements.cc index dbf2ced..3f134ad 100644 --- a/storage/perfschema/table_events_statements.cc +++ b/storage/perfschema/table_events_statements.cc @@ -384,7 +384,7 @@ int table_events_statements_common::make_row_part_1( @return 0 on success or HA_ERR_RECORD_DELETED */ int table_events_statements_common::make_row_part_2( - const sql_digest_storage *digest) { + sql_digest_storage *digest) { /* Filling up statement digest information. */ diff --git a/storage/perfschema/table_events_statements.h b/storage/perfschema/table_events_statements.h index d177155..f018b37 100644 --- a/storage/perfschema/table_events_statements.h +++ b/storage/perfschema/table_events_statements.h @@ -212,7 +212,7 @@ class table_events_statements_common : public PFS_engine_table { int make_row_part_1(PFS_events_statements *statement, sql_digest_storage *digest); - int make_row_part_2(const sql_digest_storage *digest); + int make_row_part_2(sql_digest_storage *digest); /** Current row. */ row_events_statements m_row; -- 2.8.4.53.g2337b9e