Skip to content

Commit d91b100

Browse files
authored
expression: fix tikv crash when bool like cast(bit as char) (pingcap#57484) (pingcap#59321)
close pingcap#56494
1 parent 5ff1302 commit d91b100

File tree

9 files changed

+128
-8
lines changed

9 files changed

+128
-8
lines changed

pkg/expression/builtin_cast.go

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -282,10 +282,20 @@ func (c *castAsStringFunctionClass) getFunction(ctx BuildContext, args []Express
282282
if err != nil {
283283
return nil, err
284284
}
285-
if args[0].GetType().Hybrid() {
286-
sig = &builtinCastStringAsStringSig{bf}
287-
sig.setPbCode(tipb.ScalarFuncSig_CastStringAsString)
288-
return sig, nil
285+
if ft := args[0].GetType(); ft.Hybrid() {
286+
castBitAsUnBinary := ft.GetType() == mysql.TypeBit && c.tp.GetCharset() != charset.CharsetBin
287+
if !castBitAsUnBinary {
288+
sig = &builtinCastStringAsStringSig{bf}
289+
sig.setPbCode(tipb.ScalarFuncSig_CastStringAsString)
290+
return sig, nil
291+
}
292+
// for type BIT, it maybe an invalid value for the specified charset, we need to convert it to binary first,
293+
// and then convert it to the specified charset with `HandleBinaryLiteral` in the following code.
294+
tp := types.NewFieldType(mysql.TypeString)
295+
tp.SetCharset(charset.CharsetBin)
296+
tp.SetCollate(charset.CollationBin)
297+
tp.AddFlag(mysql.BinaryFlag)
298+
args[0] = BuildCastFunction(ctx, args[0], tp)
289299
}
290300
argTp := args[0].GetType().EvalType()
291301
switch argTp {

pkg/expression/builtin_convert_charset.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,7 @@ type builtinInternalFromBinarySig struct {
169169
func (b *builtinInternalFromBinarySig) Clone() builtinFunc {
170170
newSig := &builtinInternalFromBinarySig{}
171171
newSig.cloneFrom(&b.baseBuiltinFunc)
172+
newSig.cannotConvertStringAsWarning = b.cannotConvertStringAsWarning
172173
return newSig
173174
}
174175

pkg/expression/expr_to_pb_test.go

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1580,7 +1580,9 @@ func TestExprPushDownToTiKV(t *testing.T) {
15801580
require.Len(t, pushed, 0)
15811581
require.Len(t, remained, len(exprs))
15821582

1583-
// Test Conv function
1583+
// Test Conv function, `conv` function for a BIT column should not be pushed down for its special behavior which
1584+
// is only handled in TiDB currently.
1585+
// see issue: https://github.com/pingcap/tidb/issues/51877
15841586
exprs = exprs[:0]
15851587
function, err = NewFunction(mock.NewContext(), ast.Conv, types.NewFieldType(mysql.TypeString), stringColumn, intColumn, intColumn)
15861588
require.NoError(t, err)
@@ -1589,7 +1591,11 @@ func TestExprPushDownToTiKV(t *testing.T) {
15891591
require.Len(t, pushed, len(exprs))
15901592
require.Len(t, remained, 0)
15911593
exprs = exprs[:0]
1592-
castByteAsStringFunc, err := NewFunction(mock.NewContext(), ast.Cast, types.NewFieldType(mysql.TypeString), byteColumn)
1594+
// when conv a column with type BIT, a cast function will be used to cast bit to a binary string
1595+
castTp := types.NewFieldType(mysql.TypeString)
1596+
castTp.SetCharset(charset.CharsetBin)
1597+
castTp.SetCollate(charset.CollationBin)
1598+
castByteAsStringFunc, err := NewFunction(mock.NewContext(), ast.Cast, castTp, byteColumn)
15931599
require.NoError(t, err)
15941600
function, err = NewFunction(mock.NewContext(), ast.Conv, types.NewFieldType(mysql.TypeString), castByteAsStringFunc, intColumn, intColumn)
15951601
require.NoError(t, err)

pkg/planner/core/integration_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -330,15 +330,15 @@ func TestBitColumnPushDown(t *testing.T) {
330330
tk.MustQuery(sql).Check(testkit.Rows("A"))
331331
tk.MustQuery(fmt.Sprintf("explain analyze %s", sql)).CheckAt([]int{0, 3, 6}, rows)
332332

333-
rows[1][2] = `eq(cast(test.t1.a, var_string(1)), "A")`
333+
rows[1][2] = `eq(cast(from_binary(cast(test.t1.a, binary(1))), var_string(1)), "A")`
334334
sql = "select a from t1 where cast(a as char)='A'"
335335
tk.MustQuery(sql).Check(testkit.Rows("A"))
336336
tk.MustQuery(fmt.Sprintf("explain analyze %s", sql)).CheckAt([]int{0, 3, 6}, rows)
337337

338338
tk.MustExec("insert into mysql.expr_pushdown_blacklist values('bit', 'tikv','');")
339339
tk.MustExec("admin reload expr_pushdown_blacklist;")
340340
rows = [][]any{
341-
{"Selection_5", "root", `eq(cast(test.t1.a, var_string(1)), "A")`},
341+
{"Selection_5", "root", `eq(cast(from_binary(cast(test.t1.a, binary(1))), var_string(1)), "A")`},
342342
{"└─TableReader_7", "root", "data:TableFullScan_6"},
343343
{" └─TableFullScan_6", "cop[tikv]", "keep order:false, stats:pseudo"},
344344
}

tests/integrationtest/r/expression/cast.result

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,3 +178,18 @@ id update_user
178178
2 李四
179179
4 李四
180180
1 张三
181+
drop table if exists test;
182+
create table test(a bit(24));
183+
insert into test values('中');
184+
select a from test where '中' like convert(a, char);
185+
a
186+
187+
select a from test where false not like convert(a, char);
188+
a
189+
190+
select a from test where false like convert(a, char);
191+
a
192+
truncate table test;
193+
insert into test values(0xffffff);
194+
select a from test where false not like convert(a, char);
195+
Error 1105 (HY000): Cannot convert string '\xFF\xFF\xFF' from binary to utf8mb4

tests/integrationtest/t/expression/cast.test

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,3 +113,14 @@ insert into test values(3,'张三');
113113
insert into test values(4,'李四');
114114
select * from test order by cast(update_user as char) desc , id limit 3;
115115

116+
# issue #56494, cast bit as char
117+
drop table if exists test;
118+
create table test(a bit(24));
119+
insert into test values('中');
120+
select a from test where '中' like convert(a, char);
121+
select a from test where false not like convert(a, char);
122+
select a from test where false like convert(a, char);
123+
truncate table test;
124+
insert into test values(0xffffff);
125+
-- error 1105
126+
select a from test where false not like convert(a, char);
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
load("@io_bazel_rules_go//go:def.bzl", "go_test")
2+
3+
go_test(
4+
name = "pushdowntest_test",
5+
timeout = "short",
6+
srcs = [
7+
"expr_test.go",
8+
"main_test.go",
9+
],
10+
flaky = True,
11+
deps = [
12+
"//pkg/testkit",
13+
"//tests/realtikvtest",
14+
"@com_github_stretchr_testify//require",
15+
],
16+
)
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
// Copyright 2024 PingCAP, Inc.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package pushdowntest
16+
17+
import (
18+
"testing"
19+
20+
"github.com/pingcap/tidb/pkg/testkit"
21+
"github.com/pingcap/tidb/tests/realtikvtest"
22+
"github.com/stretchr/testify/require"
23+
)
24+
25+
// TestBitCastInTiKV see issue: https://github.com/pingcap/tidb/issues/56494
26+
func TestBitCastInTiKV(t *testing.T) {
27+
store := realtikvtest.CreateMockStoreAndSetup(t)
28+
tk := testkit.NewTestKit(t, store)
29+
tk.MustExec("use test")
30+
tk.MustExec("drop table if exists t1")
31+
defer tk.MustExec("drop table if exists t1")
32+
tk.MustExec("create table t1(a bit(24))")
33+
tk.MustExec("insert into t1 values(0xffffff)")
34+
err := tk.QueryToErr("select a from t1 where false not like convert(a, char)")
35+
require.EqualError(t, err, "[tikv:3854]Cannot convert string '\\xFF\\xFF\\xFF' from binary to utf8mb4")
36+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// Copyright 2024 PingCAP, Inc.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package pushdowntest
16+
17+
import (
18+
"testing"
19+
20+
"github.com/pingcap/tidb/tests/realtikvtest"
21+
)
22+
23+
func TestMain(m *testing.M) {
24+
realtikvtest.RunTestMain(m)
25+
}

0 commit comments

Comments
 (0)