Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

types: fix a bug in casting str2str when union #37242

Merged
merged 18 commits into from
Aug 24, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 65 additions & 0 deletions executor/executor_issue_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import (
"github.com/pingcap/tidb/config"
"github.com/pingcap/tidb/kv"
"github.com/pingcap/tidb/parser/auth"
"github.com/pingcap/tidb/parser/charset"
"github.com/pingcap/tidb/parser/mysql"
"github.com/pingcap/tidb/sessionctx/variable"
"github.com/pingcap/tidb/statistics"
Expand Down Expand Up @@ -392,6 +393,70 @@ func TestIssue30971(t *testing.T) {
}
}

func TestIssue31678(t *testing.T) {
// The issue31678 is mainly about type conversion in UNION
store := testkit.CreateMockStore(t)
tk := testkit.NewTestKit(t, store)
tk.MustExec("USE test")
tk.MustExec("DROP TABLE IF EXISTS t1, t2;")

// https://github.com/pingcap/tidb/issues/31678
tk.MustExec("CREATE TABLE t1 (c VARCHAR(11)) CHARACTER SET utf8mb4")
xiongjiwei marked this conversation as resolved.
Show resolved Hide resolved
tk.MustExec("CREATE TABLE t2 (b CHAR(1) CHARACTER SET binary, i INT)")
tk.MustExec("INSERT INTO t1 (c) VALUES ('н1234567890')")
tk.MustExec("INSERT INTO t2 (b, i) VALUES ('1', 1)")
var tests = []struct {
query string
expectedFlen int
expectedCharset string
result []string
}{
{"SELECT c FROM t1 UNION SELECT b FROM t2", 44, "binary", []string{"1", "н1234567890"}},
{"SELECT c FROM t1 UNION SELECT i FROM t2", 20, "utf8mb4", []string{"1", "н1234567890"}},
{"SELECT i FROM t2 UNION SELECT c FROM t1", 20, "utf8mb4", []string{"1", "н1234567890"}},
{"SELECT b FROM t2 UNION SELECT c FROM t1", 44, "binary", []string{"1", "н1234567890"}},
}
for _, test := range tests {
tk.MustQuery(test.query).Sort().Check(testkit.Rows(test.result...))
rs, err := tk.Exec(test.query)
require.NoError(t, err)
resultFields := rs.Fields()
require.Equal(t, 1, len(resultFields), test.query)
require.Equal(t, test.expectedFlen, resultFields[0].Column.FieldType.GetFlen(), test.query)
require.Equal(t, test.expectedCharset, resultFields[0].Column.FieldType.GetCharset(), test.query)
}
tk.MustExec("DROP TABLE t1, t2;")

// test some other charset
tk.MustExec("CREATE TABLE t1 (c1 VARCHAR(5) CHARACTER SET utf8mb4, c2 VARCHAR(1) CHARACTER SET binary)")
tk.MustExec("CREATE TABLE t2 (c1 CHAR(10) CHARACTER SET GBK, c2 VARCHAR(50) CHARACTER SET binary)")
tk.MustExec("INSERT INTO t1 VALUES ('一二三四五', '1')")
tk.MustExec("INSERT INTO t2 VALUES ('一二三四五六七八九十', '1234567890')")
gbkResult, err := charset.NewCustomGBKEncoder().String("一二三四五六七八九十")
require.NoError(t, err)
tests = []struct {
query string
expectedFlen int
expectedCharset string
result []string
}{
{"SELECT c1 FROM t1 UNION SELECT c1 FROM t2", 10, "utf8mb4", []string{"一二三四五", "一二三四五六七八九十"}},
{"SELECT c1 FROM t1 UNION SELECT c2 FROM t2", 50, "binary", []string{"1234567890", "一二三四五"}},
{"SELECT c2 FROM t1 UNION SELECT c1 FROM t2", 20, "binary", []string{"1", gbkResult}},
{"SELECT c2 FROM t1 UNION SELECT c2 FROM t2", 50, "binary", []string{"1", "1234567890"}},
}
for _, test := range tests {
tk.MustQuery(test.query).Sort().Check(testkit.Rows(test.result...))
rs, err := tk.Exec(test.query)
require.NoError(t, err)
resultFields := rs.Fields()
require.Equal(t, 1, len(resultFields), test.query)
require.Equal(t, test.expectedFlen, resultFields[0].Column.FieldType.GetFlen(), test.query)
require.Equal(t, test.expectedCharset, resultFields[0].Column.FieldType.GetCharset(), test.query)
}
tk.MustExec("DROP TABLE t1, t2;")
}

