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;
|
break;
|
||||||
}
|
}
|
||||||
else if (!*in_string && inchar == '/' && *(pos+1) == '*' &&
|
else if (!*in_string && inchar == '/' && pos[1] == '*' &&
|
||||||
!(*(pos+2) == '!' || (*(pos+2) == 'M' && *(pos+3) == '!')))
|
!(pos[2] == '!' ||
|
||||||
|
(pos[2] == 'M' && pos[3] == '!') ||
|
||||||
|
pos[2] == '+'))
|
||||||
{
|
{
|
||||||
if (preserve_comments)
|
if (preserve_comments)
|
||||||
{
|
{
|
||||||
@ -2768,8 +2770,8 @@ static bool add_line(String &buffer, char *line, size_t line_length,
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{ // Add found char to buffer
|
{ // Add found char to buffer
|
||||||
if (!*in_string && inchar == '/' && *(pos + 1) == '*' &&
|
if (!*in_string && inchar == '/' && pos[1] == '*' &&
|
||||||
*(pos + 2) == '!')
|
(pos[2] == '!' || pos[2] == '+'))
|
||||||
ss_comment= 1;
|
ss_comment= 1;
|
||||||
else if (!*in_string && ss_comment && inchar == '*' && *(pos + 1) == '/')
|
else if (!*in_string && ss_comment && inchar == '*' && *(pos + 1) == '/')
|
||||||
ss_comment= 0;
|
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/opt_histogram_json.cc
|
||||||
../sql/sp_instr.cc
|
../sql/sp_instr.cc
|
||||||
../sql/sp_cursor.cc
|
../sql/sp_cursor.cc
|
||||||
|
../sql/opt_hints_parser.cc ../sql/opt_hints_parser.h
|
||||||
|
../sql/scan_char.h
|
||||||
${GEN_SOURCES}
|
${GEN_SOURCES}
|
||||||
${MYSYS_LIBWRAP_SOURCE}
|
${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
|
socketpair.c socketpair.h
|
||||||
opt_vcol_substitution.h
|
opt_vcol_substitution.h
|
||||||
opt_vcol_substitution.cc
|
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_hash.h
|
||||||
${CMAKE_CURRENT_BINARY_DIR}/lex_token.h
|
${CMAKE_CURRENT_BINARY_DIR}/lex_token.h
|
||||||
${GEN_SOURCES}
|
${GEN_SOURCES}
|
||||||
|
@ -18,8 +18,11 @@
|
|||||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1335 USA
|
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 "char_buffer.h"
|
||||||
#include "lex_string.h"
|
#include "lex_string.h"
|
||||||
|
#include "my_sys.h"
|
||||||
|
|
||||||
extern MYSQL_PLUGIN_IMPORT CHARSET_INFO *table_alias_charset;
|
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"
|
eng "Fewer than one row in the table"
|
||||||
ER_SEQUENCE_TABLE_HAS_TOO_MANY_ROWS
|
ER_SEQUENCE_TABLE_HAS_TOO_MANY_ROWS
|
||||||
eng "More than one row in the table"
|
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
|
#define SIMPLE_TOKENIZER_INCLUDED
|
||||||
|
|
||||||
|
|
||||||
|
#include "lex_ident.h"
|
||||||
|
#include "scan_char.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
A tokenizer for an ASCII7 input
|
||||||
|
*/
|
||||||
class Simple_tokenizer
|
class Simple_tokenizer
|
||||||
{
|
{
|
||||||
|
protected:
|
||||||
const char *m_ptr;
|
const char *m_ptr;
|
||||||
const char *m_end;
|
const char *m_end;
|
||||||
public:
|
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)
|
Simple_tokenizer(const char *str, size_t length)
|
||||||
:m_ptr(str), m_end(str + length)
|
:m_ptr(str), m_end(str + length)
|
||||||
{ }
|
{ }
|
||||||
@ -33,11 +43,15 @@ public:
|
|||||||
{
|
{
|
||||||
return m_ptr >= m_end;
|
return m_ptr >= m_end;
|
||||||
}
|
}
|
||||||
|
bool is_space() const
|
||||||
|
{
|
||||||
|
return m_ptr[0] == ' ' || m_ptr[0] == '\r' || m_ptr[0] == '\n';
|
||||||
|
}
|
||||||
void get_spaces()
|
void get_spaces()
|
||||||
{
|
{
|
||||||
for ( ; !eof(); m_ptr++)
|
for ( ; !eof(); m_ptr++)
|
||||||
{
|
{
|
||||||
if (m_ptr[0] != ' ')
|
if (!is_space())
|
||||||
break;
|
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
|
#endif // SIMPLE_TOKENIZER_INCLUDED
|
||||||
|
@ -855,6 +855,7 @@ Lex_input_stream::reset(char *buffer, size_t length)
|
|||||||
found_semicolon= NULL;
|
found_semicolon= NULL;
|
||||||
ignore_space= MY_TEST(m_thd->variables.sql_mode & MODE_IGNORE_SPACE);
|
ignore_space= MY_TEST(m_thd->variables.sql_mode & MODE_IGNORE_SPACE);
|
||||||
stmt_prepare_mode= FALSE;
|
stmt_prepare_mode= FALSE;
|
||||||
|
hint_comment= FALSE;
|
||||||
multi_statements= TRUE;
|
multi_statements= TRUE;
|
||||||
in_comment=NO_COMMENT;
|
in_comment=NO_COMMENT;
|
||||||
m_underscore_cs= NULL;
|
m_underscore_cs= NULL;
|
||||||
@ -2493,10 +2494,20 @@ int Lex_input_stream::lex_one_token(YYSTYPE *yylval, THD *thd)
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
in_comment= PRESERVE_COMMENT;
|
in_comment= PRESERVE_COMMENT;
|
||||||
|
yylval->lex_str.str= m_ptr;
|
||||||
yySkip(); // Accept /
|
yySkip(); // Accept /
|
||||||
yySkip(); // Accept *
|
yySkip(); // Accept *
|
||||||
comment_closed= ! consume_comment(0);
|
|
||||||
/* regular comments can have zero comments inside. */
|
/* 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:
|
Discard:
|
||||||
@ -12830,3 +12841,53 @@ bool SELECT_LEX_UNIT::is_derived_eliminated() const
|
|||||||
return true;
|
return true;
|
||||||
return derived->table->map & outer_select()->join->eliminated_tables;
|
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 "table.h"
|
||||||
#include "sql_class.h" // enum enum_column_usage
|
#include "sql_class.h" // enum enum_column_usage
|
||||||
#include "select_handler.h"
|
#include "select_handler.h"
|
||||||
|
#include "opt_hints_parser.h"
|
||||||
|
|
||||||
/* Used for flags of nesting constructs */
|
/* Used for flags of nesting constructs */
|
||||||
#define SELECT_NESTING_MAP_SIZE 64
|
#define SELECT_NESTING_MAP_SIZE 64
|
||||||
@ -2824,6 +2825,11 @@ public:
|
|||||||
*/
|
*/
|
||||||
bool multi_statements:1;
|
bool multi_statements:1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
TRUE if hint comments should be returned as a token.
|
||||||
|
*/
|
||||||
|
bool hint_comment:1;
|
||||||
|
|
||||||
/** Current line number. */
|
/** Current line number. */
|
||||||
uint yylineno;
|
uint yylineno;
|
||||||
|
|
||||||
@ -4959,6 +4965,9 @@ public:
|
|||||||
{
|
{
|
||||||
return nullptr;
|
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_LIST *table_list;
|
||||||
Table_ident *table;
|
Table_ident *table;
|
||||||
Qualified_column_ident *qualified_column_ident;
|
Qualified_column_ident *qualified_column_ident;
|
||||||
|
Optimizer_hint_parser::Hint_list *opt_hints;
|
||||||
char *simple_string;
|
char *simple_string;
|
||||||
const char *const_simple_string;
|
const char *const_simple_string;
|
||||||
chooser_compare_func_creator boolfunc2creator;
|
chooser_compare_func_creator boolfunc2creator;
|
||||||
@ -385,6 +386,8 @@ bool my_yyoverflow(short **a, YYSTYPE **b, size_t *yystacksize);
|
|||||||
|
|
||||||
%token <lex_str> '@'
|
%token <lex_str> '@'
|
||||||
|
|
||||||
|
%token HINT_COMMENT
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Special purpose tokens
|
Special purpose tokens
|
||||||
*/
|
*/
|
||||||
@ -1330,6 +1333,7 @@ bool my_yyoverflow(short **a, YYSTYPE **b, size_t *yystacksize);
|
|||||||
opt_constraint constraint opt_ident
|
opt_constraint constraint opt_ident
|
||||||
sp_block_label sp_control_label opt_place opt_db
|
sp_block_label sp_control_label opt_place opt_db
|
||||||
udt_name
|
udt_name
|
||||||
|
HINT_COMMENT opt_hint_comment
|
||||||
|
|
||||||
%type <ident_sys>
|
%type <ident_sys>
|
||||||
IDENT_sys
|
IDENT_sys
|
||||||
@ -1584,6 +1588,9 @@ bool my_yyoverflow(short **a, YYSTYPE **b, size_t *yystacksize);
|
|||||||
%type <expr_lex>
|
%type <expr_lex>
|
||||||
expr_lex
|
expr_lex
|
||||||
|
|
||||||
|
%type <opt_hints>
|
||||||
|
opt_optimizer_hint
|
||||||
|
|
||||||
%destructor
|
%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:
|
query_specification_start:
|
||||||
SELECT_SYM
|
SELECT_SYM opt_optimizer_hint
|
||||||
{
|
{
|
||||||
SELECT_LEX *sel;
|
SELECT_LEX *sel;
|
||||||
LEX *lex= Lex;
|
LEX *lex= Lex;
|
||||||
@ -13562,7 +13584,7 @@ opt_temporary:
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
insert:
|
insert:
|
||||||
INSERT
|
INSERT opt_optimizer_hint
|
||||||
{
|
{
|
||||||
Lex->sql_command= SQLCOM_INSERT;
|
Lex->sql_command= SQLCOM_INSERT;
|
||||||
Lex->duplicates= DUP_ERROR;
|
Lex->duplicates= DUP_ERROR;
|
||||||
@ -13571,7 +13593,7 @@ insert:
|
|||||||
}
|
}
|
||||||
insert_start insert_lock_option opt_ignore opt_into insert_table
|
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
|
insert_field_spec opt_insert_update opt_returning
|
||||||
stmt_end
|
stmt_end
|
||||||
@ -13582,7 +13604,7 @@ insert:
|
|||||||
;
|
;
|
||||||
|
|
||||||
replace:
|
replace:
|
||||||
REPLACE
|
REPLACE opt_optimizer_hint
|
||||||
{
|
{
|
||||||
Lex->sql_command = SQLCOM_REPLACE;
|
Lex->sql_command = SQLCOM_REPLACE;
|
||||||
Lex->duplicates= DUP_REPLACE;
|
Lex->duplicates= DUP_REPLACE;
|
||||||
@ -13591,7 +13613,7 @@ replace:
|
|||||||
}
|
}
|
||||||
insert_start replace_lock_option opt_into insert_table
|
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
|
insert_field_spec opt_returning
|
||||||
stmt_end
|
stmt_end
|
||||||
@ -13866,7 +13888,7 @@ update_table_list:
|
|||||||
/* Update rows in a table */
|
/* Update rows in a table */
|
||||||
|
|
||||||
update:
|
update:
|
||||||
UPDATE_SYM
|
UPDATE_SYM opt_optimizer_hint
|
||||||
{
|
{
|
||||||
LEX *lex= Lex;
|
LEX *lex= Lex;
|
||||||
if (Lex->main_select_push())
|
if (Lex->main_select_push())
|
||||||
@ -13901,12 +13923,12 @@ update:
|
|||||||
be too pessimistic. We will decrease lock level if possible
|
be too pessimistic. We will decrease lock level if possible
|
||||||
later while processing the statement.
|
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
|
opt_where_clause opt_order_clause delete_limit_clause
|
||||||
{
|
{
|
||||||
if ($10)
|
if ($11)
|
||||||
Select->order_list= *($10);
|
Select->order_list= *($11);
|
||||||
} stmt_end {}
|
} stmt_end {}
|
||||||
;
|
;
|
||||||
|
|
||||||
@ -13953,7 +13975,7 @@ opt_low_priority:
|
|||||||
/* Delete rows from a table */
|
/* Delete rows from a table */
|
||||||
|
|
||||||
delete:
|
delete:
|
||||||
DELETE_SYM
|
DELETE_SYM opt_optimizer_hint
|
||||||
{
|
{
|
||||||
LEX *lex= Lex;
|
LEX *lex= Lex;
|
||||||
YYPS->m_lock_type= TL_WRITE_DEFAULT;
|
YYPS->m_lock_type= TL_WRITE_DEFAULT;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user