// Copyright 2018 PingCAP, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package executor

import (
	"bytes"
	"context"
	"fmt"
	"math"
	"sync"
	"time"

	"github.com/pingcap/errors"
	"github.com/pingcap/tidb/pkg/ddl"
	"github.com/pingcap/tidb/pkg/executor/internal/exec"
	"github.com/pingcap/tidb/pkg/expression"
	"github.com/pingcap/tidb/pkg/kv"
	"github.com/pingcap/tidb/pkg/meta/autoid"
	"github.com/pingcap/tidb/pkg/meta/model"
	"github.com/pingcap/tidb/pkg/parser/ast"
	"github.com/pingcap/tidb/pkg/parser/mysql"
	"github.com/pingcap/tidb/pkg/planner/core"
	"github.com/pingcap/tidb/pkg/sessionctx"
	"github.com/pingcap/tidb/pkg/sessionctx/variable"
	"github.com/pingcap/tidb/pkg/sessiontxn"
	"github.com/pingcap/tidb/pkg/table"
	"github.com/pingcap/tidb/pkg/table/tables"
	"github.com/pingcap/tidb/pkg/tablecodec"
	"github.com/pingcap/tidb/pkg/types"
	"github.com/pingcap/tidb/pkg/util/chunk"
	"github.com/pingcap/tidb/pkg/util/collate"
	"github.com/pingcap/tidb/pkg/util/dbterror"
	"github.com/pingcap/tidb/pkg/util/dbterror/exeerrors"
	"github.com/pingcap/tidb/pkg/util/execdetails"
	"github.com/pingcap/tidb/pkg/util/logutil"
	"github.com/pingcap/tidb/pkg/util/memory"
	"github.com/pingcap/tidb/pkg/util/tracing"
	"github.com/tikv/client-go/v2/txnkv/txnsnapshot"
	"go.uber.org/zap"
	"golang.org/x/sync/errgroup"
)

// InsertValues is the data to insert.
// nolint:structcheck
type InsertValues struct {
	exec.BaseExecutor

	rowCount                    uint64
	curBatchCnt                 uint64
	maxRowsInBatch              uint64
	lastInsertID                uint64
	recordRUV2RowsColMultiply   bool
	ruv2RecordedRowsColMultiply int64

	SelectExec exec.Executor

	Table   table.Table
	Columns []*ast.ColumnName
	Lists   [][]expression.Expression

	GenExprs []expression.Expression

	insertColumns []*table.Column

	// colDefaultVals is used to store casted default value.
	// Because not every insert statement needs colDefaultVals, so we will init the buffer lazily.
	colDefaultVals  []defaultVal
	evalBuffer      chunk.MutRow
	evalBufferTypes []*types.FieldType

	allAssignmentsAreConstant bool

	hasRefCols     bool
	hasExtraHandle bool

	// lazyFillAutoID indicates whatever had been filled the autoID lazily to datum. This is used for being compatible with JDBC using getGeneratedKeys().
	// `insert|replace values` can guarantee consecutive autoID in a batch.
	// Other statements like `insert select from` don't guarantee consecutive autoID.
	// https://dev.mysql.com/doc/refman/8.0/en/innodb-auto-increment-handling.html
	lazyFillAutoID bool
	memTracker     *memory.Tracker

	rowLen int

	stats *InsertRuntimeStat

	// fkChecks contains the foreign key checkers.
	fkChecks   []*FKCheckExec
	fkCascades []*FKCascadeExec

	ignoreErr bool
}

func (e *InsertValues) rowsColMultiply() int64 {
	colCount := len(e.insertColumns)
	if e.rowCount == 0 || colCount == 0 {
		return 0
	}

	const maxInt64 = uint64(1<<63 - 1)
	if e.rowCount > maxInt64/uint64(colCount) {
		return int64(maxInt64)
	}
	return int64(e.rowCount * uint64(colCount))
}

func (e *InsertValues) recordRowsColMultiply2RUV2Metrics() {
	if !e.recordRUV2RowsColMultiply {
		return
	}
	current := e.rowsColMultiply()
	delta := current - e.ruv2RecordedRowsColMultiply
	if delta <= 0 {
		return
	}
	recordInsertRowsColMultiply2Metrics(e.Ctx().GetSessionVars(), delta)
	e.ruv2RecordedRowsColMultiply = current
}

type defaultVal struct {
	val types.Datum
	// valid indicates whether the val is evaluated. We evaluate the default value lazily.
	valid bool
}

type insertCommon interface {
	insertCommon() *InsertValues
	exec(ctx context.Context, rows [][]types.Datum) error
}

func (e *InsertValues) insertCommon() *InsertValues {
	return e
}

func (*InsertValues) exec(context.Context, [][]types.Datum) error {
	panic("derived should overload exec function")
}

// initInsertColumns sets the explicitly specified columns of an insert statement. There are three cases:
// There are three types of insert statements:
// 1 insert ... values(...)  --> name type column
// 2 insert ... set x=y...   --> set type column
// 3 insert ... (select ..)  --> name type column
// set type is converted to name type in the optimizer
// See https://dev.mysql.com/doc/refman/5.7/en/insert.html
func (e *InsertValues) initInsertColumns() error {
	var cols []*table.Column
	var missingColIdx int
	var err error

	tableCols := e.Table.Cols()

	if len(e.Columns) > 0 {
		// Process `name` and `set` type column.
		columns := make([]string, 0, len(e.Columns))
		for _, v := range e.Columns {
			columns = append(columns, v.Name.L)
		}
		cols, missingColIdx = table.FindColumns(tableCols, columns, e.Table.Meta().PKIsHandle)
		if missingColIdx >= 0 {
			return errors.Errorf(
				"INSERT INTO %s: unknown column %s",
				e.Table.Meta().Name.O, e.Columns[missingColIdx].Name.O,
			)
		}
	} else {
		// If e.Columns are empty, use all columns instead.
		cols = tableCols
	}
	for _, col := range cols {
		if !col.IsGenerated() {
			e.insertColumns = append(e.insertColumns, col)
		}
		if col.Name.L == model.ExtraHandleName.L {
			if !e.Ctx().GetSessionVars().AllowWriteRowID {
				return errors.Errorf("insert, update and replace statements for _tidb_rowid are not supported")
			}
			e.hasExtraHandle = true
		}
	}

	// Check column whether is specified only once.
	err = table.CheckOnce(cols)
	if err != nil {
		return err
	}
	return nil
}

func (e *InsertValues) initEvalBuffer() {
	numCols := len(e.Table.Cols())
	if e.hasExtraHandle {
		numCols++
	}
	e.evalBufferTypes = make([]*types.FieldType, numCols)
	for i, col := range e.Table.Cols() {
		e.evalBufferTypes[i] = &(col.FieldType)
	}
	if e.hasExtraHandle {
		e.evalBufferTypes[len(e.evalBufferTypes)-1] = types.NewFieldType(mysql.TypeLonglong)
	}
	e.evalBuffer = chunk.MutRowFromTypes(e.evalBufferTypes)
}

func (e *InsertValues) lazilyInitColDefaultValBuf() (ok bool) {
	if e.colDefaultVals != nil {
		return true
	}

	// only if values count of insert statement is more than one, use colDefaultVals to store
	// casted default values has benefits.
	if len(e.Lists) > 1 {
		e.colDefaultVals = make([]defaultVal, len(e.Table.Cols()))
		return true
	}

	return false
}

