Repository: pingcap/tidb
File: pkg/executor/admin.go (Go)

Code before:
		}
		iter := chunk.NewIterator4Chunk(e.srcChunk)
		for row := iter.Begin(); row != iter.End(); row = iter.Next() {
			// TODO: it's possible to push down the condition.
			if e.index.Meta().HasCondition() {
				ok, err := e.index.MeetPartialConditionWithChunk(row)
				if err != nil {
					return nil, err
				}
				if !ok {
					// Skip partial index not match row
					continue
				}
			}

			if result.scanRowCount >= int64(e.batchSize) {
				return e.recoverRows, nil
			}
			handle, err := e.handleCols.BuildHandle(e.Ctx().GetSessionVars().StmtCtx, row)
			if err != nil {
				return nil, err
			}
			if e.index.Meta().Global {
				handle = kv.NewPartitionHandle(e.physicalID, handle)
			}
			idxVals, err := e.buildIndexedValues(row, e.idxValsBufs[result.scanRowCount], e.colFieldTypes, idxValLen)
			if err != nil {
				return nil, err
			}
			e.idxValsBufs[result.scanRowCount] = idxVals
			rsData := tables.TryGetHandleRestoredDataWrapper(e.table.Meta(), plannercore.GetCommonHandleDatum(e.handleCols, row), nil, e.index.Meta())
			e.recoverRows = append(e.recoverRows, recoverRows{handle: handle, idxVals: idxVals, rsData: rsData, skip: true})
			result.scanRowCount++
			result.currentHandle = handle
		}
	}

	return e.recoverRows, nil
}

func (e *RecoverIndexExec) buildIndexedValues(row chunk.Row, idxVals []types.Datum, fieldTypes []*types.FieldType, idxValLen int) ([]types.Datum, error) {
	if !e.containsGenedCol {
		return extractIdxVals(row, idxVals, fieldTypes, idxValLen), nil
	}

	if e.cols == nil {
		columns, _, err := expression.ColumnInfos2ColumnsAndNames(e.Ctx().GetExprCtx(), ast.NewCIStr("mock"), e.table.Meta().Name, e.table.Meta().Columns, e.table.Meta())
		if err != nil {
			return nil, err
		}
		e.cols = columns

Review comment (bug):
`MeetPartialConditionWithChunk` expects a `chunk.Row` whose column layout matches the full table schema (`tblInfo.Columns`), but here `fetchRecoverRows` passes rows built from `e.columns`, which for non-generated-column indexes are constructed by `buildIdxColsConcatHandleCols` to contain only index columns (plus handle). For partial indexes whose condition references non-index columns (for example `KEY idx(c) WHERE b > 2)`), those predicate columns are not present in `e.columns`, so `MeetPartialConditionWithChunk` will evaluate the condition against the wrong positions, leading to incorrect filtering of rows during `ADMIN RECOVER INDEX` (rows may be erroneously included or excluded). To make this correct, either ensure `e.columns` includes all table columns (or at least all columns referenced by the partial index condition, in their original order) when `index.Meta().HasCondition()` is true, or reconstruct a full-row `chunk.Row` before calling `MeetPartialConditionWithChunk`; adding an integration test for `ADMIN RECOVER INDEX` on a partial index whose condition involves a non-index column would help guard this path.

Code after:
		}
		iter := chunk.NewIterator4Chunk(e.srcChunk)
		for row := iter.Begin(); row != iter.End(); row = iter.Next() {
			if result.scanRowCount >= int64(e.batchSize) {
				return e.recoverRows, nil
			}
			handle, err := e.handleCols.BuildHandle(e.Ctx().GetSessionVars().StmtCtx, row)
			if err != nil {
				return nil, err
			}
			if e.index.Meta().Global {
				handle = kv.NewPartitionHandle(e.physicalID, handle)
			}

			// TODO: it's possible to push down the condition.
			if e.index.Meta().HasCondition() {
				ok, err := e.index.MeetPartialConditionWithChunk(row)
				if err != nil {
					return nil, err
				}
				if !ok {
					// Skip partial index not match row. Still add the `scanRowCount` to avoid stopping the recovery too early.
					result.scanRowCount++
					result.currentHandle = handle
					continue
				}
			}

			idxVals, err := e.buildIndexedValues(row, e.idxValsBufs[result.scanRowCount], e.colFieldTypes, idxValLen)
			if err != nil {
				return nil, err
			}
			e.idxValsBufs[result.scanRowCount] = idxVals
			rsData := tables.TryGetHandleRestoredDataWrapper(e.table.Meta(), plannercore.GetCommonHandleDatum(e.handleCols, row), nil, e.index.Meta())
			e.recoverRows = append(e.recoverRows, recoverRows{handle: handle, idxVals: idxVals, rsData: rsData, skip: true})
			result.scanRowCount++
			result.currentHandle = handle
		}
	}

	return e.recoverRows, nil
}

func (e *RecoverIndexExec) buildIndexedValues(row chunk.Row, idxVals []types.Datum, fieldTypes []*types.FieldType, idxValLen int) ([]types.Datum, error) {
	if !e.containsGenedColOrPartialIndex {
		return extractIdxVals(row, idxVals, fieldTypes, idxValLen), nil
	}

	if e.cols == nil {
		columns, _, err := expression.ColumnInfos2ColumnsAndNames(e.Ctx().GetExprCtx(), ast.NewCIStr("mock"), e.table.Meta().Name, e.table.Meta().Columns, e.table.Meta())
		if err != nil {