Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
f6221c1
feat(frontend): support schema evolution in data branch diff/merge
ULookup May 28, 2026
d82470c
fix(frontend): add bounds check in projectBaseBatchToTarget
ULookup May 28, 2026
f0aa537
fix(frontend): skip target-only columns in MERGE INSERT SQL generation
ULookup May 28, 2026
03492a1
fix(frontend): correct Cols-index vs batch-index mapping in schema ev…
ULookup Jun 3, 2026
cda05f8
fix(test): add missing snapshot cleanup in merge_schema_evolve BVT
ULookup Jun 10, 2026
d23ad0d
Merge remote-tracking branch 'upstream/main' into pr-24666-work
ULookup Jul 8, 2026
3bd0acd
test: expand BVT coverage for schema evolution diff/merge
ULookup Jul 8, 2026
8bee19d
Merge remote-tracking branch 'upstream/main' into pr-24666-work
ULookup Jul 8, 2026
ab3099c
fix(frontend): correct tuple indexing for schema evolution diff/merge
ULookup Jul 8, 2026
5e041da
Merge branch 'main' into fix/issue-24549-schema-evolution-diff-merge
ULookup Jul 8, 2026
568d5f7
fix(frontend): align data branch column indexes
ULookup Jul 9, 2026
a0ed6e0
Merge branch 'main' into fix/issue-24549-schema-evolution-diff-merge
ULookup Jul 9, 2026
3cf182e
fix(frontend): align data branch column indexes
ULookup Jul 9, 2026
ee0b8aa
fix(frontend): harden schema compatibility checks
ULookup Jul 10, 2026
d5a5c6a
fix(frontend): relax schema evolution metadata checks
ULookup Jul 10, 2026
04467ee
fix(frontend): preserve branch lineage after schema evolution
ULookup Jul 10, 2026
e90a008
fix(frontend): probe LCA with common columns
ULookup Jul 10, 2026
8a998b7
fix(frontend): preserve data branch changes across alter generations
ULookup Jul 10, 2026
f0a5cb8
test(frontend): cover sparse schema conflict updates
ULookup Jul 15, 2026
e8ff378
test(frontend): fix cluster-by schema evolution BVT
ULookup Jul 15, 2026
31713c1
test(frontend): isolate lineage alter transaction BVT
ULookup Jul 15, 2026
7f51daf
test(frontend): restore lineage transaction update case
ULookup Jul 15, 2026
e0cc767
fix(frontend): use live context for branch clone lock
ULookup Jul 15, 2026
11def75
fix(frontend): preserve ancestor branch updates
ULookup Jul 15, 2026
04b977c
fix(frontend): preserve bounded historical branch updates
ULookup Jul 16, 2026
efb637b
fix: reclaim unowned historical alter lineage
ULookup Jul 17, 2026
2174674
fix: preserve altered branch lineage ownership
ULookup Jul 17, 2026
50001f3
fix(frontend): project reordered data branch batches
VioletQwQ-0 Jul 20, 2026
9e00f95
Merge remote-tracking branch 'upstream/main' into codex/takeover-2466…
VioletQwQ-0 Jul 20, 2026
9c86799
fix(frontend): preserve identity-layout branch batches
VioletQwQ-0 Jul 20, 2026
4260d7a
Merge remote-tracking branch 'upstream/main' into codex/takeover-2466…
VioletQwQ-0 Jul 20, 2026
c515a14
fix(frontend): project target-only historical type drift
VioletQwQ-0 Jul 20, 2026
d4ec9f8
fix: allow historical alter in transactions
VioletQwQ-0 Jul 20, 2026
8df839e
fix(frontend): reclaim deleted alter lineage
VioletQwQ-0 Jul 20, 2026
60ceac2
Merge upstream/main and address data branch review feedback
VioletQwQ-0 Jul 22, 2026
08cd75d
Merge remote-tracking branch 'upstream/main' into codex/takeover-2466…
VioletQwQ-0 Jul 22, 2026
7a0fb11
fix(frontend): exclude target-only columns from LCA probes
VioletQwQ-0 Jul 22, 2026
5b57d3f
fix(frontend): preserve derived composite key lineage
VioletQwQ-0 Jul 22, 2026
af4e2f7
fix(frontend): validate data branch column lineage
VioletQwQ-0 Jul 22, 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
3 changes: 3 additions & 0 deletions pkg/frontend/authenticate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15192,6 +15192,7 @@ func TestDoDropSnapshot(t *testing.T) {

bh := &backgroundExecTest{}
bh.init()
registerEmptyHistoricalLineageResults(bh)

bhStub := gostub.StubFunc(&NewBackgroundExec, bh)
defer bhStub.Reset()
Expand Down Expand Up @@ -15242,6 +15243,7 @@ func TestDoDropSnapshot(t *testing.T) {

err := doDropSnapshot(ctx, ses, ds)
convey.So(err, convey.ShouldBeNil)
convey.So(bh.executedSQLs, convey.ShouldContain, historicalAlterLineageMetadataSQL())
})

