Skip to content
Merged
Show file tree
Hide file tree
Changes from 39 commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
46f0107
fix(plan): validate child keys in modern update
ck89119 Jul 29, 2026
3cd6c94
Merge remote-tracking branch 'mo/main' into issue-26338-main
ck89119 Jul 29, 2026
b12ab25
Merge branch 'main' into issue-26338-main
mergify[bot] Jul 29, 2026
2080f5d
Merge remote-tracking branch 'mo/main' into issue-26338-main
ck89119 Jul 29, 2026
73af4f1
Merge remote-tracking branch 'origin/issue-26338-main' into issue-263…
ck89119 Jul 29, 2026
c32538b
ci: rerun arm64 SCA test
ck89119 Jul 30, 2026
bfa571f
Merge remote-tracking branch 'refs/remotes/mo/pr-26407' into issue-26…
ck89119 Jul 30, 2026
91b00f7
plan: move parent FK update actions to multi update
ck89119 Jul 30, 2026
505ebfa
Merge remote-tracking branch 'mo/main' into issue-26339-main
ck89119 Jul 30, 2026
6771349
Merge remote-tracking branch 'mo/main' into issue-26339-main
ck89119 Jul 30, 2026
be6af2c
Merge remote-tracking branch 'mo/main' into issue-26339-main
ck89119 Jul 30, 2026
8bdc0fd
Merge remote-tracking branch 'mo/main' into issue-26339-main
ck89119 Jul 30, 2026
c8d2c2f
fix(plan): validate foreign key update row images
ck89119 Jul 30, 2026
9151e79
Merge remote-tracking branch 'mo/main' into issue-26339-main
ck89119 Jul 30, 2026
7872dd8
Merge remote-tracking branch 'refs/remotes/mo/main' into issue-26339-…
ck89119 Jul 30, 2026
3fffe09
Merge remote-tracking branch 'refs/remotes/mo/main' into issue-26339-…
ck89119 Jul 30, 2026
ed6a84b
fix(plan): preserve update schema after preinsert
ck89119 Jul 30, 2026
101b0d0
Merge remote-tracking branch 'mo/main' into issue-26339-main
ck89119 Jul 30, 2026
afecfe0
fix(plan): keep FK updates cache sensitive
ck89119 Jul 31, 2026
3fe18d0
Merge remote-tracking branch 'mo/main' into issue-26339-main
ck89119 Jul 31, 2026
75222b5
Merge remote-tracking branch 'mo/main' into issue-26339-main
ck89119 Jul 31, 2026
51d0d28
Merge remote-tracking branch 'mo/main' into issue-26339-main
ck89119 Jul 31, 2026
1680d36
fix(plan): preserve typed update FK errors
ck89119 Jul 31, 2026
dbc50bd
Merge remote-tracking branch 'mo/main' into issue-26339-main
ck89119 Jul 31, 2026
4927b1f
fix: preserve typed FK errors in legacy updates
ck89119 Jul 31, 2026
085bfd0
Merge remote-tracking branch 'mo/main' into issue-26339-main
ck89119 Jul 31, 2026
f7c5e17
test: update prepared FK error result
ck89119 Jul 31, 2026
9cfa4ac
Merge remote-tracking branch 'mo/main' into issue-26339-main
ck89119 Jul 31, 2026
1a6f76b
test: expand FK update planner coverage
ck89119 Jul 31, 2026
81caf78
Merge remote-tracking branch 'mo/main' into issue-26339-main
ck89119 Jul 31, 2026
095ae78
test: cover generated columns in FK cascades
ck89119 Jul 31, 2026
c709722
Merge remote-tracking branch 'mo/main' into issue-26339-main
ck89119 Jul 31, 2026
656b9ec
test: align FK route coverage with modern index updates
ck89119 Jul 31, 2026
d98d684
Merge remote-tracking branch 'mo/main' into issue-26339-main
ck89119 Aug 1, 2026
cdc4ce0
Merge remote-tracking branch 'mo/main' into issue-26339-main
ck89119 Aug 1, 2026
957a6ec
fix: gate incomplete FK cascade row closure
ck89119 Aug 1, 2026
0e438c9
Merge remote-tracking branch 'mo/main' into issue-26339-main
ck89119 Aug 1, 2026
8968a53
fix: reject unsafe derived FK cascades
ck89119 Aug 1, 2026
f36cae4
Merge remote-tracking branch 'mo/main' into issue-26339-main
ck89119 Aug 1, 2026
30f14f3
Merge remote-tracking branch 'mo/main' into issue-26339-main
ck89119 Aug 1, 2026
9ad9251
fix: lock parent keys before FK update actions
ck89119 Aug 1, 2026
9b8a71c
Merge remote-tracking branch 'mo/main' into issue-26339-main
ck89119 Aug 1, 2026
1cd8333
Merge remote-tracking branch 'mo/main' into issue-26339-main
ck89119 Aug 2, 2026
be00085
test: exercise concurrent parent FK locking
ck89119 Aug 2, 2026
c0340ad
Merge remote-tracking branch 'mo/main' into issue-26339-main
ck89119 Aug 2, 2026
1ed215c
test: update FK parent update error expectations
ck89119 Aug 2, 2026
d721e47
Merge remote-tracking branch 'mo/main' into issue-26339-main
ck89119 Aug 2, 2026
6e6f3dd
Merge remote-tracking branch 'mo/main' into issue-26339-main
ck89119 Aug 3, 2026
b31e176
fix(plan): close parent FK update safety gaps
ck89119 Aug 3, 2026
29b06e9
fix(plan): preserve non-unique FK cascade compatibility
ck89119 Aug 3, 2026
ac5295c
Merge remote-tracking branch 'mo/main' into issue-26339-main
ck89119 Aug 3, 2026
97aaf46
Merge remote-tracking branch 'mo/main' into issue-26339-main
ck89119 Aug 3, 2026
956a8ce
test(plan): cover FK execution safety boundaries
ck89119 Aug 3, 2026
ed912c9
fix(plan): validate non-unique FK child mappings
ck89119 Aug 3, 2026
fc4e8e0
Merge remote-tracking branch 'mo/main' into issue-26339-main
ck89119 Aug 3, 2026
3199cc0
fix(plan): scope parent FK cache sensitivity
ck89119 Aug 3, 2026
b45ac19
Merge branch 'main' into issue-26339-main
mergify[bot] Aug 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
1,812 changes: 927 additions & 885 deletions pkg/pb/plan/plan.pb.go

