From 536dfdfe2f727b5dfb6bddb889150c3b235172ef Mon Sep 17 00:00:00 2001 From: tangenta Date: Mon, 4 Jan 2021 16:37:15 +0800 Subject: [PATCH] executor: always decode the value first and then the handle (#22073) --- executor/builder.go | 2 +- executor/clustered_index_test.go | 24 ++++++++++++++++++++++++ executor/executor_test.go | 1 + util/rowcodec/decoder.go | 14 ++++++++++---- 4 files changed, 36 insertions(+), 5 deletions(-) diff --git a/executor/builder.go b/executor/builder.go index e297ef0fd5..2958525d15 100644 --- a/executor/builder.go +++ b/executor/builder.go @@ -963,7 +963,7 @@ func (b *executorBuilder) buildUnionScanFromReader(reader Executor, v *plannerco } return x } - // If reader is union, it means a partitiont table and we should transfer as above. + // If reader is union, it means a partition table and we should transfer as above. if x, ok := reader.(*UnionExec); ok { for i, child := range x.children { x.children[i] = b.buildUnionScanFromReader(child, v) diff --git a/executor/clustered_index_test.go b/executor/clustered_index_test.go index e226e90a76..db1e0e636a 100644 --- a/executor/clustered_index_test.go +++ b/executor/clustered_index_test.go @@ -17,10 +17,12 @@ import ( . "github.com/pingcap/check" "github.com/pingcap/tidb/errno" "github.com/pingcap/tidb/store/tikv" + "github.com/pingcap/tidb/util/collate" "github.com/pingcap/tidb/util/testkit" ) type testClusteredSuite struct{ *baseTestSuite } +type testClusteredSerialSuite struct{ *testClusteredSuite } func (s *testClusteredSuite) SetUpTest(c *C) { } @@ -184,6 +186,28 @@ func (s *testClusteredSuite) TestClusteredPrefixingPrimaryKey(c *C) { tk.MustExec("admin check table t;") } +// Test for union scan in prefixed clustered index table. +// See https://github.com/pingcap/tidb/issues/22069. +func (s *testClusteredSerialSuite) TestClusteredUnionScanOnPrefixingPrimaryKey(c *C) { + originCollate := collate.NewCollationEnabled() + collate.SetNewCollationEnabledForTest(false) + defer collate.SetNewCollationEnabledForTest(originCollate) + tk := s.newTK(c) + tk.MustExec("drop table if exists t;") + tk.MustExec("create table t (col_1 varchar(255), col_2 tinyint, primary key idx_1 (col_1(1)));") + tk.MustExec("insert into t values ('aaaaa', -38);") + tk.MustExec("insert into t values ('bbbbb', -48);") + + tk.MustExec("begin PESSIMISTIC;") + tk.MustExec("update t set col_2 = 47 where col_1 in ('aaaaa') order by col_1,col_2;") + tk.MustQuery("select * from t;").Check(testkit.Rows("aaaaa 47", "bbbbb -48")) + tk.MustGetErrCode("insert into t values ('bb', 0);", errno.ErrDupEntry) + tk.MustGetErrCode("insert into t values ('aa', 0);", errno.ErrDupEntry) + tk.MustExec("commit;") + tk.MustQuery("select * from t;").Check(testkit.Rows("aaaaa 47", "bbbbb -48")) + tk.MustExec("admin check table t;") +} + func (s *testClusteredSuite) TestClusteredWithOldRowFormat(c *C) { tk := s.newTK(c) tk.Se.GetSessionVars().RowEncoder.Enable = false diff --git a/executor/executor_test.go b/executor/executor_test.go index c18655cd9b..debf33f310 100644 --- a/executor/executor_test.go +++ b/executor/executor_test.go @@ -119,6 +119,7 @@ var _ = Suite(&testSuite6{&baseTestSuite{}}) var _ = Suite(&testSuite7{&baseTestSuite{}}) var _ = Suite(&testSuite8{&baseTestSuite{}}) var _ = Suite(&testClusteredSuite{&baseTestSuite{}}) +var _ = SerialSuites(&testClusteredSerialSuite{&testClusteredSuite{&baseTestSuite{}}}) var _ = SerialSuites(&testShowStatsSuite{&baseTestSuite{}}) var _ = Suite(&testBypassSuite{}) var _ = Suite(&testUpdateSuite{}) diff --git a/util/rowcodec/decoder.go b/util/rowcodec/decoder.go index b9476eb794..8400991b56 100644 --- a/util/rowcodec/decoder.go +++ b/util/rowcodec/decoder.go @@ -222,6 +222,9 @@ func (decoder *ChunkDecoder) DecodeToChunk(rowData []byte, handle kv.Handle, chk continue } + // Only try to decode handle when there is no corresponding column in the value. + // This is because the information in handle may be incomplete in some cases. + // For example, prefixed clustered index like 'primary key(col1(1))' only store the leftmost 1 char in the handle. if decoder.tryAppendHandleColumn(colIdx, col, handle, chk) { continue } @@ -385,10 +388,6 @@ func (decoder *BytesDecoder) decodeToBytesInternal(outputOffset map[int64]int, h tp := fieldType2Flag(col.Ft.Tp, col.Ft.Flag&mysql.UnsignedFlag == 0) colID := col.ID offset := outputOffset[colID] - if decoder.tryDecodeHandle(values, offset, col, handle, cacheBytes) { - continue - } - idx, isNil, notFound := r.findColID(colID) if !notFound && !isNil { val := r.getData(idx) @@ -396,6 +395,13 @@ func (decoder *BytesDecoder) decodeToBytesInternal(outputOffset map[int64]int, h continue } + // Only try to decode handle when there is no corresponding column in the value. + // This is because the information in handle may be incomplete in some cases. + // For example, prefixed clustered index like 'primary key(col1(1))' only store the leftmost 1 char in the handle. + if decoder.tryDecodeHandle(values, offset, col, handle, cacheBytes) { + continue + } + if isNil { values[offset] = []byte{NilFlag} continue