From e0b9c9d0d4148b25cdaaadaff8282ffc9e441809 Mon Sep 17 00:00:00 2001 From: rebelice Date: Mon, 18 Jan 2021 12:17:58 +0800 Subject: [PATCH] cherry pick #21310 to release-4.0 Signed-off-by: ti-srebot --- executor/write_test.go | 32 ++++++++++++++++++++++++++++++++ types/datum.go | 27 ++++++++++++++++++++++++++- types/datum_test.go | 23 +++++++++++++++++++++++ 3 files changed, 81 insertions(+), 1 deletion(-) diff --git a/executor/write_test.go b/executor/write_test.go index 1e9c95c3f336f..f6570d51dba6b 100644 --- a/executor/write_test.go +++ b/executor/write_test.go @@ -1992,6 +1992,38 @@ func (s *testSuite8) TestLoadDataMissingColumn(c *C) { } +func (s *testSuite4) TestIssue18681(c *C) { + tk := testkit.NewTestKit(c, s.store) + tk.MustExec("use test") + createSQL := `drop table if exists load_data_test; + create table load_data_test (a bit(1),b bit(1),c bit(1),d bit(1));` + tk.MustExec(createSQL) + tk.MustExec("load data local infile '/tmp/nonexistence.csv' ignore into table load_data_test") + ctx := tk.Se.(sessionctx.Context) + ld, ok := ctx.Value(executor.LoadDataVarKey).(*executor.LoadDataInfo) + c.Assert(ok, IsTrue) + defer ctx.SetValue(executor.LoadDataVarKey, nil) + c.Assert(ld, NotNil) + + deleteSQL := "delete from load_data_test" + selectSQL := "select bin(a), bin(b), bin(c), bin(d) from load_data_test;" + ctx.GetSessionVars().StmtCtx.DupKeyAsWarning = true + ctx.GetSessionVars().StmtCtx.BadNullAsWarning = true + ld.SetMaxRowsInBatch(20000) + + sc := ctx.GetSessionVars().StmtCtx + originIgnoreTruncate := sc.IgnoreTruncate + defer func() { + sc.IgnoreTruncate = originIgnoreTruncate + }() + sc.IgnoreTruncate = false + tests := []testCase{ + {nil, []byte("true\tfalse\t0\t1\n"), []string{"1|0|0|1"}, nil, "Records: 1 Deleted: 0 Skipped: 0 Warnings: 0"}, + } + checkCases(tests, ld, c, tk, ctx, selectSQL, deleteSQL) + c.Assert(sc.WarningCount(), Equals, uint16(0)) +} + func (s *testSuite4) TestLoadData(c *C) { trivialMsg := "Records: 1 Deleted: 0 Skipped: 0 Warnings: 0" tk := testkit.NewTestKit(c, s.store) diff --git a/types/datum.go b/types/datum.go index eefc0fb79e8ef..a50ffe90931af 100644 --- a/types/datum.go +++ b/types/datum.go @@ -1432,13 +1432,38 @@ func (d *Datum) convertToMysqlFloatYear(sc *stmtctx.StatementContext, target *Fi return ret, err } +func (d *Datum) convertStringToMysqlBit(sc *stmtctx.StatementContext) (uint64, error) { + bitStr, err := ParseBitStr(BinaryLiteral(d.b).ToString()) + if err != nil { + // It cannot be converted to bit type, so we need to convert it to int type. + return BinaryLiteral(d.b).ToInt(sc) + } + return bitStr.ToInt(sc) +} + func (d *Datum) convertToMysqlBit(sc *stmtctx.StatementContext, target *FieldType) (Datum, error) { var ret Datum var uintValue uint64 var err error switch d.k { - case KindString, KindBytes: + case KindBytes: uintValue, err = BinaryLiteral(d.b).ToInt(sc) + case KindString: + // For single bit value, we take string like "true", "1" as 1, and "false", "0" as 0, + // this behavior is not documented in MySQL, but it behaves so, for more information, see issue #18681 + s := BinaryLiteral(d.b).ToString() + if target.Flen == 1 { + switch strings.ToLower(s) { + case "true", "1": + uintValue = 1 + case "false", "0": + uintValue = 0 + default: + uintValue, err = d.convertStringToMysqlBit(sc) + } + } else { + uintValue, err = d.convertStringToMysqlBit(sc) + } case KindInt64: // if input kind is int64 (signed), when trans to bit, we need to treat it as unsigned d.k = KindUint64 diff --git a/types/datum_test.go b/types/datum_test.go index 2d7e3810739cb..a92a71f21e56b 100644 --- a/types/datum_test.go +++ b/types/datum_test.go @@ -524,6 +524,29 @@ func prepareCompareDatums() ([]Datum, []Datum) { return vals, vals1 } +func (ts *testDatumSuite) TestStringToMysqlBit(c *C) { + tests := []struct { + a Datum + out []byte + }{ + {NewStringDatum("true"), []byte{1}}, + {NewStringDatum("false"), []byte{0}}, + {NewStringDatum("1"), []byte{1}}, + {NewStringDatum("0"), []byte{0}}, + {NewStringDatum("b'1'"), []byte{1}}, + {NewStringDatum("b'0'"), []byte{0}}, + } + sc := new(stmtctx.StatementContext) + sc.IgnoreTruncate = true + tp := NewFieldType(mysql.TypeBit) + tp.Flen = 1 + for _, tt := range tests { + bin, err := tt.a.convertToMysqlBit(nil, tp) + c.Assert(err, IsNil) + c.Assert(bin.b, BytesEquals, tt.out) + } +} + func BenchmarkCompareDatum(b *testing.B) { vals, vals1 := prepareCompareDatums() sc := new(stmtctx.StatementContext)