github.com/matrixorigin/matrixone@v1.2.0/pkg/vm/engine/tae/db/testutil/engine.go (about)

     1  // Copyright 2021 Matrix Origin
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package testutil
    16  
    17  import (
    18  	"context"
    19  	"fmt"
    20  	"strconv"
    21  	"strings"
    22  	"testing"
    23  	"time"
    24  
    25  	pkgcatalog "github.com/matrixorigin/matrixone/pkg/catalog"
    26  	"github.com/matrixorigin/matrixone/pkg/container/batch"
    27  	"github.com/matrixorigin/matrixone/pkg/container/types"
    28  	"github.com/matrixorigin/matrixone/pkg/fileservice"
    29  	"github.com/matrixorigin/matrixone/pkg/logutil"
    30  	"github.com/matrixorigin/matrixone/pkg/objectio"
    31  	"github.com/matrixorigin/matrixone/pkg/pb/api"
    32  	"github.com/matrixorigin/matrixone/pkg/vm/engine/tae/blockio"
    33  	"github.com/matrixorigin/matrixone/pkg/vm/engine/tae/catalog"
    34  	"github.com/matrixorigin/matrixone/pkg/vm/engine/tae/common"
    35  	"github.com/matrixorigin/matrixone/pkg/vm/engine/tae/containers"
    36  	"github.com/matrixorigin/matrixone/pkg/vm/engine/tae/db"
    37  	"github.com/matrixorigin/matrixone/pkg/vm/engine/tae/db/checkpoint"
    38  	"github.com/matrixorigin/matrixone/pkg/vm/engine/tae/iface/data"
    39  	"github.com/matrixorigin/matrixone/pkg/vm/engine/tae/iface/handle"
    40  	"github.com/matrixorigin/matrixone/pkg/vm/engine/tae/iface/txnif"
    41  	"github.com/matrixorigin/matrixone/pkg/vm/engine/tae/logtail"
    42  	"github.com/matrixorigin/matrixone/pkg/vm/engine/tae/options"
    43  	"github.com/matrixorigin/matrixone/pkg/vm/engine/tae/testutils"
    44  	"github.com/stretchr/testify/assert"
    45  	"github.com/stretchr/testify/require"
    46  )
    47  
    48  const (
    49  	DefaultTestDB = "db"
    50  )
    51  
    52  type CtxOldVersion struct{}
    53  
    54  type TestEngine struct {
    55  	*db.DB
    56  	t        *testing.T
    57  	schema   *catalog.Schema
    58  	tenantID uint32 // for almost tests, userID and roleID is not important
    59  }
    60  
    61  func NewTestEngineWithDir(
    62  	ctx context.Context,
    63  	dir string,
    64  	t *testing.T,
    65  	opts *options.Options,
    66  ) *TestEngine {
    67  	blockio.Start()
    68  	db := InitTestDBWithDir(ctx, dir, t, opts)
    69  	return &TestEngine{
    70  		DB: db,
    71  		t:  t,
    72  	}
    73  }
    74  
    75  func NewTestEngine(
    76  	ctx context.Context,
    77  	moduleName string,
    78  	t *testing.T,
    79  	opts *options.Options,
    80  ) *TestEngine {
    81  	blockio.Start()
    82  	db := InitTestDB(ctx, moduleName, t, opts)
    83  	return &TestEngine{
    84  		DB: db,
    85  		t:  t,
    86  	}
    87  }
    88  
    89  func (e *TestEngine) BindSchema(schema *catalog.Schema) { e.schema = schema }
    90  
    91  func (e *TestEngine) BindTenantID(tenantID uint32) { e.tenantID = tenantID }
    92  
    93  func (e *TestEngine) Restart(ctx context.Context) {
    94  	_ = e.DB.Close()
    95  	var err error
    96  	e.DB, err = db.Open(ctx, e.Dir, e.Opts)
    97  	// only ut executes this checker
    98  	e.DB.DiskCleaner.GetCleaner().AddChecker(
    99  		func(item any) bool {
   100  			min := e.DB.TxnMgr.MinTSForTest()
   101  			ckp := item.(*checkpoint.CheckpointEntry)
   102  			//logutil.Infof("min: %v, checkpoint: %v", min.ToString(), checkpoint.GetStart().ToString())
   103  			end := ckp.GetEnd()
   104  			return !end.GreaterEq(&min)
   105  		})
   106  	assert.NoError(e.t, err)
   107  }
   108  func (e *TestEngine) RestartDisableGC(ctx context.Context) {
   109  	_ = e.DB.Close()
   110  	var err error
   111  	e.Opts.GCCfg.GCTTL = 100 * time.Second
   112  	e.DB, err = db.Open(ctx, e.Dir, e.Opts)
   113  	// only ut executes this checker
   114  	e.DB.DiskCleaner.GetCleaner().AddChecker(
   115  		func(item any) bool {
   116  			min := e.DB.TxnMgr.MinTSForTest()
   117  			ckp := item.(*checkpoint.CheckpointEntry)
   118  			//logutil.Infof("min: %v, checkpoint: %v", min.ToString(), checkpoint.GetStart().ToString())
   119  			end := ckp.GetEnd()
   120  			return !end.GreaterEq(&min)
   121  		})
   122  	assert.NoError(e.t, err)
   123  }
   124  
   125  func (e *TestEngine) Close() error {
   126  	err := e.DB.Close()
   127  	blockio.Stop()
   128  	blockio.ResetPipeline()
   129  	return err
   130  }
   131  
   132  func (e *TestEngine) CreateRelAndAppend(bat *containers.Batch, createDB bool) (handle.Database, handle.Relation) {
   133  	clonedSchema := e.schema.Clone()
   134  	return CreateRelationAndAppend(e.t, e.tenantID, e.DB, DefaultTestDB, clonedSchema, bat, createDB)
   135  }
   136  
   137  func (e *TestEngine) CheckRowsByScan(exp int, applyDelete bool) {
   138  	txn, rel := e.GetRelation()
   139  	CheckAllColRowsByScan(e.t, rel, exp, applyDelete)
   140  	assert.NoError(e.t, txn.Commit(context.Background()))
   141  }
   142  func (e *TestEngine) ForceCheckpoint() {
   143  	err := e.BGCheckpointRunner.ForceFlushWithInterval(e.TxnMgr.Now(), context.Background(), time.Second*2, time.Millisecond*10)
   144  	assert.NoError(e.t, err)
   145  	err = e.BGCheckpointRunner.ForceIncrementalCheckpoint(e.TxnMgr.Now(), false)
   146  	assert.NoError(e.t, err)
   147  }
   148  
   149  func (e *TestEngine) ForceLongCheckpoint() {
   150  	err := e.BGCheckpointRunner.ForceFlush(e.TxnMgr.Now(), context.Background(), 20*time.Second)
   151  	assert.NoError(e.t, err)
   152  	err = e.BGCheckpointRunner.ForceIncrementalCheckpoint(e.TxnMgr.Now(), false)
   153  	assert.NoError(e.t, err)
   154  }
   155  
   156  func (e *TestEngine) DropRelation(t *testing.T) {
   157  	txn, err := e.StartTxn(nil)
   158  	assert.NoError(t, err)
   159  	db, err := txn.GetDatabase(DefaultTestDB)
   160  	assert.NoError(t, err)
   161  	_, err = db.DropRelationByName(e.schema.Name)
   162  	assert.NoError(t, err)
   163  	assert.NoError(t, txn.Commit(context.Background()))
   164  }
   165  func (e *TestEngine) GetRelation() (txn txnif.AsyncTxn, rel handle.Relation) {
   166  	return GetRelation(e.t, e.tenantID, e.DB, DefaultTestDB, e.schema.Name)
   167  }
   168  func (e *TestEngine) GetRelationWithTxn(txn txnif.AsyncTxn) (rel handle.Relation) {
   169  	return GetRelationWithTxn(e.t, txn, DefaultTestDB, e.schema.Name)
   170  }
   171  
   172  func (e *TestEngine) CompactBlocks(skipConflict bool) {
   173  	CompactBlocks(e.t, e.tenantID, e.DB, DefaultTestDB, e.schema, skipConflict)
   174  }
   175  
   176  func (e *TestEngine) MergeBlocks(skipConflict bool) {
   177  	MergeBlocks(e.t, e.tenantID, e.DB, DefaultTestDB, e.schema, skipConflict)
   178  }
   179  
   180  func (e *TestEngine) GetDB(name string) (txn txnif.AsyncTxn, db handle.Database) {
   181  	txn, err := e.DB.StartTxn(nil)
   182  	txn.BindAccessInfo(e.tenantID, 0, 0)
   183  	assert.NoError(e.t, err)
   184  	db, err = txn.GetDatabase(name)
   185  	assert.NoError(e.t, err)
   186  	return
   187  }
   188  
   189  func (e *TestEngine) GetTestDB() (txn txnif.AsyncTxn, db handle.Database) {
   190  	return e.GetDB(DefaultTestDB)
   191  }
   192  
   193  func (e *TestEngine) DoAppend(bat *containers.Batch) {
   194  	txn, rel := e.GetRelation()
   195  	err := rel.Append(context.Background(), bat)
   196  	assert.NoError(e.t, err)
   197  	assert.NoError(e.t, txn.Commit(context.Background()))
   198  }
   199  
   200  func (e *TestEngine) DoAppendWithTxn(bat *containers.Batch, txn txnif.AsyncTxn, skipConflict bool) (err error) {
   201  	rel := e.GetRelationWithTxn(txn)
   202  	err = rel.Append(context.Background(), bat)
   203  	if !skipConflict {
   204  		assert.NoError(e.t, err)
   205  	}
   206  	return
   207  }
   208  
   209  func (e *TestEngine) TryAppend(bat *containers.Batch) {
   210  	txn, err := e.DB.StartTxn(nil)
   211  	txn.BindAccessInfo(e.tenantID, 0, 0)
   212  	assert.NoError(e.t, err)
   213  	db, err := txn.GetDatabase(DefaultTestDB)
   214  	assert.NoError(e.t, err)
   215  	rel, err := db.GetRelationByName(e.schema.Name)
   216  	if err != nil {
   217  		_ = txn.Rollback(context.Background())
   218  		return
   219  	}
   220  
   221  	err = rel.Append(context.Background(), bat)
   222  	if err != nil {
   223  		_ = txn.Rollback(context.Background())
   224  		return
   225  	}
   226  	_ = txn.Commit(context.Background())
   227  }
   228  func (e *TestEngine) DeleteAll(skipConflict bool) error {
   229  	txn, rel := e.GetRelation()
   230  	schema := rel.GetMeta().(*catalog.TableEntry).GetLastestSchemaLocked()
   231  	pkName := schema.GetPrimaryKey().Name
   232  	it := rel.MakeObjectIt()
   233  	for it.Valid() {
   234  		blk := it.GetObject()
   235  		defer blk.Close()
   236  		blkCnt := uint16(blk.BlkCnt())
   237  		for i := uint16(0); i < blkCnt; i++ {
   238  			view, err := blk.GetColumnDataByName(context.Background(), i, catalog.PhyAddrColumnName, common.DefaultAllocator)
   239  			assert.NoError(e.t, err)
   240  			defer view.Close()
   241  			view.ApplyDeletes()
   242  			pkView, err := blk.GetColumnDataByName(context.Background(), i, pkName, common.DefaultAllocator)
   243  			assert.NoError(e.t, err)
   244  			defer pkView.Close()
   245  			pkView.ApplyDeletes()
   246  			err = rel.DeleteByPhyAddrKeys(view.GetData(), pkView.GetData())
   247  			assert.NoError(e.t, err)
   248  		}
   249  		it.Next()
   250  	}
   251  	// CheckAllColRowsByScan(e.t, rel, 0, true)
   252  	err := txn.Commit(context.Background())
   253  	if !skipConflict {
   254  		CheckAllColRowsByScan(e.t, rel, 0, true)
   255  		assert.NoError(e.t, err)
   256  	}
   257  	return err
   258  }
   259  
   260  func (e *TestEngine) Truncate() {
   261  	txn, db := e.GetTestDB()
   262  	_, err := db.TruncateByName(e.schema.Name)
   263  	assert.NoError(e.t, err)
   264  	assert.NoError(e.t, txn.Commit(context.Background()))
   265  }
   266  func (e *TestEngine) GlobalCheckpoint(
   267  	endTs types.TS,
   268  	versionInterval time.Duration,
   269  	enableAndCleanBGCheckpoint bool,
   270  ) error {
   271  	if enableAndCleanBGCheckpoint {
   272  		e.DB.BGCheckpointRunner.DisableCheckpoint()
   273  		defer e.DB.BGCheckpointRunner.EnableCheckpoint()
   274  		e.DB.BGCheckpointRunner.CleanPenddingCheckpoint()
   275  	}
   276  	if e.DB.BGCheckpointRunner.GetPenddingIncrementalCount() == 0 {
   277  		testutils.WaitExpect(4000, func() bool {
   278  			flushed := e.DB.BGCheckpointRunner.IsAllChangesFlushed(types.TS{}, endTs, false)
   279  			return flushed
   280  		})
   281  		flushed := e.DB.BGCheckpointRunner.IsAllChangesFlushed(types.TS{}, endTs, true)
   282  		assert.True(e.t, flushed)
   283  	}
   284  	err := e.DB.BGCheckpointRunner.ForceGlobalCheckpoint(endTs, versionInterval)
   285  	assert.NoError(e.t, err)
   286  	return nil
   287  }
   288  
   289  func (e *TestEngine) IncrementalCheckpoint(
   290  	end types.TS,
   291  	enableAndCleanBGCheckpoint bool,
   292  	waitFlush bool,
   293  	truncate bool,
   294  ) error {
   295  	if enableAndCleanBGCheckpoint {
   296  		e.DB.BGCheckpointRunner.DisableCheckpoint()
   297  		defer e.DB.BGCheckpointRunner.EnableCheckpoint()
   298  		e.DB.BGCheckpointRunner.CleanPenddingCheckpoint()
   299  	}
   300  	if waitFlush {
   301  		testutils.WaitExpect(4000, func() bool {
   302  			flushed := e.DB.BGCheckpointRunner.IsAllChangesFlushed(types.TS{}, end, false)
   303  			return flushed
   304  		})
   305  		flushed := e.DB.BGCheckpointRunner.IsAllChangesFlushed(types.TS{}, end, true)
   306  		assert.True(e.t, flushed)
   307  	}
   308  	err := e.DB.BGCheckpointRunner.ForceIncrementalCheckpoint(end, false)
   309  	assert.NoError(e.t, err)
   310  	if truncate {
   311  		lsn := e.DB.BGCheckpointRunner.MaxLSNInRange(end)
   312  		entry, err := e.DB.Wal.RangeCheckpoint(1, lsn)
   313  		assert.NoError(e.t, err)
   314  		assert.NoError(e.t, entry.WaitDone())
   315  		testutils.WaitExpect(1000, func() bool {
   316  			return e.Runtime.Scheduler.GetPenddingLSNCnt() == 0
   317  		})
   318  	}
   319  	return nil
   320  }
   321  
   322  func (e *TestEngine) TryDeleteByDeltaloc(vals []any) (ok bool, err error) {
   323  	txn, err := e.StartTxn(nil)
   324  	assert.NoError(e.t, err)
   325  	ok, err = e.TryDeleteByDeltalocWithTxn(vals, txn)
   326  	if ok {
   327  		assert.NoError(e.t, txn.Commit(context.Background()))
   328  	} else {
   329  		assert.NoError(e.t, txn.Rollback(context.Background()))
   330  	}
   331  	return
   332  }
   333  
   334  func (e *TestEngine) TryDeleteByDeltalocWithTxn(vals []any, txn txnif.AsyncTxn) (ok bool, err error) {
   335  	rel := e.GetRelationWithTxn(txn)
   336  
   337  	idOffsetsMap := make(map[common.ID][]uint32)
   338  	for _, val := range vals {
   339  		filter := handle.NewEQFilter(val)
   340  		id, offset, err := rel.GetByFilter(context.Background(), filter)
   341  		assert.NoError(e.t, err)
   342  		offsets, ok := idOffsetsMap[*id]
   343  		if !ok {
   344  			offsets = make([]uint32, 0)
   345  		}
   346  		offsets = append(offsets, offset)
   347  		idOffsetsMap[*id] = offsets
   348  	}
   349  
   350  	for id, offsets := range idOffsetsMap {
   351  		obj, err := rel.GetMeta().(*catalog.TableEntry).GetObjectByID(id.ObjectID())
   352  		assert.NoError(e.t, err)
   353  		_, blkOffset := id.BlockID.Offsets()
   354  		deltaLoc, err := MockCNDeleteInS3(e.Runtime.Fs, obj.GetObjectData(), blkOffset, e.schema, txn, offsets)
   355  		assert.NoError(e.t, err)
   356  		ok, err = rel.TryDeleteByDeltaloc(&id, deltaLoc)
   357  		assert.NoError(e.t, err)
   358  		if !ok {
   359  			return ok, err
   360  		}
   361  	}
   362  	ok = true
   363  	return
   364  }
   365  
   366  func InitTestDBWithDir(
   367  	ctx context.Context,
   368  	dir string,
   369  	t *testing.T,
   370  	opts *options.Options,
   371  ) *db.DB {
   372  	db, _ := db.Open(ctx, dir, opts)
   373  	// only ut executes this checker
   374  	db.DiskCleaner.GetCleaner().AddChecker(
   375  		func(item any) bool {
   376  			min := db.TxnMgr.MinTSForTest()
   377  			ckp := item.(*checkpoint.CheckpointEntry)
   378  			//logutil.Infof("min: %v, checkpoint: %v", min.ToString(), checkpoint.GetStart().ToString())
   379  			end := ckp.GetEnd()
   380  			return !end.GreaterEq(&min)
   381  		})
   382  	return db
   383  }
   384  
   385  func InitTestDB(
   386  	ctx context.Context,
   387  	moduleName string,
   388  	t *testing.T,
   389  	opts *options.Options,
   390  ) *db.DB {
   391  	dir := testutils.InitTestEnv(moduleName, t)
   392  	db, _ := db.Open(ctx, dir, opts)
   393  	// only ut executes this checker
   394  	db.DiskCleaner.GetCleaner().AddChecker(
   395  		func(item any) bool {
   396  			min := db.TxnMgr.MinTSForTest()
   397  			ckp := item.(*checkpoint.CheckpointEntry)
   398  			//logutil.Infof("min: %v, checkpoint: %v", min.ToString(), checkpoint.GetStart().ToString())
   399  			end := ckp.GetEnd()
   400  			return !end.GreaterEq(&min)
   401  		})
   402  	return db
   403  }
   404  
   405  func writeIncrementalCheckpoint(
   406  	t *testing.T,
   407  	start, end types.TS,
   408  	c *catalog.Catalog,
   409  	checkpointBlockRows int,
   410  	checkpointSize int,
   411  	fs fileservice.FileService,
   412  ) (objectio.Location, objectio.Location) {
   413  	factory := logtail.IncrementalCheckpointDataFactory(start, end, false, false)
   414  	data, err := factory(c)
   415  	assert.NoError(t, err)
   416  	defer data.Close()
   417  	cnLocation, tnLocation, _, err := data.WriteTo(fs, checkpointBlockRows, checkpointSize)
   418  	assert.NoError(t, err)
   419  	return cnLocation, tnLocation
   420  }
   421  
   422  func tnReadCheckpoint(t *testing.T, location objectio.Location, fs fileservice.FileService) *logtail.CheckpointData {
   423  	reader, err := blockio.NewObjectReader(fs, location)
   424  	assert.NoError(t, err)
   425  	data := logtail.NewCheckpointData(common.CheckpointAllocator)
   426  	err = data.ReadFrom(
   427  		context.Background(),
   428  		logtail.CheckpointCurrentVersion,
   429  		location,
   430  		reader,
   431  		fs,
   432  	)
   433  	assert.NoError(t, err)
   434  	return data
   435  }
   436  func cnReadCheckpoint(t *testing.T, tid uint64, location objectio.Location, fs fileservice.FileService) (ins, del, cnIns, segDel *api.Batch, cb []func()) {
   437  	ins, del, cnIns, segDel, cb = cnReadCheckpointWithVersion(t, tid, location, fs, logtail.CheckpointCurrentVersion)
   438  	return
   439  }
   440  
   441  func ReadSnapshotCheckpoint(t *testing.T, tid uint64, location objectio.Location, fs fileservice.FileService) (ins, del, cnIns, segDel *api.Batch, cb []func()) {
   442  	ins, del, cnIns, segDel, cb = cnReadCheckpointWithVersion(t, tid, location, fs, logtail.CheckpointCurrentVersion)
   443  	return
   444  }
   445  
   446  func cnReadCheckpointWithVersion(t *testing.T, tid uint64, location objectio.Location, fs fileservice.FileService, ver uint32) (ins, del, cnIns, segDel *api.Batch, cb []func()) {
   447  	locs := make([]string, 0)
   448  	locs = append(locs, location.String())
   449  	locs = append(locs, strconv.Itoa(int(ver)))
   450  	locations := strings.Join(locs, ";")
   451  	entries, cb, err := logtail.LoadCheckpointEntries(
   452  		context.Background(),
   453  		locations,
   454  		tid,
   455  		"tbl",
   456  		0,
   457  		"db",
   458  		common.CheckpointAllocator,
   459  		fs,
   460  	)
   461  	assert.NoError(t, err)
   462  	for i := len(entries) - 1; i >= 0; i-- {
   463  		e := entries[i]
   464  		if e.TableName == fmt.Sprintf("_%d_obj", tid) {
   465  			segDel = e.Bat
   466  		} else if e.EntryType == api.Entry_Delete {
   467  			del = e.Bat
   468  			if tid != pkgcatalog.MO_DATABASE_ID && tid != pkgcatalog.MO_TABLES_ID && tid != pkgcatalog.MO_COLUMNS_ID {
   469  				cnIns = entries[i-1].Bat
   470  				i--
   471  			}
   472  		} else {
   473  			ins = e.Bat
   474  		}
   475  	}
   476  	for _, c := range cb {
   477  		c()
   478  	}
   479  	return
   480  }
   481  
   482  func checkTNCheckpointData(ctx context.Context, t *testing.T, data *logtail.CheckpointData,
   483  	start, end types.TS, c *catalog.Catalog) {
   484  	factory := logtail.IncrementalCheckpointDataFactory(start, end, false, false)
   485  	data2, err := factory(c)
   486  	assert.NoError(t, err)
   487  	defer data2.Close()
   488  
   489  	bats1 := data.GetBatches()
   490  	bats2 := data2.GetBatches()
   491  	assert.Equal(t, len(bats1), len(bats2))
   492  	for i, bat := range bats1 {
   493  		// skip metabatch
   494  		if i == 0 {
   495  			continue
   496  		}
   497  		bat2 := bats2[i]
   498  		// t.Logf("check bat %d", i)
   499  		isBatchEqual(ctx, t, bat, bat2)
   500  	}
   501  }
   502  
   503  func getBatchLength(bat *containers.Batch) int {
   504  	length := 0
   505  	for _, vec := range bat.Vecs {
   506  		if vec.Length() > length {
   507  			length = vec.Length()
   508  		}
   509  	}
   510  	return length
   511  }
   512  
   513  func isBatchEqual(ctx context.Context, t *testing.T, bat1, bat2 *containers.Batch) {
   514  	oldver := -1
   515  	if ver := ctx.Value(CtxOldVersion{}); ver != nil {
   516  		oldver = ver.(int)
   517  	}
   518  	require.Equal(t, getBatchLength(bat1), getBatchLength(bat2))
   519  	require.Equal(t, len(bat1.Vecs), len(bat2.Vecs))
   520  	for i := 0; i < getBatchLength(bat1); i++ {
   521  		for j, vec1 := range bat1.Vecs {
   522  			vec2 := bat2.Vecs[j]
   523  			if vec1.Length() == 0 || vec2.Length() == 0 {
   524  				// for commitTS and rowid in checkpoint
   525  				// logutil.Warnf("empty vec attr %v", bat1.Attrs[j])
   526  				continue
   527  			}
   528  			if oldver >= 0 && oldver <= int(logtail.CheckpointVersion5) && // read old version checkpoint
   529  				logtail.CheckpointCurrentVersion > logtail.CheckpointVersion5 && // check on new version
   530  				bat1.Attrs[j] == pkgcatalog.BlockMeta_MemTruncPoint {
   531  				// memTruncatePoint vector is committs vec in old checkpoint
   532  				// it can't be the same with newly collected on new version checkpoint, just skip it
   533  				logutil.Infof("isBatchEqual skip attr %v for ver.%d on ver.%d", bat1.Attrs[j], oldver, logtail.CheckpointCurrentVersion)
   534  				continue
   535  			}
   536  			// t.Logf("attr %v, row %d", bat1.Attrs[j], i)
   537  			require.Equal(t, vec1.Get(i), vec2.Get(i), "name is \"%v\"", bat1.Attrs[j])
   538  		}
   539  	}
   540  }
   541  
   542  func isProtoTNBatchEqual(ctx context.Context, t *testing.T, bat1 *api.Batch, bat2 *containers.Batch) {
   543  	if bat1 == nil {
   544  		if bat2 == nil {
   545  			return
   546  		}
   547  		assert.Equal(t, 0, getBatchLength(bat2))
   548  	} else {
   549  		moIns, err := batch.ProtoBatchToBatch(bat1)
   550  		assert.NoError(t, err)
   551  		tnIns := containers.ToTNBatch(moIns, common.DefaultAllocator)
   552  		isBatchEqual(ctx, t, tnIns, bat2)
   553  	}
   554  }
   555  
   556  func checkCNCheckpointData(ctx context.Context, t *testing.T, tid uint64, ins, del, cnIns, segDel *api.Batch, start, end types.TS, c *catalog.Catalog) {
   557  	if tid == pkgcatalog.MO_DATABASE_ID {
   558  		checkMODatabase(ctx, t, ins, del, cnIns, segDel, start, end, c)
   559  	} else if tid == pkgcatalog.MO_TABLES_ID {
   560  		checkMOTables(ctx, t, ins, del, cnIns, segDel, start, end, c)
   561  	} else if tid == pkgcatalog.MO_COLUMNS_ID {
   562  		checkMOColumns(ctx, t, ins, del, cnIns, segDel, start, end, c)
   563  	} else {
   564  		checkUserTables(ctx, t, tid, ins, del, cnIns, segDel, start, end, c)
   565  	}
   566  }
   567  
   568  func checkMODatabase(ctx context.Context, t *testing.T, ins, del, cnIns, segDel *api.Batch, start, end types.TS, c *catalog.Catalog) {
   569  	collector := logtail.NewIncrementalCollector(start, end, false)
   570  	p := &catalog.LoopProcessor{}
   571  	p.DatabaseFn = collector.VisitDB
   572  	err := c.RecurLoop(p)
   573  	assert.NoError(t, err)
   574  	data2 := collector.OrphanData()
   575  	defer data2.Close()
   576  	ins2, _, del2, _ := data2.GetDBBatchs()
   577  
   578  	isProtoTNBatchEqual(ctx, t, ins, ins2)
   579  	isProtoTNBatchEqual(ctx, t, del, del2)
   580  	assert.Nil(t, cnIns)
   581  	assert.Nil(t, segDel)
   582  }
   583  
   584  func checkMOTables(ctx context.Context, t *testing.T, ins, del, cnIns, segDel *api.Batch, start, end types.TS, c *catalog.Catalog) {
   585  	collector := logtail.NewIncrementalCollector(start, end, false)
   586  	p := &catalog.LoopProcessor{}
   587  	p.TableFn = collector.VisitTable
   588  	err := c.RecurLoop(p)
   589  	assert.NoError(t, err)
   590  	data2 := collector.OrphanData()
   591  	defer data2.Close()
   592  	ins2, _, _, del2, _ := data2.GetTblBatchs()
   593  
   594  	isProtoTNBatchEqual(ctx, t, ins, ins2)
   595  	isProtoTNBatchEqual(ctx, t, del, del2)
   596  	assert.Nil(t, cnIns)
   597  	assert.Nil(t, segDel)
   598  }
   599  
   600  func checkMOColumns(ctx context.Context, t *testing.T, ins, del, cnIns, segDel *api.Batch, start, end types.TS, c *catalog.Catalog) {
   601  	collector := logtail.NewIncrementalCollector(start, end, false)
   602  	p := &catalog.LoopProcessor{}
   603  	p.TableFn = collector.VisitTable
   604  	err := c.RecurLoop(p)
   605  	assert.NoError(t, err)
   606  	data2 := collector.OrphanData()
   607  	bats := data2.GetBatches()
   608  	ins2 := bats[logtail.TBLColInsertIDX]
   609  	del2 := bats[logtail.TBLColDeleteIDX]
   610  
   611  	isProtoTNBatchEqual(ctx, t, ins, ins2)
   612  	isProtoTNBatchEqual(ctx, t, del, del2)
   613  	assert.Nil(t, cnIns)
   614  	assert.Nil(t, segDel)
   615  }
   616  
   617  func checkUserTables(ctx context.Context, t *testing.T, tid uint64, ins, del, cnIns, seg *api.Batch, start, end types.TS, c *catalog.Catalog) {
   618  	collector := logtail.NewIncrementalCollector(start, end, false)
   619  	p := &catalog.LoopProcessor{}
   620  	p.TombstoneFn = func(be data.Tombstone) error {
   621  		if be.GetObject().(*catalog.ObjectEntry).GetTable().ID != tid {
   622  			return nil
   623  		}
   624  		return collector.VisitTombstone(be)
   625  	}
   626  	p.ObjectFn = func(se *catalog.ObjectEntry) error {
   627  		if se.GetTable().ID != tid {
   628  			return nil
   629  		}
   630  		return collector.VisitObj(se)
   631  	}
   632  	err := c.RecurLoop(p)
   633  	assert.NoError(t, err)
   634  	collector.LoadAndCollectObject(c, collector.VisitObj)
   635  	data2 := collector.OrphanData()
   636  	bats := data2.GetBatches()
   637  	ins2 := bats[logtail.BLKMetaInsertIDX]
   638  	// del2 := bats[logtail.BLKMetaDeleteIDX]
   639  	// cnIns2 := bats[logtail.BLKCNMetaInsertIDX]
   640  	seg2 := bats[logtail.ObjectInfoIDX]
   641  
   642  	isProtoTNBatchEqual(ctx, t, ins, ins2)
   643  	// isProtoTNBatchEqual(ctx, t, del, del2)// del is always empty after block is removed
   644  	// isProtoTNBatchEqual(ctx, t, cnIns, cnIns2)
   645  
   646  	// seg batch doesn't exist before ckp V9
   647  	if seg != nil {
   648  		isProtoTNBatchEqual(ctx, t, seg, seg2)
   649  	}
   650  }
   651  
   652  func GetUserTablesInsBatch(t *testing.T, tid uint64, start, end types.TS, c *catalog.Catalog) (*containers.Batch, *containers.Batch) {
   653  	collector := logtail.NewIncrementalCollector(start, end, false)
   654  	p := &catalog.LoopProcessor{}
   655  	p.TombstoneFn = func(be data.Tombstone) error {
   656  		if be.GetObject().(*catalog.ObjectEntry).GetTable().ID != tid {
   657  			return nil
   658  		}
   659  		return collector.VisitTombstone(be)
   660  	}
   661  	p.ObjectFn = func(se *catalog.ObjectEntry) error {
   662  		if se.GetTable().ID != tid {
   663  			return nil
   664  		}
   665  		return collector.VisitObj(se)
   666  	}
   667  	err := c.RecurLoop(p)
   668  	assert.NoError(t, err)
   669  	collector.LoadAndCollectObject(c, collector.VisitObj)
   670  	data := collector.OrphanData()
   671  	bats := data.GetBatches()
   672  	return bats[logtail.BLKMetaInsertIDX], bats[logtail.ObjectInfoIDX]
   673  }
   674  
   675  func CheckCheckpointReadWrite(
   676  	t *testing.T,
   677  	start, end types.TS,
   678  	c *catalog.Catalog,
   679  	checkpointBlockRows int,
   680  	checkpointSize int,
   681  	fs fileservice.FileService,
   682  ) {
   683  	location, _ := writeIncrementalCheckpoint(t, start, end, c, checkpointBlockRows, checkpointSize, fs)
   684  	tnData := tnReadCheckpoint(t, location, fs)
   685  
   686  	checkTNCheckpointData(context.Background(), t, tnData, start, end, c)
   687  	p := &catalog.LoopProcessor{}
   688  
   689  	ins, del, cnIns, seg, cbs := cnReadCheckpoint(t, pkgcatalog.MO_DATABASE_ID, location, fs)
   690  	checkCNCheckpointData(context.Background(), t, pkgcatalog.MO_DATABASE_ID, ins, del, cnIns, seg, start, end, c)
   691  	for _, cb := range cbs {
   692  		if cb != nil {
   693  			cb()
   694  		}
   695  	}
   696  	ins, del, cnIns, seg, cbs = cnReadCheckpoint(t, pkgcatalog.MO_TABLES_ID, location, fs)
   697  	checkCNCheckpointData(context.Background(), t, pkgcatalog.MO_TABLES_ID, ins, del, cnIns, seg, start, end, c)
   698  	for _, cb := range cbs {
   699  		if cb != nil {
   700  			cb()
   701  		}
   702  	}
   703  	ins, del, cnIns, seg, cbs = cnReadCheckpoint(t, pkgcatalog.MO_COLUMNS_ID, location, fs)
   704  	checkCNCheckpointData(context.Background(), t, pkgcatalog.MO_COLUMNS_ID, ins, del, cnIns, seg, start, end, c)
   705  	for _, cb := range cbs {
   706  		if cb != nil {
   707  			cb()
   708  		}
   709  	}
   710  
   711  	p.TableFn = func(te *catalog.TableEntry) error {
   712  		ins, del, cnIns, seg, cbs := cnReadCheckpoint(t, te.ID, location, fs)
   713  		checkCNCheckpointData(context.Background(), t, te.ID, ins, del, cnIns, seg, start, end, c)
   714  		for _, cb := range cbs {
   715  			if cb != nil {
   716  				cb()
   717  			}
   718  		}
   719  		return nil
   720  	}
   721  }
   722  
   723  func (e *TestEngine) CheckReadCNCheckpoint() {
   724  	tids := []uint64{1, 2, 3}
   725  	p := &catalog.LoopProcessor{}
   726  	p.TableFn = func(te *catalog.TableEntry) error {
   727  		tids = append(tids, te.ID)
   728  		return nil
   729  	}
   730  	err := e.Catalog.RecurLoop(p)
   731  	assert.NoError(e.t, err)
   732  	ckps := e.BGCheckpointRunner.GetAllIncrementalCheckpoints()
   733  	for _, ckp := range ckps {
   734  		for _, tid := range tids {
   735  			ins, del, cnIns, seg, cbs := cnReadCheckpointWithVersion(e.t, tid, ckp.GetLocation(), e.Opts.Fs, ckp.GetVersion())
   736  			ctx := context.Background()
   737  			ctx = context.WithValue(ctx, CtxOldVersion{}, int(ckp.GetVersion()))
   738  			checkCNCheckpointData(ctx, e.t, tid, ins, del, cnIns, seg, ckp.GetStart(), ckp.GetEnd(), e.Catalog)
   739  			for _, cb := range cbs {
   740  				if cb != nil {
   741  					cb()
   742  				}
   743  			}
   744  		}
   745  	}
   746  }
   747  
   748  func (e *TestEngine) CheckCollectDeleteInRange() {
   749  	txn, rel := e.GetRelation()
   750  	ForEachObject(rel, func(obj handle.Object) error {
   751  		meta := obj.GetMeta().(*catalog.ObjectEntry)
   752  		deleteBat, _, err := meta.GetObjectData().CollectDeleteInRange(
   753  			context.Background(), types.TS{}, txn.GetStartTS(), false, common.DefaultAllocator,
   754  		)
   755  		assert.NoError(e.t, err)
   756  		pkDef := e.schema.GetPrimaryKey()
   757  		deleteRowIDs := deleteBat.GetVectorByName(catalog.AttrRowID)
   758  		deletePKs := deleteBat.GetVectorByName(catalog.AttrPKVal)
   759  		blkCnt := obj.BlkCnt()
   760  		pkVectors := make([]*containers.ColumnView, blkCnt)
   761  		rowIDVectors := make([]*containers.ColumnView, blkCnt)
   762  		for i := uint16(0); i < uint16(blkCnt); i++ {
   763  			pkVectors[i], err = meta.GetObjectData().GetColumnDataById(context.Background(), txn, e.schema, i, pkDef.Idx, common.DefaultAllocator)
   764  			assert.NoError(e.t, err)
   765  			rowIDVectors[i], err = meta.GetObjectData().GetColumnDataById(context.Background(), txn, e.schema, i, e.schema.PhyAddrKey.Idx, common.DefaultAllocator)
   766  			assert.NoError(e.t, err)
   767  		}
   768  		for i := 0; i < deleteBat.Length(); i++ {
   769  			rowID := deleteRowIDs.Get(i).(types.Rowid)
   770  			offset := rowID.GetRowOffset()
   771  			_, blkOffset := rowID.BorrowBlockID().Offsets()
   772  			appendRowID := rowIDVectors[blkOffset].GetData().Get(int(offset)).(types.Rowid)
   773  			e.t.Logf("delete rowID %v pk %v, append rowID %v pk %v", rowID.String(), deletePKs.Get(i), appendRowID.String(), pkVectors[blkOffset].GetData().Get(int(offset)))
   774  			assert.Equal(e.t, pkVectors[blkOffset].GetData().Get(int(offset)), deletePKs.Get(i))
   775  		}
   776  		return nil
   777  	})
   778  	err := txn.Commit(context.Background())
   779  	assert.NoError(e.t, err)
   780  }
   781  
   782  func (e *TestEngine) CheckObjectInfo(onlyCheckName bool) {
   783  	p := &catalog.LoopProcessor{}
   784  	p.ObjectFn = func(se *catalog.ObjectEntry) error {
   785  		se.LoopChainLocked(func(node *catalog.MVCCNode[*catalog.ObjectMVCCNode]) bool {
   786  			if se.GetTable().GetDB().ID == pkgcatalog.MO_CATALOG_ID {
   787  				return true
   788  			}
   789  			flushed := true
   790  			if se.IsAppendable() && !node.HasDropCommitted() {
   791  				flushed = false
   792  			}
   793  			if onlyCheckName || !flushed {
   794  				assert.Equal(e.t, objectio.BuildObjectNameWithObjectID(&se.ID),
   795  					node.BaseNode.ObjectStats.ObjectLocation().Name(),
   796  					"load %v, get %v",
   797  					se.ID.String(),
   798  					node.BaseNode.ObjectStats.String())
   799  				if flushed {
   800  					stats, err := se.LoadObjectInfoWithTxnTS(node.Start)
   801  					assert.NoError(e.t, err)
   802  					assert.Equal(e.t, stats.ObjectLocation().Extent(),
   803  						node.BaseNode.ObjectStats.ObjectLocation().Extent(),
   804  						"load %v, get %v",
   805  						stats.String(),
   806  						node.BaseNode.ObjectStats.String())
   807  
   808  				}
   809  			} else {
   810  				stats, err := se.LoadObjectInfoWithTxnTS(node.Start)
   811  				assert.NoError(e.t, err)
   812  				assert.Equal(e.t, stats, node.BaseNode.ObjectStats, "load %v, get %v", stats.String(), node.BaseNode.ObjectStats.String())
   813  			}
   814  			return true
   815  		})
   816  		return nil
   817  	}
   818  	err := e.Catalog.RecurLoop(p)
   819  	assert.NoError(e.t, err)
   820  }