Browse Source
MDEV-33281 Implement optimizer hints
MDEV-33281 Implement optimizer hints
Implementing a recursive descent parser for optimizer hints.pull/3878/head
committed by
Oleg Smirnov
15 changed files with 3604 additions and 16 deletions
-
10client/mysql.cc
-
2libmysqld/CMakeLists.txt
-
1366mysql-test/main/opt_hints.result
-
619mysql-test/main/opt_hints.test
-
1sql/CMakeLists.txt
-
3sql/lex_ident.h
-
105sql/opt_hints_parser.cc
-
609sql/opt_hints_parser.h
-
53sql/scan_char.h
-
8sql/share/errmsg-utf8.txt
-
534sql/simple_parser.h
-
196sql/simple_tokenizer.h
-
63sql/sql_lex.cc
-
9sql/sql_lex.h
-
42sql/sql_yacc.yy
1366
mysql-test/main/opt_hints.result
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
@ -0,0 +1,619 @@ |
|||
--echo # WL#8017 Infrastructure for Optimizer Hints |
|||
--enable_prepare_warnings |
|||
|
|||
CREATE TABLE t1(f1 INT, f2 INT); |
|||
INSERT INTO t1 VALUES |
|||
(1,1),(2,2),(3,3); |
|||
|
|||
CREATE TABLE t2(f1 INT NOT NULL, f2 INT NOT NULL, f3 CHAR(200), KEY(f1, f2)); |
|||
INSERT INTO t2 VALUES |
|||
(1,1, 'qwerty'),(1,2, 'qwerty'),(1,3, 'qwerty'), |
|||
(2,1, 'qwerty'),(2,2, 'qwerty'),(2,3, 'qwerty'), (2,4, 'qwerty'),(2,5, 'qwerty'), |
|||
(3,1, 'qwerty'),(3,4, 'qwerty'), |
|||
(4,1, 'qwerty'),(4,2, 'qwerty'),(4,3, 'qwerty'), (4,4, 'qwerty'), |
|||
(1,1, 'qwerty'),(1,2, 'qwerty'),(1,3, 'qwerty'), |
|||
(2,1, 'qwerty'),(2,2, 'qwerty'),(2,3, 'qwerty'), (2,4, 'qwerty'),(2,5, 'qwerty'), |
|||
(3,1, 'qwerty'),(3,4, 'qwerty'), |
|||
(4,1, 'qwerty'),(4,2, 'qwerty'),(4,3, 'qwerty'), (4,4, 'qwerty'); |
|||
|
|||
CREATE TABLE t3 (f1 INT NOT NULL, f2 INT, f3 VARCHAR(32), |
|||
PRIMARY KEY(f1), KEY f2_idx(f1), KEY f3_idx(f3)); |
|||
INSERT INTO t3 VALUES |
|||
(1, 1, 'qwerty'), (2, 1, 'ytrewq'), |
|||
(3, 2, 'uiop'), (4, 2, 'poiu'), (5, 2, 'lkjh'), |
|||
(6, 2, 'uiop'), (7, 2, 'poiu'), (8, 2, 'lkjh'), |
|||
(9, 2, 'uiop'), (10, 2, 'poiu'), (11, 2, 'lkjh'), |
|||
(12, 2, 'uiop'), (13, 2, 'poiu'), (14, 2, 'lkjh'); |
|||
INSERT INTO t3 SELECT f1 + 20, f2, f3 FROM t3; |
|||
INSERT INTO t3 SELECT f1 + 40, f2, f3 FROM t3; |
|||
|
|||
ANALYZE TABLE t1; |
|||
ANALYZE TABLE t2; |
|||
ANALYZE TABLE t3; |
|||
|
|||
|
|||
--echo # NO_RANGE_OPTIMIZATION hint testing |
|||
set optimizer_switch=default; |
|||
|
|||
--disable_ps2_protocol |
|||
--echo # Check statistics with no hint |
|||
FLUSH STATUS; |
|||
SELECT f1 FROM t3 WHERE f1 > 30 AND f1 < 33; |
|||
SHOW STATUS LIKE 'handler_read%'; |
|||
|
|||
--echo # Check statistics with hint |
|||
FLUSH STATUS; |
|||
SELECT /*+ NO_RANGE_OPTIMIZATION(t3 PRIMARY, f2_idx) */ f1 FROM t3 WHERE f1 > 30 AND f1 < 33; |
|||
SHOW STATUS LIKE 'handler_read%'; |
|||
--enable_ps2_protocol |
|||
|
|||
EXPLAIN EXTENDED SELECT f1 FROM t3 WHERE f1 > 30 AND f1 < 33; |
|||
--echo # Turn off range access for PRIMARY key |
|||
--echo # Should use range access by f2_idx key |
|||
EXPLAIN EXTENDED SELECT /*+ NO_RANGE_OPTIMIZATION(t3 PRIMARY) */ f1 FROM t3 WHERE f1 > 30 AND f1 < 33; |
|||
--echo # Turn off range access for PRIMARY & f2_idx keys |
|||
--echo # Should use index access |
|||
EXPLAIN EXTENDED SELECT /*+ NO_RANGE_OPTIMIZATION(t3 PRIMARY, f2_idx) */ f1 FROM t3 WHERE f1 > 30 AND f1 < 33; |
|||
--echo # Turn off range access for all keys |
|||
--echo # Should use index access |
|||
EXPLAIN EXTENDED SELECT /*+ NO_RANGE_OPTIMIZATION(t3) */ f1 FROM t3 WHERE f1 > 30 AND f1 < 33; |
|||
--echo # Turn off range access for PRIMARY & f2_idx keys |
|||
--echo # Should use index access |
|||
EXPLAIN EXTENDED SELECT /*+ NO_RANGE_OPTIMIZATION(t3 PRIMARY) NO_RANGE_OPTIMIZATION(t3 f2_idx) */ f1 FROM t3 WHERE f1 > 30 AND f1 < 33; |
|||
|
|||
--echo # NO_ICP hint testing |
|||
set optimizer_switch='index_condition_pushdown=on'; |
|||
|
|||
CREATE TABLE t4 (x INT, y INT, KEY x_idx(x), KEY y_idx(y)); |
|||
INSERT INTO t4 (x) VALUES (1),(2),(3),(4),(5),(6),(7),(8),(9),(10),(11),(12),(13); |
|||
UPDATE t4 SET y=x; |
|||
|
|||
EXPLAIN EXTENDED SELECT * FROM |
|||
(SELECT t4.x, t5.y FROM t4, t4 t5 WHERE t4.y = 8 AND t5.x BETWEEN 7 AND t4.y+0) AS TD; |
|||
|
|||
EXPLAIN EXTENDED SELECT * FROM |
|||
(SELECT /*+ NO_ICP(t5 x_idx, y_idx) */ t4.x, t5.y FROM t4, t4 t5 |
|||
WHERE t4.y = 8 AND t5.x BETWEEN 7 AND t4.y+0) AS TD; |
|||
|
|||
EXPLAIN EXTENDED SELECT /*+ NO_ICP(t5@qb1 x_idx) */ * FROM |
|||
(SELECT /*+ QB_NAME(QB1) */ t4.x, t5.y FROM t4, t4 t5 |
|||
WHERE t4.y = 8 AND t5.x BETWEEN 7 AND t4.y+0) AS TD; |
|||
|
|||
--echo # Expected warning for z_idx key, unresolved name. |
|||
EXPLAIN EXTENDED SELECT * FROM |
|||
(SELECT /*+ NO_ICP(t5 y_idx, x_idx, z_idx) */ t4.x, t5.y FROM t4, t4 t5 |
|||
WHERE t4.y = 8 AND t5.x BETWEEN 7 AND t4.y+0) AS TD; |
|||
|
|||
--echo # ICP should still be used |
|||
EXPLAIN EXTENDED SELECT * FROM |
|||
(SELECT /*+ NO_ICP(t5 y_idx) */ t4.x, t5.y FROM t4, t4 t5 |
|||
WHERE t4.y = 8 AND t5.x BETWEEN 7 AND t4.y+0) AS TD; |
|||
|
|||
--echo # BKA & NO_BKA hint testing |
|||
set optimizer_switch=default; |
|||
set optimizer_switch='mrr=on,mrr_cost_based=off'; |
|||
set join_cache_level=6; |
|||
|
|||
CREATE TABLE t10(a INT); |
|||
INSERT INTO t10 VALUES (0),(1),(2),(3),(4),(5),(6),(7),(8),(9); |
|||
CREATE TABLE t11(a INT); |
|||
INSERT INTO t11 SELECT A.a + B.a* 10 + C.a * 100 from t10 A, t10 B, t10 C; |
|||
CREATE TABLE t12(a INT, b INT); |
|||
INSERT INTO t12 SELECT a,a from t10; |
|||
CREATE TABLE t13(a INT, b INT, c INT, filler CHAR(100), key (a,b)); |
|||
INSERT INTO t13 select a,a,a, 'filler-data' FROM t11; |
|||
|
|||
--echo # Make sure BKA is expected to be used when there are no hints |
|||
EXPLAIN |
|||
SELECT * FROM t12, t13 WHERE t12.a=t13.a AND (t13.b+1 <= t13.b+1); |
|||
|
|||
--echo # Disable BKA |
|||
set optimizer_switch='join_cache_bka=off'; |
|||
EXPLAIN EXTENDED |
|||
SELECT * FROM t12, t13 WHERE t12.a=t13.a AND (t13.b+1 <= t13.b+1); |
|||
|
|||
--disable_ps2_protocol |
|||
--echo # Check statistics without hint |
|||
FLUSH STATUS; |
|||
SELECT * FROM t12, t13 WHERE t12.a=t13.a AND (t13.b+1 <= t13.b+1); |
|||
SHOW STATUS LIKE 'handler_read%'; |
|||
|
|||
--echo # Check statistics with hint |
|||
FLUSH STATUS; |
|||
SELECT /*+ BKA() */ * FROM t12, t13 WHERE t12.a=t13.a AND (t13.b+1 <= t13.b+1); |
|||
SHOW STATUS LIKE 'handler_read%'; |
|||
--enable_ps2_protocol |
|||
|
|||
EXPLAIN EXTENDED SELECT /*+ BKA(t13) */ * FROM t12, t13 |
|||
WHERE t12.a=t13.a AND (t13.b+1 <= t13.b+1); |
|||
|
|||
EXPLAIN EXTENDED SELECT /*+ BKA() */ * FROM t12, t13 |
|||
WHERE t12.a=t13.a AND (t13.b+1 <= t13.b+1); |
|||
|
|||
EXPLAIN EXTENDED SELECT /*+ BKA(t12, t13) */ * FROM t12, t13 |
|||
WHERE t12.a=t13.a AND (t13.b+1 <= t13.b+1); |
|||
|
|||
EXPLAIN EXTENDED SELECT /*+ BKA(t12) */ * FROM t12, t13 |
|||
WHERE t12.a=t13.a AND (t13.b+1 <= t13.b+1); |
|||
|
|||
EXPLAIN EXTENDED SELECT /*+ QB_NAME(QB1) BKA(t13@QB1) */ * FROM t12, t13 |
|||
WHERE t12.a=t13.a AND (t13.b+1 <= t13.b+1); |
|||
|
|||
--echo # Enable BKA |
|||
set optimizer_switch='join_cache_bka=on'; |
|||
|
|||
EXPLAIN EXTENDED SELECT /*+ NO_BKA(t13) */ * FROM t12, t13 |
|||
WHERE t12.a=t13.a AND (t13.b+1 <= t13.b+1); |
|||
|
|||
EXPLAIN EXTENDED SELECT /*+ NO_BKA() */ * FROM t12, t13 |
|||
WHERE t12.a=t13.a AND (t13.b+1 <= t13.b+1); |
|||
|
|||
EXPLAIN EXTENDED SELECT /*+ NO_BKA(t12, t13) */ * FROM t12, t13 |
|||
WHERE t12.a=t13.a AND (t13.b+1 <= t13.b+1); |
|||
|
|||
EXPLAIN EXTENDED SELECT /*+ NO_BKA(t12) */ * FROM t12, t13 |
|||
WHERE t12.a=t13.a AND (t13.b+1 <= t13.b+1); |
|||
|
|||
EXPLAIN EXTENDED SELECT /*+ QB_NAME(QB1) NO_BKA(t13@QB1) */ * FROM t12, t13 |
|||
WHERE t12.a=t13.a AND (t13.b+1 <= t13.b+1); |
|||
|
|||
--echo # UPDATE|DELETE|INSERT hint testing |
|||
EXPLAIN EXTENDED UPDATE t3 |
|||
SET f3 = 'mnbv' WHERE f1 > 30 AND f1 < 33 AND (t3.f1, t3.f2, t3.f3) IN |
|||
(SELECT t2.f1, t2.f2, t2.f3 FROM t1,t2 WHERE t1.f1=t2.f1 AND |
|||
t2.f2 BETWEEN t1.f1 AND t1.f2 AND t2.f2 + 1 >= t1.f1 + 1); |
|||
|
|||
--echo # Turn off range access for PRIMARY key. |
|||
--echo # Range access should be used for f2_idx key. |
|||
EXPLAIN EXTENDED UPDATE /*+ NO_RANGE_OPTIMIZATION(t3 PRIMARY) */ t3 |
|||
SET f3 = 'mnbv' WHERE f1 > 30 AND f1 < 33 AND (t3.f1, t3.f2, t3.f3) IN |
|||
(SELECT /*+ BKA(t2) NO_BNL(t1) */ t2.f1, t2.f2, t2.f3 FROM t1,t2 WHERE t1.f1=t2.f1 AND |
|||
t2.f2 BETWEEN t1.f1 AND t1.f2 AND t2.f2 + 1 >= t1.f1 + 1); |
|||
|
|||
--echo # Turn off range access for all keys. |
|||
EXPLAIN EXTENDED UPDATE /*+ NO_RANGE_OPTIMIZATION(t3) */ t3 |
|||
SET f3 = 'mnbv' WHERE f1 > 30 AND f1 < 33 AND (t3.f1, t3.f2, t3.f3) IN |
|||
(SELECT t2.f1, t2.f2, t2.f3 FROM t1,t2 WHERE t1.f1=t2.f1 AND |
|||
t2.f2 BETWEEN t1.f1 AND t1.f2 AND t2.f2 + 1 >= t1.f1 + 1); |
|||
|
|||
EXPLAIN EXTENDED DELETE FROM t3 |
|||
WHERE f1 > 30 AND f1 < 33 AND (t3.f1, t3.f2, t3.f3) IN |
|||
(SELECT /*+ QB_NAME(qb1) */ t2.f1, t2.f2, t2.f3 FROM t1,t2 WHERE t1.f1=t2.f1 AND |
|||
t2.f2 BETWEEN t1.f1 AND t1.f2 AND t2.f2 + 1 >= t1.f1 + 1); |
|||
|
|||
--echo # Turn off range access. Range access should not be used. |
|||
EXPLAIN EXTENDED |
|||
DELETE /*+ NO_RANGE_OPTIMIZATION(t3 PRIMARY, f2_idx) NO_BNL(t1@QB1) */ FROM t3 |
|||
WHERE f1 > 30 AND f1 < 33 AND (t3.f1, t3.f2, t3.f3) IN |
|||
(SELECT /*+ QB_NAME(qb1) */ t2.f1, t2.f2, t2.f3 FROM t1,t2 WHERE t1.f1=t2.f1 AND |
|||
t2.f2 BETWEEN t1.f1 AND t1.f2 AND t2.f2 + 1 >= t1.f1 + 1); |
|||
|
|||
--echo # Make sure ICP is expected to be used when there are no hints |
|||
EXPLAIN EXTENDED INSERT INTO t3(f1, f2, f3) |
|||
(SELECT t4.x, t5.y, 'filler' FROM t4, t4 t5 WHERE t4.y = 8 AND t5.x BETWEEN 7 AND t4.y+0); |
|||
|
|||
--echo # Turn off ICP. ICP should not be used. |
|||
EXPLAIN EXTENDED INSERT INTO t3(f1, f2, f3) |
|||
(SELECT /*+ NO_ICP(t5) */t4.x, t5.y, 'filler' FROM t4, t4 t5 |
|||
WHERE t4.y = 8 AND t5.x BETWEEN 7 AND t4.y+0); |
|||
|
|||
--echo # Turn off ICP for a particular table |
|||
EXPLAIN EXTENDED INSERT /*+ NO_ICP(t5@QB1) */ INTO t3(f1, f2, f3) |
|||
(SELECT /*+ QB_NAME(qb1) */ t4.x, t5.y, 'filler' FROM t4, t4 t5 |
|||
WHERE t4.y = 8 AND t5.x BETWEEN 7 AND t4.y+0); |
|||
|
|||
--echo # Turn off ICP for a particular table and a key |
|||
EXPLAIN EXTENDED INSERT /*+ NO_ICP(t5@QB1 x_idx) */ INTO t3(f1, f2, f3) |
|||
(SELECT /*+ QB_NAME(qb1) */ t4.x, t5.y, 'filler' FROM t4, t4 t5 |
|||
WHERE t4.y = 8 AND t5.x BETWEEN 7 AND t4.y+0); |
|||
|
|||
--echo # Misc tests |
|||
|
|||
--echo # Should issue warning |
|||
EXPLAIN EXTENDED SELECT /*+ QB_NAME(qb1) QB_NAME(qb1 ) */ * FROM t2; |
|||
--echo # Should issue warning |
|||
EXPLAIN EXTENDED SELECT /*+ BKA(@qb1) QB_NAME(qb1) */ t2.f1, t2.f2, t2.f3 FROM t1,t2 |
|||
WHERE t1.f1=t2.f1 AND t2.f2 BETWEEN t1.f1 and t1.f2 and t2.f2 + 1 >= t1.f1 + 1; |
|||
|
|||
--echo # Should not crash |
|||
PREPARE stmt1 FROM "SELECT /*+ BKA(t2) */ t2.f1, t2.f2, t2.f3 FROM t1,t2 |
|||
WHERE t1.f1=t2.f1 AND t2.f2 BETWEEN t1.f1 and t1.f2 and t2.f2 + 1 >= t1.f1 + 1"; |
|||
EXECUTE stmt1; |
|||
EXECUTE stmt1; |
|||
DEALLOCATE PREPARE stmt1; |
|||
|
|||
--echo # Check use of alias |
|||
set optimizer_switch='join_cache_bka=off'; |
|||
EXPLAIN EXTENDED |
|||
SELECT * FROM t12, t13 WHERE t12.a=t13.a AND (t13.b+1 <= t13.b+1); |
|||
|
|||
--echo # Turn on BKA for multiple tables. BKA should be used for tbl13. |
|||
EXPLAIN EXTENDED SELECT /*+ BKA(tbl12, tbl13) */ * FROM t12 tbl12, t13 tbl13 |
|||
WHERE tbl12.a=tbl13.a AND (tbl13.b+1 <= tbl13.b+1); |
|||
|
|||
--echo # Print warnings for nonexistent names |
|||
EXPLAIN EXTENDED |
|||
SELECT /*+ BKA(t2) NO_BNL(t1) BKA(t3) NO_RANGE_OPTIMIZATION(t3 idx1) NO_RANGE_OPTIMIZATION(t3) */ |
|||
t2.f1, t2.f2, t2.f3 FROM t1,t2 WHERE t1.f1=t2.f1 AND |
|||
t2.f2 BETWEEN t1.f1 AND t1.f2 AND t2.f2 + 1 >= t1.f1 + 1; |
|||
|
|||
--echo # Check illegal syntax |
|||
EXPLAIN EXTENDED SELECT /*+ BKA(qb1 t3@qb1) */ f2 FROM |
|||
(SELECT /*+ QB_NAME(qb1) */ f2, f3, f1 FROM t3 WHERE f1 > 2 AND f3 = 'poiu') AS TD |
|||
WHERE TD.f1 > 2 AND TD.f3 = 'poiu'; |
|||
|
|||
--echo # Check illegal syntax |
|||
EXPLAIN EXTENDED SELECT * FROM |
|||
(SELECT /*+ QB_NAME(qb1) BKA(@qb1 t1@qb1, t2@qb1, t3) */ t2.f1, t2.f2, t2.f3 FROM t1,t2,t3) tt; |
|||
|
|||
--echo # Check '@qb_name table_name' syntax |
|||
EXPLAIN EXTENDED SELECT /*+ BKA(@qb1 t13) */ * FROM (SELECT /*+ QB_NAME(QB1) */ t12.a, t13.b FROM t12, t13 |
|||
WHERE t12.a=t13.a AND (t13.b+1 <= t13.b+1)) AS s1; |
|||
|
|||
--echo # Check that original table name is not recognized if alias is used. |
|||
EXPLAIN EXTENDED SELECT /*+ BKA(tbl2) */ * FROM t12 tbl12, t13 tbl13 |
|||
WHERE tbl12.a=tbl13.a AND (tbl13.b+1 <= tbl13.b+1); |
|||
|
|||
--disable_ps2_protocol |
|||
--echo # Check that PS and conventional statements give the same result. |
|||
FLUSH STATUS; |
|||
SELECT /*+ BKA(t13) */ * FROM t12, t13 WHERE t12.a=t13.a AND (t13.b+1 <= t13.b+1); |
|||
SHOW STATUS LIKE 'handler_read%'; |
|||
--enable_ps2_protocol |
|||
|
|||
PREPARE stmt1 FROM "SELECT /*+ BKA(t13) */ * FROM t12, t13 WHERE t12.a=t13.a AND (t13.b+1 <= t13.b+1)"; |
|||
FLUSH STATUS; |
|||
EXECUTE stmt1; |
|||
SHOW STATUS LIKE 'handler_read%'; |
|||
|
|||
FLUSH STATUS; |
|||
EXECUTE stmt1; |
|||
SHOW STATUS LIKE 'handler_read%'; |
|||
|
|||
DEALLOCATE PREPARE stmt1; |
|||
|
|||
DROP TABLE t1, t2, t3, t10, t11, t12, t13; |
|||
|
|||
--echo # BNL & NO_BNL hint testing |
|||
|
|||
set optimizer_switch=default; |
|||
|
|||
CREATE TABLE t1 (a INT, b INT); |
|||
INSERT INTO t1 VALUES (1,1),(2,2); |
|||
CREATE TABLE t2 (a INT, b INT); |
|||
INSERT INTO t2 VALUES (1,1),(2,2); |
|||
CREATE TABLE t3 (a INT, b INT); |
|||
INSERT INTO t3 VALUES (1,1),(2,2); |
|||
|
|||
--disable_ps2_protocol |
|||
--echo # Check statistics without hint |
|||
FLUSH STATUS; |
|||
SELECT t1.* FROM t1,t2,t3; |
|||
SHOW STATUS LIKE 'handler_read%'; |
|||
|
|||
--echo # Check statistics with hint |
|||
FLUSH STATUS; |
|||
SELECT /*+ NO_BNL() */t1.* FROM t1,t2,t3; |
|||
SHOW STATUS LIKE 'handler_read%'; |
|||
--enable_ps2_protocol |
|||
|
|||
EXPLAIN EXTENDED SELECT t1.* FROM t1,t2,t3; |
|||
EXPLAIN EXTENDED SELECT /*+ NO_BNL() */t1.* FROM t1,t2,t3; |
|||
EXPLAIN EXTENDED SELECT /*+ NO_BNL(t2, t3) */t1.* FROM t1,t2,t3; |
|||
EXPLAIN EXTENDED SELECT /*+ NO_BNL(t1, t3) */t1.* FROM t1,t2,t3; |
|||
|
|||
--echo # MariaDB does not have optimizer_switch='block_nested_loop=off' |
|||
--echo # as MySQL does, so in fact we cannot disable BNL join. The cases below |
|||
--echo # test the BNL() hint, although it does not affect the execution plan |
|||
EXPLAIN EXTENDED SELECT t1.* FROM t1,t2,t3; |
|||
EXPLAIN EXTENDED SELECT /*+ BNL() */t1.* FROM t1,t2,t3; |
|||
EXPLAIN EXTENDED SELECT /*+ BNL(t2, t3) */t1.* FROM t1,t2,t3; |
|||
EXPLAIN EXTENDED SELECT /*+ BNL(t1, t3) */t1.* FROM t1,t2,t3; |
|||
EXPLAIN EXTENDED SELECT /*+ BNL(t2) BNL(t3) */t1.* FROM t1,t2,t3; |
|||
|
|||
DROP TABLE t1, t2, t3; |
|||
|
|||
--echo # BNL in subquery |
|||
set optimizer_switch = DEFAULT; |
|||
CREATE TABLE t1 (a INT, b INT, PRIMARY KEY (a)); |
|||
CREATE TABLE t2 (a INT); |
|||
CREATE TABLE t3 (a INT, b INT, INDEX a (a,b)); |
|||
INSERT INTO t1 VALUES (1,10), (2,20), (3,30), (4,40); |
|||
INSERT INTO t2 VALUES (2), (3), (4), (5); |
|||
INSERT INTO t3 VALUES (10,3), (20,4), (30,5); |
|||
ANALYZE TABLE t1, t2, t3; |
|||
|
|||
EXPLAIN EXTENDED SELECT /*+ QB_NAME(q) */ * FROM t1 JOIN t2 ON t1.b = t2.a WHERE |
|||
t2.a IN (SELECT /*+ QB_NAME(subq1) */ t3.b FROM t3 JOIN t1 t4 ON t3.b = t4.b); |
|||
|
|||
EXPLAIN EXTENDED SELECT /*+ QB_NAME(q) NO_BNL() */ * FROM t1 JOIN t2 ON t1.b = t2.a WHERE |
|||
t2.a IN (SELECT /*+ QB_NAME(subq1) */ t3.b FROM t3 JOIN t1 t4 ON t3.b = t4.b); |
|||
|
|||
EXPLAIN EXTENDED SELECT /*+ QB_NAME(q) NO_BNL(t1, t2) */ * FROM t1 JOIN t2 ON t1.b = t2.a WHERE |
|||
t2.a IN (SELECT /*+ QB_NAME(subq1) */ t3.b FROM t3 JOIN t1 t4 ON t3.b = t4.b); |
|||
|
|||
EXPLAIN EXTENDED SELECT /*+ QB_NAME(q) NO_BNL(@subq1) */ * FROM t1 JOIN t2 ON t1.b = t2.a WHERE |
|||
t2.a IN (SELECT /*+ QB_NAME(subq1) */ t3.b FROM t3 JOIN t1 t4 ON t3.b = t4.b); |
|||
|
|||
EXPLAIN EXTENDED SELECT /*+ QB_NAME(q) NO_BNL(t4@subq1) */ * FROM t1 JOIN t2 ON t1.b = t2.a WHERE |
|||
t2.a IN (SELECT /*+ QB_NAME(subq1) */ t3.b FROM t3 JOIN t1 t4 ON t3.b = t4.b); |
|||
|
|||
EXPLAIN EXTENDED SELECT /*+ QB_NAME(q) NO_BNL(t3@subq1,t4@subq1) */ * FROM t1 JOIN t2 ON t1.b = t2.a WHERE |
|||
t2.a IN (SELECT /*+ QB_NAME(subq1) */ t3.b FROM t3 JOIN t1 t4 ON t3.b = t4.b); |
|||
|
|||
EXPLAIN EXTENDED SELECT /*+ QB_NAME(q) NO_BNL(@subq1 t3, t4) */ * FROM t1 JOIN t2 ON t1.b = t2.a WHERE |
|||
t2.a IN (SELECT /*+ QB_NAME(subq1) */ t3.b FROM t3 JOIN t1 t4 ON t3.b = t4.b); |
|||
|
|||
EXPLAIN EXTENDED SELECT /*+ QB_NAME(q) */ * FROM t1 JOIN t2 ON t1.b = t2.a WHERE |
|||
t2.a IN (SELECT /*+ QB_NAME(subq1) NO_BNL(t3, t4) */ t3.b FROM t3 JOIN t1 t4 ON t3.b = t4.b); |
|||
|
|||
DROP TABLE t1, t2, t3, t4; |
|||
|
|||
--echo # MRR & NO_MRR hint testing |
|||
set optimizer_switch=default; |
|||
|
|||
CREATE TABLE t1 |
|||
( |
|||
f1 int NOT NULL DEFAULT '0', |
|||
f2 int NOT NULL DEFAULT '0', |
|||
f3 int NOT NULL DEFAULT '0', |
|||
INDEX idx1(f2, f3), INDEX idx2(f3) |
|||
); |
|||
|
|||
INSERT INTO t1(f1) VALUES (1), (2), (3), (4), (5), (6), (7), (8); |
|||
INSERT INTO t1(f2, f3) VALUES (3,4), (3,4); |
|||
ANALYZE TABLE t1; |
|||
|
|||
set optimizer_switch='mrr=on,mrr_cost_based=off'; |
|||
|
|||
--disable_ps2_protocol |
|||
--echo # Check statistics without hint |
|||
FLUSH STATUS; |
|||
SELECT * FROM t1 WHERE f2 <= 3 AND 3 <= f3; |
|||
SHOW STATUS LIKE 'handler_read%'; |
|||
|
|||
--echo # Check statistics with hint |
|||
FLUSH STATUS; |
|||
SELECT /*+ NO_MRR(t1) */ * FROM t1 WHERE f2 <= 3 AND 3 <= f3; |
|||
SHOW STATUS LIKE 'handler_read%'; |
|||
|
|||
--echo # Make sure hints are preserved in a stored procedure body |
|||
CREATE PROCEDURE p() SELECT /*+ NO_MRR(t1) */ * FROM t1 WHERE f2 <= 3 AND 3 <= f3; |
|||
SHOW CREATE PROCEDURE p; |
|||
FLUSH STATUS; |
|||
CALL p(); |
|||
SHOW STATUS LIKE 'handler_read%'; |
|||
|
|||
DROP PROCEDURE p; |
|||
--enable_ps2_protocol |
|||
|
|||
EXPLAIN EXTENDED SELECT * FROM t1 WHERE f2 <= 3 AND 3 <= f3; |
|||
--echo # Turn off MRR. MRR should not be used. |
|||
EXPLAIN EXTENDED SELECT /*+ NO_MRR(t1) */ * FROM t1 WHERE f2 <= 3 AND 3 <= f3; |
|||
--echo # Turn off MRR. MRR should not be used. |
|||
EXPLAIN EXTENDED SELECT /*+ NO_MRR(t1 idx2) */ * FROM t1 WHERE f2 <= 3 AND 3 <= f3; |
|||
--echo # Turn off MRR for unused key. MRR should be used. |
|||
EXPLAIN EXTENDED SELECT /*+ NO_MRR(t1 idx1) */ * FROM t1 WHERE f2 <= 3 AND 3 <= f3; |
|||
|
|||
set optimizer_switch='mrr=off,mrr_cost_based=off'; |
|||
|
|||
EXPLAIN EXTENDED SELECT * FROM t1 WHERE f2 <= 3 AND 3 <= f3; |
|||
--echo # Turn on MRR. MRR should be used. |
|||
EXPLAIN EXTENDED SELECT /*+ MRR(t1) */ * FROM t1 WHERE f2 <= 3 AND 3 <= f3; |
|||
--echo # Turn on MRR. MRR should be used. |
|||
EXPLAIN EXTENDED SELECT /*+ MRR(t1 IDX2) */ * FROM t1 WHERE f2 <= 3 AND 3 <= f3; |
|||
--echo # Turn on MRR for unused key. MRR should not be used. |
|||
EXPLAIN EXTENDED SELECT /*+ MRR(t1 idx1) */ * FROM t1 WHERE f2 <= 3 AND 3 <= f3; |
|||
|
|||
set optimizer_switch='mrr=off,mrr_cost_based=on'; |
|||
|
|||
EXPLAIN EXTENDED SELECT * FROM t1 WHERE f2 <= 3 AND 3 <= f3; |
|||
--echo # Turn on MRR. MRR should be used. |
|||
EXPLAIN EXTENDED SELECT /*+ MRR(t1) */ * FROM t1 WHERE f2 <= 3 AND 3 <= f3; |
|||
--echo # Turn on MRR. MRR should be used. |
|||
EXPLAIN EXTENDED SELECT /*+ MRR(t1 idx2) */ * FROM t1 WHERE f2 <= 3 AND 3 <= f3; |
|||
--echo # Turn on MRR for unused key. MRR should not be used. |
|||
EXPLAIN EXTENDED SELECT /*+ MRR(t1 IDX1) */ * FROM t1 WHERE f2 <= 3 AND 3 <= f3; |
|||
|
|||
DROP TABLE t1; |
|||
|
|||
set optimizer_switch=default; |
|||
|
|||
--echo # |
|||
--echo # Duplicate hints |
|||
--echo # |
|||
|
|||
CREATE TABLE t1 (i INT PRIMARY KEY); |
|||
|
|||
SELECT /*+ BKA() BKA() */ 1; |
|||
SELECT /*+ BKA(t1) BKA(t1) */ * FROM t1; |
|||
SELECT /*+ QB_NAME(q1) BKA(t1@q1) BKA(t1@q1) */ * FROM t1; |
|||
SELECT /*+ QB_NAME(q1) NO_ICP(@q1 t1 PRIMARY) NO_ICP(@q1 t1 PRIMARY) */ * FROM t1; |
|||
|
|||
DROP TABLE t1; |
|||
|
|||
--echo # WL#8016 Parser for optimizer hints |
|||
|
|||
|
|||
CREATE TABLE t1 (i INT, j INT); |
|||
CREATE INDEX i1 ON t1(i); |
|||
CREATE INDEX i2 ON t1(j); |
|||
|
|||
--echo |
|||
--echo # invalid hint sequences, must issue warnings: |
|||
--echo |
|||
SELECT /*+*/ 1; |
|||
SELECT /*+ */ 1; |
|||
SELECT /*+ * ** / // /* */ 1; |
|||
SELECT /*+ @ */ 1; |
|||
SELECT /*+ @foo */ 1; |
|||
SELECT /*+ foo@bar */ 1; |
|||
SELECT /*+ foo @bar */ 1; |
|||
SELECT /*+ `@` */ 1; |
|||
SELECT /*+ `@foo` */ 1; |
|||
SELECT /*+ `foo@bar` */ 1; |
|||
SELECT /*+ `foo @bar` */ 1; |
|||
SELECT /*+ BKA( @) */ 1; |
|||
SELECT /*+ BKA( @) */ 1; |
|||
SELECT /*+ BKA(t1 @) */ 1; |
|||
|
|||
--echo |
|||
--echo # We don't support "*/" inside quoted identifiers (syntax error): |
|||
--echo |
|||
|
|||
--error ER_PARSE_ERROR |
|||
SELECT /*+ BKA(`test*/`) */ 1; |
|||
|
|||
--echo |
|||
--echo # invalid hint sequences, must issue warnings: |
|||
--echo |
|||
SELECT /*+ NO_ICP() */ 1; |
|||
SELECT /*+NO_ICP()*/ 1; |
|||
SELECT /*+ NO_ICP () */ 1; |
|||
SELECT /*+ NO_ICP ( ) */ 1; |
|||
|
|||
SELECT /*+ NO_ICP() */ 1 UNION SELECT 1; |
|||
(SELECT /*+ NO_ICP() */ 1) UNION (SELECT 1); |
|||
|
|||
--echo # OLEGS: this one does not issue a warning although should: |
|||
((SELECT /* + NO_ICP() */ 1)); |
|||
|
|||
UPDATE /*+ NO_ICP() */ t1 SET i = 10; |
|||
INSERT /*+ NO_ICP() */ INTO t1 VALUES (); |
|||
DELETE /*+ NO_ICP() */ FROM t1 WHERE 1; |
|||
|
|||
|
|||
SELECT /*+ BKA(a b) */ 1 FROM t1 a, t1 b; |
|||
|
|||
SELECT /*+ NO_ICP(i1) */ 1 FROM t1; |
|||
SELECT /*+ NO_ICP(i1 i2) */ 1 FROM t1; |
|||
SELECT /*+ NO_ICP(@qb ident) */ 1 FROM t1; |
|||
|
|||
--echo |
|||
--echo # valid hint sequences, no warnings expected: |
|||
--echo |
|||
SELECT /*+ BKA(t1) */ 1 FROM t1; |
|||
EXPLAIN EXTENDED SELECT /*+ QB_NAME(qb1) */ 1 UNION SELECT /*+ QB_NAME(qb2) */ 1; |
|||
EXPLAIN EXTENDED (SELECT /*+ QB_NAME(qb1) */ 1) UNION (SELECT /*+ QB_NAME(qb2) */ 1); |
|||
|
|||
--echo # |
|||
--echo # test explainable statements for hint support: |
|||
--echo # they should warn with a hint syntax error near "test */" |
|||
--echo # |
|||
|
|||
EXPLAIN EXTENDED SELECT /*+ test */ 1; |
|||
EXPLAIN EXTENDED INSERT /*+ test */ INTO t1 VALUES (10, 10); |
|||
EXPLAIN EXTENDED UPDATE /*+ test */ t1 SET i = 10 WHERE j = 10; |
|||
EXPLAIN EXTENDED DELETE /*+ test */ FROM t1 WHERE i = 10; |
|||
|
|||
--echo |
|||
--echo # non-alphabetic and non-ASCII identifiers, should warn: |
|||
--echo |
|||
|
|||
CREATE INDEX 3rd_index ON t1(i, j); |
|||
SELECT /*+ NO_ICP(3rd_index) */ 1 FROM t1; |
|||
|
|||
CREATE INDEX $index ON t1(j, i); |
|||
SELECT /*+ NO_ICP($index) */ 1 FROM t1; |
|||
|
|||
CREATE TABLE ` quoted name test` (i INT); |
|||
SELECT /*+ BKA(` quoted name test`) */ 1 FROM t1; |
|||
SELECT /*+ BKA(` quoted name test`@`select#1`) */ 1 FROM t1; |
|||
DROP TABLE ` quoted name test`; |
|||
|
|||
SET SQL_MODE = 'ANSI_QUOTES'; |
|||
|
|||
CREATE TABLE " quoted name test" (i INT); |
|||
SELECT /*+ BKA(" quoted name test") */ 1 FROM t1; |
|||
SELECT /*+ BKA(" quoted name test"@"select#1") */ 1 FROM t1; |
|||
|
|||
CREATE TABLE `test1``test2``` (i INT); |
|||
|
|||
SELECT /*+ BKA(`test1``test2```) */ 1; |
|||
SELECT /*+ BKA("test1""test2""") */ 1; |
|||
|
|||
SET SQL_MODE = ''; |
|||
--echo # should warn: |
|||
SELECT /*+ BKA(" quoted name test") */ 1 FROM t1; |
|||
|
|||
DROP TABLE ` quoted name test`; |
|||
DROP TABLE `test1``test2```; |
|||
|
|||
--echo # Valid hints, no warning: |
|||
EXPLAIN EXTENDED SELECT /*+ QB_NAME(`*`) */ 1; |
|||
EXPLAIN EXTENDED SELECT /*+ QB_NAME(`a*`) */ 1; |
|||
EXPLAIN EXTENDED SELECT /*+ QB_NAME(`*b`) */ 1; |
|||
EXPLAIN EXTENDED SELECT /*+ QB_NAME(`a |
|||
b`) */ 1; |
|||
|
|||
--echo # hint syntax error: empty quoted identifier |
|||
EXPLAIN EXTENDED SELECT /*+ QB_NAME(``) */ 1; |
|||
|
|||
SET NAMES utf8; |
|||
EXPLAIN EXTENDED SELECT /*+ QB_NAME(`\BF``\BF`) */ 1; |
|||
|
|||
CREATE TABLE tableТ (i INT); |
|||
|
|||
--echo # invalid hints, should warn: |
|||
SELECT /*+ BKA(tableТ) */ 1 FROM t1; |
|||
SELECT /*+ BKA(test@tableТ) */ 1 FROM t1; |
|||
DROP TABLE tableТ; |
|||
|
|||
CREATE TABLE таблица (i INT); |
|||
|
|||
SELECT /*+ BKA(`таблица`) */ 1 FROM t1; |
|||
SELECT /*+ BKA(таблица) */ 1 FROM t1; |
|||
SELECT /*+ BKA(test@таблица) */ 1 FROM t1; |
|||
|
|||
SELECT /*+ NO_ICP(`\D1`) */ 1 FROM t1; |
|||
|
|||
DROP TABLE таблица; |
|||
|
|||
--echo |
|||
--echo # derived tables and other subqueries: |
|||
--echo |
|||
|
|||
SELECT * FROM (SELECT /*+ DEBUG_HINT3 */ 1) a; |
|||
SELECT (SELECT /*+ DEBUG_HINT3 */ 1); |
|||
SELECT 1 FROM DUAL WHERE 1 IN (SELECT /*+ DEBUG_HINT3 */ 1); |
|||
|
|||
--echo |
|||
--echo # invalid hint sequences (should warn): |
|||
--echo |
|||
SELECT /*+ 10 */ 1; |
|||
SELECT /*+ NO_ICP() */ 1; |
|||
SELECT /*+ NO_ICP(10) */ 1; |
|||
SELECT /*+ NO_ICP( */ 1; |
|||
SELECT /*+ NO_ICP) */ 1; |
|||
SELECT /*+ NO_ICP(t1 */ 1; |
|||
SELECT /*+ NO_ICP(t1 ( */ 1; |
|||
(SELECT 1) UNION (SELECT /*+ NO_ICP() */ 1); |
|||
|
|||
INSERT INTO t1 VALUES (1, 1), (2, 2); |
|||
|
|||
--echo |
|||
--echo # wrong place for hint, so recognize that stuff as a regular commentary: |
|||
--echo |
|||
|
|||
SELECT 1 FROM /*+ regular commentary, not a hint! */ t1; |
|||
SELECT 1 FROM /*+ #1 */ t1 WHERE /*+ #2 */ 1 /*+ #3 */; |
|||
|
|||
--echo # Warnings expected: |
|||
SELECT /*+ NO_ICP() */ 1 |
|||
FROM /*+ regular commentary, not a hint! */ t1; |
|||
|
|||
SELECT /*+ NO_ICP(t1) bad_hint */ 1 FROM t1; |
|||
|
|||
SELECT /*+ |
|||
NO_ICP(@qb ident) |
|||
*/ 1 FROM t1; |
|||
|
|||
SELECT /*+ |
|||
? bad syntax |
|||
*/ 1; |
|||
|
|||
SELECT |
|||
/*+ ? bad syntax */ 1; |
|||
|
|||
DROP TABLE t1; |
|||
set optimizer_switch=default; |
@ -0,0 +1,105 @@ |
|||
/*
|
|||
Copyright (c) 2024, MariaDB |
|||
|
|||
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-1335 USA |
|||
*/ |
|||
|
|||
|
|||
#include "opt_hints_parser.h"
|
|||
#include "sql_error.h"
|
|||
#include "mysqld_error.h"
|
|||
#include "sql_class.h"
|
|||
|
|||
// This method is for debug purposes
|
|||
bool Optimizer_hint_parser::parse_token_list(THD *thd) |
|||
{ |
|||
for ( ; ; m_look_ahead_token= get_token(m_cs)) |
|||
{ |
|||
char tmp[200]; |
|||
my_snprintf(tmp, sizeof(tmp), "TOKEN: %d %.*s", |
|||
(int) m_look_ahead_token.id(), |
|||
(int) m_look_ahead_token.length, |
|||
m_look_ahead_token.str); |
|||
push_warning(thd, Sql_condition::WARN_LEVEL_WARN, |
|||
ER_UNKNOWN_ERROR, tmp); |
|||
if (m_look_ahead_token.id() == TokenID::tNULL || |
|||
m_look_ahead_token.id() == TokenID::tEOF) |
|||
break; |
|||
} |
|||
return true; // Success
|
|||
} |
|||
|
|||
|
|||
void Optimizer_hint_parser::push_warning_syntax_error(THD *thd) |
|||
{ |
|||
const char *msg= ER_THD(thd, ER_WARN_OPTIMIZER_HINT_SYNTAX_ERROR); |
|||
ErrConvString txt(m_look_ahead_token.str, strlen(m_look_ahead_token.str), |
|||
thd->variables.character_set_client); |
|||
push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, |
|||
ER_PARSE_ERROR, ER_THD(thd, ER_PARSE_ERROR), |
|||
msg, txt.ptr(), 1); |
|||
} |
|||
|
|||
|
|||
bool |
|||
Optimizer_hint_parser:: |
|||
Table_name_list_container::add(Optimizer_hint_parser *p, |
|||
Table_name &&elem) |
|||
{ |
|||
Table_name *pe= (Table_name*) p->m_thd->alloc(sizeof(*pe)); |
|||
if (!pe) |
|||
return true; |
|||
*pe= std::move(elem); |
|||
return push_back(pe, p->m_thd->mem_root); |
|||
} |
|||
|
|||
|
|||
bool |
|||
Optimizer_hint_parser:: |
|||
Hint_param_table_list_container::add(Optimizer_hint_parser *p, |
|||
Hint_param_table &&elem) |
|||
{ |
|||
Hint_param_table *pe= (Hint_param_table*) p->m_thd->alloc(sizeof(*pe)); |
|||
if (!pe) |
|||
return true; |
|||
*pe= std::move(elem); |
|||
return push_back(pe, p->m_thd->mem_root); |
|||
} |
|||
|
|||
|
|||
bool |
|||
Optimizer_hint_parser:: |
|||
Hint_param_index_list_container::add(Optimizer_hint_parser *p, |
|||
Hint_param_index &&elem) |
|||
{ |
|||
Hint_param_index *pe= (Hint_param_index*) p->m_thd->alloc(sizeof(*pe)); |
|||
if (!pe) |
|||
return true; |
|||
*pe= std::move(elem); |
|||
return push_back(pe, p->m_thd->mem_root); |
|||
} |
|||
|
|||
|
|||
bool |
|||
Optimizer_hint_parser:: |
|||
Hint_list_container::add(Optimizer_hint_parser *p, |
|||
Hint &&elem) |
|||
{ |
|||
Hint *pe= (Hint*) p->m_thd->alloc(sizeof(*pe)); |
|||
if (!pe) |
|||
return true; |
|||
*pe= std::move(elem); |
|||
return push_back(pe, p->m_thd->mem_root); |
|||
} |
@ -0,0 +1,609 @@ |
|||
#ifndef OPT_HINTS_PARSER_H |
|||
#define OPT_HINTS_PARSER_H |
|||
/* |
|||
Copyright (c) 2024, MariaDB |
|||
|
|||
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-1335 USA |
|||
*/ |
|||
|
|||
|
|||
#include "simple_tokenizer.h" |
|||
#include "sql_list.h" |
|||
#include "simple_parser.h" |
|||
|
|||
|
|||
class Optimizer_hint_tokenizer: public Extended_string_tokenizer |
|||
{ |
|||
public: |
|||
Optimizer_hint_tokenizer(CHARSET_INFO *cs, const LEX_CSTRING &hint) |
|||
:Extended_string_tokenizer(cs, hint) |
|||
{ } |
|||
|
|||
// Let's use "enum class" to easier distinguish token IDs vs rule names |
|||
enum class TokenID |
|||
{ |
|||
// Special purpose tokens: |
|||
tNULL= 0, // returned if the tokenizer failed to detect a token |
|||
// also used if the parser failed to parse a token |
|||
tEMPTY= 1, // returned on empty optional constructs in a grammar like: |
|||
// rule ::= [ rule1 ] |
|||
// when rule1 does not present in the input. |
|||
tEOF= 2, // returned when the end of input is reached |
|||
|
|||
// One character tokens |
|||
tCOMMA= ',', |
|||
tAT= '@', |
|||
tLPAREN= '(', |
|||
tRPAREN= ')', |
|||
|
|||
// Keywords |
|||
keyword_BKA, |
|||
keyword_BNL, |
|||
keyword_NO_BKA, |
|||
keyword_NO_BNL, |
|||
keyword_NO_ICP, |
|||
keyword_NO_MRR, |
|||
keyword_NO_RANGE_OPTIMIZATION, |
|||
keyword_MRR, |
|||
keyword_QB_NAME, |
|||
|
|||
// Other token types |
|||
tIDENT |
|||
}; |
|||
|
|||
protected: |
|||
|
|||
TokenID find_keyword(const LEX_CSTRING &str) |
|||
{ |
|||
switch (str.length) |
|||
{ |
|||
case 3: |
|||
if ("BKA"_Lex_ident_column.streq(str)) return TokenID::keyword_BKA; |
|||
if ("BNL"_Lex_ident_column.streq(str)) return TokenID::keyword_BNL; |
|||
if ("MRR"_Lex_ident_column.streq(str)) return TokenID::keyword_MRR; |
|||
break; |
|||
|
|||
case 6: |
|||
if ("NO_BKA"_Lex_ident_column.streq(str)) return TokenID::keyword_NO_BKA; |
|||
if ("NO_BNL"_Lex_ident_column.streq(str)) return TokenID::keyword_NO_BNL; |
|||
if ("NO_ICP"_Lex_ident_column.streq(str)) return TokenID::keyword_NO_ICP; |
|||
if ("NO_MRR"_Lex_ident_column.streq(str)) return TokenID::keyword_NO_MRR; |
|||
break; |
|||
|
|||
case 7: |
|||
if ("QB_NAME"_Lex_ident_column.streq(str)) |
|||
return TokenID::keyword_QB_NAME; |
|||
break; |
|||
|
|||
case 21: |
|||
if ("NO_RANGE_OPTIMIZATION"_Lex_ident_column.streq(str)) |
|||
return TokenID::keyword_NO_RANGE_OPTIMIZATION; |
|||
break; |
|||
} |
|||
return TokenID::tIDENT; |
|||
} |
|||
|
|||
public: |
|||
|
|||
class Token: public Lex_cstring |
|||
{ |
|||
protected: |
|||
TokenID m_id; |
|||
public: |
|||
Token() |
|||
:Lex_cstring(), m_id(TokenID::tNULL) |
|||
{ } |
|||
Token(const LEX_CSTRING &str, TokenID id) |
|||
:Lex_cstring(str), m_id(id) |
|||
{ } |
|||
TokenID id() const { return m_id; } |
|||
static Token empty(const char *pos) |
|||
{ |
|||
return Token(Lex_cstring(pos, pos), TokenID::tEMPTY); |
|||
} |
|||
operator bool() const |
|||
{ |
|||
return m_id != TokenID::tNULL; |
|||
} |
|||
}; |
|||
|
|||
Token get_token(CHARSET_INFO *cs) |
|||
{ |
|||
get_spaces(); |
|||
if (eof()) |
|||
return Token(Lex_cstring(m_ptr, m_ptr), TokenID::tEOF); |
|||
const char head= m_ptr[0]; |
|||
if (head == '`' || head=='"') |
|||
{ |
|||
const Token_with_metadata delimited_ident= get_quoted_string(); |
|||
if (delimited_ident.length) |
|||
return Token(delimited_ident, TokenID::tIDENT); |
|||
} |
|||
const Token_with_metadata ident= get_ident(); |
|||
if (ident.length) |
|||
return Token(ident, ident.m_extended_chars ? |
|||
TokenID::tIDENT : find_keyword(ident)); |
|||
if (!get_char(',')) |
|||
return Token(Lex_cstring(m_ptr - 1, 1), TokenID::tCOMMA); |
|||
if (!get_char('@')) |
|||
return Token(Lex_cstring(m_ptr - 1, 1), TokenID::tAT); |
|||
if (!get_char('(')) |
|||
return Token(Lex_cstring(m_ptr - 1, 1), TokenID::tLPAREN); |
|||
if (!get_char(')')) |
|||
return Token(Lex_cstring(m_ptr - 1, 1), TokenID::tRPAREN); |
|||
return Token(Lex_cstring(m_ptr, m_ptr), TokenID::tNULL); |
|||
} |
|||
}; |
|||
|
|||
|
|||
class Optimizer_hint_parser: public Optimizer_hint_tokenizer, |
|||
public Parser_templates |
|||
{ |
|||
private: |
|||
Token m_look_ahead_token; |
|||
THD *m_thd; |
|||
bool m_syntax_error; |
|||
bool m_fatal_error; |
|||
public: |
|||
Optimizer_hint_parser(THD *thd, CHARSET_INFO *cs, const LEX_CSTRING &hint) |
|||
:Optimizer_hint_tokenizer(cs, hint), |
|||
m_look_ahead_token(get_token(cs)), |
|||
m_thd(thd), |
|||
m_syntax_error(false), |
|||
m_fatal_error(false) |
|||
{ } |
|||
bool set_syntax_error() |
|||
{ |
|||
m_syntax_error= true; |
|||
return false; |
|||
} |
|||
bool set_fatal_error() |
|||
{ |
|||
m_fatal_error= true; |
|||
return false; |
|||
} |
|||
TokenID look_ahead_token_id() const |
|||
{ |
|||
return is_error() ? TokenID::tNULL : m_look_ahead_token.id(); |
|||
} |
|||
/* |
|||
Return an empty token at the position of the current |
|||
look ahead token with a zero length. Used for optional grammar constructs. |
|||
|
|||
For example, if the grammar is "rule ::= ruleA [ruleB] ruleC" |
|||
and the input is "A C", then: |
|||
- the optional rule "ruleB" will point to the input position "C" |
|||
with a zero length |
|||
- while the rule "ruleC" will point to the same input position "C" |
|||
with a non-zero length |
|||
*/ |
|||
Token empty_token() const |
|||
{ |
|||
return Token::empty(m_look_ahead_token.str); |
|||
} |
|||
static Token null_token() |
|||
{ |
|||
return Token(); |
|||
} |
|||
|
|||
/* |
|||
Return the current look ahead token and scan the next one |
|||
*/ |
|||
Token shift() |
|||
{ |
|||
DBUG_ASSERT(!is_error()); |
|||
const Token res= m_look_ahead_token; |
|||
m_look_ahead_token= get_token(m_cs); |
|||
return res; |
|||
} |
|||
|
|||
public: |
|||
/* |
|||
Return the current look ahead token if it matches the given ID |
|||
and scan the next one. |
|||
*/ |
|||
Token token(TokenID id) |
|||
{ |
|||
if (m_look_ahead_token.id() != id || is_error()) |
|||
return Token(); |
|||
return shift(); |
|||
} |
|||
|
|||
bool is_error() const |
|||
{ |
|||
return m_syntax_error || m_fatal_error; |
|||
} |
|||
bool is_syntax_error() const |
|||
{ |
|||
return m_syntax_error; |
|||
} |
|||
bool is_fatal_error() const |
|||
{ |
|||
return m_fatal_error; |
|||
} |
|||
|
|||
bool parse_token_list(THD *thd); // For debug purposes |
|||
|
|||
void push_warning_syntax_error(THD *thd); |
|||
|
|||
|
|||
private: |
|||
|
|||
using PARSER= Optimizer_hint_parser; // for a shorter notation |
|||
|
|||
// Rules consisting of a single token |
|||
|
|||
class TokenAT: public TOKEN<PARSER, TokenID::tAT> |
|||
{ |
|||
public: |
|||
using TOKEN::TOKEN; |
|||
}; |
|||
|
|||
class TokenEOF: public TOKEN<PARSER, TokenID::tEOF> |
|||
{ |
|||
public: |
|||
using TOKEN::TOKEN; |
|||
}; |
|||
|
|||
class Keyword_QB_NAME: public TOKEN<PARSER, TokenID::keyword_QB_NAME> |
|||
{ |
|||
public: |
|||
using TOKEN::TOKEN; |
|||
}; |
|||
|
|||
class Identifier: public TOKEN<PARSER, TokenID::tIDENT> |
|||
{ |
|||
public: |
|||
using TOKEN::TOKEN; |
|||
}; |
|||
|
|||
class LParen: public TOKEN<PARSER, TokenID::tLPAREN> |
|||
{ |
|||
public: |
|||
using TOKEN::TOKEN; |
|||
}; |
|||
|
|||
class RParen: public TOKEN<PARSER, TokenID::tRPAREN> |
|||
{ |
|||
public: |
|||
using TOKEN::TOKEN; |
|||
}; |
|||
|
|||
|
|||
// Rules consisting of multiple choices of tokens |
|||
|
|||
// table_level_hint_type ::= BKA | BNL | NO_BKA | NO_BNL |
|||
class Table_level_hint_type_cond |
|||
{ |
|||
public: |
|||
static bool allowed_token_id(TokenID id) |
|||
{ |
|||
return id == TokenID::keyword_BKA || |
|||
id == TokenID::keyword_BNL || |
|||
id == TokenID::keyword_NO_BKA || |
|||
id == TokenID::keyword_NO_BNL; |
|||
} |
|||
}; |
|||
class Table_level_hint_type: public TokenChoice<PARSER, |
|||
Table_level_hint_type_cond> |
|||
{ |
|||
public: |
|||
using TokenChoice::TokenChoice; |
|||
}; |
|||
|
|||
|
|||
// index_level_hint_type ::= MRR | NO_RANGE_OPTIMIZATION | NO_ICP | NO_MRR |
|||
class Index_level_hint_type_cond |
|||
{ |
|||
public: |
|||
static bool allowed_token_id(TokenID id) |
|||
{ |
|||
return id == TokenID::keyword_MRR || |
|||
id == TokenID::keyword_NO_RANGE_OPTIMIZATION || |
|||
id == TokenID::keyword_NO_ICP || |
|||
id == TokenID::keyword_NO_MRR; |
|||
} |
|||
}; |
|||
class Index_level_hint_type: public TokenChoice<PARSER, |
|||
Index_level_hint_type_cond> |
|||
{ |
|||
public: |
|||
using TokenChoice::TokenChoice; |
|||
}; |
|||
|
|||
|
|||
// Identifiers of various kinds |
|||
|
|||
|
|||
// query_block_name ::= identifier |
|||
class Query_block_name: public Identifier |
|||
{ |
|||
public: |
|||
using Identifier::Identifier; |
|||
}; |
|||
|
|||
// table_name ::= identifier |
|||
class Table_name: public Identifier |
|||
{ |
|||
public: |
|||
using Identifier::Identifier; |
|||
}; |
|||
|
|||
// hint_param_index ::= identifier |
|||
class Hint_param_index: public Identifier |
|||
{ |
|||
public: |
|||
using Identifier::Identifier; |
|||
}; |
|||
|
|||
|
|||
// More complex rules |
|||
|
|||
/* |
|||
at_query_block_name ::= @ query_block_name |
|||
*/ |
|||
class At_query_block_name: public AND2<PARSER, TokenAT, Query_block_name> |
|||
{ |
|||
public: |
|||
using AND2::AND2; |
|||
using AND2::operator=; |
|||
}; |
|||
|
|||
/* |
|||
opt_qb_name ::= [ @ query_block_name ] |
|||
*/ |
|||
class Opt_qb_name: public OPT<PARSER, At_query_block_name> |
|||
{ |
|||
public: |
|||
using OPT::OPT; |
|||
}; |
|||
|
|||
/* |
|||
hint_param_table ::= table_name opt_qb_name |
|||
*/ |
|||
class Hint_param_table: public AND2<PARSER, Table_name, Opt_qb_name> |
|||
{ |
|||
public: |
|||
using AND2::AND2; |
|||
}; |
|||
|
|||
|
|||
/* |
|||
hint_param_table_list ::= hint_param_table [ {, hint_param_table}... ] |
|||
opt_hint_param_table_list ::= [ hint_param_table_list ] |
|||
*/ |
|||
class Hint_param_table_list_container: public List<Hint_param_table> |
|||
{ |
|||
public: |
|||
Hint_param_table_list_container() |
|||
{ } |
|||
bool add(Optimizer_hint_parser *p, Hint_param_table &&table); |
|||
size_t count() const { return elements; } |
|||
}; |
|||
|
|||
class Opt_hint_param_table_list: public LIST<PARSER, |
|||
Hint_param_table_list_container, |
|||
Hint_param_table, |
|||
TokenID::tCOMMA, 0> |
|||
{ |
|||
using LIST::LIST; |
|||
}; |
|||
|
|||
/* |
|||
table_name_list ::= table_name [ {, table_name }... ] |
|||
opt_table_name_list ::= [ table_name_list ] |
|||
*/ |
|||
class Table_name_list_container: public List<Table_name> |
|||
{ |
|||
public: |
|||
Table_name_list_container() |
|||
{ } |
|||
bool add(Optimizer_hint_parser *p, Table_name &&table); |
|||
size_t count() const { return elements; } |
|||
}; |
|||
|
|||
class Opt_table_name_list: public LIST<PARSER, |
|||
Table_name_list_container, |
|||
Table_name, TokenID::tCOMMA, 0> |
|||
{ |
|||
public: |
|||
using LIST::LIST; |
|||
}; |
|||
|
|||
|
|||
/* |
|||
hint_param_index_list ::= hint_param_index [ {, hint_param_index }...] |
|||
opt_hint_param_index_list ::= [ hint_param_index_list ] |
|||
*/ |
|||
class Hint_param_index_list_container: public List<Hint_param_index> |
|||
{ |
|||
public: |
|||
Hint_param_index_list_container() |
|||
{ } |
|||
bool add(Optimizer_hint_parser *p, Hint_param_index &&table); |
|||
size_t count() const { return elements; } |
|||
}; |
|||
|
|||
class Opt_hint_param_index_list: public LIST<PARSER, |
|||
Hint_param_index_list_container, |
|||
Hint_param_index, |
|||
TokenID::tCOMMA, 0> |
|||
{ |
|||
public: |
|||
using LIST::LIST; |
|||
}; |
|||
|
|||
|
|||
/* |
|||
hint_param_table_ext ::= hint_param_table |
|||
| @ query_block_name table_name |
|||
*/ |
|||
class At_query_block_name_table_name: public AND2<PARSER, |
|||
At_query_block_name, |
|||
Table_name> |
|||
{ |
|||
public: |
|||
using AND2::AND2; |
|||
}; |
|||
|
|||
class Hint_param_table_ext_container: public Query_block_name, |
|||
public Table_name |
|||
{ |
|||
public: |
|||
Hint_param_table_ext_container() |
|||
{ } |
|||
Hint_param_table_ext_container(const Hint_param_table &hint_param_table) |
|||
:Query_block_name(hint_param_table), Table_name(hint_param_table) |
|||
{ } |
|||
Hint_param_table_ext_container(const At_query_block_name_table_name &qbt) |
|||
:Query_block_name(qbt), Table_name(qbt) |
|||
{ } |
|||
operator bool() const |
|||
{ |
|||
return Query_block_name::operator bool() && Table_name::operator bool(); |
|||
} |
|||
}; |
|||
|
|||
class Hint_param_table_ext: public OR2C<PARSER, |
|||
Hint_param_table_ext_container, |
|||
Hint_param_table, |
|||
At_query_block_name_table_name> |
|||
{ |
|||
public: |
|||
using OR2C::OR2C; |
|||
}; |
|||
|
|||
|
|||
/* |
|||
at_query_block_name_opt_table_name_list ::= |
|||
@ query_block_name opt_table_name_list |
|||
*/ |
|||
class At_query_block_name_opt_table_name_list: public AND2< |
|||
PARSER, |
|||
At_query_block_name, |
|||
Opt_table_name_list> |
|||
{ |
|||
public: |
|||
using AND2::AND2; |
|||
}; |
|||
|
|||
|
|||
/* |
|||
table_level_hint_body: @ query_block_name opt_table_name_list |
|||
| opt_hint_param_table_list |
|||
*/ |
|||
class Table_level_hint_body: public OR2< |
|||
PARSER, |
|||
At_query_block_name_opt_table_name_list, |
|||
Opt_hint_param_table_list> |
|||
{ |
|||
public: |
|||
using OR2::OR2; |
|||
}; |
|||
|
|||
|
|||
// table_level_hint ::= table_level_hint_type ( table_level_hint_body ) |
|||
class Table_level_hint: public AND4<PARSER, |
|||
Table_level_hint_type, |
|||
LParen, |
|||
Table_level_hint_body, |
|||
RParen> |
|||
{ |
|||
public: |
|||
using AND4::AND4; |
|||
}; |
|||
|
|||
|
|||
// index_level_hint_body ::= hint_param_table_ext opt_hint_param_index_list |
|||
class Index_level_hint_body: public AND2<PARSER, |
|||
Hint_param_table_ext, |
|||
Opt_hint_param_index_list> |
|||
{ |
|||
public: |
|||
using AND2::AND2; |
|||
}; |
|||
|
|||
|
|||
// index_level_hint ::= index_level_hint_type ( index_level_hint_body ) |
|||
class Index_level_hint: public AND4<PARSER, |
|||
Index_level_hint_type, |
|||
LParen, |
|||
Index_level_hint_body, |
|||
RParen> |
|||
{ |
|||
public: |
|||
using AND4::AND4; |
|||
}; |
|||
|
|||
|
|||
// qb_name_hint ::= QB_NAME ( query_block_name ) |
|||
class Qb_name_hint: public AND4<PARSER, |
|||
Keyword_QB_NAME, |
|||
LParen, |
|||
Query_block_name, |
|||
RParen> |
|||
{ |
|||
public: |
|||
using AND4::AND4; |
|||
}; |
|||
|
|||
|
|||
/* |
|||
hint ::= index_level_hint |
|||
| table_level_hint |
|||
| qb_name_hint |
|||
*/ |
|||
class Hint: public OR3<PARSER, |
|||
Index_level_hint, |
|||
Table_level_hint, |
|||
Qb_name_hint> |
|||
{ |
|||
public: |
|||
using OR3::OR3; |
|||
}; |
|||
|
|||
|
|||
// hint_list ::= hint [ hint... ] |
|||
class Hint_list_container: public List<Hint> |
|||
{ |
|||
public: |
|||
Hint_list_container() |
|||
{ } |
|||
bool add(Optimizer_hint_parser *p, Hint &&hint); |
|||
size_t count() const { return elements; } |
|||
}; |
|||
|
|||
public: |
|||
|
|||
class Hint_list: public LIST<PARSER, Hint_list_container, |
|||
Hint, TokenID::tNULL/*not separated list*/, 1> |
|||
{ |
|||
public: |
|||
using LIST::LIST; |
|||
}; |
|||
|
|||
/* |
|||
The main rule: |
|||
hints ::= hint_list EOF |
|||
*/ |
|||
class Hints: public AND2<PARSER, Hint_list, TokenEOF> |
|||
{ |
|||
public: |
|||
using AND2::AND2; |
|||
}; |
|||
|
|||
}; |
|||
|
|||
#endif // OPT_HINTS_PARSER |
@ -0,0 +1,53 @@ |
|||
/* Copyright (c) 2024, MariaDB Corporation. |
|||
|
|||
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-1335 USA */ |
|||
|
|||
#ifndef SCAN_CHAR_H |
|||
#define SCAN_CHAR_H |
|||
|
|||
|
|||
/** |
|||
A helper class to store the head character of a string, |
|||
with help of a charlen() call. |
|||
*/ |
|||
class Scan_char |
|||
{ |
|||
const char *m_ptr; // The start of the character |
|||
int m_length; // The result: |
|||
// >0 - the character octet length |
|||
// <=0 - an error (e.g. end of input, wrong byte sequence) |
|||
public: |
|||
Scan_char(CHARSET_INFO *const cs, const char *str, const char *end) |
|||
:m_ptr(str), m_length(cs->charlen(str, end)) |
|||
{ } |
|||
// Compare if two non-erroneous characters are equal |
|||
bool eq(const Scan_char &rhs) const |
|||
{ |
|||
DBUG_ASSERT(m_length > 0); |
|||
DBUG_ASSERT(rhs.m_length > 0); |
|||
return m_length == rhs.m_length && |
|||
!memcmp(m_ptr, rhs.m_ptr, (size_t) m_length); |
|||
} |
|||
// Compare if two possibly erroneous characters are equal |
|||
bool eq_safe(const Scan_char &rhs) const |
|||
{ |
|||
return m_length == rhs.m_length && m_length > 0 && |
|||
!memcmp(m_ptr, rhs.m_ptr, (size_t) m_length); |
|||
} |
|||
const char *ptr() const { return m_ptr; } |
|||
int length() const { return m_length; } |
|||
}; |
|||
|
|||
|
|||
#endif // SCAN_CHAR_H |
@ -0,0 +1,534 @@ |
|||
#ifndef SIMPLE_PARSER_H |
|||
#define SIMPLE_PARSER_H |
|||
/* |
|||
Copyright (c) 2024, MariaDB |
|||
|
|||
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-1335 USA |
|||
*/ |
|||
|
|||
|
|||
#include "simple_tokenizer.h" |
|||
|
|||
class Parser_templates |
|||
{ |
|||
protected: |
|||
|
|||
// Templates to parse common rule sequences |
|||
|
|||
/* |
|||
A rule consisting of a single token, e.g.: |
|||
rule ::= @ |
|||
rule ::= IDENT |
|||
*/ |
|||
template<class PARSER, typename PARSER::TokenID tid> |
|||
class TOKEN: public PARSER::Token |
|||
{ |
|||
public: |
|||
TOKEN() |
|||
{ } |
|||
TOKEN(const class PARSER::Token &tok) |
|||
:PARSER::Token(tok) |
|||
{ } |
|||
TOKEN(class PARSER::Token &&tok) |
|||
:PARSER::Token(std::move(tok)) |
|||
{ } |
|||
TOKEN(PARSER *p) |
|||
:PARSER::Token(p->token(tid)) |
|||
{ } |
|||
static TOKEN empty(const PARSER &p) |
|||
{ |
|||
return TOKEN(p.empty_token()); |
|||
} |
|||
}; |
|||
|
|||
|
|||
/* |
|||
A rule consisting of a choice of multiple tokens |
|||
rule ::= TOK1 | TOK2 | TOK3 |
|||
*/ |
|||
template<class PARSER, class COND> |
|||
class TokenChoice: public PARSER::Token |
|||
{ |
|||
public: |
|||
TokenChoice() |
|||
{ } |
|||
TokenChoice(PARSER *p) |
|||
:PARSER::Token(COND::allowed_token_id(p->look_ahead_token_id()) ? |
|||
p->shift() : |
|||
p->null_token()) |
|||
{ |
|||
DBUG_ASSERT(!p->is_error() || !PARSER::Token::operator bool()); |
|||
} |
|||
}; |
|||
|
|||
|
|||
/* |
|||
An optional rule: |
|||
opt_rule ::= [ rule ] |
|||
*/ |
|||
template<class PARSER, class RULE> |
|||
class OPT: public RULE |
|||
{ |
|||
public: |
|||
OPT() |
|||
{ } |
|||
OPT(PARSER *p) |
|||
:RULE(p) |
|||
{ |
|||
if (!RULE::operator bool() && !p->is_error()) |
|||
{ |
|||
RULE::operator=(RULE::empty(*p)); |
|||
DBUG_ASSERT(RULE::operator bool()); |
|||
} |
|||
} |
|||
}; |
|||
|
|||
|
|||
/* |
|||
A rule consisting of two other rules in a row: |
|||
rule ::= rule1 rule2 |
|||
*/ |
|||
template<class PARSER, class A, class B> |
|||
class AND2: public A, public B |
|||
{ |
|||
public: |
|||
AND2() |
|||
:A(), B() |
|||
{ } |
|||
AND2(AND2 && rhs) |
|||
:A(std::move(static_cast<A&&>(rhs))), |
|||
B(std::move(static_cast<B&&>(rhs))) |
|||
{ } |
|||
AND2(A &&a, B &&b) |
|||
:A(std::move(a)), B(std::move(b)) |
|||
{ } |
|||
AND2 & operator=(AND2 &&rhs) |
|||
{ |
|||
A::operator=(std::move(static_cast<A&&>(rhs))); |
|||
B::operator=(std::move(static_cast<B&&>(rhs))); |
|||
return *this; |
|||
} |
|||
AND2(PARSER *p) |
|||
:A(p), |
|||
B(A::operator bool() ? B(p) : B()) |
|||
{ |
|||
if (A::operator bool() && !B::operator bool()) |
|||
{ |
|||
p->set_syntax_error(); |
|||
// Reset A to have A, B reported as "false" by their operator bool() |
|||
A::operator=(std::move(A())); |
|||
} |
|||
DBUG_ASSERT(!operator bool() || !p->is_error()); |
|||
} |
|||
operator bool() const |
|||
{ |
|||
return A::operator bool() && B::operator bool(); |
|||
} |
|||
static AND2 empty(const PARSER &p) |
|||
{ |
|||
return AND2(A::empty(p), B::empty(p)); |
|||
} |
|||
}; |
|||
|
|||
|
|||
/* |
|||
A rule consisting of three other rules in a row: |
|||
rule ::= rule1 rule2 rule3 |
|||
*/ |
|||
template<class PARSER, class A, class B, class C> |
|||
class AND3: public A, public B, public C |
|||
{ |
|||
public: |
|||
AND3() |
|||
:A(), B(), C() |
|||
{ } |
|||
AND3(AND3 && rhs) |
|||
:A(std::move(static_cast<A&&>(rhs))), |
|||
B(std::move(static_cast<B&&>(rhs))), |
|||
C(std::move(static_cast<C&&>(rhs))) |
|||
{ } |
|||
AND3(A &&a, B &&b, C &&c) |
|||
:A(std::move(a)), B(std::move(b)), C(std::move(c)) |
|||
{ } |
|||
AND3 & operator=(AND3 &&rhs) |
|||
{ |
|||
A::operator=(std::move(static_cast<A&&>(rhs))); |
|||
B::operator=(std::move(static_cast<B&&>(rhs))); |
|||
C::operator=(std::move(static_cast<C&&>(rhs))); |
|||
return *this; |
|||
} |
|||
AND3(PARSER *p) |
|||
:A(p), |
|||
B(A::operator bool() ? B(p) : B()), |
|||
C(A::operator bool() && B::operator bool() ? C(p) : C()) |
|||
{ |
|||
if (A::operator bool() && (!B::operator bool() || !C::operator bool())) |
|||
{ |
|||
p->set_syntax_error(); |
|||
// Reset A to have A, B, C reported as "false" by their operator bool() |
|||
A::operator=(A()); |
|||
B::operator=(B()); |
|||
C::operator=(C()); |
|||
} |
|||
DBUG_ASSERT(!operator bool() || !p->is_error()); |
|||
} |
|||
operator bool() const |
|||
{ |
|||
return A::operator bool() && B::operator bool() && C::operator bool(); |
|||
} |
|||
static AND3 empty(const PARSER &p) |
|||
{ |
|||
return AND3(A::empty(p), B::empty(p), C::empty()); |
|||
} |
|||
}; |
|||
|
|||
|
|||
/* |
|||
A rule consisting of three other rules in a row: |
|||
rule ::= rule1 rule2 rule3 rule4 |
|||
*/ |
|||
template<class PARSER, class A, class B, class C, class D> |
|||
class AND4: public A, public B, public C, public D |
|||
{ |
|||
public: |
|||
AND4() |
|||
:A(), B(), C(), D() |
|||
{ } |
|||
AND4(AND4 && rhs) |
|||
:A(std::move(static_cast<A&&>(rhs))), |
|||
B(std::move(static_cast<B&&>(rhs))), |
|||
C(std::move(static_cast<C&&>(rhs))), |
|||
D(std::move(static_cast<D&&>(rhs))) |
|||
{ } |
|||
AND4(A &&a, B &&b, C &&c, D &&d) |
|||
:A(std::move(a)), B(std::move(b)), C(std::move(c)), D(std::move(d)) |
|||
{ } |
|||
AND4 & operator=(AND4 &&rhs) |
|||
{ |
|||
A::operator=(std::move(static_cast<A&&>(rhs))); |
|||
B::operator=(std::move(static_cast<B&&>(rhs))); |
|||
C::operator=(std::move(static_cast<C&&>(rhs))); |
|||
D::operator=(std::move(static_cast<D&&>(rhs))); |
|||
return *this; |
|||
} |
|||
AND4(PARSER *p) |
|||
:A(p), |
|||
B(A::operator bool() ? B(p) : B()), |
|||
C(A::operator bool() && B::operator bool() ? C(p) : C()), |
|||
D(A::operator bool() && B::operator bool() && C::operator bool() ? |
|||
D(p) : D()) |
|||
{ |
|||
if (A::operator bool() && |
|||
(!B::operator bool() || !C::operator bool() || !D::operator bool())) |
|||
{ |
|||
p->set_syntax_error(); |
|||
// Reset A to have A, B, C reported as "false" by their operator bool() |
|||
A::operator=(A()); |
|||
B::operator=(B()); |
|||
C::operator=(C()); |
|||
D::operator=(D()); |
|||
} |
|||
DBUG_ASSERT(!operator bool() || !p->is_error()); |
|||
} |
|||
operator bool() const |
|||
{ |
|||
return A::operator bool() && B::operator bool() && |
|||
C::operator bool() && D::operator bool(); |
|||
} |
|||
static AND4 empty(const PARSER &p) |
|||
{ |
|||
return AND4(A::empty(p), B::empty(p), C::empty(), D::empty()); |
|||
} |
|||
}; |
|||
|
|||
|
|||
/* |
|||
A rule consisting of a choice of rwo rules: |
|||
rule ::= rule1 | rule2 |
|||
|
|||
For the cases when the two branches have incompatible storage. |
|||
*/ |
|||
template<class PARSER, class A, class B> |
|||
class OR2: public A, public B |
|||
{ |
|||
public: |
|||
OR2() |
|||
{ } |
|||
OR2(OR2 &&rhs) |
|||
:A(std::move(static_cast<A&&>(rhs))), |
|||
B(std::move(static_cast<B&&>(rhs))) |
|||
{ } |
|||
OR2(A && rhs) |
|||
:A(std::move(rhs)), B() |
|||
{ } |
|||
OR2(B && rhs) |
|||
:A(), B(std::move(rhs)) |
|||
{ } |
|||
OR2 & operator=(OR2 &&rhs) |
|||
{ |
|||
A::operator=(std::move(static_cast<A&&>(rhs))); |
|||
B::operator=(std::move(static_cast<B&&>(rhs))); |
|||
return *this; |
|||
} |
|||
OR2(PARSER *p) |
|||
:A(p), B(A::operator bool() ? B() :B(p)) |
|||
{ |
|||
DBUG_ASSERT(!operator bool() || !p->is_error()); |
|||
} |
|||
operator bool() const |
|||
{ |
|||
return A::operator bool() || B::operator bool(); |
|||
} |
|||
}; |
|||
|
|||
|
|||
/* |
|||
A rule consisting of a choice of rwo rules, e.g. |
|||
rule ::= rule1 | rule2 |
|||
|
|||
For the cases when the two branches have a compatible storage, |
|||
passed as a CONTAINER, which must have constructors: |
|||
CONTAINER(const A &a) |
|||
CONTAINER(const B &b) |
|||
*/ |
|||
template<class PARSER, class CONTAINER, class A, class B> |
|||
class OR2C: public CONTAINER |
|||
{ |
|||
public: |
|||
OR2C() |
|||
{ } |
|||
OR2C(A &&a) |
|||
:CONTAINER(std::move(a)) |
|||
{ } |
|||
OR2C(B &&b) |
|||
:CONTAINER(std::move(b)) |
|||
{ } |
|||
OR2C(OR2C &&rhs) |
|||
:CONTAINER(std::move(rhs)) |
|||
{ } |
|||
OR2C & operator=(OR2C &&rhs) |
|||
{ |
|||
CONTAINER::operator=(std::move(rhs)); |
|||
return *this; |
|||
} |
|||
OR2C & operator=(A &&rhs) |
|||
{ |
|||
CONTAINER::operator=(std::move(rhs)); |
|||
return *this; |
|||
} |
|||
OR2C & operator=(B &&rhs) |
|||
{ |
|||
CONTAINER::operator=(std::move(rhs)); |
|||
return *this; |
|||
} |
|||
OR2C(PARSER *p) |
|||
:CONTAINER(A(p)) |
|||
{ |
|||
if (CONTAINER::operator bool() || |
|||
CONTAINER::operator=(B(p))) |
|||
return; |
|||
DBUG_ASSERT(!CONTAINER::operator bool()); |
|||
} |
|||
}; |
|||
|
|||
|
|||
/* |
|||
A rule consisting of a choice of thee rules: |
|||
rule ::= rule1 | rule2 | rule3 |
|||
|
|||
For the case when the three branches have incompatible storage |
|||
*/ |
|||
template<class PARSER, class A, class B, class C> |
|||
class OR3: public A, public B, public C |
|||
{ |
|||
public: |
|||
OR3() |
|||
{ } |
|||
OR3(OR3 &&rhs) |
|||
:A(std::move(static_cast<A&&>(rhs))), |
|||
B(std::move(static_cast<B&&>(rhs))), |
|||
C(std::move(static_cast<C&&>(rhs))) |
|||
{ } |
|||
OR3 & operator=(OR3 &&rhs) |
|||
{ |
|||
A::operator=(std::move(static_cast<A&&>(rhs))); |
|||
B::operator=(std::move(static_cast<B&&>(rhs))); |
|||
C::operator=(std::move(static_cast<C&&>(rhs))); |
|||
return *this; |
|||
} |
|||
OR3(PARSER *p) |
|||
:A(p), |
|||
B(A::operator bool() ? B() : B(p)), |
|||
C(A::operator bool() || B::operator bool() ? C() : C(p)) |
|||
{ |
|||
DBUG_ASSERT(!operator bool() || !p->is_error()); |
|||
} |
|||
operator bool() const |
|||
{ |
|||
return A::operator bool() || B::operator bool() || C::operator bool(); |
|||
} |
|||
}; |
|||
|
|||
/* |
|||
A rule consisting of a choice of three rules, e.g. |
|||
rule ::= rule1 | rule2 | rule3 |
|||
|
|||
For the cases when the three branches have a compatible storage, |
|||
passed as a CONTAINER, which must have constructors: |
|||
CONTAINER(const A &a) |
|||
CONTAINER(const B &b) |
|||
CONTAINER(const C &c) |
|||
*/ |
|||
template<class PARSER, class CONTAINER, class A, class B, class C> |
|||
class OR3C: public CONTAINER |
|||
{ |
|||
public: |
|||
OR3C() |
|||
{ } |
|||
OR3C(OR3C &&rhs) |
|||
:CONTAINER(std::move(rhs)) |
|||
{ } |
|||
OR3C(A &&a) |
|||
:CONTAINER(std::move(a)) |
|||
{ } |
|||
OR3C(B &&b) |
|||
:CONTAINER(std::move(b)) |
|||
{ } |
|||
OR3C(C &&c) |
|||
:CONTAINER(std::move(c)) |
|||
{ } |
|||
OR3C & operator=(OR3C &&rhs) |
|||
{ |
|||
CONTAINER::operator=(std::move(rhs)); |
|||
return *this; |
|||
} |
|||
OR3C & operator=(A &&rhs) |
|||
{ |
|||
CONTAINER::operator=(std::move(rhs)); |
|||
return *this; |
|||
} |
|||
OR3C & operator=(B &&rhs) |
|||
{ |
|||
CONTAINER::operator=(std::move(rhs)); |
|||
return *this; |
|||
} |
|||
OR3C & operator=(C &&rhs) |
|||
{ |
|||
CONTAINER::operator=(std::move(rhs)); |
|||
return *this; |
|||
} |
|||
|
|||
OR3C(PARSER *p) |
|||
:CONTAINER(A(p)) |
|||
{ |
|||
if (CONTAINER::operator bool() || |
|||
CONTAINER::operator=(B(p)) || |
|||
CONTAINER::operator=(C(p))) |
|||
return; |
|||
DBUG_ASSERT(!CONTAINER::operator bool()); |
|||
} |
|||
}; |
|||
|
|||
|
|||
/* |
|||
A list with at least MIN_COUNT elements (typlically 0 or 1), |
|||
with or without a token separator between elements: |
|||
|
|||
list ::= element [ {, element }... ] // with a separator |
|||
list ::= element [ element ... ] // without a separator |
|||
|
|||
Pass the null-token special purpose ID in SEP for a non-separated list, |
|||
or a real token ID for a separated list. |
|||
|
|||
If MIN_COUNT is 0, then the list becomes optional, |
|||
which corresponds to the following grammar: |
|||
|
|||
list ::= [ element [ {, element }... ] ] // with a separator |
|||
list ::= [ element [ element ... ] ] // without a separator |
|||
*/ |
|||
template<class PARSER, |
|||
class LIST_CONTAINER, class ELEMENT, |
|||
typename PARSER::TokenID SEP, size_t MIN_COUNT> |
|||
class LIST: public LIST_CONTAINER |
|||
{ |
|||
protected: |
|||
bool m_error; |
|||
public: |
|||
LIST() |
|||
:m_error(true) |
|||
{ } |
|||
LIST(LIST &&rhs) |
|||
:LIST_CONTAINER(std::move(rhs)), |
|||
m_error(rhs.m_error) |
|||
{ } |
|||
LIST & operator=(LIST &&rhs) |
|||
{ |
|||
LIST_CONTAINER::operator=(std::move(rhs)); |
|||
m_error= rhs.m_error; |
|||
return *this; |
|||
} |
|||
LIST(PARSER *p) |
|||
:m_error(true) |
|||
{ |
|||
// Determine if the caller wants a separated or a non-separated list |
|||
const bool separated= SEP != PARSER::null_token().id(); |
|||
for ( ; ; ) |
|||
{ |
|||
ELEMENT elem(p); |
|||
if (!elem) |
|||
{ |
|||
if (LIST_CONTAINER::count() == 0 || !separated) |
|||
{ |
|||
/* |
|||
Could not get the very first element, |
|||
or not-first element in a non-separated list. |
|||
*/ |
|||
m_error= p->is_error(); |
|||
DBUG_ASSERT(!m_error || !operator bool()); |
|||
return; |
|||
} |
|||
// Could not get the next element after the separator |
|||
p->set_syntax_error(); |
|||
m_error= true; |
|||
DBUG_ASSERT(!operator bool()); |
|||
return; |
|||
} |
|||
if (LIST_CONTAINER::add(p, std::move(elem))) |
|||
{ |
|||
p->set_fatal_error(); |
|||
m_error= true; |
|||
DBUG_ASSERT(!operator bool()); |
|||
return; |
|||
} |
|||
if (separated) |
|||
{ |
|||
if (!p->token(SEP)) |
|||
{ |
|||
m_error= false; |
|||
DBUG_ASSERT(operator bool()); |
|||
return; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
operator bool() const |
|||
{ |
|||
return !m_error && LIST_CONTAINER::count() >= MIN_COUNT; |
|||
} |
|||
}; |
|||
|
|||
}; |
|||
|
|||
#endif // SIMPLE_PARSER_H |
Write
Preview
Loading…
Cancel
Save
Reference in new issue