// insertRows processes `insert|replace into values ()` or `insert|replace into set x=y`
func insertRows(ctx context.Context, base insertCommon) (err error) {
	e := base.insertCommon()
	sessVars := e.Ctx().GetSessionVars()
	batchSize := sessVars.DMLBatchSize
	batchInsert := sessVars.BatchInsert && !sessVars.InTxn() && variable.EnableBatchDML.Load() && batchSize > 0

	e.lazyFillAutoID = true
	evalRowFunc := e.fastEvalRow
	if !e.allAssignmentsAreConstant {
		evalRowFunc = e.evalRow
	}

	rows := make([][]types.Datum, 0, len(e.Lists))
	memUsageOfRows := int64(0)
	memTracker := e.memTracker
	for i, list := range e.Lists {
		e.rowCount++
		var row []types.Datum
		row, err = evalRowFunc(ctx, list, i)
		if err != nil {
			return err
		}
		rows = append(rows, row)
		if batchInsert && e.rowCount%uint64(batchSize) == 0 {
			memUsageOfRows = types.EstimatedMemUsage(rows[0], len(rows))
			memTracker.Consume(memUsageOfRows)
			// Before batch insert, fill the batch allocated autoIDs.
			rows, err = e.lazyAdjustAutoIncrementDatum(ctx, rows)
			if err != nil {
				return err
			}
			rows, err = e.fillAutoEmbeddingDatum(ctx, rows)
			if err != nil {
				return err
			}
			if err = base.exec(ctx, rows); err != nil {
				return err
			}
			e.recordRowsColMultiply2RUV2Metrics()
			rows = rows[:0]
			memTracker.Consume(-memUsageOfRows)
			memUsageOfRows = 0
			if err = e.doBatchInsert(ctx); err != nil {
				return err
			}
		}
	}
	if len(rows) != 0 {
		memUsageOfRows = types.EstimatedMemUsage(rows[0], len(rows))
		memTracker.Consume(memUsageOfRows)
	}
	// Fill the batch allocated autoIDs.
	rows, err = e.lazyAdjustAutoIncrementDatum(ctx, rows)
	if err != nil {
		return err
	}
	rows, err = e.fillAutoEmbeddingDatum(ctx, rows)
	if err != nil {
		return err
	}
	err = base.exec(ctx, rows)
	if err != nil {
		return err
	}
	e.recordRowsColMultiply2RUV2Metrics()
	memTracker.Consume(-memUsageOfRows)
	return nil
}

func completeInsertErr(col *model.ColumnInfo, val *types.Datum, rowIdx int, err error) error {
	var (
		colTp   byte
		colName string
	)
	if col != nil {
		colTp = col.GetType()
		colName = col.Name.String()
	}

	if types.ErrDataTooLong.Equal(err) {
		err = resetErrDataTooLong(colName, rowIdx+1, err)
	} else if types.ErrOverflow.Equal(err) {
		err = types.ErrWarnDataOutOfRange.FastGenByArgs(colName, rowIdx+1)
	} else if types.ErrTruncated.Equal(err) {
		err = types.ErrTruncated.FastGenByArgs(colName, rowIdx+1)
	} else if types.ErrTruncatedWrongVal.Equal(err) && (colTp == mysql.TypeDuration || colTp == mysql.TypeDatetime || colTp == mysql.TypeDate || colTp == mysql.TypeTimestamp) {
		valStr, err1 := val.ToString()
		if err1 != nil {
			logutil.BgLogger().Debug("time truncated error", zap.Error(err1))
		}
		err = exeerrors.ErrTruncateWrongInsertValue.FastGenByArgs(types.TypeStr(colTp), valStr, colName, rowIdx+1)
	} else if types.ErrTruncatedWrongVal.Equal(err) || types.ErrWrongValue.Equal(err) {
		valStr, err1 := val.ToString()
		if err1 != nil {
			logutil.BgLogger().Debug("truncated/wrong value error", zap.Error(err1))
		}
		err = table.ErrTruncatedWrongValueForField.FastGenByArgs(types.TypeStr(colTp), valStr, colName, rowIdx+1)
	} else if types.ErrWarnDataOutOfRange.Equal(err) {
		err = types.ErrWarnDataOutOfRange.FastGenByArgs(colName, rowIdx+1)
	}
	return err
}

func completeLoadErr(col *model.ColumnInfo, rowIdx int, err error) error {
	var (
		colName string
	)
	if col != nil {
		colName = col.Name.String()
	}

	if types.ErrDataTooLong.Equal(err) {
		err = types.ErrTruncated.FastGen("Data truncated for column '%v' at row %v", colName, rowIdx)
	}
	return err
}

func (e *InsertValues) handleErr(col *table.Column, val *types.Datum, rowIdx int, err error) error {
	if err == nil {
		return nil
	}

	// Convert the error with full messages.
	var c *model.ColumnInfo
	if col != nil {
		c = col.ColumnInfo
	}
	if e.Ctx().GetSessionVars().StmtCtx.InLoadDataStmt {
		err = completeLoadErr(c, rowIdx, err)
	} else {
		err = completeInsertErr(c, val, rowIdx, err)
	}
	if col != nil && col.GetType() == mysql.TypeTimestamp &&
		types.ErrTimestampInDSTTransition.Equal(err) {
		newErr := exeerrors.ErrTruncateWrongInsertValue.FastGenByArgs(types.TypeStr(col.GetType()), val.GetString(), col.Name.O, rowIdx+1)
		// IGNORE takes precedence over STRICT mode.
		if !e.ignoreErr && e.Ctx().GetSessionVars().SQLMode.HasStrictMode() {
			return newErr
		}
		// timestamp already adjusted to end of DST transition, convert error to warning
		e.Ctx().GetSessionVars().StmtCtx.AppendWarning(newErr)
		return nil
	}

	// TODO: should not filter all types of errors here.
	if err != nil {
		ec := e.Ctx().GetSessionVars().StmtCtx.ErrCtx()
		return errors.AddStack(ec.HandleErrorWithAlias(kv.ErrKeyExists, err, err))
	}
	return nil
}

// evalRow evaluates a to-be-inserted row. The value of the column may base on another column,
// so we use setValueForRefColumn to fill the empty row some default values when needFillDefaultValues is true.
func (e *InsertValues) evalRow(ctx context.Context, list []expression.Expression, rowIdx int) ([]types.Datum, error) {
	rowLen := len(e.Table.Cols())
	if e.hasExtraHandle {
		rowLen++
	}
	row := make([]types.Datum, rowLen)
	hasValue := make([]bool, rowLen)

	// For statements like `insert into t set a = b + 1`.
	if e.hasRefCols {
		if err := e.setValueForRefColumn(row, hasValue); err != nil {
			return nil, err
		}
	}

	e.evalBuffer.SetDatums(row...)
	sctx := e.Ctx()
	evalCtx := sctx.GetExprCtx().GetEvalCtx()
	sc := sctx.GetSessionVars().StmtCtx
	warnCnt := int(sc.WarningCount())
	for i, expr := range list {
		val, err := expr.Eval(evalCtx, e.evalBuffer.ToRow())
		if err != nil {
			return nil, err
		}
		val1, err := table.CastValue(sctx, val, e.insertColumns[i].ToInfo(), false, false)
		if err = e.handleErr(e.insertColumns[i], &val, rowIdx, err); err != nil {
			return nil, err
		}
		if newWarnings := sc.TruncateWarnings(warnCnt); len(newWarnings) > 0 {
			for k := range newWarnings {
				newWarnings[k].Err = completeInsertErr(e.insertColumns[i].ColumnInfo, &val, rowIdx, newWarnings[k].Err)
			}
			sc.AppendWarnings(newWarnings)
			warnCnt += len(newWarnings)
		}

		offset := e.insertColumns[i].Offset
		val1.Copy(&row[offset])
		hasValue[offset] = true
		e.evalBuffer.SetDatum(offset, val1)
	}
	// Row may lack of generated column, autoIncrement column, empty column here.
	return e.fillRow(ctx, row, hasValue, rowIdx)
}

var emptyRow chunk.Row

func (e *InsertValues) fastEvalRow(ctx context.Context, list []expression.Expression, rowIdx int) (
	[]types.Datum, error,
) {
	rowLen := len(e.Table.Cols())
	if e.hasExtraHandle {
		rowLen++
	}
	row := make([]types.Datum, rowLen)
	hasValue := make([]bool, rowLen)
	sctx := e.Ctx()
	evalCtx := sctx.GetExprCtx().GetEvalCtx()
	sc := sctx.GetSessionVars().StmtCtx
	warnCnt := int(sc.WarningCount())
	for i, expr := range list {
		con := expr.(*expression.Constant)
		val, err := con.Eval(evalCtx, emptyRow)
		if err = e.handleErr(e.insertColumns[i], &val, rowIdx, err); err != nil {
			return nil, err
		}
		val1, err := table.CastValue(sctx, val, e.insertColumns[i].ToInfo(), false, false)
		if err = e.handleErr(e.insertColumns[i], &val, rowIdx, err); err != nil {
			return nil, err
		}
		if newWarnings := sc.TruncateWarnings(warnCnt); len(newWarnings) > 0 {
			for k := range newWarnings {
				newWarnings[k].Err = completeInsertErr(e.insertColumns[i].ColumnInfo, &val, rowIdx, newWarnings[k].Err)
			}
			sc.AppendWarnings(newWarnings)
			warnCnt += len(newWarnings)
		}
		offset := e.insertColumns[i].Offset
		row[offset], hasValue[offset] = val1, true
	}
	return e.fillRow(ctx, row, hasValue, rowIdx)
}