convey.Convey("doDropSnapshot success", t, func() {
Expand All @@ -15253,6 +15255,7 @@ func TestDoDropSnapshot(t *testing.T) {

bh := &backgroundExecTest{}
bh.init()
registerEmptyHistoricalLineageResults(bh)

bhStub := gostub.StubFunc(&NewBackgroundExec, bh)
defer bhStub.Reset()
Expand Down
165 changes: 164 additions & 1 deletion pkg/frontend/clone.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,21 +17,29 @@ package frontend
import (
"context"
"fmt"
"sort"
"strconv"
"strings"
"time"

"github.com/matrixorigin/matrixone/pkg/catalog"
"github.com/matrixorigin/matrixone/pkg/common/moerr"
"github.com/matrixorigin/matrixone/pkg/container/batch"
"github.com/matrixorigin/matrixone/pkg/container/types"
"github.com/matrixorigin/matrixone/pkg/container/vector"
"github.com/matrixorigin/matrixone/pkg/defines"
"github.com/matrixorigin/matrixone/pkg/objectio"
"github.com/matrixorigin/matrixone/pkg/pb/lock"
plan2 "github.com/matrixorigin/matrixone/pkg/pb/plan"
"github.com/matrixorigin/matrixone/pkg/pb/timestamp"
"github.com/matrixorigin/matrixone/pkg/sql/colexec/lockop"
"github.com/matrixorigin/matrixone/pkg/sql/parsers/dialect"
"github.com/matrixorigin/matrixone/pkg/sql/parsers/tree"
"github.com/matrixorigin/matrixone/pkg/sql/plan"
"github.com/matrixorigin/matrixone/pkg/sql/plan/function"
"github.com/matrixorigin/matrixone/pkg/txn/client"
"github.com/matrixorigin/matrixone/pkg/vm/engine"
"github.com/matrixorigin/matrixone/pkg/vm/process"
)

const (
Expand Down Expand Up @@ -70,6 +78,144 @@ type cloneReceipt struct {
srcAccountName string
}

type dataBranchCloneLockCtxKey struct{}

func withDataBranchCloneLockContext(
proc *process.Process,
ctx context.Context,
lockRows func() error,
) error {
oldCtx := proc.Ctx
proc.Ctx = ctx
defer func() {
proc.Ctx = oldCtx
}()
return lockRows()
}

func lockDataBranchCloneSource(
ctx context.Context,
ses *Session,
fromAccountID uint32,
databaseName, tableName string,
) error {
if locked, _ := ctx.Value(dataBranchCloneLockCtxKey{}).(bool); !locked {
return nil
}
txnOp := ses.proc.GetTxnOperator()
if !txnOp.Txn().IsPessimistic() {
// LockRows is intentionally a no-op for optimistic transactions.
// Keep DATA BRANCH available in that mode; ALTER records the lineage
// from its statement snapshot, and a missing edge caused by a true
// concurrent copy-and-swap is detected by the legacy-lineage guard
// before DIFF/MERGE can return an incorrect result.
return nil
}
sourceCtx := defines.AttachAccountId(ctx, fromAccountID)
eng := ses.proc.GetSessionInfo().StorageEngine
db, err := eng.Database(sourceCtx, catalog.MO_CATALOG, txnOp)
if err != nil {
return err
}
rel, err := db.Relation(sourceCtx, catalog.MO_TABLES, nil)
if err != nil {
return err
}
lockBat, err := dataBranchCloneCatalogLockBatch(
ses.proc, fromAccountID, databaseName, tableName,
)
if err != nil {
return err
}
defer lockBat.Vecs[0].Free(ses.proc.Mp())
// ALTER locks this exact mo_tables composite key exclusively. A shared
// catalog-row lock serializes source-ID/snapshot selection with ALTER while
// allowing source-table DML and sibling branch clones to continue.
return withDataBranchCloneLockContext(ses.proc, sourceCtx, func() error {
return lockop.LockRows(
eng,
ses.proc,
rel,
rel.GetTableID(sourceCtx),
lockBat,
0,
*lockBat.Vecs[0].GetType(),
lock.LockMode_Shared,
lock.Sharding_None,
fromAccountID,
)
})
}

func dataBranchCloneCatalogLockBatch(
proc *process.Process,
accountID uint32,
databaseName, tableName string,
) (*batch.Batch, error) {
inputs := make([]*vector.Vector, 3)
defer func() {
for _, input := range inputs {
if input != nil {
input.Free(proc.GetMPool())
}
}
}()
inputs[0] = vector.NewVec(types.T_uint32.ToType())
if err := vector.AppendFixed(inputs[0], accountID, false, proc.GetMPool()); err != nil {
return nil, err
}
for i, name := range []string{databaseName, tableName} {
inputs[i+1] = vector.NewVec(types.T_varchar.ToType())
if err := vector.AppendBytes(inputs[i+1], []byte(name), false, proc.GetMPool()); err != nil {
return nil, err
}
}
encoded, err := function.RunFunctionDirectly(
proc, function.SerialFunctionEncodeID, inputs, 1,
)
if err != nil {
return nil, err
}
bat := batch.NewWithSize(1)
bat.SetVector(0, encoded)
return bat, nil
}

func lockDataBranchCloneDatabaseSources(
ctx context.Context,
ses *Session,
source cloneDatabaseSource,
) error {
if locked, _ := ctx.Value(dataBranchCloneLockCtxKey{}).(bool); !locked {
return nil
}
if source.snapshot != nil && source.snapshot.TS != nil {
return nil
}
fromAccountID := source.opAccountId
if source.snapshot != nil && source.snapshot.Tenant != nil {
fromAccountID = source.snapshot.Tenant.TenantID
}
tables := append([]*tableInfo(nil), source.srcTblInfos...)
sort.Slice(tables, func(i, j int) bool {
if tables[i].dbName != tables[j].dbName {
return tables[i].dbName < tables[j].dbName
}
return tables[i].tblName < tables[j].tblName
})
for _, table := range tables {
if table.typ == view {
continue
}
if err := lockDataBranchCloneSource(
ctx, ses, fromAccountID, table.dbName, table.tblName,
); err != nil {
return err
}
}
return nil
}

func getBackExecutor(
ctx context.Context,
ses *Session,
Expand Down Expand Up @@ -317,6 +463,17 @@ func handleCloneTable(
err = moerr.NewInternalErrorNoCtxf("only sys can clone table to another account")
return
}
if snapshot == nil || snapshot.TS == nil {
if err = lockDataBranchCloneSource(
reqCtx,
ses,
fromAccountId,
stmt.SrcTable.SchemaName.String(),
stmt.SrcTable.ObjectName.String(),
); err != nil {
return
}
}

ctx = defines.AttachAccountId(reqCtx, toAccountId)

Expand Down Expand Up @@ -441,6 +598,9 @@ func handleCloneDatabaseWithSource(
return
}
}
if err = lockDataBranchCloneDatabaseSources(reqCtx, ses, source); err != nil {
return
}

ctx1 = defines.AttachAccountId(reqCtx, source.toAccountId)
if err = bh.Exec(ctx1,
Expand Down Expand Up @@ -675,7 +835,10 @@ func updateBranchMetaTable(
tcc.SetContext(srcCtx)
defer tcc.SetContext(origCtx)

if _, srcTblDef, err = tcc.Resolve(receipt.srcDb, receipt.srcTbl, nil); err != nil {
// The metadata parent must be the physical generation that supplied the
// clone data. For snapshot clones that can differ from the table currently
// reachable by name after one or more copy-and-swap ALTERs.
if _, srcTblDef, err = tcc.Resolve(receipt.srcDb, receipt.srcTbl, receipt.snapshot); err != nil {
return err
}
if srcTblDef == nil {
Expand Down
34 changes: 34 additions & 0 deletions pkg/frontend/clone_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
package frontend

import (
"context"
"errors"
"testing"

"github.com/stretchr/testify/require"
Expand All @@ -23,6 +25,38 @@ import (
"github.com/matrixorigin/matrixone/pkg/pb/timestamp"
)

func TestWithDataBranchCloneLockContext(t *testing.T) {
proc := newValidateSession(t).proc
oldCtx, cancel := context.WithCancel(context.Background())
cancel()
proc.Ctx = oldCtx

lockCtx := context.WithValue(context.Background(), struct{}{}, "current")
wantErr := errors.New("lock failed")
err := withDataBranchCloneLockContext(proc, lockCtx, func() error {
require.Same(t, lockCtx, proc.Ctx)
require.NoError(t, proc.Ctx.Err())
return wantErr
})

require.ErrorIs(t, err, wantErr)
require.Same(t, oldCtx, proc.Ctx)
}

func TestDataBranchCloneCatalogLockBatch(t *testing.T) {
ses := newValidateSession(t)
mp := ses.proc.Mp()
baseline := mp.CurrNB()

bat, err := dataBranchCloneCatalogLockBatch(ses.proc, 7, "db", "tbl")
require.NoError(t, err)
require.Len(t, bat.Vecs, 1)
require.Equal(t, 1, bat.Vecs[0].Length())
require.NotEmpty(t, bat.Vecs[0].GetBytesAt(0))
bat.Vecs[0].Free(mp)
require.Equal(t, baseline, mp.CurrNB())
}

func Test_prepareCloneViewSnapshot(t *testing.T) {
original := &plan.Snapshot{
Tenant: &plan.SnapshotTenant{TenantID: 1001},
Expand Down
Loading
Loading