MDEV-33281 Implement optimizer hints
Implementing a recursive descent parser for optimizer hints.
This commit is contained in:
parent
495d96709f
commit
6340c23933
@ -2730,8 +2730,10 @@ static bool add_line(String &buffer, char *line, size_t line_length,
|
||||
|
||||
break;
|
||||
}
|
||||
else if (!*in_string && inchar == '/' && *(pos+1) == '*' &&
|
||||
!(*(pos+2) == '!' || (*(pos+2) == 'M' && *(pos+3) == '!')))
|
||||
else if (!*in_string && inchar == '/' && pos[1] == '*' &&
|
||||
!(pos[2] == '!' ||
|
||||
(pos[2] == 'M' && pos[3] == '!') ||
|
||||
pos[2] == '+'))
|
||||
{
|
||||
if (preserve_comments)
|
||||
{
|
||||
@ -2768,8 +2770,8 @@ static bool add_line(String &buffer, char *line, size_t line_length,
|
||||
}
|
||||
else
|
||||
{ // Add found char to buffer
|
||||
if (!*in_string && inchar == '/' && *(pos + 1) == '*' &&
|
||||
*(pos + 2) == '!')
|
||||
if (!*in_string && inchar == '/' && pos[1] == '*' &&
|
||||
(pos[2] == '!' || pos[2] == '+'))
|
||||
ss_comment= 1;
|
||||
else if (!*in_string && ss_comment && inchar == '*' && *(pos + 1) == '/')
|
||||
ss_comment= 0;
|
||||
|
@ -158,6 +158,8 @@ SET(SQL_EMBEDDED_SOURCES emb_qcache.cc libmysqld.c lib_sql.cc
|
||||
../sql/opt_histogram_json.cc
|
||||
../sql/sp_instr.cc
|
||||
../sql/sp_cursor.cc
|
||||
../sql/opt_hints_parser.cc ../sql/opt_hints_parser.h
|
||||
../sql/scan_char.h
|
||||
${GEN_SOURCES}
|
||||
${MYSYS_LIBWRAP_SOURCE}
|
||||
)
|
||||
|
1366
mysql-test/main/opt_hints.result
Normal file
1366
mysql-test/main/opt_hints.result
Normal file
File diff suppressed because it is too large
Load Diff
619
mysql-test/main/opt_hints.test
Normal file
619
mysql-test/main/opt_hints.test
Normal 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;
|
@ -193,6 +193,7 @@ SET (SQL_SOURCE
|
||||
socketpair.c socketpair.h
|
||||
opt_vcol_substitution.h
|
||||
opt_vcol_substitution.cc
|
||||
opt_hints_parser.cc opt_hints_parser.h scan_char.h
|
||||
${CMAKE_CURRENT_BINARY_DIR}/lex_hash.h
|
||||
${CMAKE_CURRENT_BINARY_DIR}/lex_token.h
|
||||
${GEN_SOURCES}
|
||||
|
@ -18,8 +18,11 @@
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1335 USA
|
||||
*/
|
||||
|
||||
#include "my_global.h"
|
||||
#include "m_ctype.h"
|
||||
#include "char_buffer.h"
|
||||
#include "lex_string.h"
|
||||
#include "my_sys.h"
|
||||
|
||||
extern MYSQL_PLUGIN_IMPORT CHARSET_INFO *table_alias_charset;
|
||||
|
||||
|
105
sql/opt_hints_parser.cc
Normal file
105
sql/opt_hints_parser.cc
Normal file
@ -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);
|
||||
}
|
609
sql/opt_hints_parser.h
Normal file
609
sql/opt_hints_parser.h
Normal file
@ -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
|
53
sql/scan_char.h
Normal file
53
sql/scan_char.h
Normal file
@ -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
|
@ -12312,3 +12312,11 @@ ER_SEQUENCE_TABLE_HAS_TOO_FEW_ROWS
|
||||
eng "Fewer than one row in the table"
|
||||
ER_SEQUENCE_TABLE_HAS_TOO_MANY_ROWS
|
||||
eng "More than one row in the table"
|
||||
ER_WARN_OPTIMIZER_HINT_SYNTAX_ERROR
|
||||
eng "Optimizer hint syntax error"
|
||||
ER_WARN_CONFLICTING_HINT
|
||||
eng "Hint %s is ignored as conflicting/duplicated"
|
||||
ER_WARN_UNKNOWN_QB_NAME
|
||||
eng "Query block name %s is not found for %s hint"
|
||||
ER_UNRESOLVED_HINT_NAME
|
||||
eng "Unresolved name %s for %s hint"
|
||||
|
534
sql/simple_parser.h
Normal file
534
sql/simple_parser.h
Normal file
@ -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
|
@ -17,11 +17,21 @@
|
||||
#define SIMPLE_TOKENIZER_INCLUDED
|
||||
|
||||
|
||||
#include "lex_ident.h"
|
||||
#include "scan_char.h"
|
||||
|
||||
/**
|
||||
A tokenizer for an ASCII7 input
|
||||
*/
|
||||
class Simple_tokenizer
|
||||
{
|
||||
protected:
|
||||
const char *m_ptr;
|
||||
const char *m_end;
|
||||
public:
|
||||
Simple_tokenizer(const LEX_CSTRING &str)
|
||||
:m_ptr(str.str), m_end(str.str + str.length)
|
||||
{ }
|
||||
Simple_tokenizer(const char *str, size_t length)
|
||||
:m_ptr(str), m_end(str + length)
|
||||
{ }
|
||||
@ -33,11 +43,15 @@ public:
|
||||
{
|
||||
return m_ptr >= m_end;
|
||||
}
|
||||
bool is_space() const
|
||||
{
|
||||
return m_ptr[0] == ' ' || m_ptr[0] == '\r' || m_ptr[0] == '\n';
|
||||
}
|
||||
void get_spaces()
|
||||
{
|
||||
for ( ; !eof(); m_ptr++)
|
||||
{
|
||||
if (m_ptr[0] != ' ')
|
||||
if (!is_space())
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -82,4 +96,184 @@ public:
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
A tokenizer for a character set aware input.
|
||||
*/
|
||||
class Extended_string_tokenizer: public Simple_tokenizer
|
||||
{
|
||||
protected:
|
||||
|
||||
CHARSET_INFO *m_cs;
|
||||
|
||||
class Token_metadata
|
||||
{
|
||||
public:
|
||||
bool m_extended_chars:1;
|
||||
bool m_double_quotes:1;
|
||||
Token_metadata()
|
||||
:m_extended_chars(false), m_double_quotes(false)
|
||||
{ }
|
||||
};
|
||||
|
||||
class Token_with_metadata: public Lex_cstring,
|
||||
public Token_metadata
|
||||
{
|
||||
public:
|
||||
Token_with_metadata()
|
||||
{ }
|
||||
Token_with_metadata(const char *str, size_t length,
|
||||
const Token_metadata &metadata)
|
||||
:Lex_cstring(str, length), Token_metadata(metadata)
|
||||
{ }
|
||||
Token_with_metadata(const char *str)
|
||||
:Lex_cstring(str, (size_t) 0), Token_metadata()
|
||||
{ }
|
||||
};
|
||||
|
||||
/*
|
||||
Get a non-delimited identifier for a 8-bit character set
|
||||
*/
|
||||
Token_with_metadata get_ident_8bit(const char *str, const char *end) const
|
||||
{
|
||||
DBUG_ASSERT(m_cs->mbmaxlen == 1);
|
||||
Token_with_metadata res(str);
|
||||
for ( ; str < end && m_cs->ident_map[(uchar) *str]; str++, res.length++)
|
||||
{
|
||||
if (*str & 0x80)
|
||||
res.m_extended_chars= true;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
/*
|
||||
Get a non-identifier for a multi-byte character set
|
||||
*/
|
||||
Token_with_metadata get_ident_mb(const char *str, const char *end) const
|
||||
{
|
||||
DBUG_ASSERT(m_cs->mbmaxlen > 1);
|
||||
Token_with_metadata res(str);
|
||||
for ( ; m_cs->ident_map[(uchar) *str]; )
|
||||
{
|
||||
int char_length= m_cs->charlen(str, end);
|
||||
if (char_length <= 0)
|
||||
break;
|
||||
str+= char_length;
|
||||
res.length+= (size_t) char_length;
|
||||
res.m_extended_chars|= char_length > 1;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
/*
|
||||
Get a non-delimited identifier
|
||||
*/
|
||||
Token_with_metadata get_ident(const char *str, const char *end)
|
||||
{
|
||||
return m_cs->mbmaxlen == 1 ? get_ident_8bit(str, end) :
|
||||
get_ident_mb(str, end);
|
||||
}
|
||||
|
||||
/*
|
||||
Get a quoted string or a quoted identifier.
|
||||
The quote character is determined by the current head character
|
||||
pointed by str. The result is returned together with the left
|
||||
and the right quotes.
|
||||
*/
|
||||
Token_with_metadata get_quoted_string(const char *str, const char *end)
|
||||
{
|
||||
Token_with_metadata res(str);
|
||||
const Scan_char quote(m_cs, str, end);
|
||||
if (quote.length() <= 0)
|
||||
{
|
||||
/*
|
||||
Could not get the left quote character:
|
||||
- the end of the input reached, or
|
||||
- a bad byte sequence found.
|
||||
Return a null token to signal the error to the caller.
|
||||
*/
|
||||
return Token_with_metadata();
|
||||
}
|
||||
str+= quote.length();
|
||||
res.length+= (size_t) quote.length();
|
||||
|
||||
for ( ; ; )
|
||||
{
|
||||
const Scan_char ch(m_cs, str, end);
|
||||
if (ch.length() <= 0)
|
||||
{
|
||||
/*
|
||||
Could not find the right quote character:
|
||||
- the end of the input reached before the quote was not found, or
|
||||
- a bad byte sequences found
|
||||
Return a null token to signal the error to the caller.
|
||||
*/
|
||||
return Token_with_metadata();
|
||||
}
|
||||
str+= ch.length();
|
||||
res.length+= (size_t) ch.length();
|
||||
if (quote.eq(ch))
|
||||
{
|
||||
if (quote.eq_safe(Scan_char(m_cs, str, end)))
|
||||
{
|
||||
/*
|
||||
Two quotes in a row found:
|
||||
- `a``b`
|
||||
- "a""b"
|
||||
*/
|
||||
str+= quote.length();
|
||||
res.length+= (size_t) quote.length();
|
||||
res.m_extended_chars|= quote.length() > 1;
|
||||
res.m_double_quotes= true;
|
||||
continue;
|
||||
}
|
||||
return res; // The right quote found
|
||||
}
|
||||
res.m_extended_chars|= ch.length() > 1;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
public:
|
||||
Extended_string_tokenizer(CHARSET_INFO *cs, const LEX_CSTRING &str)
|
||||
:Simple_tokenizer(str),
|
||||
m_cs(cs)
|
||||
{ }
|
||||
|
||||
// Skip all leading spaces
|
||||
void get_spaces()
|
||||
{
|
||||
for ( ; !eof(); m_ptr++)
|
||||
{
|
||||
if (!my_isspace(m_cs, *m_ptr))
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Get a non-delimited identifier.
|
||||
Can return an empty token if the head character is not an identifier
|
||||
character.
|
||||
*/
|
||||
Token_with_metadata get_ident()
|
||||
{
|
||||
const Token_with_metadata tok= get_ident(m_ptr, m_end);
|
||||
m_ptr+= tok.length;
|
||||
return tok;
|
||||
}
|
||||
|
||||
/*
|
||||
Get a quoted string or a quoted identifier.
|
||||
Can return a null token if there were errors
|
||||
(e.g. unexpected end of the input, bad byte sequence).
|
||||
*/
|
||||
Token_with_metadata get_quoted_string()
|
||||
{
|
||||
const Token_with_metadata tok= get_quoted_string(m_ptr, m_end);
|
||||
m_ptr+= tok.length;
|
||||
return tok;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
#endif // SIMPLE_TOKENIZER_INCLUDED
|
||||
|
@ -855,6 +855,7 @@ Lex_input_stream::reset(char *buffer, size_t length)
|
||||
found_semicolon= NULL;
|
||||
ignore_space= MY_TEST(m_thd->variables.sql_mode & MODE_IGNORE_SPACE);
|
||||
stmt_prepare_mode= FALSE;
|
||||
hint_comment= FALSE;
|
||||
multi_statements= TRUE;
|
||||
in_comment=NO_COMMENT;
|
||||
m_underscore_cs= NULL;
|
||||
@ -2493,10 +2494,20 @@ int Lex_input_stream::lex_one_token(YYSTYPE *yylval, THD *thd)
|
||||
else
|
||||
{
|
||||
in_comment= PRESERVE_COMMENT;
|
||||
yylval->lex_str.str= m_ptr;
|
||||
yySkip(); // Accept /
|
||||
yySkip(); // Accept *
|
||||
comment_closed= ! consume_comment(0);
|
||||
/* regular comments can have zero comments inside. */
|
||||
if ((comment_closed= ! consume_comment(0)) && hint_comment)
|
||||
{
|
||||
if (yylval->lex_str.str[2]=='+')
|
||||
{
|
||||
next_state= MY_LEX_START;
|
||||
yylval->lex_str.length= m_ptr - yylval->lex_str.str;
|
||||
restore_in_comment_state();
|
||||
return HINT_COMMENT;
|
||||
}
|
||||
}
|
||||
}
|
||||
/*
|
||||
Discard:
|
||||
@ -12830,3 +12841,53 @@ bool SELECT_LEX_UNIT::is_derived_eliminated() const
|
||||
return true;
|
||||
return derived->table->map & outer_select()->join->eliminated_tables;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
Parse optimizer hints and return as Hint_list allocated on thd->mem_root.
|
||||
|
||||
The caller should check both return value and thd->is_error()
|
||||
to know what happened, as follows:
|
||||
|
||||
Return value thd->is_error() Meaning
|
||||
------------ --------------- -------
|
||||
rc != nullptr false the hints were parsed without errors
|
||||
rc != nullptr true not possible
|
||||
rc == nullptr false no hints, empty hints, hint parse error
|
||||
rc == nullptr true fatal error, such as EOM
|
||||
*/
|
||||
Optimizer_hint_parser::Hint_list *
|
||||
LEX::parse_optimizer_hints(const LEX_CSTRING &hints_str)
|
||||
{
|
||||
DBUG_ASSERT(!hints_str.str || hints_str.length >= 5);
|
||||
if (!hints_str.str)
|
||||
return nullptr; // There were no a hint comment
|
||||
|
||||
// Instantiate the query hint parser.
|
||||
// Remove the leading '/*+' and trailing '*/'
|
||||
// when passing hints to the parser.
|
||||
Optimizer_hint_parser p(thd, thd->charset(),
|
||||
Lex_cstring(hints_str.str + 3, hints_str.length - 5));
|
||||
// Parse hints
|
||||
Optimizer_hint_parser::Hints hints(&p);
|
||||
DBUG_ASSERT(!p.is_error() || !hints);
|
||||
|
||||
if (p.is_fatal_error())
|
||||
{
|
||||
/*
|
||||
Fatal error (e.g. EOM), have the caller fail.
|
||||
The SQL error should be in DA already.
|
||||
*/
|
||||
DBUG_ASSERT(thd->is_error());
|
||||
return nullptr; // Continue, the caller will test thd->is_error()
|
||||
}
|
||||
|
||||
if (!hints) // Hint parsing failed with a syntax error
|
||||
{
|
||||
p.push_warning_syntax_error(thd);
|
||||
return nullptr; // Continue and ignore hints.
|
||||
}
|
||||
|
||||
// Hints were not empty and were parsed without errors
|
||||
return new (thd->mem_root) Optimizer_hint_parser::Hint_list(std::move(hints));
|
||||
}
|
||||
|
@ -40,6 +40,7 @@
|
||||
#include "table.h"
|
||||
#include "sql_class.h" // enum enum_column_usage
|
||||
#include "select_handler.h"
|
||||
#include "opt_hints_parser.h"
|
||||
|
||||
/* Used for flags of nesting constructs */
|
||||
#define SELECT_NESTING_MAP_SIZE 64
|
||||
@ -2824,6 +2825,11 @@ public:
|
||||
*/
|
||||
bool multi_statements:1;
|
||||
|
||||
/**
|
||||
TRUE if hint comments should be returned as a token.
|
||||
*/
|
||||
bool hint_comment:1;
|
||||
|
||||
/** Current line number. */
|
||||
uint yylineno;
|
||||
|
||||
@ -4959,6 +4965,9 @@ public:
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Optimizer_hint_parser::Hint_list *
|
||||
parse_optimizer_hints(const LEX_CSTRING &hint);
|
||||
};
|
||||
|
||||
|
||||
|
@ -278,6 +278,7 @@ void _CONCAT_UNDERSCORED(turn_parser_debug_on,yyparse)()
|
||||
TABLE_LIST *table_list;
|
||||
Table_ident *table;
|
||||
Qualified_column_ident *qualified_column_ident;
|
||||
Optimizer_hint_parser::Hint_list *opt_hints;
|
||||
char *simple_string;
|
||||
const char *const_simple_string;
|
||||
chooser_compare_func_creator boolfunc2creator;
|
||||
@ -385,6 +386,8 @@ bool my_yyoverflow(short **a, YYSTYPE **b, size_t *yystacksize);
|
||||
|
||||
%token <lex_str> '@'
|
||||
|
||||
%token HINT_COMMENT
|
||||
|
||||
/*
|
||||
Special purpose tokens
|
||||
*/
|
||||
@ -1330,6 +1333,7 @@ bool my_yyoverflow(short **a, YYSTYPE **b, size_t *yystacksize);
|
||||
opt_constraint constraint opt_ident
|
||||
sp_block_label sp_control_label opt_place opt_db
|
||||
udt_name
|
||||
HINT_COMMENT opt_hint_comment
|
||||
|
||||
%type <ident_sys>
|
||||
IDENT_sys
|
||||
@ -1584,6 +1588,9 @@ bool my_yyoverflow(short **a, YYSTYPE **b, size_t *yystacksize);
|
||||
%type <expr_lex>
|
||||
expr_lex
|
||||
|
||||
%type <opt_hints>
|
||||
opt_optimizer_hint
|
||||
|
||||
%destructor
|
||||
{
|
||||
/*
|
||||
@ -8951,8 +8958,23 @@ table_value_constructor:
|
||||
}
|
||||
;
|
||||
|
||||
opt_hint_comment:
|
||||
/*empty */ { $$= null_clex_str; }
|
||||
| HINT_COMMENT { $$= $1; }
|
||||
;
|
||||
|
||||
opt_optimizer_hint:
|
||||
{ YYLIP->hint_comment= true; }
|
||||
opt_hint_comment
|
||||
{
|
||||
YYLIP->hint_comment= false;
|
||||
if (!($$= Lex->parse_optimizer_hints($2)) && thd->is_error())
|
||||
MYSQL_YYABORT;
|
||||
}
|
||||
;
|
||||
|
||||
query_specification_start:
|
||||
SELECT_SYM
|
||||
SELECT_SYM opt_optimizer_hint
|
||||
{
|
||||
SELECT_LEX *sel;
|
||||
LEX *lex= Lex;
|
||||
@ -13562,7 +13584,7 @@ opt_temporary:
|
||||
*/
|
||||
|
||||
insert:
|
||||
INSERT
|
||||
INSERT opt_optimizer_hint
|
||||
{
|
||||
Lex->sql_command= SQLCOM_INSERT;
|
||||
Lex->duplicates= DUP_ERROR;
|
||||
@ -13571,7 +13593,7 @@ insert:
|
||||
}
|
||||
insert_start insert_lock_option opt_ignore opt_into insert_table
|
||||
{
|
||||
Select->set_lock_for_tables($4, true, false);
|
||||
Select->set_lock_for_tables($5, true, false);
|
||||
}
|
||||
insert_field_spec opt_insert_update opt_returning
|
||||
stmt_end
|
||||
@ -13582,7 +13604,7 @@ insert:
|
||||
;
|
||||
|
||||
replace:
|
||||
REPLACE
|
||||
REPLACE opt_optimizer_hint
|
||||
{
|
||||
Lex->sql_command = SQLCOM_REPLACE;
|
||||
Lex->duplicates= DUP_REPLACE;
|
||||
@ -13591,7 +13613,7 @@ replace:
|
||||
}
|
||||
insert_start replace_lock_option opt_into insert_table
|
||||
{
|
||||
Select->set_lock_for_tables($4, true, false);
|
||||
Select->set_lock_for_tables($5, true, false);
|
||||
}
|
||||
insert_field_spec opt_returning
|
||||
stmt_end
|
||||
@ -13866,7 +13888,7 @@ update_table_list:
|
||||
/* Update rows in a table */
|
||||
|
||||
update:
|
||||
UPDATE_SYM
|
||||
UPDATE_SYM opt_optimizer_hint
|
||||
{
|
||||
LEX *lex= Lex;
|
||||
if (Lex->main_select_push())
|
||||
@ -13901,12 +13923,12 @@ update:
|
||||
be too pessimistic. We will decrease lock level if possible
|
||||
later while processing the statement.
|
||||
*/
|
||||
slex->set_lock_for_tables($3, slex->table_list.elements == 1, false);
|
||||
slex->set_lock_for_tables($4, slex->table_list.elements == 1, false);
|
||||
}
|
||||
opt_where_clause opt_order_clause delete_limit_clause
|
||||
{
|
||||
if ($10)
|
||||
Select->order_list= *($10);
|
||||
if ($11)
|
||||
Select->order_list= *($11);
|
||||
} stmt_end {}
|
||||
;
|
||||
|
||||
@ -13953,7 +13975,7 @@ opt_low_priority:
|
||||
/* Delete rows from a table */
|
||||
|
||||
delete:
|
||||
DELETE_SYM
|
||||
DELETE_SYM opt_optimizer_hint
|
||||
{
|
||||
LEX *lex= Lex;
|
||||
YYPS->m_lock_type= TL_WRITE_DEFAULT;
|
||||
|
Loading…
x
Reference in New Issue
Block a user