// Copyright 2020 PingCAP, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // See the License for the specific language governing permissions and // limitations under the License. package parser_test import ( "testing" "github.com/pingcap/tidb/parser" "github.com/pingcap/tidb/parser/ast" "github.com/pingcap/tidb/parser/model" "github.com/pingcap/tidb/parser/mysql" "github.com/stretchr/testify/require" ) func TestParseHint(t *testing.T) { testCases := []struct { input string mode mysql.SQLMode output []*ast.TableOptimizerHint errs []string }{ { input: "", errs: []string{`Optimizer hint syntax error at line 1 `}, }, { input: "MEMORY_QUOTA(8 MB) MEMORY_QUOTA(6 GB)", output: []*ast.TableOptimizerHint{ { HintName: model.NewCIStr("MEMORY_QUOTA"), HintData: int64(8 * 1024 * 1024), }, { HintName: model.NewCIStr("MEMORY_QUOTA"), HintData: int64(6 * 1024 * 1024 * 1024), }, }, }, { input: "QB_NAME(qb1) QB_NAME(`qb2`), QB_NAME(TRUE) QB_NAME(\"ANSI quoted\") QB_NAME(_utf8), QB_NAME(0b10) QB_NAME(0x1a)", mode: mysql.ModeANSIQuotes, output: []*ast.TableOptimizerHint{ { HintName: model.NewCIStr("QB_NAME"), QBName: model.NewCIStr("qb1"), }, { HintName: model.NewCIStr("QB_NAME"), QBName: model.NewCIStr("qb2"), }, { HintName: model.NewCIStr("QB_NAME"), QBName: model.NewCIStr("TRUE"), }, { HintName: model.NewCIStr("QB_NAME"), QBName: model.NewCIStr("ANSI quoted"), }, { HintName: model.NewCIStr("QB_NAME"), QBName: model.NewCIStr("_utf8"), }, { HintName: model.NewCIStr("QB_NAME"), QBName: model.NewCIStr("0b10"), }, { HintName: model.NewCIStr("QB_NAME"), QBName: model.NewCIStr("0x1a"), }, }, }, { input: "QB_NAME(1)", errs: []string{`Optimizer hint syntax error at line 1 `}, }, { input: "QB_NAME('string literal')", errs: []string{`Optimizer hint syntax error at line 1 `}, }, { input: "QB_NAME(many identifiers)", errs: []string{`Optimizer hint syntax error at line 1 `}, }, { input: "QB_NAME(@qb1)", errs: []string{`Optimizer hint syntax error at line 1 `}, }, { input: "QB_NAME(b'10')", errs: []string{ `Cannot use bit-value literal`, `Optimizer hint syntax error at line 1 `, }, }, { input: "QB_NAME(x'1a')", errs: []string{ `Cannot use hexadecimal literal`, `Optimizer hint syntax error at line 1 `, }, }, { input: "JOIN_FIXED_ORDER() BKA()", errs: []string{ `Optimizer hint JOIN_FIXED_ORDER is not supported`, `Optimizer hint BKA is not supported`, }, }, { input: "HASH_JOIN() TIDB_HJ(@qb1) INL_JOIN(x, `y y`.z) MERGE_JOIN(w@`First QB`)", output: []*ast.TableOptimizerHint{ { HintName: model.NewCIStr("HASH_JOIN"), }, { HintName: model.NewCIStr("TIDB_HJ"), QBName: model.NewCIStr("qb1"), }, { HintName: model.NewCIStr("INL_JOIN"), Tables: []ast.HintTable{ {TableName: model.NewCIStr("x")}, {DBName: model.NewCIStr("y y"), TableName: model.NewCIStr("z")}, }, }, { HintName: model.NewCIStr("MERGE_JOIN"), Tables: []ast.HintTable{ {TableName: model.NewCIStr("w"), QBName: model.NewCIStr("First QB")}, }, }, }, }, { input: "USE_INDEX_MERGE(@qb1 tbl1 x, y, z) IGNORE_INDEX(tbl2@qb2) USE_INDEX(tbl3 PRIMARY) FORCE_INDEX(tbl4@qb3 c1)", output: []*ast.TableOptimizerHint{ { HintName: model.NewCIStr("USE_INDEX_MERGE"), Tables: []ast.HintTable{{TableName: model.NewCIStr("tbl1")}}, QBName: model.NewCIStr("qb1"), Indexes: []model.CIStr{model.NewCIStr("x"), model.NewCIStr("y"), model.NewCIStr("z")}, }, { HintName: model.NewCIStr("IGNORE_INDEX"), Tables: []ast.HintTable{{TableName: model.NewCIStr("tbl2"), QBName: model.NewCIStr("qb2")}}, }, { HintName: model.NewCIStr("USE_INDEX"), Tables: []ast.HintTable{{TableName: model.NewCIStr("tbl3")}}, Indexes: []model.CIStr{model.NewCIStr("PRIMARY")}, }, { HintName: model.NewCIStr("FORCE_INDEX"), Tables: []ast.HintTable{{TableName: model.NewCIStr("tbl4"), QBName: model.NewCIStr("qb3")}}, Indexes: []model.CIStr{model.NewCIStr("c1")}, }, }, }, { input: "USE_INDEX(@qb1 tbl1 partition(p0) x) USE_INDEX_MERGE(@qb2 tbl2@qb2 partition(p0, p1) x, y, z)", output: []*ast.TableOptimizerHint{ { HintName: model.NewCIStr("USE_INDEX"), Tables: []ast.HintTable{{ TableName: model.NewCIStr("tbl1"), PartitionList: []model.CIStr{model.NewCIStr("p0")}, }}, QBName: model.NewCIStr("qb1"), Indexes: []model.CIStr{model.NewCIStr("x")}, }, { HintName: model.NewCIStr("USE_INDEX_MERGE"), Tables: []ast.HintTable{{ TableName: model.NewCIStr("tbl2"), QBName: model.NewCIStr("qb2"), PartitionList: []model.CIStr{model.NewCIStr("p0"), model.NewCIStr("p1")}, }}, QBName: model.NewCIStr("qb2"), Indexes: []model.CIStr{model.NewCIStr("x"), model.NewCIStr("y"), model.NewCIStr("z")}, }, }, }, { input: `SET_VAR(sbs = 16M) SET_VAR(fkc=OFF) SET_VAR(os="mcb=off") set_var(abc=1) set_var(os2='mcb2=off')`, output: []*ast.TableOptimizerHint{ { HintName: model.NewCIStr("SET_VAR"), HintData: ast.HintSetVar{ VarName: "sbs", Value: "16M", }, }, { HintName: model.NewCIStr("SET_VAR"), HintData: ast.HintSetVar{ VarName: "fkc", Value: "OFF", }, }, { HintName: model.NewCIStr("SET_VAR"), HintData: ast.HintSetVar{ VarName: "os", Value: "mcb=off", }, }, { HintName: model.NewCIStr("set_var"), HintData: ast.HintSetVar{ VarName: "abc", Value: "1", }, }, { HintName: model.NewCIStr("set_var"), HintData: ast.HintSetVar{ VarName: "os2", Value: "mcb2=off", }, }, }, }, { input: "USE_TOJA(TRUE) IGNORE_PLAN_CACHE() USE_CASCADES(TRUE) QUERY_TYPE(@qb1 OLAP) QUERY_TYPE(OLTP) NO_INDEX_MERGE()", output: []*ast.TableOptimizerHint{ { HintName: model.NewCIStr("USE_TOJA"), HintData: true, }, { HintName: model.NewCIStr("IGNORE_PLAN_CACHE"), }, { HintName: model.NewCIStr("USE_CASCADES"), HintData: true, }, { HintName: model.NewCIStr("QUERY_TYPE"), QBName: model.NewCIStr("qb1"), HintData: model.NewCIStr("OLAP"), }, { HintName: model.NewCIStr("QUERY_TYPE"), HintData: model.NewCIStr("OLTP"), }, { HintName: model.NewCIStr("NO_INDEX_MERGE"), }, }, }, { input: "READ_FROM_STORAGE(@foo TIKV[a, b], TIFLASH[c, d]) HASH_AGG() SEMI_JOIN_REWRITE() READ_FROM_STORAGE(TIKV[e])", output: []*ast.TableOptimizerHint{ { HintName: model.NewCIStr("READ_FROM_STORAGE"), HintData: model.NewCIStr("TIKV"), QBName: model.NewCIStr("foo"), Tables: []ast.HintTable{ {TableName: model.NewCIStr("a")}, {TableName: model.NewCIStr("b")}, }, }, { HintName: model.NewCIStr("READ_FROM_STORAGE"), HintData: model.NewCIStr("TIFLASH"), QBName: model.NewCIStr("foo"), Tables: []ast.HintTable{ {TableName: model.NewCIStr("c")}, {TableName: model.NewCIStr("d")}, }, }, { HintName: model.NewCIStr("HASH_AGG"), }, { HintName: model.NewCIStr("SEMI_JOIN_REWRITE"), }, { HintName: model.NewCIStr("READ_FROM_STORAGE"), HintData: model.NewCIStr("TIKV"), Tables: []ast.HintTable{ {TableName: model.NewCIStr("e")}, }, }, }, }, { input: "unknown_hint()", errs: []string{`Optimizer hint syntax error at line 1 `}, }, { input: "set_var(timestamp = 1.5)", errs: []string{ `Cannot use decimal number`, `Optimizer hint syntax error at line 1 `, }, }, { input: "set_var(timestamp = _utf8mb4'1234')", // Optimizer hint doesn't recognize _charset'strings'. errs: []string{`Optimizer hint syntax error at line 1 `}, }, { input: "set_var(timestamp = 9999999999999999999999999999999999999)", errs: []string{ `integer value is out of range`, `Optimizer hint syntax error at line 1 `, }, }, { input: "time_range('2020-02-20 12:12:12',456)", errs: []string{ `Optimizer hint syntax error at line 1 `, }, }, { input: "time_range(456,'2020-02-20 12:12:12')", errs: []string{ `Optimizer hint syntax error at line 1 `, }, }, { input: "TIME_RANGE('2020-02-20 12:12:12','2020-02-20 13:12:12')", output: []*ast.TableOptimizerHint{ { HintName: model.NewCIStr("TIME_RANGE"), HintData: ast.HintTimeRange{ From: "2020-02-20 12:12:12", To: "2020-02-20 13:12:12", }, }, }, }, } for _, tc := range testCases { output, errs := parser.ParseHint("/*+"+tc.input+"*/", tc.mode, parser.Pos{Line: 1}) require.Lenf(t, errs, len(tc.errs), "input = %s,\n... errs = %q", tc.input, errs) for i, err := range errs { require.Errorf(t, err, "input = %s, i = %d", tc.input, i) require.Containsf(t, err.Error(), tc.errs[i], "input = %s, i = %d", tc.input, i) } require.Equalf(t, tc.output, output, "input = %s,\n... output = %q", tc.input, output) } }