Repository: oom-ai/oomstore (go)

Issue:
bug: if revision id is nil, online get/multi-get will panic
recurrent:

```shell
$ oomcli init
$ oomcli apply -f /tmp/tmp/meta.yaml
applied
$ oomcli get meta entity -o yaml
items:
  - kind: Entity
    name: device
    description: device
    groups:
      - name: phone
        category: batch
        description: phone
        features:
          - name: price
            value-type: int64
            description: price
          - name: model
            value-type: string
            description: model
  - kind: Entity
    name: user
    description: user
    groups:
      - name: student
        category: batch
        description: student
        features:
          - name: age
            value-type: int64
            description: age
      - name: user-click
        category: stream
        snapshot-interval: 1s
        description: user click post feature
        features:
          - name: last_5_click_posts
            value-type: string
            description: user last 5 click posts
          - name: number_of_user_started_posts
            value-type: int64
            description: number of posts that users stared today
$ oomcli get online -k 1 --feature phone.price
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x10e150c]

goroutine 1 [running]:
github.com/oom-ai/oomstore/internal/database/online/sqlutil.MultiGet({0x1da14e8, 0xc000120000}, 0x410f76, {{0xc000243690, 0x1, 0x1}, {0x1, {0xc00061b100, 0x5}, {0xc00061b130, ...}, ...}, ...}, ...)
        /home/lianxm/github.com/oomstore/internal/database/online/sqlutil/get.go:48 +0xec
github.com/oom-ai/oomstore/internal/database/online/sqlite.(*DB).MultiGet(0x20, {0x1da14e8, 0xc000120000}, {{0xc000243690, 0x1, 0x1}, {0x1, {0xc00061b100, 0x5}, {0xc00061b130, ...}, ...}, ...})
        /home/lianxm/github.com/oomstore/internal/database/online/sqlite/store.go:40 +0x6d
github.com/oom-ai/oomstore/pkg/oomstore.(*OomStore).getFeatureValueMap(0xc000163600, {0x1da14e8, 0xc000120000}, {0xc000243690, 0x1, 0x1}, 0x16e0e98, 0x1aa5ce6)
        /home/lianxm/github.com/oomstore/pkg/oomstore/online_query.go:111 +0x1e7
github.com/oom-ai/oomstore/pkg/oomstore.(*OomStore).OnlineMultiGet(0xc000163600, {0x1da14e8, 0xc000120000}, {{0xc0002436a0, 0x1, 0x1}, {0xc000243690, 0x1, 0x1}})
        /home/lianxm/github.com/oomstore/pkg/oomstore/online_query.go:84 +0x1c5
github.com/oom-ai/oomstore/oomcli/cmd.glob..func19(0x2b1b440, {0x1aa29c5, 0x4, 0x4})
        /home/lianxm/github.com/oomstore/oomcli/cmd/get_online.go:28 +0xf5
github.com/spf13/cobra.(*Command).execute(0x2b1b440, {0xc000162e80, 0x4, 0x4})
        /home/lianxm/go/pkg/mod/github.com/spf13/cobra@v1.3.0/command.go:860 +0x5f8
github.com/spf13/cobra.(*Command).ExecuteC(0x2b1cac0)
        /home/lianxm/go/pkg/mod/github.com/spf13/cobra@v1.3.0/command.go:974 +0x3bc
github.com/spf13/cobra.(*Command).Execute(...)
        /home/lianxm/go/pkg/mod/github.com/spf13/cobra@v1.3.0/command.go:902
github.com/oom-ai/oomstore/oomcli/cmd.Execute()
        /home/lianxm/github.com/oomstore/oomcli/cmd/root.go:32 +0x25
main.main()
        /home/lianxm/github.com/oomstore/oomcli/main.go:6 +0x17
```

Pull request description:
Fix/online get
this PR does:
1. use *int rather than int for `GetOpt::RevisionID` and `MultiGetOpt::RevisionID`, and add enough comments
2. check nil in oomstore layer
3. check nil in internal db layer
4. online db add unit-test `TestNoRevisionID`

resolve #1119 