// setValueForRefColumn set some default values for the row to eval the row value with other columns,
// it follows these rules:
//  1. for nullable and no default value column, use NULL.
//  2. for nullable and have default value column, use it's default value.
//  3. for not null column, use zero value even in strict mode.
//  4. for auto_increment column, use zero value.
//  5. for generated column, use NULL.
func (e *InsertValues) setValueForRefColumn(row []types.Datum, hasValue []bool) error {
	for i, c := range e.Table.Cols() {
		d, err := e.getColDefaultValue(i, c)
		if err == nil {
			row[i] = d
			if !mysql.HasAutoIncrementFlag(c.GetFlag()) {
				// It is an interesting behavior in MySQL.
				// If the value of auto ID is not explicit, MySQL use 0 value for auto ID when it is
				// evaluated by another column, but it should be used once only.
				// When we fill it as an auto ID column, it should be set as it used to be.
				// So just keep `hasValue` false for auto ID, and the others set true.
				hasValue[c.Offset] = true
			}
		} else if table.ErrNoDefaultValue.Equal(err) {
			row[i] = table.GetZeroValue(c.ToInfo())
			hasValue[c.Offset] = false
		} else if e.handleErr(c, &d, 0, err) != nil {
			return err
		}
	}
	return nil
}

func insertRowsFromSelect(ctx context.Context, base insertCommon) error {
	// process `insert|replace into ... select ... from ...`
	e := base.insertCommon()
	selectExec := e.Children(0)
	fields := exec.RetTypes(selectExec)
	chk := exec.TryNewCacheChunk(selectExec)
	iter := chunk.NewIterator4Chunk(chk)
	rows := make([][]types.Datum, 0, chk.Capacity())

	sessVars := e.Ctx().GetSessionVars()
	batchSize := sessVars.DMLBatchSize
	batchInsert := sessVars.BatchInsert && !sessVars.InTxn() && variable.EnableBatchDML.Load() && batchSize > 0
	memUsageOfRows := int64(0)
	memUsageOfExtraCols := int64(0)
	memTracker := e.memTracker
	extraColsInSel := make([][]types.Datum, 0, chk.Capacity())
	// In order to ensure the correctness of the `transaction write throughput` SLI statistics,
	// just ignore the transaction which contain `insert|replace into ... select ... from ...` statement.
	e.Ctx().GetTxnWriteThroughputSLI().SetInvalid()
	for {
		err := exec.Next(ctx, selectExec, chk)
		if err != nil {
			return err
		}
		if chk.NumRows() == 0 {
			break
		}
		chkMemUsage := chk.MemoryUsage()
		memTracker.Consume(chkMemUsage)
		var totalMemDelta int64
		for innerChunkRow := iter.Begin(); innerChunkRow != iter.End(); innerChunkRow = iter.Next() {
			innerRow := innerChunkRow.GetDatumRow(fields)
			e.rowCount++
			row, err := e.getRow(ctx, innerRow)
			if err != nil {
				return err
			}
			extraColsInSel = append(extraColsInSel, innerRow[e.rowLen:])
			rows = append(rows, row)
			if batchInsert && e.rowCount%uint64(batchSize) == 0 {
				memUsageOfRows = types.EstimatedMemUsage(rows[0], len(rows))
				memUsageOfExtraCols = types.EstimatedMemUsage(extraColsInSel[0], len(extraColsInSel))
				totalMemDelta += memUsageOfRows + memUsageOfExtraCols
				e.Ctx().GetSessionVars().CurrInsertBatchExtraCols = extraColsInSel
				rows, err = e.fillAutoEmbeddingDatum(ctx, rows)
				if err != nil {
					return err
				}
				if err = base.exec(ctx, rows); err != nil {
					return err
				}
				e.recordRowsColMultiply2RUV2Metrics()
				rows = rows[:0]
				extraColsInSel = extraColsInSel[:0]
				totalMemDelta += -memUsageOfRows - memUsageOfExtraCols
				memUsageOfRows = 0
				if err = e.doBatchInsert(ctx); err != nil {
					return err
				}
			}
		}
		memTracker.Consume(totalMemDelta)

		if len(rows) != 0 {
			memUsageOfRows = types.EstimatedMemUsage(rows[0], len(rows))
			memUsageOfExtraCols = types.EstimatedMemUsage(extraColsInSel[0], len(extraColsInSel))
			memTracker.Consume(memUsageOfRows + memUsageOfExtraCols)
			e.Ctx().GetSessionVars().CurrInsertBatchExtraCols = extraColsInSel
		}
		rows, err = e.fillAutoEmbeddingDatum(ctx, rows)
		if err != nil {
			return err
		}
		err = base.exec(ctx, rows)
		if err != nil {
			return err
		}
		e.recordRowsColMultiply2RUV2Metrics()
		rows = rows[:0]
		extraColsInSel = extraColsInSel[:0]
		memTracker.Consume(-memUsageOfRows - memUsageOfExtraCols - chkMemUsage)
	}
	return nil
}

func (e *InsertValues) doBatchInsert(ctx context.Context) error {
	e.Ctx().StmtCommit(ctx)
	if err := sessiontxn.NewTxnInStmt(ctx, e.Ctx()); err != nil {
		// We should return a special error for batch insert.
		return exeerrors.ErrBatchInsertFail.GenWithStack("BatchInsert failed with error: %v", err)
	}
	return nil
}

// getRow gets the row which from `insert into select from` or `load data`.
// The input values from these two statements are datums instead of
// expressions which are used in `insert into set x=y`.
func (e *InsertValues) getRow(ctx context.Context, vals []types.Datum) ([]types.Datum, error) {
	row := make([]types.Datum, len(e.Table.Cols()))
	hasValue := make([]bool, len(e.Table.Cols()))
	sc := e.Ctx().GetSessionVars().StmtCtx
	warnCnt := int(sc.WarningCount())

	inLoadData := e.Ctx().GetSessionVars().StmtCtx.InLoadDataStmt

	for i := 0; i < e.rowLen; i++ {
		col := e.insertColumns[i].ToInfo()
		casted, err := table.CastValue(e.Ctx(), vals[i], col, false, false)
		if newErr := e.handleErr(e.insertColumns[i], &vals[i], int(e.rowCount), err); newErr != nil {
			if inLoadData {
				return nil, newErr
			}
			return nil, err
		}

		offset := e.insertColumns[i].Offset
		row[offset] = casted
		hasValue[offset] = true

		if inLoadData {
			if newWarnings := sc.TruncateWarnings(warnCnt); len(newWarnings) > 0 {
				for k := range newWarnings {
					newWarnings[k].Err = completeLoadErr(col, int(e.rowCount), newWarnings[k].Err)
				}
				sc.AppendWarnings(newWarnings)
				warnCnt += len(newWarnings)
			}
		}
	}

	return e.fillRow(ctx, row, hasValue, 0)
}

// getColDefaultValue gets the column default value.
func (e *InsertValues) getColDefaultValue(idx int, col *table.Column) (d types.Datum, err error) {
	if !col.DefaultIsExpr && e.colDefaultVals != nil && e.colDefaultVals[idx].valid {
		return e.colDefaultVals[idx].val, nil
	}

	var defaultVal types.Datum
	if col.DefaultIsExpr && col.DefaultExpr != nil {
		defaultVal, err = table.EvalColDefaultExpr(e.Ctx().GetExprCtx(), col.ToInfo(), col.DefaultExpr)
	} else {
		if err := table.CheckNoDefaultValueForInsert(e.Ctx().GetSessionVars().StmtCtx, col.ToInfo()); err != nil {
			return types.Datum{}, err
		}
		defaultVal, err = table.GetColDefaultValue(e.Ctx().GetExprCtx(), col.ToInfo())
	}
	if err != nil {
		return types.Datum{}, err
	}
	if initialized := e.lazilyInitColDefaultValBuf(); initialized && !col.DefaultIsExpr {
		e.colDefaultVals[idx].val = defaultVal
		e.colDefaultVals[idx].valid = true
	}

	return defaultVal, nil
}