Large diffs are not rendered by default.

8 changes: 8 additions & 0 deletions pkg/sql/colexec/multi_update/affect_rows_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,14 @@ func TestUpsertAffectRowsAccounting(t *testing.T) {
require.EqualValues(t, 0, op.GetAffectedRows())
})

t.Run("foreign key side effects never affect rows", func(t *testing.T) {
op := newAffectRowsTestOp(actionUpdate, true)
op.MultiUpdateCtx = []*MultiUpdateCtx{{IgnoreAffectedRows: true}}
op.addInsertAffectRows(UpdateMainTable, 4)
op.addDeleteAffectRows(UpdateMainTable, 3)
require.EqualValues(t, 0, op.GetAffectedRows())
})

t.Run("batch upsert: new + updated rows", func(t *testing.T) {
// 2 brand new rows (INSERT only) + 3 conflicting rows (DELETE + INSERT):
// inserts cover all 5 rows, deletes cover the 3 conflicts => 5 + 3 = 8.
Expand Down
1 change: 1 addition & 0 deletions pkg/sql/colexec/multi_update/multi_update_partition.go
Original file line number Diff line number Diff line change
Expand Up @@ -443,6 +443,7 @@ func (ctx *MultiUpdateCtx) clone() *MultiUpdateCtx {
PartitionCols: ctx.PartitionCols,
SkipInsertOnNullPk: ctx.SkipInsertOnNullPk,
InsertPkColIdx: ctx.InsertPkColIdx,
IgnoreAffectedRows: ctx.IgnoreAffectedRows,
}
objRef := *ctx.ObjRef
def := *ctx.TableDef
Expand Down
6 changes: 5 additions & 1 deletion pkg/sql/colexec/multi_update/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,8 @@ type MultiUpdateCtx struct {
SkipInsertOnNullPk bool
// InsertPkColIdx is the PK column's index within InsertCols. It is only
// used with SkipInsertOnNullPk for REPLACE delete-only rows.
InsertPkColIdx int
InsertPkColIdx int
IgnoreAffectedRows bool
}

func (update MultiUpdate) TypeName() string {
Expand Down Expand Up @@ -230,5 +231,8 @@ func (update *MultiUpdate) addDeleteAffectRows(tableType UpdateTableType, rowCou
}

func (update *MultiUpdate) doAddAffectedRows(affectedRows uint64) {
if len(update.MultiUpdateCtx) > 0 && update.MultiUpdateCtx[0].IgnoreAffectedRows {
return
}
update.ctr.affectedRows += affectedRows
}
1 change: 1 addition & 0 deletions pkg/sql/compile/operator.go
Original file line number Diff line number Diff line change
Expand Up @@ -896,6 +896,7 @@ func constructMultiUpdate(
PartitionCols: partitionCols,
SkipInsertOnNullPk: updateCtx.SkipInsertOnNullPk,
InsertPkColIdx: int(updateCtx.InsertPkColIdx),
IgnoreAffectedRows: updateCtx.IgnoreAffectedRows,
}
}
arg.Action = action
Expand Down
2 changes: 2 additions & 0 deletions pkg/sql/compile/remoterun.go
Original file line number Diff line number Diff line change
Expand Up @@ -867,6 +867,7 @@ func convertToPipelineInstruction(op vm.Operator, proc *process.Process, ctx *sc
TableDef: muCtx.TableDef,
SkipInsertOnNullPk: muCtx.SkipInsertOnNullPk,
InsertPkColIdx: int32(muCtx.InsertPkColIdx),
IgnoreAffectedRows: muCtx.IgnoreAffectedRows,
CountDeleteAffectRows: t.CountDeleteAffectRows,
}

Expand Down Expand Up @@ -1374,6 +1375,7 @@ func convertToVmOperator(opr *pipeline.Instruction, ctx *scopeContext, eng engin
TableDef: muCtx.TableDef,
SkipInsertOnNullPk: muCtx.SkipInsertOnNullPk,
InsertPkColIdx: int(muCtx.InsertPkColIdx),
IgnoreAffectedRows: muCtx.IgnoreAffectedRows,
}

arg.MultiUpdateCtx[i].InsertCols = make([]int, len(muCtx.InsertCols))
Expand Down
9 changes: 7 additions & 2 deletions pkg/sql/compile/remoterun_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -716,8 +716,9 @@ func Test_DMLOperatorSerializationRoundtrip(t *testing.T) {
op := &multi_update.MultiUpdate{
MultiUpdateCtx: []*multi_update.MultiUpdateCtx{
{
ObjRef: &plan.ObjectRef{ObjName: "t1"},
TableDef: &plan.TableDef{Name: "t1"},
ObjRef: &plan.ObjectRef{ObjName: "t1"},
TableDef: &plan.TableDef{Name: "t1"},
IgnoreAffectedRows: true,
},
},
Action: multi_update.UpdateWriteTable,
Expand All @@ -727,12 +728,16 @@ func Test_DMLOperatorSerializationRoundtrip(t *testing.T) {
require.NoError(t, err)
require.True(t, pipeInstr.MultiUpdate.UpdateCtxList[0].CountDeleteAffectRows,
"serialized UpdateCtx must carry CountDeleteAffectRows")
require.True(t, pipeInstr.MultiUpdate.UpdateCtxList[0].IgnoreAffectedRows,
"serialized UpdateCtx must carry IgnoreAffectedRows")

restored, err := convertToVmOperator(pipeInstr, ctx, nil)
require.NoError(t, err)
restoredOp := restored.(*multi_update.MultiUpdate)
require.True(t, restoredOp.CountDeleteAffectRows,
"CountDeleteAffectRows must survive the remote pipeline round-trip")
require.True(t, restoredOp.MultiUpdateCtx[0].IgnoreAffectedRows,
"IgnoreAffectedRows must survive the remote pipeline round-trip")
})

t.Run("MultiUpdate_RejectZeroTemporal", func(t *testing.T) {
Expand Down
2 changes: 1 addition & 1 deletion pkg/sql/plan/bind_insert.go
Original file line number Diff line number Diff line change
Expand Up @@ -1061,7 +1061,7 @@ func (builder *QueryBuilder) appendModernChildFkMarkOks(
selectTag int32,
childColPos func(colName string) int32,
) (int32, []*plan.Expr, error) {
selectNode := builder.qry.Nodes[lastNodeID]
selectNode := builder.updateInputProjectNode(lastNodeID)
inputTypes := make([]plan.Type, len(selectNode.ProjectList))
for i, expr := range selectNode.ProjectList {
inputTypes[i] = expr.Typ
Expand Down
62 changes: 24 additions & 38 deletions pkg/sql/plan/bind_update.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,17 @@ func (builder *QueryBuilder) bindUpdate(stmt *tree.Update, bindCtx *BindContext)
if err != nil {
return 0, err
}
for i, tableDef := range dmlCtx.tableDefs {
if len(dmlCtx.updateCol2Expr[i]) > 0 &&
(len(tableDef.Fkeys) > 0 || len(tableDef.RefChildTbls) > 0) {
// FK validation and parent-side actions depend on the current
// foreign_key_checks value. Mark the plan before that value is
// inspected so a plan built with checks disabled cannot be reused
// after they are enabled (or vice versa).
builder.qry.HasForeignKeyAction = true
break
}
}
if err = validateUpdateTargetSubqueries(builder.compCtx, stmt, dmlCtx.objRefs, dmlCtx.tableDefs); err != nil {
return 0, err
}
Expand Down Expand Up @@ -399,44 +410,6 @@ func (builder *QueryBuilder) bindUpdate(stmt *tree.Update, bindCtx *BindContext)
}
}

fkChecksEnabled, err := IsForeignKeyChecksEnabled(builder.compCtx)
if err != nil {
return 0, err
}
if updateMayDependOnForeignKeys(dmlCtx, newColName2Idx) {
// The plan shape and planner route depend on foreign_key_checks.
// Preserve that dependency even while checks are disabled so prepared
// and generic plan caches rebuild after either session-state transition.
builder.qry.HasForeignKeyAction = true
}
if fkChecksEnabled {
for i, tableDef := range dmlCtx.tableDefs {
if updateAutoIncrCols[i] &&
len(affectedUpdateChildFks(tableDef, dmlCtx.aliases[i], newColName2Idx)) > 0 {
return 0, newLegacyUpdatePlannerRouteError(
updateRouteReasonAutoIncrement,
moerr.NewUnsupportedDML(
builder.compCtx.GetContext(),
"auto_increment foreign key update",
),
)
}
}
}

lastNodeID, selectNodeTag, selectNode, err = builder.appendUpdateForeignKeyChecks(
bindCtx,
dmlCtx,
lastNodeID,
selectNodeTag,
oldColName2Idx,
newColName2Idx,
fkChecksEnabled,
)
if err != nil {
return 0, err
}

for i, tableDef := range dmlCtx.tableDefs {
if updateAutoIncrCols[i] {
lastNodeID = builder.appendNode(&plan.Node{
Expand All @@ -453,6 +426,19 @@ func (builder *QueryBuilder) bindUpdate(stmt *tree.Update, bindCtx *BindContext)
}
}

lastNodeID, selectNodeTag, selectNode, err = builder.appendUpdateForeignKeyChecks(
bindCtx,
dmlCtx,
lastNodeID,
selectNodeTag,
selectNode,
oldColName2Idx,
newColName2Idx,
)
if err != nil {
return 0, err
}

idxScanNodes := make([][]*plan.Node, len(dmlCtx.tableDefs))
pkNeedUpdate := make([]bool, len(dmlCtx.tableDefs))
idxNeedUpdate := make([][]bool, len(dmlCtx.tableDefs))
Expand Down
Loading
Loading