Patch:
diff --git a/internal/database/online/cassandra/get.go b/internal/database/online/cassandra/get.go
index 3a46ee08..c881045d 100644
--- a/internal/database/online/cassandra/get.go
+++ b/internal/database/online/cassandra/get.go
@@ -14,6 +14,10 @@ import (
 )
 
 func (db *DB) Get(ctx context.Context, opt online.GetOpt) (dbutil.RowMap, error) {
+	if err := opt.Validate(); err != nil {
+		return nil, err
+	}
+
 	var tableName string
 	if opt.Group.Category == types.CategoryBatch {
 		tableName = dbutil.OnlineBatchTableName(*opt.RevisionID)
@@ -45,6 +49,10 @@ func (db *DB) Get(ctx context.Context, opt online.GetOpt) (dbutil.RowMap, error)
 
 // response: map[entity_key]map[feature_name]feature_value
 func (db *DB) MultiGet(ctx context.Context, opt online.MultiGetOpt) (map[string]dbutil.RowMap, error) {
+	if err := opt.Validate(); err != nil {
+		return nil, err
+	}
+
 	var (
 		tableName    string
 		placeholders = getPlaceholders(len(opt.EntityKeys))
diff --git a/internal/database/online/dynamodb/get.go b/internal/database/online/dynamodb/get.go
index b827edb6..cdb1c8f8 100644
--- a/internal/database/online/dynamodb/get.go
+++ b/internal/database/online/dynamodb/get.go
@@ -20,6 +20,10 @@ const (
 )
 
 func (db *DB) Get(ctx context.Context, opt online.GetOpt) (dbutil.RowMap, error) {
+	if err := opt.Validate(); err != nil {
+		return nil, err
+	}
+
 	var tableName string
 	if opt.Group.Category == oomTypes.CategoryBatch {
 		tableName = dbutil.OnlineBatchTableName(*opt.RevisionID)
@@ -48,6 +52,10 @@ func (db *DB) Get(ctx context.Context, opt online.GetOpt) (dbutil.RowMap, error)
 
 // response: map[entity_key]map[feature_name]feature_value
 func (db *DB) MultiGet(ctx context.Context, opt online.MultiGetOpt) (map[string]dbutil.RowMap, error) {
+	if err := opt.Validate(); err != nil {
+		return nil, err
+	}
+
 	var tableName string
 	if opt.Group.Category == oomTypes.CategoryBatch {
 		tableName = dbutil.OnlineBatchTableName(*opt.RevisionID)
@@ -93,6 +101,10 @@ func batchGetItem(ctx context.Context, db *DB, keys []map[string]types.Attribute
 		}
 	}
 
+	if result == nil {
+		return nil
+	}
+
 	for _, item := range result.Responses[tableName] {
 		entityKeyValue, ok := item[entityName]
 		if !ok {
diff --git a/internal/database/online/redis/get.go b/internal/database/online/redis/get.go
index 0f90ae03..55fee975 100644
--- a/internal/database/online/redis/get.go
+++ b/internal/database/online/redis/get.go
@@ -11,6 +11,10 @@ import (
 )
 
 func (db *DB) Get(ctx context.Context, opt online.GetOpt) (dbutil.RowMap, error) {
+	if err := opt.Validate(); err != nil {
+		return nil, err
+	}
+
 	var (
 		key string
 		err error
@@ -55,6 +59,10 @@ func (db *DB) Get(ctx context.Context, opt online.GetOpt) (dbutil.RowMap, error)
 
 // response: map[entity_key]map[feature_name]feature_value
 func (db *DB) MultiGet(ctx context.Context, opt online.MultiGetOpt) (map[string]dbutil.RowMap, error) {
+	if err := opt.Validate(); err != nil {
+		return nil, err
+	}
+
 	res := make(map[string]dbutil.RowMap)
 	for _, entityKey := range opt.EntityKeys {
 		rowMap, err := db.Get(ctx, online.GetOpt{
diff --git a/internal/database/online/sqlutil/get.go b/internal/database/online/sqlutil/get.go
index b199937a..1d247163 100644
--- a/internal/database/online/sqlutil/get.go
+++ b/internal/database/online/sqlutil/get.go
@@ -14,6 +14,10 @@ import (
 )
 
 func Get(ctx context.Context, db *sqlx.DB, opt online.GetOpt, backend types.BackendType) (dbutil.RowMap, error) {
+	if err := opt.Validate(); err != nil {
+		return nil, err
+	}
+
 	var tableName string
 	if opt.Group.Category == types.CategoryBatch {
 		tableName = dbutil.OnlineBatchTableName(*opt.RevisionID)
@@ -43,6 +47,10 @@ func Get(ctx context.Context, db *sqlx.DB, opt online.GetOpt, backend types.Back
 
 // response: map[entity_key]map[feature_name]feature_value
 func MultiGet(ctx context.Context, db *sqlx.DB, opt online.MultiGetOpt, backend types.BackendType) (map[string]dbutil.RowMap, error) {
+	if err := opt.Validate(); err != nil {
+		return nil, err
+	}
+
 	var tableName string
 	if opt.Group.Category == types.CategoryBatch {
 		tableName = dbutil.OnlineBatchTableName(*opt.RevisionID)
@@ -53,14 +61,16 @@ func MultiGet(ctx context.Context, db *sqlx.DB, opt online.MultiGetOpt, backend
 	entityName := opt.Group.Entity.Name
 	featureNames := opt.Features.Names()
 	qt := dbutil.QuoteFn(backend)
-	query := fmt.Sprintf(`SELECT %s, %s FROM %s WHERE %s in (?);`, qt(entityName), qt(featureNames...), qt(tableName), qt(entityName))
-	sql, args, err := sqlx.In(query, opt.EntityKeys)
+	query, args, err := sqlx.In(fmt.Sprintf(`SELECT %s, %s FROM %s WHERE %s in (?);`, qt(entityName), qt(featureNames...), qt(tableName), qt(entityName)), opt.EntityKeys)
 	if err != nil {
 		return nil, errdefs.WithStack(err)
 	}
 
-	rows, err := db.QueryxContext(ctx, db.Rebind(sql), args...)
+	rows, err := db.QueryxContext(ctx, db.Rebind(query), args...)
 	if err != nil {
+		if err == sql.ErrNoRows || dbutil.IsTableNotFoundError(err, backend) {
+			return make(map[string]dbutil.RowMap), nil
+		}
 		return nil, errdefs.WithStack(err)
 	}
 	defer rows.Close()
diff --git a/internal/database/online/tikv/get.go b/internal/database/online/tikv/get.go
index 9e03fa13..b025210c 100644
--- a/internal/database/online/tikv/get.go
+++ b/internal/database/online/tikv/get.go
@@ -34,6 +34,10 @@ func (db *DB) Get(ctx context.Context, opt online.GetOpt) (dbutil.RowMap, error)
 }
 
 func (db *DB) MultiGet(ctx context.Context, opt online.MultiGetOpt) (map[string]dbutil.RowMap, error) {
+	if err := opt.Validate(); err != nil {
+		return nil, err
+	}
+
 	var (
 		serializedPrefixKey string
 		err                 error
diff --git a/internal/database/online/types.go b/internal/database/online/types.go
index c9c65a25..9c8d8343 100644
--- a/internal/database/online/types.go
+++ b/internal/database/online/types.go
@@ -1,23 +1,42 @@
 package online
 
 import (
+	"github.com/oom-ai/oomstore/pkg/errdefs"
 	"github.com/oom-ai/oomstore/pkg/oomstore/types"
 )
 
 type GetOpt struct {
-	EntityKey  string
-	Group      types.Group
-	Features   types.FeatureList
+	EntityKey string
+	Group     types.Group
+	Features  types.FeatureList
+
+	// Only works when get batch features, it should be nil when get stream features
 	RevisionID *int
 }
 
+func (g *GetOpt) Validate() error {
+	if g.Group.Category == types.CategoryBatch && g.RevisionID == nil {
+		return errdefs.Errorf("invalid GetOpt: the revisionID of get batch feature cannot be null")
+	}
+	return nil
+}
+
 type MultiGetOpt struct {
 	EntityKeys []string
 	Group      types.Group
 	Features   types.FeatureList
+
+	// Only works when get batch features, it should be nil when get stream features
 	RevisionID *int
 }
 
+func (m *MultiGetOpt) Validate() error {
+	if m.Group.Category == types.CategoryBatch && m.RevisionID == nil {
+		return errdefs.Errorf("invalid MultiGetOpt: the revisionID of get batch feature cannot be null")
+	}
+	return nil
+}
+
 type ImportOpt struct {
 	Group        types.Group
 	Features     types.FeatureList
diff --git a/oomagent/server.go b/oomagent/server.go
index 32d44bbc..bdec4a4b 100644
--- a/oomagent/server.go
+++ b/oomagent/server.go
@@ -195,13 +195,10 @@ func (s *server) ChannelJoin(stream codegen.OomAgent_ChannelJoinServer) error {
 	// We need to read the first request to get the feature names and value names
 	firstReq, err := stream.Recv()
 	if err != nil {
-		if err == io.EOF {
-			return status.Error(codes.InvalidArgument, "invalid request: empty feature")
-		}
 		return wrapErr(err)
 	}
 	if firstReq.GetEntityRow() == nil {
-		return nil
+		return wrapErr(err)
 	}
 
 	// This channel receives requests from the client.
diff --git a/pkg/oomstore/online_query.go b/pkg/oomstore/online_query.go
index 997aadc9..bb63f89e 100644
--- a/pkg/oomstore/online_query.go
+++ b/pkg/oomstore/online_query.go
@@ -38,15 +38,21 @@ func (s *OomStore) OnlineGet(ctx context.Context, opt types.OnlineGetOpt) (*type
 
 	featureMap := groupFeaturesByGroupID(features)
 
-	for _, featureList := range featureMap {
-		if len(featureList) == 0 {
+	for _, features := range featureMap {
+		if len(features) == 0 {
 			continue
 		}
+
+		group := features[0].Group
+		if group.Category == types.CategoryBatch && group.OnlineRevisionID == nil {
+			return nil, errdefs.Errorf("group %s has nil,please make sure you have already sync", group.Name)
+		}
+
 		featureValues, err := s.online.Get(ctx, online.GetOpt{
 			EntityKey:  opt.EntityKey,
-			Group:      *featureList[0].Group,
-			Features:   featureList,
-			RevisionID: featureList[0].Group.OnlineRevisionID,
+			Group:      *group,
+			Features:   features,
+			RevisionID: group.OnlineRevisionID,
 		})
 		if err != nil {
 			return nil, err
@@ -108,11 +114,17 @@ func (s *OomStore) getFeatureValueMap(ctx context.Context, entityKeys []string,
 		if len(features) == 0 {
 			continue
 		}
+
+		group := features[0].Group
+		if group.Category == types.CategoryBatch && group.OnlineRevisionID == nil {
+			return nil, errdefs.Errorf("group %s has nil,please make sure you have already sync", group.Name)
+		}
+
 		featureValues, err := s.online.MultiGet(ctx, online.MultiGetOpt{
 			EntityKeys: entityKeys,
-			Group:      *features[0].Group,
+			Group:      *group,
 			Features:   features,
-			RevisionID: features[0].Group.OnlineRevisionID,
+			RevisionID: group.OnlineRevisionID,
 		})
 		if err != nil {
 			return nil, err


Test patch:
diff --git a/internal/database/online/cassandra/store_test.go b/internal/database/online/cassandra/store_test.go
index 2608fe11..b172546e 100644
--- a/internal/database/online/cassandra/store_test.go
+++ b/internal/database/online/cassandra/store_test.go
@@ -58,6 +58,10 @@ func TestGetExisted(t *testing.T) {
 	test_impl.TestGetExisted(t, prepareStore, destroyStore(KEYSPACE))
 }
 
+func TestGetNoRevision(t *testing.T) {
+	test_impl.TestGetNoRevision(t, prepareStore, destroyStore(KEYSPACE))
+}
+
 func TestGetNotExistedEntityKey(t *testing.T) {
 	test_impl.TestGetNotExistedEntityKey(t, prepareStore, destroyStore(KEYSPACE))
 }
diff --git a/internal/database/online/dynamodb/store_test.go b/internal/database/online/dynamodb/store_test.go
index c1a6d878..5e8c854f 100644
--- a/internal/database/online/dynamodb/store_test.go
+++ b/internal/database/online/dynamodb/store_test.go
@@ -49,6 +49,10 @@ func TestGetExisted(t *testing.T) {
 	test_impl.TestGetExisted(t, prepareStore, destroyStore(t))
 }
 
+func TestGetNoRevision(t *testing.T) {
+	test_impl.TestGetNoRevision(t, prepareStore, destroyStore(t))
+}
+
 func TestGetNotExistedEntityKey(t *testing.T) {
 	test_impl.TestGetNotExistedEntityKey(t, prepareStore, destroyStore(t))
 }
diff --git a/internal/database/online/mysql/store_test.go b/internal/database/online/mysql/store_test.go
index a6ae64ad..954c1023 100644
--- a/internal/database/online/mysql/store_test.go
+++ b/internal/database/online/mysql/store_test.go
@@ -37,6 +37,10 @@ func TestGetExisted(t *testing.T) {
 	test_impl.TestGetExisted(t, prepareStore, runtime_mysql.DestroyStore(DATABASE))
 }
 
+func TestGetNoRevision(t *testing.T) {
+	test_impl.TestGetNoRevision(t, prepareStore, runtime_mysql.DestroyStore(DATABASE))
+}
+
 func TestGetNotExistedEntityKey(t *testing.T) {
 	test_impl.TestGetNotExistedEntityKey(t, prepareStore, runtime_mysql.DestroyStore(DATABASE))
 }
diff --git a/internal/database/online/postgres/store_test.go b/internal/database/online/postgres/store_test.go
index 9789d29c..f1da7d0e 100644
--- a/internal/database/online/postgres/store_test.go
+++ b/internal/database/online/postgres/store_test.go
@@ -37,6 +37,10 @@ func TestGetExisted(t *testing.T) {
 	test_impl.TestGetExisted(t, prepareStore, runtime_pg.DestroyStore(DATABASE))
 }
 
+func TestGetNoRevision(t *testing.T) {
+	test_impl.TestGetNoRevision(t, prepareStore, runtime_pg.DestroyStore(DATABASE))
+}
+
 func TestGetNotExistedEntityKey(t *testing.T) {
 	test_impl.TestGetNotExistedEntityKey(t, prepareStore, runtime_pg.DestroyStore(DATABASE))
 }
diff --git a/internal/database/online/redis/store_test.go b/internal/database/online/redis/store_test.go
index b79d7284..68f0870d 100644
--- a/internal/database/online/redis/store_test.go
+++ b/internal/database/online/redis/store_test.go
@@ -35,6 +35,10 @@ func TestGetExisted(t *testing.T) {
 	test_impl.TestGetExisted(t, prepareStore, destroyStore)
 }
 
+func TestGetNotRevision(t *testing.T) {
+	test_impl.TestGetNoRevision(t, prepareStore, destroyStore)
+}
+
 func TestGetNotExistedEntityKey(t *testing.T) {
 	test_impl.TestGetNotExistedEntityKey(t, prepareStore, destroyStore)
 }
diff --git a/internal/database/online/sqlite/store_test.go b/internal/database/online/sqlite/store_test.go
index e7ad81f9..039f9d57 100644
--- a/internal/database/online/sqlite/store_test.go
+++ b/internal/database/online/sqlite/store_test.go
@@ -36,6 +36,10 @@ func TestGetExisted(t *testing.T) {
 	test_impl.TestGetExisted(t, prepareStore, destroyStore)
 }
 
+func TestGetNoRevision(t *testing.T) {
+	test_impl.TestGetNoRevision(t, prepareStore, destroyStore)
+}
+
 func TestGetNotExistedEntityKey(t *testing.T) {
 	test_impl.TestGetNotExistedEntityKey(t, prepareStore, destroyStore)
 }
diff --git a/internal/database/online/test_impl/get.go b/internal/database/online/test_impl/get.go
index 39f0e7c7..338c625e 100644
--- a/internal/database/online/test_impl/get.go
+++ b/internal/database/online/test_impl/get.go
@@ -3,10 +3,9 @@ package test_impl
 import (
 	"testing"
 
-	"github.com/oom-ai/oomstore/pkg/oomstore/types"
+	"github.com/stretchr/testify/require"
 
 	"github.com/oom-ai/oomstore/internal/database/online"
-	"github.com/stretchr/testify/require"
 )
 
 func TestGetExisted(t *testing.T, prepareStore PrepareStoreFn, destroyStore DestroyStoreFn) {
@@ -19,12 +18,10 @@ func TestGetExisted(t *testing.T, prepareStore PrepareStoreFn, destroyStore Dest
 
 		for _, target := range s.Data {
 			opt := online.GetOpt{
-				EntityKey: target.EntityKey(),
-				Group:     s.Group,
-				Features:  s.Features,
-			}
-			if s.Group.Category == types.CategoryBatch {
-				opt.RevisionID = &s.Revision.ID
+				EntityKey:  target.EntityKey(),
+				Group:      s.Group,
+				Features:   s.Features,
+				RevisionID: &s.Revision.ID,
 			}
 			rs, err := store.Get(ctx, opt)
 			require.NoError(t, err)
@@ -36,6 +33,40 @@ func TestGetExisted(t *testing.T, prepareStore PrepareStoreFn, destroyStore Dest
 	}
 }
 
+func TestGetNoRevision(t *testing.T, prepareStore PrepareStoreFn, destroyStore DestroyStoreFn) {
+	t.Cleanup(destroyStore)
+	ctx, store := prepareStore(t)
+	defer store.Close()
+
+	t.Run("get", func(t *testing.T) {
+		rs, err := store.Get(ctx, online.GetOpt{
+			EntityKey: SampleSmall.Entity.Name,
+			Group:     SampleSmall.Group,
+			Features:  SampleSmall.Features,
+		})
+		require.EqualError(t, err, "invalid GetOpt: the revisionID of get batch feature cannot be null")
+		require.Nil(t, rs)
+	})
+
+	t.Run("multi get", func(t *testing.T) {
+		for _, s := range []*Sample{&SampleSmall, &SampleMedium} {
+			keys := []string{"not-existed-key"}
+			for _, r := range s.Data {
+				keys = append(keys, r.EntityKey())
+			}
+			rs, err := store.MultiGet(ctx, online.MultiGetOpt{
+				EntityKeys: keys,
+				Group:      s.Group,
+				Features:   s.Features,
+			})
+			require.EqualError(t, err, "invalid MultiGetOpt: the revisionID of get batch feature cannot be null")
+			require.Nil(t, rs)
+		}
+
+	})
+
+}
+
 func TestGetNotExistedEntityKey(t *testing.T, prepareStore PrepareStoreFn, destroystore DestroyStoreFn) {
 	t.Cleanup(destroystore)
 	ctx, store := prepareStore(t)
diff --git a/internal/database/online/test_impl/push.go b/internal/database/online/test_impl/push.go
index 7b7b5105..335086de 100644
--- a/internal/database/online/test_impl/push.go
+++ b/internal/database/online/test_impl/push.go
@@ -35,10 +35,9 @@ func TestPush(t *testing.T, prepareStore PrepareStoreFn, destoryStore DestroySto
 		FeatureValues: []interface{}{int64(1)},
 	}))
 	rs, err := store.Get(ctx, online.GetOpt{
-		EntityKey:  "user1",
-		Group:      group,
-		Features:   types.FeatureList{feature1},
-		RevisionID: nil,
+		EntityKey: "user1",
+		Group:     group,
+		Features:  types.FeatureList{feature1},
 	})
 	assert.NoError(t, err)
 	assert.Equal(t, map[string]interface{}{
@@ -53,10 +52,9 @@ func TestPush(t *testing.T, prepareStore PrepareStoreFn, destoryStore DestroySto
 		FeatureValues: []interface{}{int64(2)},
 	}))
 	rs, err = store.Get(ctx, online.GetOpt{
-		EntityKey:  "user1",
-		Group:      group,
-		Features:   types.FeatureList{feature1},
-		RevisionID: nil,
+		EntityKey: "user1",
+		Group:     group,
+		Features:  types.FeatureList{feature1},
 	})
 	assert.NoError(t, err)
 	assert.Equal(t, map[string]interface{}{
@@ -71,10 +69,9 @@ func TestPush(t *testing.T, prepareStore PrepareStoreFn, destoryStore DestroySto
 		FeatureValues: []interface{}{int64(3), "post2"},
 	}))
 	rs, err = store.Get(ctx, online.GetOpt{
-		EntityKey:  "user1",
-		Group:      group,
-		Features:   types.FeatureList{feature1, feature2},
-		RevisionID: nil,
+		EntityKey: "user1",
+		Group:     group,
+		Features:  types.FeatureList{feature1, feature2},
 	})
 	assert.NoError(t, err)
 	assert.Equal(t, map[string]interface{}{
diff --git a/internal/database/online/tidb/store_test.go b/internal/database/online/tidb/store_test.go
index 54ad2cf6..faa99d78 100644
--- a/internal/database/online/tidb/store_test.go
+++ b/internal/database/online/tidb/store_test.go
@@ -37,6 +37,10 @@ func TestGetExisted(t *testing.T) {
 	test_impl.TestGetExisted(t, prepareStore, runtime_tidb.DestroyStore(DATABASE))
 }
 
+func TestGetNoRevision(t *testing.T) {
+	test_impl.TestGetNoRevision(t, prepareStore, runtime_tidb.DestroyStore(DATABASE))
+}
+
 func TestGetNotExistedEntityKey(t *testing.T) {
 	test_impl.TestGetNotExistedEntityKey(t, prepareStore, runtime_tidb.DestroyStore(DATABASE))
 }
diff --git a/internal/database/online/tikv/store_test.go b/internal/database/online/tikv/store_test.go
index 52f22d0f..c632442b 100644
--- a/internal/database/online/tikv/store_test.go
+++ b/internal/database/online/tikv/store_test.go
@@ -45,6 +45,10 @@ func TestGetExisted(t *testing.T) {
 	test_impl.TestGetExisted(t, prepareStore, destroyStore(t))
 }
 
+func TestGetNoRevision(t *testing.T) {
+	test_impl.TestGetNoRevision(t, prepareStore, destroyStore(t))
+}
+
 func TestGetNotExistedEntityKey(t *testing.T) {
 	test_impl.TestGetNotExistedEntityKey(t, prepareStore, destroyStore(t))
 }
diff --git a/oomagent/test/test_channel_join.sh b/oomagent/test/test_channel_join.sh
index 43819e4f..9e34ffc0 100755
--- a/oomagent/test/test_channel_join.sh
+++ b/oomagent/test/test_channel_join.sh
@@ -100,35 +100,6 @@ ERROR:
   Code: Internal
   Message: invalid full feature name: 'invalid_feature_name'
 EOF
-)
-  assert_eq "$case" "$expected" "$actual"
-
-  case="empty entity rows"
-  arg=$(cat <<-EOF
-{
-  "join_features": [
-    "driver_stats.conv_rate"
-  ]
-}
-EOF
-)
-  actual=$(testgrpc ChannelJoin <<<"$arg" 2>&1 || flag=true)
-  expected=$(cat <<-EOF
-EOF
-)
-  assert_eq "$case" "$expected" "$actual"
-
-
-   case="invalid request"
-  arg=$(cat <<-EOF
-EOF
-)
-  actual=$(testgrpc ChannelJoin <<<"$arg" 2>&1 || flag=true)
-  expected=$(cat <<-EOF
-ERROR:
-  Code: InvalidArgument
-  Message: invalid request: empty feature
-EOF
 )
   assert_eq "$case" "$expected" "$actual"
 }