// CSE Only: This function evaluates the values of the auto-embedding columns by batch, and
// then fills the values into the rows.
// This function must be performed after other default columns are filled, so that the
// auto-embedding columns can be evaluated based on the other columns' values.
func (e *InsertValues) fillAutoEmbeddingDatum(ctx context.Context, rows [][]types.Datum) (
	[][]types.Datum, error,
) {
	// Fast path: If there is no auto-embedding generated column at all, then we can skip this step.
	{
		if len(e.GenExprs) == 0 {
			return rows, nil
		}
		hasAutoEmbeddingExpr := false
		for _, col := range e.Table.Cols() {
			if col.IsGenerated() && expression.IsAutoEmbedFnCallAST(col.GeneratedExpr.Internal()) {
				hasAutoEmbeddingExpr = true
				break
			}
		}
		if !hasAutoEmbeddingExpr {
			return rows, nil
		}
	}

	rowCntInLoadData := uint64(0)
	if e.Ctx().GetSessionVars().StmtCtx.InLoadDataStmt {
		rowCntInLoadData = e.rowCount
	}

	// Note that EMBED_TEXT() will batch 16 values together, so here we are allowing
	// 50 concurrent requests.
	// TODO: Currently each expr takes one goroutine. We may want to optimize it later.
	eg, egCtx := errgroup.WithContext(ctx)
	eg.SetLimit(16 * 50)

	rowMu := &sync.Mutex{}

	for rowIdx, row := range rows {
		gIdx := -1
		for colIdx, gCol := range e.Table.Cols() {
			if !gCol.IsGenerated() {
				continue
			}
			gIdx++
			if !expression.IsAutoEmbedFnCallAST(gCol.GeneratedExpr.Internal()) {
				continue
			}

			// gIdx will be modified before goroutine starts, so we need to capture it into
			// a new variable.
			genExpr := e.GenExprs[gIdx]

			eg.Go(func() error {
				// The group will be cancelled if preior goroutine was already failed.
				// In this case no need to start more work.
				if egCtx.Err() != nil {
					return context.Cause(egCtx)
				}
				sctx := e.Ctx()
				evalCtx := sctx.GetExprCtx().GetEvalCtx()
				val, err := genExpr.Eval(evalCtx, chunk.MutRowFromDatums(row).ToRow())
				if e.Ctx().GetSessionVars().StmtCtx.HandleTruncate(err) != nil {
					return err
				}
				rowMu.Lock()
				rows[rowIdx][colIdx], err = table.CastValue(e.Ctx(), val, gCol.ToInfo(), false, false)
				rowMu.Unlock()
				if err = e.handleErr(gCol, &val, rowIdx, err); err != nil {
					return err
				}
				return gCol.HandleBadNull(e.Ctx().GetSessionVars().StmtCtx.ErrCtx(), &row[colIdx], rowCntInLoadData)
			})
		}
	}

	err := eg.Wait()
	if err != nil {
		return nil, err
	}

	return rows, nil
}

// fillColValue fills the column value if it is not set in the insert statement.
func (e *InsertValues) fillColValue(
	ctx context.Context,
	datum types.Datum,
	idx int,
	column *table.Column,
	hasValue bool,
) (types.Datum, error) {
	if mysql.HasAutoIncrementFlag(column.GetFlag()) {
		if !hasValue && mysql.HasNoDefaultValueFlag(column.ToInfo().GetFlag()) {
			vars := e.Ctx().GetSessionVars()
			sc := vars.StmtCtx
			if vars.SQLMode.HasStrictMode() {
				return datum, table.ErrNoDefaultValue.FastGenByArgs(column.ToInfo().Name)
			}
			sc.AppendWarning(table.ErrNoDefaultValue.FastGenByArgs(column.ToInfo().Name))
		}

		if e.lazyFillAutoID {
			// Handle hasValue info in autoIncrement column previously for lazy handle.
			if !hasValue {
				datum.SetNull()
			}
			// Store the plain datum of autoIncrement column directly for lazy handle.
			return datum, nil
		}
		d, err := e.adjustAutoIncrementDatum(ctx, datum, hasValue, column)
		if err != nil {
			return types.Datum{}, err
		}
		return d, nil
	}
	tblInfo := e.Table.Meta()
	if ddl.IsAutoRandomColumnID(tblInfo, column.ID) {
		d, err := e.adjustAutoRandomDatum(ctx, datum, hasValue, column)
		if err != nil {
			return types.Datum{}, err
		}
		return d, nil
	}
	if column.ID == model.ExtraHandleID && hasValue {
		d, err := e.adjustImplicitRowID(ctx, datum, hasValue, column)
		if err != nil {
			return types.Datum{}, err
		}
		return d, nil
	}
	if !hasValue {
		d, err := e.getColDefaultValue(idx, column)
		if e.handleErr(column, &datum, 0, err) != nil {
			return types.Datum{}, err
		}
		return d, nil
	}
	return datum, nil
}

// fillRow fills generated columns, auto_increment column and empty column.
// For NOT NULL column, it will return error or use zero value based on sql_mode.
// When lazyFillAutoID is true, fill row will lazily handle auto increment datum for lazy batch allocation.
// `insert|replace values` can guarantee consecutive autoID in a batch.
// Other statements like `insert select from` don't guarantee consecutive autoID.
// https://dev.mysql.com/doc/refman/8.0/en/innodb-auto-increment-handling.html
func (e *InsertValues) fillRow(ctx context.Context, row []types.Datum, hasValue []bool, rowIdx int) (
	[]types.Datum, error,
) {
	gCols := make([]*table.Column, 0)
	tCols := e.Table.Cols()
	if e.hasExtraHandle {
		col := &table.Column{}
		col.ColumnInfo = model.NewExtraHandleColInfo()
		col.ColumnInfo.Offset = len(tCols)
		tCols = append(tCols, col)
	}
	rowCntInLoadData := uint64(0)
	if e.Ctx().GetSessionVars().StmtCtx.InLoadDataStmt {
		rowCntInLoadData = e.rowCount
	}

	for i, c := range tCols {
		var err error
		// Evaluate the generated columns later after real columns set
		if c.IsGenerated() {
			gCols = append(gCols, c)
		} else {
			// Get the default value for all no value columns, the auto increment column is different from the others.
			if row[i], err = e.fillColValue(ctx, row[i], i, c, hasValue[i]); err != nil {
				return nil, err
			}
			if !e.lazyFillAutoID || (e.lazyFillAutoID && !mysql.HasAutoIncrementFlag(c.GetFlag())) {
				if err = c.HandleBadNull(e.Ctx().GetSessionVars().StmtCtx.ErrCtx(), &row[i], rowCntInLoadData); err != nil {
					return nil, err
				}
			}
		}
	}

	// Handle exchange partition
	tbl := e.Table.Meta()
	if tbl.ExchangePartitionInfo != nil && tbl.GetPartitionInfo() == nil {
		if err := checkRowForExchangePartition(e.Ctx(), row, tbl); err != nil {
			return nil, err
		}
	}

	sctx := e.Ctx()
	evalCtx := sctx.GetExprCtx().GetEvalCtx()
	sc := sctx.GetSessionVars().StmtCtx
	warnCnt := int(sc.WarningCount())
	for i, gCol := range gCols {
		// CSE only:
		// Skip all auto-embedding generated columns. We will handle them later in a batch.
		if expression.IsAutoEmbedFnCallAST(gCol.GeneratedExpr.Internal()) {
			continue
		}
		colIdx := gCol.ColumnInfo.Offset
		val, err := e.GenExprs[i].Eval(evalCtx, chunk.MutRowFromDatums(row).ToRow())
		if err != nil && gCol.FieldType.IsArray() {
			return nil, completeError(tbl, gCol.Offset, rowIdx, err)
		}
		if e.Ctx().GetSessionVars().StmtCtx.HandleTruncate(err) != nil {
			return nil, err
		}
		row[colIdx], err = table.CastValue(e.Ctx(), val, gCol.ToInfo(), false, false)
		if err = e.handleErr(gCol, &val, rowIdx, err); err != nil {
			return nil, err
		}
		if newWarnings := sc.TruncateWarnings(warnCnt); len(newWarnings) > 0 {
			for k := range newWarnings {
				newWarnings[k].Err = completeInsertErr(gCol.ColumnInfo, &val, rowIdx, newWarnings[k].Err)
			}
			sc.AppendWarnings(newWarnings)
			warnCnt += len(newWarnings)
		}
		// Handle the bad null error.
		if err = gCol.HandleBadNull(sc.ErrCtx(), &row[colIdx], rowCntInLoadData); err != nil {
			return nil, err
		}
	}
	return row, nil
}