func TestIndexJoin31494(t *testing.T) {
store, dom := testkit.CreateMockStoreAndDomain(t)
tk := testkit.NewTestKit(t, store)
Expand Down
15 changes: 15 additions & 0 deletions planner/core/logical_plan_builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import (
"github.com/pingcap/tidb/metrics"
"github.com/pingcap/tidb/parser"
"github.com/pingcap/tidb/parser/ast"
"github.com/pingcap/tidb/parser/charset"
"github.com/pingcap/tidb/parser/format"
"github.com/pingcap/tidb/parser/model"
"github.com/pingcap/tidb/parser/mysql"
Expand Down Expand Up @@ -1512,6 +1513,19 @@ func unionJoinFieldType(a, b *types.FieldType) *types.FieldType {
return resultTp
}

// Set the flen of the union column using the max flen in children.
func (b *PlanBuilder) setUnionFlen(resultTp *types.FieldType, cols []expression.Expression) {
isBinary := resultTp.GetCharset() == charset.CharsetBin
for i := 0; i < len(cols); i++ {
childTp := cols[i].GetType()
childTpCharLen := 1
if isBinary {
childTpCharLen = charset.CharacterSetInfos[childTp.GetCharset()].Maxlen
}
resultTp.SetFlen(mathutil.Max(resultTp.GetFlen(), childTpCharLen*childTp.GetFlen()))
}
}

func (b *PlanBuilder) buildProjection4Union(_ context.Context, u *LogicalUnionAll) error {
unionCols := make([]*expression.Column, 0, u.children[0].Schema().Len())
names := make([]*types.FieldName, 0, u.children[0].Schema().Len())
Expand All @@ -1532,6 +1546,7 @@ func (b *PlanBuilder) buildProjection4Union(_ context.Context, u *LogicalUnionAl
}
resultTp.SetCharset(collation.Charset)
resultTp.SetCollate(collation.Collation)
b.setUnionFlen(resultTp, tmpExprs)
names = append(names, &types.FieldName{ColName: u.children[0].OutputNames()[i].ColName})
unionCols = append(unionCols, &expression.Column{
RetType: resultTp,
Expand Down
2 changes: 1 addition & 1 deletion types/convert_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -389,7 +389,7 @@ func TestConvertToString(t *testing.T) {
sc := new(stmtctx.StatementContext)
outputDatum, err := inputDatum.ConvertTo(sc, ft)
if tt.input != tt.output {
require.True(t, ErrDataTooLong.Equal(err))
require.True(t, ErrDataTooLong.Equal(err), "flen: %d, charset: %s, input: %s, output: %s", tt.flen, tt.charset, tt.input, tt.output)
} else {
require.NoError(t, err)
}
Expand Down
2 changes: 1 addition & 1 deletion types/datum.go
Original file line number Diff line number Diff line change
Expand Up @@ -1069,7 +1069,7 @@ func (d *Datum) convertToString(sc *stmtctx.StatementContext, target *FieldType)
func ProduceStrWithSpecifiedTp(s string, tp *FieldType, sc *stmtctx.StatementContext, padZero bool) (_ string, err error) {
flen, chs := tp.GetFlen(), tp.GetCharset()
if flen >= 0 {
// overflowed stores the part of the string that is out of the length contraint, it is later checked to see if the
// overflowed stores the part of the string that is out of the length constraint, it is later checked to see if the
// overflowed part is all whitespaces
var overflowed string
var characterLen int
Expand Down