Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
30b1f12
table: Global index did not correctly use partID+_tidb_rowid
mjonss Dec 29, 2025
22d0a74
ddl, meta, tablecodec: add version metadata for global index backward…
mjonss Dec 30, 2025
e102f6a
bazel_prepare
mjonss Jan 1, 2026
fd722bb
Cleanup
mjonss Jan 1, 2026
6059bde
Linting
mjonss Jan 1, 2026
2e01584
Linting
mjonss Jan 1, 2026
f13baf2
Removed debug code that failed linting
mjonss Jan 1, 2026
dd33176
Cleanup
mjonss Jan 1, 2026
3b824df
Added test for old version global index
mjonss Jan 1, 2026
7013ec4
Fixed case where INSERT did not use PartitionHandle for adding index …
mjonss Jan 1, 2026
ee50be1
Updated test and fixed found issue
mjonss Jan 1, 2026
ef6420f
Fixed issue with clustered IntHandle tables
mjonss Jan 1, 2026
cac17ca
temp commit for trouble-shooting how to check if Clustered or not...
mjonss Jan 2, 2026
ea3d427
pkg/ddl: fix GlobalIndexVersion assignment for tables with inline PRI…
mjonss Jan 2, 2026
c175fcb
cleanup of debug in test
mjonss Jan 2, 2026
236ef4d
use patched tikv-server to run realtikv tests
tangenta Jan 5, 2026
78e8421
Update pkg/tablecodec/tablecodec.go
mjonss Jan 7, 2026
d0fb8f3
Addressed review comment about pre-scanning PK for setting Clustered …
mjonss Jan 7, 2026
af46056
Fixed review comment about checking V1 only once per batch of fetchRo…
mjonss Jan 7, 2026
20bd0f5
Linting
mjonss Jan 7, 2026
a3c4848
Merge remote-tracking branch 'pingcap/master' into global-index-non-c…
mjonss Jan 7, 2026
8ed5a61
Addressed review comments, moved logic for using partitioned actualHa…
mjonss Jan 9, 2026
eee5ec1
Linting
mjonss Jan 11, 2026
732ed50
Merge remote-tracking branch 'pingcap/master' into global-index-non-c…
mjonss Jan 11, 2026
e1a3fa2
Removed use of special built tikv, since V1 is now merged into tikv/m…
mjonss Jan 15, 2026
a18ee7b
Merge remote-tracking branch 'pingcap/master' into global-index-non-c…
mjonss Jan 15, 2026
d3d17e0
Removed non-needed change
mjonss Jan 15, 2026
ea589be
Removed more non-needed changes
mjonss Jan 16, 2026
edc40d8
Added back a comment that should not have been removed...
mjonss Jan 16, 2026
bfcc751
Merge remote-tracking branch 'pingcap/master' into global-index-non-c…
mjonss Jan 21, 2026
c4c252d
Added tests for copilots review comments
mjonss Jan 21, 2026
27c42a2
Apply suggestions from code review
mjonss Jan 23, 2026
0473d15
Merge remote-tracking branch 'pingcap/master' into global-index-non-c…
mjonss Jan 23, 2026
d5c43fa
Fixed issue with nested PartitionHandle
mjonss Jan 23, 2026
fc2ce59
bazel_prepare
mjonss Jan 26, 2026
d7a3a30
Merge remote-tracking branch 'pingcap/master' into global-index-non-c…
mjonss Jan 26, 2026
b1565a9
Fix overwritten global index entries for unique null keys
mjonss Jan 30, 2026
cb95d58
Merge remote-tracking branch 'pingcap/master' into global-index-non-c…
mjonss Jan 30, 2026
34df699
Test fix
mjonss Feb 3, 2026
c1045f7
Removed using subtests in tablecodec_test
mjonss Feb 3, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 53 additions & 6 deletions pkg/ddl/create_table.go
Original file line number Diff line number Diff line change
Expand Up @@ -1328,6 +1328,25 @@ func BuildTableInfo(
existedColsMap[v.Name.L] = struct{}{}
}
foreignKeyID := tbInfo.MaxForeignKeyID

// This pre-scan is necessary because index building in the main loop may need to check
// tbInfo.HasClusteredIndex() before the PRIMARY KEY constraint is fully processed.
for _, constr := range constraints {
if constr.Tp == ast.ConstraintPrimaryKey {
isSingleIntPK := isSingleIntPKFromTableInfo(constr, tbInfo)

if ShouldBuildClusteredIndex(ctx.GetClusteredIndexDefMode(), constr.Option, isSingleIntPK) {
if isSingleIntPK {
tbInfo.PKIsHandle = true
} else {
tbInfo.IsCommonHandle = true
tbInfo.CommonHandleVersion = 1
}
}
break // Only one PRIMARY KEY possible
}
}