func completeError(tbl *model.TableInfo, offset int, rowIdx int, err error) error {
	name := "expression_index"
	for _, idx := range tbl.Indices {
		for _, column := range idx.Columns {
			if column.Offset == offset {
				name = idx.Name.O
				break
			}
		}
	}

	if expression.ErrInvalidJSONForFuncIndex.Equal(err) {
		return expression.ErrInvalidJSONForFuncIndex.GenWithStackByArgs(name)
	}
	if types.ErrOverflow.Equal(err) {
		return expression.ErrDataOutOfRangeFuncIndex.GenWithStackByArgs(name, rowIdx+1)
	}
	if types.ErrDataTooLong.Equal(err) {
		return expression.ErrFuncIndexDataIsTooLong.GenWithStackByArgs(name)
	}
	return err
}

// isAutoNull can help judge whether a datum is AutoIncrement Null quickly.
// This used to help lazyFillAutoIncrement to find consecutive N datum backwards for batch autoID alloc.
func (e *InsertValues) isAutoNull(_ context.Context, d types.Datum, col *table.Column) bool {
	var err error
	var recordID int64
	if !d.IsNull() {
		recordID, err = getAutoRecordID(d, &col.FieldType, true)
		if err != nil {
			return false
		}
	}
	// Use the value if it's not null and not 0.
	if recordID != 0 {
		return false
	}
	// Change NULL to auto id.
	// Change value 0 to auto id, if NoAutoValueOnZero SQL mode is not set.
	if d.IsNull() || e.Ctx().GetSessionVars().SQLMode&mysql.ModeNoAutoValueOnZero == 0 {
		return true
	}
	return false
}

func findAutoIncrementColumn(t table.Table) (col *table.Column, offsetInRow int, found bool) {
	for i, c := range t.Cols() {
		if mysql.HasAutoIncrementFlag(c.GetFlag()) {
			return c, i, true
		}
	}
	return nil, -1, false
}

func setDatumAutoIDAndCast(ctx sessionctx.Context, d *types.Datum, id int64, col *table.Column) error {
	d.SetAutoID(id, col.GetFlag())
	var err error
	*d, err = table.CastValue(ctx, *d, col.ToInfo(), false, false)
	if err == nil && d.GetInt64() < id {
		// Auto ID is out of range.
		sc := ctx.GetSessionVars().StmtCtx
		insertPlan, ok := sc.GetPlan().(*core.Insert)
		if ok && sc.TypeFlags().TruncateAsWarning() && len(insertPlan.OnDuplicate) > 0 {
			// Fix issue #38950: AUTO_INCREMENT is incompatible with mysql
			// An auto id out of range error occurs in `insert ignore into ... on duplicate ...`.
			// We should allow the SQL to be executed successfully.
			return nil
		}
		// The truncated ID is possible to duplicate with an existing ID.
		// To prevent updating unrelated rows in the REPLACE statement, it is better to throw an error.
		return autoid.ErrAutoincReadFailed
	}
	return err
}

// lazyAdjustAutoIncrementDatum is quite similar to adjustAutoIncrementDatum
// except it will cache auto increment datum previously for lazy batch allocation of autoID.
func (e *InsertValues) lazyAdjustAutoIncrementDatum(ctx context.Context, rows [][]types.Datum) (
	[][]types.Datum, error,
) {
	// Not in lazyFillAutoID mode means no need to fill.
	if !e.lazyFillAutoID {
		return rows, nil
	}
	col, idx, found := findAutoIncrementColumn(e.Table)
	if !found {
		return rows, nil
	}
	sessVars := e.Ctx().GetSessionVars()
	retryInfo := sessVars.RetryInfo
	rowCount := len(rows)
	for processedIdx := 0; processedIdx < rowCount; processedIdx++ {
		autoDatum := rows[processedIdx][idx]

		var err error
		var recordID int64
		if !autoDatum.IsNull() {
			recordID, err = getAutoRecordID(autoDatum, &col.FieldType, true)
			if err != nil {
				return nil, err
			}
		}
		// Use the value if it's not null and not 0.
		if recordID != 0 {
			alloc := e.Table.Allocators(e.Ctx().GetTableCtx()).Get(autoid.AutoIncrementType)
			err = alloc.Rebase(ctx, recordID, true)
			if err != nil {
				return nil, err
			}
			e.Ctx().GetSessionVars().StmtCtx.InsertID = uint64(recordID)
			retryInfo.AddAutoIncrementID(recordID)
			continue
		}

		// Change NULL to auto id.
		// Change value 0 to auto id, if NoAutoValueOnZero SQL mode is not set.
		if autoDatum.IsNull() || e.Ctx().GetSessionVars().SQLMode&mysql.ModeNoAutoValueOnZero == 0 {
			// Consume the auto IDs in RetryInfo first.
			for retryInfo.Retrying && processedIdx < rowCount {
				nextID, ok := retryInfo.GetCurrAutoIncrementID()
				if !ok {
					break
				}
				err = setDatumAutoIDAndCast(e.Ctx(), &rows[processedIdx][idx], nextID, col)
				if err != nil {
					return nil, err
				}
				processedIdx++
				if processedIdx == rowCount {
					return rows, nil
				}
			}
			// Find consecutive num.
			start := processedIdx
			cnt := 1
			for processedIdx+1 < rowCount && e.isAutoNull(ctx, rows[processedIdx+1][idx], col) {
				processedIdx++
				cnt++
			}
			// AllocBatchAutoIncrementValue allocates batch N consecutive autoIDs.
			// The max value can be derived from adding the increment value to min for cnt-1 times.
			minv, increment, err := table.AllocBatchAutoIncrementValue(ctx, e.Table, e.Ctx(), cnt)
			if e.handleErr(col, &autoDatum, cnt, err) != nil {
				return nil, err
			}
			// It's compatible with mysql setting the first allocated autoID to lastInsertID.
			// Cause autoID may be specified by user, judge only the first row is not suitable.
			if e.lastInsertID == 0 {
				e.lastInsertID = uint64(minv)
			}
			// Assign autoIDs to rows.
			for j := 0; j < cnt; j++ {
				offset := j + start
				id := int64(uint64(minv) + uint64(j)*uint64(increment))
				err = setDatumAutoIDAndCast(e.Ctx(), &rows[offset][idx], id, col)
				if err != nil {
					return nil, err
				}
				retryInfo.AddAutoIncrementID(id)
			}
			continue
		}

		err = setDatumAutoIDAndCast(e.Ctx(), &rows[processedIdx][idx], recordID, col)
		if err != nil {
			return nil, err
		}
		retryInfo.AddAutoIncrementID(recordID)
	}
	return rows, nil
}

