Skip to content

Commit 977aba8

Browse files
lyzx2001ti-chi-bot
authored andcommitted
This is an automated cherry-pick of pingcap#45241
Signed-off-by: ti-chi-bot <[email protected]>
1 parent 905a155 commit 977aba8

File tree

3 files changed

+460
-0
lines changed

3 files changed

+460
-0
lines changed

br/pkg/lightning/backend/kv/BUILD.bazel

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,13 +46,22 @@ go_test(
4646
name = "kv_test",
4747
timeout = "short",
4848
srcs = [
49+
<<<<<<< HEAD
50+
=======
51+
"base_test.go",
52+
"kv2sql_test.go",
53+
>>>>>>> de99f81e580 (lightning: fix lightning failed to log encoding error (#45241))
4954
"session_internal_test.go",
5055
"session_test.go",
5156
"sql2kv_test.go",
5257
],
5358
embed = [":kv"],
5459
flaky = True,
5560
race = "on",
61+
<<<<<<< HEAD
62+
=======
63+
shard_count = 19,
64+
>>>>>>> de99f81e580 (lightning: fix lightning failed to log encoding error (#45241))
5665
deps = [
5766
"//br/pkg/lightning/common",
5867
"//br/pkg/lightning/log",

br/pkg/lightning/backend/kv/base.go

Lines changed: 376 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,376 @@
1+
// Copyright 2023 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 kv
16+
17+
import (
18+
"context"
19+
"math/rand"
20+
21+
"github.com/pingcap/errors"
22+
"github.com/pingcap/tidb/br/pkg/lightning/backend/encode"
23+
"github.com/pingcap/tidb/br/pkg/lightning/common"
24+
"github.com/pingcap/tidb/br/pkg/lightning/log"
25+
"github.com/pingcap/tidb/br/pkg/logutil"
26+
"github.com/pingcap/tidb/br/pkg/redact"
27+
"github.com/pingcap/tidb/expression"
28+
"github.com/pingcap/tidb/meta/autoid"
29+
"github.com/pingcap/tidb/parser/model"
30+
"github.com/pingcap/tidb/parser/mysql"
31+
"github.com/pingcap/tidb/sessionctx/variable"
32+
"github.com/pingcap/tidb/table"
33+
"github.com/pingcap/tidb/table/tables"
34+
"github.com/pingcap/tidb/types"
35+
"github.com/pingcap/tidb/util/chunk"
36+
"go.uber.org/zap"
37+
"go.uber.org/zap/zapcore"
38+
)
39+
40+
const (
41+
maxLogLength = 512 * 1024
42+
)
43+
44+
// ExtraHandleColumnInfo is the column info of extra handle column.
45+
var ExtraHandleColumnInfo = model.NewExtraHandleColInfo()
46+
47+
// GeneratedCol generated column info.
48+
type GeneratedCol struct {
49+
// index of the column in the table
50+
Index int
51+
Expr expression.Expression
52+
}
53+
54+
// AutoIDConverterFn is a function to convert auto id.
55+
type AutoIDConverterFn func(int64) int64
56+
57+
// RowArrayMarshaller wraps a slice of types.Datum for logging the content into zap.
58+
type RowArrayMarshaller []types.Datum
59+
60+
var kindStr = [...]string{
61+
types.KindNull: "null",
62+
types.KindInt64: "int64",
63+
types.KindUint64: "uint64",
64+
types.KindFloat32: "float32",
65+
types.KindFloat64: "float64",
66+
types.KindString: "string",
67+
types.KindBytes: "bytes",
68+
types.KindBinaryLiteral: "binary",
69+
types.KindMysqlDecimal: "decimal",
70+
types.KindMysqlDuration: "duration",
71+
types.KindMysqlEnum: "enum",
72+
types.KindMysqlBit: "bit",
73+
types.KindMysqlSet: "set",
74+
types.KindMysqlTime: "time",
75+
types.KindInterface: "interface",
76+
types.KindMinNotNull: "min",
77+
types.KindMaxValue: "max",
78+
types.KindRaw: "raw",
79+
types.KindMysqlJSON: "json",
80+
}
81+
82+
// MarshalLogArray implements the zapcore.ArrayMarshaler interface
83+
func (row RowArrayMarshaller) MarshalLogArray(encoder zapcore.ArrayEncoder) error {
84+
var totalLength = 0
85+
for _, datum := range row {
86+
kind := datum.Kind()
87+
var str string
88+
var err error
89+
switch kind {
90+
case types.KindNull:
91+
str = "NULL"
92+
case types.KindMinNotNull:
93+
str = "-inf"
94+
case types.KindMaxValue:
95+
str = "+inf"
96+
default:
97+
str, err = datum.ToString()
98+
if err != nil {
99+
return err
100+
}
101+
}
102+
if len(str) > maxLogLength {
103+
str = str[0:1024] + " (truncated)"
104+
}
105+
totalLength += len(str)
106+
if totalLength >= maxLogLength {
107+
encoder.AppendString("The row has been truncated, and the log has exited early.")
108+
return nil
109+
}
110+
if err := encoder.AppendObject(zapcore.ObjectMarshalerFunc(func(enc zapcore.ObjectEncoder) error {
111+
enc.AddString("kind", kindStr[kind])
112+
enc.AddString("val", redact.String(str))
113+
return nil
114+
})); err != nil {
115+
return err
116+
}
117+
}
118+
return nil
119+
}
120+
121+
// BaseKVEncoder encodes a row into a KV pair.
122+
type BaseKVEncoder struct {
123+
GenCols []GeneratedCol
124+
SessionCtx *Session
125+
Table table.Table
126+
Columns []*table.Column
127+
AutoRandomColID int64
128+
// convert auto id for shard rowid or auto random id base on row id generated by lightning
129+
AutoIDFn AutoIDConverterFn
130+
131+
logger *zap.Logger
132+
recordCache []types.Datum
133+
// the first auto-generated ID in the current encoder.
134+
// if there's no auto-generated id column or the column value is not auto-generated, it will be 0.
135+
LastInsertID uint64
136+
}
137+
138+
// NewBaseKVEncoder creates a new BaseKVEncoder.
139+
func NewBaseKVEncoder(config *encode.EncodingConfig) (*BaseKVEncoder, error) {
140+
meta := config.Table.Meta()
141+
cols := config.Table.Cols()
142+
se := NewSession(&config.SessionOptions, config.Logger)
143+
// Set CommonAddRecordCtx to session to reuse the slices and BufStore in AddRecord
144+
recordCtx := tables.NewCommonAddRecordCtx(len(cols))
145+
tables.SetAddRecordCtx(se, recordCtx)
146+
147+
var autoRandomColID int64
148+
autoIDFn := func(id int64) int64 { return id }
149+
if meta.ContainsAutoRandomBits() {
150+
col := common.GetAutoRandomColumn(meta)
151+
autoRandomColID = col.ID
152+
153+
shardFmt := autoid.NewShardIDFormat(&col.FieldType, meta.AutoRandomBits, meta.AutoRandomRangeBits)
154+
shard := rand.New(rand.NewSource(config.AutoRandomSeed)).Int63()
155+
autoIDFn = func(id int64) int64 {
156+
return shardFmt.Compose(shard, id)
157+
}
158+
} else if meta.ShardRowIDBits > 0 {
159+
rd := rand.New(rand.NewSource(config.AutoRandomSeed)) // nolint:gosec
160+
mask := int64(1)<<meta.ShardRowIDBits - 1
161+
shift := autoid.RowIDBitLength - meta.ShardRowIDBits - 1
162+
autoIDFn = func(id int64) int64 {
163+
rd.Seed(id)
164+
shardBits := (int64(rd.Uint32()) & mask) << shift
165+
return shardBits | id
166+
}
167+
}
168+
169+
// collect expressions for evaluating stored generated columns
170+
genCols, err := CollectGeneratedColumns(se, meta, cols)
171+
if err != nil {
172+
return nil, errors.Annotate(err, "failed to parse generated column expressions")
173+
}
174+
return &BaseKVEncoder{
175+
GenCols: genCols,
176+
SessionCtx: se,
177+
Table: config.Table,
178+
Columns: cols,
179+
AutoRandomColID: autoRandomColID,
180+
AutoIDFn: autoIDFn,
181+
logger: config.Logger.Logger,
182+
}, nil
183+
}
184+
185+
// GetOrCreateRecord returns a record slice from the cache if possible, otherwise creates a new one.
186+
func (e *BaseKVEncoder) GetOrCreateRecord() []types.Datum {
187+
if e.recordCache != nil {
188+
return e.recordCache
189+
}
190+
return make([]types.Datum, 0, len(e.Columns)+1)
191+
}
192+
193+
// Record2KV converts a row into a KV pair.
194+
func (e *BaseKVEncoder) Record2KV(record, originalRow []types.Datum, rowID int64) (*Pairs, error) {
195+
_, err := e.Table.AddRecord(e.SessionCtx, record)
196+
if err != nil {
197+
e.logger.Error("kv encode failed",
198+
zap.Array("originalRow", RowArrayMarshaller(originalRow)),
199+
zap.Array("convertedRow", RowArrayMarshaller(record)),
200+
log.ShortError(err),
201+
)
202+
return nil, errors.Trace(err)
203+
}
204+
kvPairs := e.SessionCtx.TakeKvPairs()
205+
for i := 0; i < len(kvPairs.Pairs); i++ {
206+
var encoded [9]byte // The max length of encoded int64 is 9.
207+
kvPairs.Pairs[i].RowID = common.EncodeIntRowIDToBuf(encoded[:0], rowID)
208+
}
209+
e.recordCache = record[:0]
210+
return kvPairs, nil
211+
}
212+
213+
// ProcessColDatum processes the datum of a column.
214+
func (e *BaseKVEncoder) ProcessColDatum(col *table.Column, rowID int64, inputDatum *types.Datum) (types.Datum, error) {
215+
value, err := e.getActualDatum(col, rowID, inputDatum)
216+
if err != nil {
217+
return value, err
218+
}
219+
220+
if e.IsAutoRandomCol(col.ToInfo()) {
221+
meta := e.Table.Meta()
222+
shardFmt := autoid.NewShardIDFormat(&col.FieldType, meta.AutoRandomBits, meta.AutoRandomRangeBits)
223+
// this allocator is the same as the allocator in table importer, i.e. PanickingAllocators. below too.
224+
alloc := e.Table.Allocators(e.SessionCtx).Get(autoid.AutoRandomType)
225+
if err := alloc.Rebase(context.Background(), value.GetInt64()&shardFmt.IncrementalMask(), false); err != nil {
226+
return value, errors.Trace(err)
227+
}
228+
}
229+
if IsAutoIncCol(col.ToInfo()) {
230+
alloc := e.Table.Allocators(e.SessionCtx).Get(autoid.AutoIncrementType)
231+
if err := alloc.Rebase(context.Background(), GetAutoRecordID(value, &col.FieldType), false); err != nil {
232+
return value, errors.Trace(err)
233+
}
234+
}
235+
return value, nil
236+
}
237+
238+
func (e *BaseKVEncoder) getActualDatum(col *table.Column, rowID int64, inputDatum *types.Datum) (types.Datum, error) {
239+
var (
240+
value types.Datum
241+
err error
242+
)
243+
244+
isBadNullValue := false
245+
if inputDatum != nil {
246+
value, err = table.CastValue(e.SessionCtx, *inputDatum, col.ToInfo(), false, false)
247+
if err != nil {
248+
return value, err
249+
}
250+
if err := col.CheckNotNull(&value, 0); err == nil {
251+
return value, nil // the most normal case
252+
}
253+
isBadNullValue = true
254+
}
255+
// handle special values
256+
switch {
257+
case IsAutoIncCol(col.ToInfo()):
258+
// we still need a conversion, e.g. to catch overflow with a TINYINT column.
259+
value, err = table.CastValue(e.SessionCtx,
260+
types.NewIntDatum(rowID), col.ToInfo(), false, false)
261+
if err == nil && e.LastInsertID == 0 {
262+
e.LastInsertID = value.GetUint64()
263+
}
264+
case e.IsAutoRandomCol(col.ToInfo()):
265+
var val types.Datum
266+
realRowID := e.AutoIDFn(rowID)
267+
if mysql.HasUnsignedFlag(col.GetFlag()) {
268+
val = types.NewUintDatum(uint64(realRowID))
269+
} else {
270+
val = types.NewIntDatum(realRowID)
271+
}
272+
value, err = table.CastValue(e.SessionCtx, val, col.ToInfo(), false, false)
273+
if err == nil && e.LastInsertID == 0 {
274+
e.LastInsertID = value.GetUint64()
275+
}
276+
case col.IsGenerated():
277+
// inject some dummy value for gen col so that MutRowFromDatums below sees a real value instead of nil.
278+
// if MutRowFromDatums sees a nil it won't initialize the underlying storage and cause SetDatum to panic.
279+
value = types.GetMinValue(&col.FieldType)
280+
case isBadNullValue:
281+
err = col.HandleBadNull(&value, e.SessionCtx.Vars.StmtCtx, 0)
282+
default:
283+
// copy from the following GetColDefaultValue function, when this is true it will use getColDefaultExprValue
284+
if col.DefaultIsExpr {
285+
// the expression rewriter requires a non-nil TxnCtx.
286+
e.SessionCtx.Vars.TxnCtx = new(variable.TransactionContext)
287+
defer func() {
288+
e.SessionCtx.Vars.TxnCtx = nil
289+
}()
290+
}
291+
value, err = table.GetColDefaultValue(e.SessionCtx, col.ToInfo())
292+
}
293+
return value, err
294+
}
295+
296+
// IsAutoRandomCol checks if the column is auto random column.
297+
func (e *BaseKVEncoder) IsAutoRandomCol(col *model.ColumnInfo) bool {
298+
return e.Table.Meta().ContainsAutoRandomBits() && col.ID == e.AutoRandomColID
299+
}
300+
301+
// EvalGeneratedColumns evaluates the generated columns.
302+
func (e *BaseKVEncoder) EvalGeneratedColumns(record []types.Datum,
303+
cols []*table.Column) (errCol *model.ColumnInfo, err error) {
304+
return evalGeneratedColumns(e.SessionCtx, record, cols, e.GenCols)
305+
}
306+
307+
// LogKVConvertFailed logs the error when converting a row to KV pair failed.
308+
func (e *BaseKVEncoder) LogKVConvertFailed(row []types.Datum, j int, colInfo *model.ColumnInfo, err error) error {
309+
var original types.Datum
310+
if 0 <= j && j < len(row) {
311+
original = row[j]
312+
row = row[j : j+1]
313+
}
314+
315+
e.logger.Error("kv convert failed",
316+
zap.Array("original", RowArrayMarshaller(row)),
317+
zap.Int("originalCol", j),
318+
zap.String("colName", colInfo.Name.O),
319+
zap.Stringer("colType", &colInfo.FieldType),
320+
log.ShortError(err),
321+
)
322+
323+
if len(original.GetString()) >= maxLogLength {
324+
originalPrefix := original.GetString()[0:1024] + " (truncated)"
325+
e.logger.Error("failed to convert kv value", logutil.RedactAny("origVal", originalPrefix),
326+
zap.Stringer("fieldType", &colInfo.FieldType), zap.String("column", colInfo.Name.O),
327+
zap.Int("columnID", j+1))
328+
} else {
329+
e.logger.Error("failed to convert kv value", logutil.RedactAny("origVal", original.GetValue()),
330+
zap.Stringer("fieldType", &colInfo.FieldType), zap.String("column", colInfo.Name.O),
331+
zap.Int("columnID", j+1))
332+
}
333+
return errors.Annotatef(
334+
err,
335+
"failed to cast value as %s for column `%s` (#%d)", &colInfo.FieldType, colInfo.Name.O, j+1,
336+
)
337+
}
338+
339+
// LogEvalGenExprFailed logs the error when evaluating the generated column expression failed.
340+
func (e *BaseKVEncoder) LogEvalGenExprFailed(row []types.Datum, colInfo *model.ColumnInfo, err error) error {
341+
e.logger.Error("kv convert failed: cannot evaluate generated column expression",
342+
zap.Array("original", RowArrayMarshaller(row)),
343+
zap.String("colName", colInfo.Name.O),
344+
log.ShortError(err),
345+
)
346+
347+
return errors.Annotatef(
348+
err,
349+
"failed to evaluate generated column expression for column `%s`",
350+
colInfo.Name.O,
351+
)
352+
}
353+
354+
// TruncateWarns resets the warnings in session context.
355+
func (e *BaseKVEncoder) TruncateWarns() {
356+
e.SessionCtx.Vars.StmtCtx.TruncateWarnings(0)
357+
}
358+
359+
func evalGeneratedColumns(se *Session, record []types.Datum, cols []*table.Column,
360+
genCols []GeneratedCol) (errCol *model.ColumnInfo, err error) {
361+
mutRow := chunk.MutRowFromDatums(record)
362+
for _, gc := range genCols {
363+
col := cols[gc.Index].ToInfo()
364+
evaluated, err := gc.Expr.Eval(mutRow.ToRow())
365+
if err != nil {
366+
return col, err
367+
}
368+
value, err := table.CastValue(se, evaluated, col, false, false)
369+
if err != nil {
370+
return col, err
371+
}
372+
mutRow.SetDatum(gc.Index, value)
373+
record[gc.Index] = value
374+
}
375+
return nil, nil
376+
}

0 commit comments

Comments
 (0)