for _, constr := range constraints {
var hiddenCols []*model.ColumnInfo
if constr.Tp != ast.ConstraintColumnar {
Expand Down Expand Up @@ -1380,7 +1399,7 @@ func BuildTableInfo(
// all partial primary key.
return nil, dbterror.ErrUnsupportedAddPartialIndex.GenWithStackByArgs("create an primary key with partial index is not supported")
}
isSingleIntPK := isSingleIntPK(constr, lastCol)
isSingleIntPK := isSingleIntPKFromCol(constr, lastCol)
if ShouldBuildClusteredIndex(ctx.GetClusteredIndexDefMode(), constr.Option, isSingleIntPK) {
if isSingleIntPK {
tbInfo.PKIsHandle = true
Expand Down Expand Up @@ -1676,18 +1695,46 @@ func addIndexForForeignKey(ctx *metabuild.Context, tbInfo *model.TableInfo) erro
return nil
}

func isSingleIntPK(constr *ast.Constraint, lastCol *model.ColumnInfo) bool {
if len(constr.Keys) != 1 {
return false
}
switch lastCol.GetType() {
func isIntCol(col *model.ColumnInfo) bool {
switch col.GetType() {
case mysql.TypeLong, mysql.TypeLonglong,
mysql.TypeTiny, mysql.TypeShort, mysql.TypeInt24:
return true
}
return false
}

// isSingleIntPKFromTableInfo determines if a constraint represents a single integer primary key
// by looking up the column information from the table info. This is used during the pre-scan
// phase before CheckPKOnGeneratedColumn is called.
func isSingleIntPKFromTableInfo(constr *ast.Constraint, tbInfo *model.TableInfo) bool {
// Multi-column PKs are not single integer PKs
if len(constr.Keys) != 1 {
return false
}

// Expression-based PKs (e.g., PRIMARY KEY ((col+1))) are not single integer PKs
if constr.Keys[0].Expr != nil {
return false
}

// Find the column in the table
colName := constr.Keys[0].Column.Name.L
for _, col := range tbInfo.Columns {
if col.Name.L == colName {
return isIntCol(col)
}
}
return false
}

func isSingleIntPKFromCol(constr *ast.Constraint, lastCol *model.ColumnInfo) bool {
if len(constr.Keys) != 1 {
return false
}
return isIntCol(lastCol)
}

// ShouldBuildClusteredIndex is used to determine whether the CREATE TABLE statement should build a clustered index table.
func ShouldBuildClusteredIndex(mode vardef.ClusteredIndexDefMode, opt *ast.IndexOption, isSingleIntPK bool) bool {
if opt == nil || opt.PrimaryKeyTp == ast.PrimaryKeyTypeDefault {
Expand Down
55 changes: 46 additions & 9 deletions pkg/ddl/index.go
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,36 @@ func getIndexColumnLength(col *model.ColumnInfo, colLen int, columnarIndexType m
}
}

// Set global index version for new global indexes.
// Version 1 is needed for non-clustered tables to prevent collisions after
// EXCHANGE PARTITION due to duplicate _tidb_rowid values.
// For non-unique indexes, the handle is always encoded in the key.
// For unique indexes with NULL values, the handle is also encoded in the key
// (since NULL != NULL, multiple NULLs are allowed).
// In both cases, we need the partition ID in the key to distinguish rows
// from different partitions that may have the same _tidb_rowid.
// Clustered tables don't have this issue and use version 0.
func setGlobalIndexVersion(tblInfo *model.TableInfo, idxInfo *model.IndexInfo) {
idxInfo.GlobalIndexVersion = 0
if idxInfo.Global && !tblInfo.HasClusteredIndex() {
needPartitionInKey := !idxInfo.Unique
if !needPartitionInKey {
nullCols := getNullColInfos(tblInfo, idxInfo.Columns)
if len(nullCols) > 0 {
needPartitionInKey = true
}
}
if needPartitionInKey {
idxInfo.GlobalIndexVersion = model.GlobalIndexVersionV1
failpoint.Inject("SetGlobalIndexVersion", func(val failpoint.Value) {
if valInt, ok := val.(int); ok {
idxInfo.GlobalIndexVersion = uint8(valInt)
}
})
}
}
}

// decimal using a binary format that packs nine decimal (base 10) digits into four bytes.
func calcBytesLengthForDecimal(m int) int {
return (m / 9 * 4) + ((m%9)+1)/2
Expand Down Expand Up @@ -410,6 +440,7 @@ func BuildIndexInfo(
idxInfo.Tp = indexOption.Tp
}
idxInfo.Global = indexOption.Global
setGlobalIndexVersion(tblInfo, idxInfo)

conditionString, err := CheckAndBuildIndexConditionString(tblInfo, indexOption.Condition)
if err != nil {
Expand Down Expand Up @@ -697,15 +728,15 @@ func setIndexVisibility(tblInfo *model.TableInfo, name ast.CIStr, invisible bool
}
}

func getNullColInfos(tblInfo *model.TableInfo, indexInfo *model.IndexInfo) ([]*model.ColumnInfo, error) {
nullCols := make([]*model.ColumnInfo, 0, len(indexInfo.Columns))
for _, colName := range indexInfo.Columns {
func getNullColInfos(tblInfo *model.TableInfo, cols []*model.IndexColumn) []*model.ColumnInfo {
nullCols := make([]*model.ColumnInfo, 0, len(cols))
for _, colName := range cols {
col := model.FindColumnInfo(tblInfo.Columns, colName.Name.L)
if !mysql.HasNotNullFlag(col.GetFlag()) || mysql.HasPreventNullInsertFlag(col.GetFlag()) {
nullCols = append(nullCols, col)
}
}
return nullCols, nil
return nullCols
}

func checkPrimaryKeyNotNull(jobCtx *jobContext, w *worker, job *model.Job,
Expand All @@ -718,10 +749,7 @@ func checkPrimaryKeyNotNull(jobCtx *jobContext, w *worker, job *model.Job,
if err != nil {
return nil, err
}
nullCols, err := getNullColInfos(tblInfo, indexInfo)
if err != nil {
return nil, err
}
nullCols := getNullColInfos(tblInfo, indexInfo.Columns)
if len(nullCols) == 0 {
return nil, nil
}
Expand Down Expand Up @@ -2454,7 +2482,16 @@ func (w *baseIndexWorker) fetchRowColVals(txn kv.Transaction, taskRange reorgBac
if index.Meta().HasCondition() {
return false, dbterror.ErrUnsupportedAddPartialIndex.GenWithStackByArgs("add partial index without fast reorg")
}
idxRecord, err1 := w.getIndexRecord(index.Meta(), handle, recordKey)
actualHandle := handle
// For global indexes V1+ on partitioned tables, we need to wrap the handle
// with the partition ID to create a PartitionHandle.
// This is critical for non-clustered tables after EXCHANGE PARTITION,
// where duplicate _tidb_rowid values exist across partitions.
// Legacy indexes (version 0) don't use PartitionHandle in the key.
if index.Meta().Global && index.Meta().GlobalIndexVersion >= model.GlobalIndexVersionV1 {
actualHandle = kv.NewPartitionHandle(taskRange.physicalTable.GetPhysicalID(), handle)
}
idxRecord, err1 := w.getIndexRecord(index.Meta(), actualHandle, recordKey)
if err1 != nil {
return false, errors.Trace(err1)
}
Expand Down
2 changes: 2 additions & 0 deletions pkg/ddl/partition.go
Original file line number Diff line number Diff line change
Expand Up @@ -626,6 +626,7 @@ func buildTablePartitionInfo(ctx *metabuild.Context, s *ast.PartitionOptions, tb
dupCheck[strings.ToLower(idxUpdate.Name)] = struct{}{}
if idxUpdate.Option != nil && idxUpdate.Option.Global {
tbInfo.Indices[idxOffset].Global = true
setGlobalIndexVersion(tbInfo, tbInfo.Indices[idxOffset])
} else {
tbInfo.Indices[idxOffset].Global = false
}
Expand Down Expand Up @@ -3293,6 +3294,7 @@ func (w *worker) onReorganizePartition(jobCtx *jobContext, job *model.Job) (ver
tblInfo.Partition.DDLChangedIndex[index.ID] = false
tblInfo.Partition.DDLChangedIndex[newIndex.ID] = true
newIndex.Global = newGlobal
setGlobalIndexVersion(tblInfo, newIndex)
tblInfo.Indices = append(tblInfo.Indices, newIndex)
}
failpoint.Inject("reorgPartCancel1", func(val failpoint.Value) {
Expand Down
10 changes: 2 additions & 8 deletions pkg/ddl/rollingback.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,7 @@ import (

// UpdateColsNull2NotNull changes the null option of columns of an index.
func UpdateColsNull2NotNull(tblInfo *model.TableInfo, indexInfo *model.IndexInfo) error {
nullCols, err := getNullColInfos(tblInfo, indexInfo)
if err != nil {
return errors.Trace(err)
}
nullCols := getNullColInfos(tblInfo, indexInfo.Columns)

for _, col := range nullCols {
col.AddFlag(mysql.NotNullFlag)
Expand Down Expand Up @@ -66,10 +63,7 @@ func convertAddIdxJob2RollbackJob(
originalState := allIndexInfos[0].State
for _, indexInfo := range allIndexInfos {
if indexInfo.Primary {
nullCols, err := getNullColInfos(tblInfo, indexInfo)
if err != nil {
return 0, errors.Trace(err)
}
nullCols := getNullColInfos(tblInfo, indexInfo.Columns)
for _, col := range nullCols {
// Field PreventNullInsertFlag flag reset.
col.DelFlag(mysql.PreventNullInsertFlag)
Expand Down
1 change: 1 addition & 0 deletions pkg/ddl/tests/partition/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ go_test(
"db_partition_test.go",
"error_injection_test.go",
"exchange_partition_test.go",
"global_index_version_test.go",
"main_test.go",
"multi_domain_test.go",
"placement_test.go",
Expand Down
Loading