func (e *InsertValues) adjustAutoIncrementDatum(
	ctx context.Context, d types.Datum, hasValue bool, c *table.Column,
) (types.Datum, error) {
	sessVars := e.Ctx().GetSessionVars()
	retryInfo := sessVars.RetryInfo
	if retryInfo.Retrying {
		id, ok := retryInfo.GetCurrAutoIncrementID()
		if ok {
			err := setDatumAutoIDAndCast(e.Ctx(), &d, id, c)
			if err != nil {
				return types.Datum{}, err
			}
			return d, nil
		}
	}

	var err error
	var recordID int64
	if !hasValue {
		d.SetNull()
	}
	if !d.IsNull() {
		recordID, err = getAutoRecordID(d, &c.FieldType, true)
		if err != nil {
			return types.Datum{}, err
		}
	}
	// Use the value if it's not null and not 0.
	if recordID != 0 {
		err = e.Table.Allocators(e.Ctx().GetTableCtx()).Get(autoid.AutoIncrementType).Rebase(ctx, recordID, true)
		if err != nil {
			return types.Datum{}, err
		}
		e.Ctx().GetSessionVars().StmtCtx.InsertID = uint64(recordID)
		retryInfo.AddAutoIncrementID(recordID)
		return d, nil
	}

	// Change NULL to auto id.
	// Change value 0 to auto id, if NoAutoValueOnZero SQL mode is not set.
	if d.IsNull() || e.Ctx().GetSessionVars().SQLMode&mysql.ModeNoAutoValueOnZero == 0 {
		recordID, err = table.AllocAutoIncrementValue(ctx, e.Table, e.Ctx())
		if e.handleErr(c, &d, 0, err) != nil {
			return types.Datum{}, err
		}
		// It's compatible with mysql setting the first allocated autoID to lastInsertID.
		// Cause autoID may be specified by user, judge only the first row is not suitable.
		if e.lastInsertID == 0 {
			e.lastInsertID = uint64(recordID)
		}
	}

	err = setDatumAutoIDAndCast(e.Ctx(), &d, recordID, c)
	if err != nil {
		return types.Datum{}, err
	}
	retryInfo.AddAutoIncrementID(recordID)
	return d, nil
}

func getAutoRecordID(d types.Datum, target *types.FieldType, isInsert bool) (int64, error) {
	var recordID int64
	switch target.GetType() {
	case mysql.TypeFloat, mysql.TypeDouble:
		f := d.GetFloat64()
		if isInsert {
			recordID = int64(math.Round(f))
		} else {
			recordID = int64(f)
		}
	case mysql.TypeTiny, mysql.TypeShort, mysql.TypeInt24, mysql.TypeLong, mysql.TypeLonglong:
		recordID = d.GetInt64()
	default:
		return 0, errors.Errorf("unexpected field type [%v]", target.GetType())
	}

	return recordID, nil
}

func (e *InsertValues) adjustAutoRandomDatum(
	ctx context.Context, d types.Datum, hasValue bool, c *table.Column,
) (types.Datum, error) {
	retryInfo := e.Ctx().GetSessionVars().RetryInfo
	if retryInfo.Retrying {
		autoRandomID, ok := retryInfo.GetCurrAutoRandomID()
		if ok {
			err := setDatumAutoIDAndCast(e.Ctx(), &d, autoRandomID, c)
			if err != nil {
				return types.Datum{}, err
			}
			return d, nil
		}
	}

	var err error
	var recordID int64
	if !hasValue {
		d.SetNull()
	}
	if !d.IsNull() {
		recordID, err = getAutoRecordID(d, &c.FieldType, true)
		if err != nil {
			return types.Datum{}, err
		}
	}
	// Use the value if it's not null and not 0.
	if recordID != 0 {
		if !e.Ctx().GetSessionVars().AllowAutoRandExplicitInsert {
			return types.Datum{}, dbterror.ErrInvalidAutoRandom.GenWithStackByArgs(autoid.AutoRandomExplicitInsertDisabledErrMsg)
		}
		err = e.rebaseAutoRandomID(ctx, recordID, &c.FieldType)
		if err != nil {
			return types.Datum{}, err
		}
		e.Ctx().GetSessionVars().StmtCtx.InsertID = uint64(recordID)
		err = setDatumAutoIDAndCast(e.Ctx(), &d, recordID, c)
		if err != nil {
			return types.Datum{}, err
		}
		retryInfo.AddAutoRandomID(recordID)
		return d, nil
	}

	// Change NULL to auto id.
	// Change value 0 to auto id, if NoAutoValueOnZero SQL mode is not set.
	if d.IsNull() || e.Ctx().GetSessionVars().SQLMode&mysql.ModeNoAutoValueOnZero == 0 {
		recordID, err = e.allocAutoRandomID(ctx, &c.FieldType)
		if err != nil {
			return types.Datum{}, err
		}
		// It's compatible with mysql setting the first allocated autoID to lastInsertID.
		// Cause autoID may be specified by user, judge only the first row is not suitable.
		if e.lastInsertID == 0 {
			e.lastInsertID = uint64(recordID)
		}
	}

	err = setDatumAutoIDAndCast(e.Ctx(), &d, recordID, c)
	if err != nil {
		return types.Datum{}, err
	}
	retryInfo.AddAutoRandomID(recordID)
	return d, nil
}

// allocAutoRandomID allocates a random id for primary key column. It assumes tableInfo.AutoRandomBits > 0.
func (e *InsertValues) allocAutoRandomID(ctx context.Context, fieldType *types.FieldType) (int64, error) {
	alloc := e.Table.Allocators(e.Ctx().GetTableCtx()).Get(autoid.AutoRandomType)
	tableInfo := e.Table.Meta()
	increment := e.Ctx().GetSessionVars().AutoIncrementIncrement
	offset := e.Ctx().GetSessionVars().AutoIncrementOffset
	_, autoRandomID, err := alloc.Alloc(ctx, 1, int64(increment), int64(offset))
	if err != nil {
		return 0, err
	}
	shardFmt := autoid.NewShardIDFormat(fieldType, tableInfo.AutoRandomBits, tableInfo.AutoRandomRangeBits)
	if shardFmt.IncrementalMask()&autoRandomID != autoRandomID {
		return 0, autoid.ErrAutoRandReadFailed
	}
	_, err = e.Ctx().Txn(true)
	if err != nil {
		return 0, err
	}
	currentShard := e.Ctx().GetSessionVars().GetRowIDShardGenerator().GetCurrentShard(1)
	return shardFmt.Compose(currentShard, autoRandomID), nil
}

func (e *InsertValues) rebaseAutoRandomID(ctx context.Context, recordID int64, fieldType *types.FieldType) error {
	if recordID < 0 {
		return nil
	}
	alloc := e.Table.Allocators(e.Ctx().GetTableCtx()).Get(autoid.AutoRandomType)
	tableInfo := e.Table.Meta()

	shardFmt := autoid.NewShardIDFormat(fieldType, tableInfo.AutoRandomBits, tableInfo.AutoRandomRangeBits)
	autoRandomID := shardFmt.IncrementalMask() & recordID

	return alloc.Rebase(ctx, autoRandomID, true)
}

func (e *InsertValues) adjustImplicitRowID(
	ctx context.Context, d types.Datum, hasValue bool, c *table.Column,
) (types.Datum, error) {
	var err error
	var recordID int64
	if !hasValue {
		d.SetNull()
	}
	if !d.IsNull() {
		recordID = d.GetInt64()
	}
	// Use the value if it's not null and not 0.
	if recordID != 0 {
		if !e.Ctx().GetSessionVars().AllowWriteRowID {
			return types.Datum{}, errors.Errorf("insert, update and replace statements for _tidb_rowid are not supported")
		}
		err = e.rebaseImplicitRowID(ctx, recordID)
		if err != nil {
			return types.Datum{}, err
		}
		d.SetInt64(recordID)
		return d, nil
	}
	// Change NULL to auto id.
	// Change value 0 to auto id, if NoAutoValueOnZero SQL mode is not set.
	if d.IsNull() || e.Ctx().GetSessionVars().SQLMode&mysql.ModeNoAutoValueOnZero == 0 {
		_, err := e.Ctx().Txn(true)
		if err != nil {
			return types.Datum{}, errors.Trace(err)
		}
		intHandle, err := tables.AllocHandle(ctx, e.Ctx().GetTableCtx(), e.Table)
		if err != nil {
			return types.Datum{}, err
		}
		recordID = intHandle.IntValue()
	}
	err = setDatumAutoIDAndCast(e.Ctx(), &d, recordID, c)
	if err != nil {
		return types.Datum{}, err
	}
	return d, nil
}

func (e *InsertValues) rebaseImplicitRowID(ctx context.Context, recordID int64) error {
	if recordID < 0 {
		return nil
	}
	alloc := e.Table.Allocators(e.Ctx().GetTableCtx()).Get(autoid.RowIDAllocType)
	tableInfo := e.Table.Meta()

	shardFmt := autoid.NewShardIDFormat(
		types.NewFieldType(mysql.TypeLonglong),
		tableInfo.ShardRowIDBits,
		autoid.RowIDBitLength,
	)
	newTiDBRowIDBase := shardFmt.IncrementalMask() & recordID

	return alloc.Rebase(ctx, newTiDBRowIDBase, true)
}

func (e *InsertValues) handleWarning(err error) {
	sc := e.Ctx().GetSessionVars().StmtCtx
	sc.AppendWarning(err)
}

func (e *InsertValues) collectRuntimeStatsEnabled() bool {
	if e.RuntimeStats() != nil {
		if e.stats == nil {
			snapshotStats := &txnsnapshot.SnapshotRuntimeStats{}
			e.stats = &InsertRuntimeStat{
				BasicRuntimeStats:     e.RuntimeStats(),
				SnapshotRuntimeStats:  snapshotStats,
				AllocatorRuntimeStats: autoid.NewAllocatorRuntimeStats(),
			}
		}
		return true
	}
	return false
}

func (e *InsertValues) handleDuplicateKey(ctx context.Context, txn kv.Transaction, uk *keyValueWithDupInfo, replace bool, r toBeCheckedRow) (bool, error) {
	if !replace {
		e.Ctx().GetSessionVars().StmtCtx.AppendWarning(uk.dupErr)
		if txnCtx := e.Ctx().GetSessionVars().TxnCtx; txnCtx.IsPessimistic && e.Ctx().GetSessionVars().LockUnchangedKeys {
			txnCtx.AddUnchangedKeyForLock(uk.newKey)
		}
		return true, nil
	}
	_, handle, err := tables.FetchDuplicatedHandle(ctx, uk.newKey, true, txn)
	if err != nil {
		return false, err
	}
	if handle == nil {
		return false, nil
	}
	return e.removeRow(ctx, txn, handle, r, true)
}

// batchCheckAndInsert checks rows with duplicate errors.
// All duplicate rows will be ignored and appended as duplicate warnings.
func (e *InsertValues) batchCheckAndInsert(
	ctx context.Context, rows [][]types.Datum,
	addRecord func(ctx context.Context, row []types.Datum, dupKeyCheck table.DupKeyCheckMode) error,
	replace bool,
) error {
	defer tracing.StartRegion(ctx, "InsertValues.batchCheckAndInsert").End()
	start := time.Now()
	// Get keys need to be checked.
	toBeCheckedRows, err := getKeysNeedCheck(e.Ctx(), e.Table, rows)
	if err != nil {
		return err
	}

	txn, err := e.Ctx().Txn(true)
	if err != nil {
		return err
	}
	setOptionForTopSQL(e.Ctx().GetSessionVars().StmtCtx, txn)
	sc := e.Ctx().GetSessionVars().StmtCtx
	for _, fkc := range e.fkChecks {
		err = fkc.checkRows(ctx, sc, txn, toBeCheckedRows)
		if err != nil {
			return err
		}
	}
	prefetchStart := time.Now()
	// Fill cache using BatchGet, the following Get requests don't need to visit TiKV.
	// Temporary table need not to do prefetch because its all data are stored in the memory.
	if e.Table.Meta().TempTableType == model.TempTableNone {
		if _, err = prefetchUniqueIndices(ctx, txn, toBeCheckedRows); err != nil {
			return err
		}
	}

	if e.stats != nil {
		e.stats.FKCheckTime += prefetchStart.Sub(start)
		e.stats.Prefetch += time.Since(prefetchStart)
	}

	// append warnings and get no duplicated error rows
	for i, r := range toBeCheckedRows {
		if r.ignored {
			continue
		}
		if r.handleKey != nil {
			_, err := txn.Get(ctx, r.handleKey.newKey)
			if err == nil {
				if !replace {
					e.Ctx().GetSessionVars().StmtCtx.AppendWarning(r.handleKey.dupErr)
					if txnCtx := e.Ctx().GetSessionVars().TxnCtx; txnCtx.IsPessimistic &&
						e.Ctx().GetSessionVars().LockUnchangedKeys {
						// lock duplicated row key on insert-ignore
						txnCtx.AddUnchangedKeyForLock(r.handleKey.newKey)
					}
					continue
				}
				handle, err := tablecodec.DecodeRowKey(r.handleKey.newKey)
				if err != nil {
					return err
				}
				unchanged, err2 := e.removeRow(ctx, txn, handle, r, false)
				if err2 != nil {
					return err2
				}
				if unchanged {
					// we don't need to add the identical row again, but the
					// counter should act as if we did.
					e.Ctx().GetSessionVars().StmtCtx.AddCopiedRows(1)
					continue
				}
			} else if !kv.IsErrNotFound(err) {
				return err
			}
		}

		rowInserted := false
		for _, uk := range r.uniqueKeys {
			_, err := txn.Get(ctx, uk.newKey)
			if err != nil && !kv.IsErrNotFound(err) {
				return err
			}
			if err == nil {
				rowInserted, err = e.handleDuplicateKey(ctx, txn, uk, replace, r)
				if err != nil {
					return err
				}
				if rowInserted {
					break
				}
				continue
			}
			if tablecodec.IsTempIndexKey(uk.newKey) {
				tablecodec.TempIndexKey2IndexKey(uk.newKey)
				_, err = txn.Get(ctx, uk.newKey)
				if err != nil && !kv.IsErrNotFound(err) {
					return err
				}
				if err == nil {
					rowInserted, err = e.handleDuplicateKey(ctx, txn, uk, replace, r)
					if err != nil {
						return err
					}
					if rowInserted {
						break
					}
				}
			}
		}

		if rowInserted {
			continue
		}

		// If row was checked with no duplicate keys,
		// it should be added to values map for the further row check.
		// There may be duplicate keys inside the insert statement.
		e.Ctx().GetSessionVars().StmtCtx.AddCopiedRows(1)
		// all the rows have been checked, so it is safe to use DupKeyCheckSkip
		err = addRecord(ctx, rows[i], table.DupKeyCheckSkip)
		if err != nil {
			// throw warning when violate check constraint
			if table.ErrCheckConstraintViolated.Equal(err) {
				if !sc.InLoadDataStmt {
					sc.AppendWarning(err)
				}
				continue
			}
			return err
		}
	}
	if e.stats != nil {
		e.stats.CheckInsertTime += time.Since(start)
	}
	return nil
}

// removeRow removes the duplicate row and cleanup its keys in the key-value map.
// But if the to-be-removed row equals to the to-be-added row, no remove or add
// things to do and return (true, nil).
func (e *InsertValues) removeRow(
	ctx context.Context,
	txn kv.Transaction,
	handle kv.Handle,
	r toBeCheckedRow,
	inReplace bool,
) (bool, error) {
	newRow := r.row
	oldRow, err := getOldRow(ctx, e.Ctx(), txn, r.t, handle, e.GenExprs)
	if err != nil {
		logutil.BgLogger().Error(
			"get old row failed when replace",
			zap.String("handle", handle.String()),
			zap.String("toBeInsertedRow", types.DatumsToStrNoErr(r.row)),
		)
		if kv.IsErrNotFound(err) {
			err = errors.NotFoundf("can not be duplicated row, due to old row not found. handle %s", handle)
		}
		return false, err
	}

	identical, err := e.equalDatumsAsBinary(oldRow, newRow)
	if err != nil {
		return false, err
	}
	if identical {
		if inReplace {
			e.Ctx().GetSessionVars().StmtCtx.AddAffectedRows(1)
		}
		keySet := lockRowKey
		if e.Ctx().GetSessionVars().LockUnchangedKeys {
			keySet |= lockUniqueKeys
		}
		if _, err := addUnchangedKeysForLockByRow(e.Ctx(), r.t, handle, oldRow, keySet); err != nil {
			return false, err
		}
		return true, nil
	}

	if ph, ok := handle.(kv.PartitionHandle); ok {
		err = e.Table.(table.PartitionedTable).GetPartition(ph.PartitionID).RemoveRecord(e.Ctx().GetTableCtx(), txn, ph.Handle, oldRow)
	} else {
		err = r.t.RemoveRecord(e.Ctx().GetTableCtx(), txn, handle, oldRow)
	}
	if err != nil {
		return false, err
	}
	err = onRemoveRowForFK(e.Ctx(), oldRow, e.fkChecks, e.fkCascades, e.ignoreErr)
	if err != nil {
		return false, err
	}
	if inReplace {
		e.Ctx().GetSessionVars().StmtCtx.AddAffectedRows(1)
	} else {
		e.Ctx().GetSessionVars().StmtCtx.AddDeletedRows(1)
	}

	return false, nil
}

// equalDatumsAsBinary compare if a and b contains the same datum values in binary collation.
func (e *InsertValues) equalDatumsAsBinary(a []types.Datum, b []types.Datum) (bool, error) {
	if len(a) != len(b) {
		return false, nil
	}
	for i, ai := range a {
		v, err := ai.Compare(e.Ctx().GetSessionVars().StmtCtx.TypeCtx(), &b[i], collate.GetBinaryCollator())
		if err != nil {
			return false, errors.Trace(err)
		}
		if v != 0 {
			return false, nil
		}
	}
	return true, nil
}

func (e *InsertValues) addRecord(ctx context.Context, row []types.Datum, dupKeyCheck table.DupKeyCheckMode) error {
	return e.addRecordWithAutoIDHint(ctx, row, 0, dupKeyCheck)
}

func (e *InsertValues) addRecordWithAutoIDHint(
	ctx context.Context, row []types.Datum, reserveAutoIDCount int, dupKeyCheck table.DupKeyCheckMode,
) (err error) {
	vars := e.Ctx().GetSessionVars()
	txn, err := e.Ctx().Txn(true)
	if err != nil {
		return err
	}
	pessimisticLazyCheck := getPessimisticLazyCheckMode(vars)
	if reserveAutoIDCount > 0 {
		_, err = e.Table.AddRecord(e.Ctx().GetTableCtx(), txn, row, table.WithCtx(ctx), table.WithReserveAutoIDHint(reserveAutoIDCount), dupKeyCheck, pessimisticLazyCheck)
	} else {
		_, err = e.Table.AddRecord(e.Ctx().GetTableCtx(), txn, row, table.WithCtx(ctx), dupKeyCheck, pessimisticLazyCheck)
	}
	if err != nil {
		return err
	}
	vars.StmtCtx.AddAffectedRows(1)
	if e.lastInsertID != 0 {
		vars.SetLastInsertID(e.lastInsertID)
	}
	if dupKeyCheck != table.DupKeyCheckSkip {
		for _, fkc := range e.fkChecks {
			err = fkc.insertRowNeedToCheck(vars.StmtCtx, row)
			if err != nil {
				return err
			}
		}
	}

	if e.Table.Meta().TTLInfo != nil {
		// update the TTL metrics if the table is a TTL table
		vars.TxnCtx.InsertTTLRowsCount++
	}

	return nil
}

// CreateSession will be assigned by session package.
var CreateSession func(ctx sessionctx.Context) (sessionctx.Context, error)

// CloseSession will be assigned by session package.
var CloseSession func(ctx sessionctx.Context)

// InsertRuntimeStat record the stat about insert and check
type InsertRuntimeStat struct {
	*execdetails.BasicRuntimeStats
	*txnsnapshot.SnapshotRuntimeStats
	*autoid.AllocatorRuntimeStats
	CheckInsertTime time.Duration
	Prefetch        time.Duration
	FKCheckTime     time.Duration
}

func (e *InsertRuntimeStat) String() string {
	buf := bytes.NewBuffer(make([]byte, 0, 32))
	var allocatorStatsStr string
	if e.AllocatorRuntimeStats != nil {
		allocatorStatsStr = e.AllocatorRuntimeStats.String()
	}
	if e.CheckInsertTime == 0 {
		// For replace statement.
		if allocatorStatsStr != "" {
			buf.WriteString(allocatorStatsStr)
		}
		if e.Prefetch > 0 && e.SnapshotRuntimeStats != nil {
			if buf.Len() > 0 {
				buf.WriteString(", ")
			}
			buf.WriteString("prefetch: ")
			buf.WriteString(execdetails.FormatDuration(e.Prefetch))
			buf.WriteString(", rpc: {")
			buf.WriteString(e.SnapshotRuntimeStats.String())
			buf.WriteString("}")
		}
		return buf.String()
	}
	if allocatorStatsStr != "" {
		buf.WriteString("prepare: {total: ")
		buf.WriteString(execdetails.FormatDuration(time.Duration(e.BasicRuntimeStats.GetTime()) - e.CheckInsertTime))
		buf.WriteString(", ")
		buf.WriteString(allocatorStatsStr)
		buf.WriteString("}, ")
	} else {
		buf.WriteString("prepare: ")
		buf.WriteString(execdetails.FormatDuration(time.Duration(e.BasicRuntimeStats.GetTime()) - e.CheckInsertTime))
		buf.WriteString(", ")
	}
	if e.Prefetch > 0 {
		fmt.Fprintf(
			buf, "check_insert: {total_time: %v, mem_insert_time: %v, prefetch: %v",
			execdetails.FormatDuration(e.CheckInsertTime),
			execdetails.FormatDuration(e.CheckInsertTime-e.Prefetch),
			execdetails.FormatDuration(e.Prefetch),
		)
		if e.FKCheckTime > 0 {
			fmt.Fprintf(buf, ", fk_check: %v", execdetails.FormatDuration(e.FKCheckTime))
		}
		if e.SnapshotRuntimeStats != nil {
			if rpc := e.SnapshotRuntimeStats.String(); len(rpc) > 0 {
				fmt.Fprintf(buf, ", rpc:{%s}", rpc)
			}
		}
		buf.WriteString("}")
	} else {
		fmt.Fprintf(buf, "insert:%v", execdetails.FormatDuration(e.CheckInsertTime))
		if e.SnapshotRuntimeStats != nil {
			if rpc := e.SnapshotRuntimeStats.String(); len(rpc) > 0 {
				fmt.Fprintf(buf, ", rpc:{%s}", rpc)
			}
		}
	}
	return buf.String()
}

// Clone implements the RuntimeStats interface.
func (e *InsertRuntimeStat) Clone() execdetails.RuntimeStats {
	newRs := &InsertRuntimeStat{
		CheckInsertTime: e.CheckInsertTime,
		Prefetch:        e.Prefetch,
		FKCheckTime:     e.FKCheckTime,
	}
	if e.SnapshotRuntimeStats != nil {
		snapshotStats := e.SnapshotRuntimeStats.Clone()
		newRs.SnapshotRuntimeStats = snapshotStats
	}
	if e.BasicRuntimeStats != nil {
		basicStats := e.BasicRuntimeStats.Clone()
		newRs.BasicRuntimeStats = basicStats.(*execdetails.BasicRuntimeStats)
	}
	if e.AllocatorRuntimeStats != nil {
		newRs.AllocatorRuntimeStats = e.AllocatorRuntimeStats.Clone()
	}
	return newRs
}

// Merge implements the RuntimeStats interface.
func (e *InsertRuntimeStat) Merge(other execdetails.RuntimeStats) {
	tmp, ok := other.(*InsertRuntimeStat)
	if !ok {
		return
	}
	if tmp.SnapshotRuntimeStats != nil {
		if e.SnapshotRuntimeStats == nil {
			snapshotStats := tmp.SnapshotRuntimeStats.Clone()
			e.SnapshotRuntimeStats = snapshotStats
		} else {
			e.SnapshotRuntimeStats.Merge(tmp.SnapshotRuntimeStats)
		}
	}
	if tmp.BasicRuntimeStats != nil {
		if e.BasicRuntimeStats == nil {
			basicStats := tmp.BasicRuntimeStats.Clone()
			e.BasicRuntimeStats = basicStats.(*execdetails.BasicRuntimeStats)
		} else {
			e.BasicRuntimeStats.Merge(tmp.BasicRuntimeStats)
		}
	}
	if tmp.AllocatorRuntimeStats != nil {
		if e.AllocatorRuntimeStats == nil {
			e.AllocatorRuntimeStats = tmp.AllocatorRuntimeStats.Clone()
		} else {
			e.AllocatorRuntimeStats.Merge(tmp.AllocatorRuntimeStats)
		}
	}
	e.Prefetch += tmp.Prefetch
	e.FKCheckTime += tmp.FKCheckTime
	e.CheckInsertTime += tmp.CheckInsertTime
}

// Tp implements the RuntimeStats interface.
func (*InsertRuntimeStat) Tp() int {
	return execdetails.TpInsertRuntimeStat
}
