Repository: oom-ai/oomstore
Pull request #987 (2022-01-17 02:43:52)

Rename feature full name


Patch:
diff --git a/internal/database/metadata/informer/informer.go b/internal/database/metadata/informer/informer.go
index fdbb38b5..fbf81c96 100644
--- a/internal/database/metadata/informer/informer.go
+++ b/internal/database/metadata/informer/informer.go
@@ -88,6 +88,6 @@ func (f *Informer) Cache() *Cache {
 	return f.cache.Load().(*Cache)
 }
 
-func (f *Informer) ListCachedFeature(ctx context.Context, fullNames *[]string) types.FeatureList {
-	return f.Cache().Features.List(fullNames).Copy()
+func (f *Informer) ListCachedFeature(ctx context.Context, featureNames *[]string) types.FeatureList {
+	return f.Cache().Features.List(featureNames).Copy()
 }
diff --git a/oomagent/server.go b/oomagent/server.go
index 0a5f60a6..a607cf55 100644
--- a/oomagent/server.go
+++ b/oomagent/server.go
@@ -32,8 +32,8 @@ func (s *server) HealthCheck(ctx context.Context, req *empty.Empty) (*empty.Empt
 
 func (s *server) OnlineGet(ctx context.Context, req *codegen.OnlineGetRequest) (*codegen.OnlineGetResponse, error) {
 	result, err := s.oomstore.OnlineGet(ctx, types.OnlineGetOpt{
-		FeatureFullNames: req.FeatureNames,
-		EntityKey:        req.EntityKey,
+		FeatureNames: req.FeatureNames,
+		EntityKey:    req.EntityKey,
 	})
 	if err != nil {
 		return nil, internalError(err.Error())
@@ -52,8 +52,8 @@ func (s *server) OnlineGet(ctx context.Context, req *codegen.OnlineGetRequest) (
 
 func (s *server) OnlineMultiGet(ctx context.Context, req *codegen.OnlineMultiGetRequest) (*codegen.OnlineMultiGetResponse, error) {
 	result, err := s.oomstore.OnlineMultiGet(ctx, types.OnlineMultiGetOpt{
-		FeatureFullNames: req.FeatureNames,
-		EntityKeys:       req.EntityKeys,
+		FeatureNames: req.FeatureNames,
+		EntityKeys:   req.EntityKeys,
 	})
 	if err != nil {
 		return nil, internalError(err.Error())
@@ -203,9 +203,9 @@ func (s *server) ChannelJoin(stream codegen.OomAgent_ChannelJoinServer) error {
 	// This goroutine runs the join operation, and send whatever joined as the response
 	go func() {
 		joinResult, err := s.oomstore.ChannelJoin(context.Background(), types.ChannelJoinOpt{
-			FeatureFullNames: firstReq.FeatureNames,
-			EntityRows:       entityRows,
-			ValueNames:       firstReq.ValueNames,
+			FeatureNames: firstReq.FeatureNames,
+			EntityRows:   entityRows,
+			ValueNames:   firstReq.ValueNames,
 		})
 		if err != nil {
 			globalErr = err
@@ -272,9 +272,9 @@ func (s *server) ChannelJoin(stream codegen.OomAgent_ChannelJoinServer) error {
 
 func (s *server) Join(ctx context.Context, req *codegen.JoinRequest) (*codegen.JoinResponse, error) {
 	err := s.oomstore.Join(ctx, types.JoinOpt{
-		FeatureFullNames: req.FeatureNames,
-		InputFilePath:    req.InputFilePath,
-		OutputFilePath:   req.OutputFilePath,
+		FeatureNames:   req.FeatureNames,
+		InputFilePath:  req.InputFilePath,
+		OutputFilePath: req.OutputFilePath,
 	})
 	if err != nil {
 		return nil, internalError(err.Error())
@@ -286,9 +286,9 @@ func (s *server) Join(ctx context.Context, req *codegen.JoinRequest) (*codegen.J
 func (s *server) ChannelExport(req *codegen.ChannelExportRequest, stream codegen.OomAgent_ChannelExportServer) error {
 	ctx := context.Background()
 	exportResult, err := s.oomstore.ChannelExport(ctx, types.ChannelExportOpt{
-		FeatureFullNames: req.FeatureNames,
-		UnixMilli:        req.UnixMilli,
-		Limit:            req.Limit,
+		FeatureNames: req.FeatureNames,
+		UnixMilli:    req.UnixMilli,
+		Limit:        req.Limit,
 	})
 	if err != nil {
 		return internalError(err.Error())
@@ -317,10 +317,10 @@ func (s *server) ChannelExport(req *codegen.ChannelExportRequest, stream codegen
 
 func (s *server) Export(ctx context.Context, req *codegen.ExportRequest) (*codegen.ExportResponse, error) {
 	err := s.oomstore.Export(ctx, types.ExportOpt{
-		FeatureFullNames: req.FeatureNames,
-		UnixMilli:        req.UnixMilli,
-		Limit:            req.Limit,
-		OutputFilePath:   req.OutputFilePath,
+		FeatureNames:   req.FeatureNames,
+		UnixMilli:      req.UnixMilli,
+		Limit:          req.Limit,
+		OutputFilePath: req.OutputFilePath,
 	})
 	if err != nil {
 		return nil, internalError(err.Error())
diff --git a/oomcli/cmd/edit_feature.go b/oomcli/cmd/edit_feature.go
index f23bbd09..36744d3a 100644
--- a/oomcli/cmd/edit_feature.go
+++ b/oomcli/cmd/edit_feature.go
@@ -30,7 +30,7 @@ var editFeatureCmd = &cobra.Command{
 		if len(args) > 1 {
 			exitf("argument at most one, got %d", len(args))
 		} else if len(args) == 1 {
-			editFeatureOpt.FeatureFullNames = &[]string{args[0]}
+			editFeatureOpt.FeatureNames = &[]string{args[0]}
 		}
 
 	},
diff --git a/oomcli/cmd/export.go b/oomcli/cmd/export.go
index 0f75cefe..3803247c 100644
--- a/oomcli/cmd/export.go
+++ b/oomcli/cmd/export.go
@@ -35,7 +35,7 @@ func init() {
 	flags := exportCmd.Flags()
 	exportOutput = flags.StringP("output", "o", ASCIITable, "output format [csv,ascii_table]")
 
-	flags.StringSliceVar(&exportOpt.FeatureFullNames, "feature", nil, "select feature names")
+	flags.StringSliceVar(&exportOpt.FeatureNames, "feature", nil, "select feature names")
 
 	flags.Int64VarP(&exportOpt.UnixMilli, "unix-milli", "u", 0, "max unix timestamp")
 	_ = exportCmd.MarkFlagRequired("revision-id")
diff --git a/oomcli/cmd/get_meta_feature.go b/oomcli/cmd/get_meta_feature.go
index e2f88463..9ddc3199 100644
--- a/oomcli/cmd/get_meta_feature.go
+++ b/oomcli/cmd/get_meta_feature.go
@@ -28,7 +28,7 @@ var getMetaFeatureCmd = &cobra.Command{
 			getMetaFeatureOpt.GroupName = nil
 		}
 		if len(args) == 1 {
-			getMetaFeatureOpt.FeatureFullNames = &[]string{args[0]}
+			getMetaFeatureOpt.FeatureNames = &[]string{args[0]}
 		}
 	},
 	Run: func(cmd *cobra.Command, args []string) {
@@ -61,8 +61,8 @@ func queryFeatures(ctx context.Context, oomStore *oomstore.OomStore, opt types.L
 		return nil, fmt.Errorf("failed getting features, error %v\n", err)
 	}
 
-	if opt.FeatureFullNames != nil && len(features) == 0 {
-		return nil, errdefs.Errorf("feature '%s' not found", (*opt.FeatureFullNames)[0])
+	if opt.FeatureNames != nil && len(features) == 0 {
+		return nil, errdefs.Errorf("feature '%s' not found", (*opt.FeatureNames)[0])
 	}
 
 	return features, nil
diff --git a/oomcli/cmd/get_online.go b/oomcli/cmd/get_online.go
index a6276872..8c71ff23 100644
--- a/oomcli/cmd/get_online.go
+++ b/oomcli/cmd/get_online.go
@@ -43,7 +43,7 @@ func init() {
 	flags.StringSliceVarP(&multiGetOpt.EntityKeys, "entity-keys", "k", nil, "entity keys")
 	_ = getOnlineCmd.MarkFlagRequired("entity-keys")
 
-	flags.StringSliceVar(&multiGetOpt.FeatureFullNames, "feature", nil, "feature full names")
+	flags.StringSliceVar(&multiGetOpt.FeatureNames, "feature", nil, "feature full names")
 	_ = getOnlineCmd.MarkFlagRequired("feature")
 }
 
@@ -67,7 +67,7 @@ func printOnlineFeaturesInCSV(featureValues map[string]*types.FeatureValues) err
 		w = csv.NewWriter(os.Stdout)
 
 		keys   = entityKeys(featureValues)
-		header = append([]string{featureValues[keys[0]].EntityName}, featureValues[keys[0]].FeatureFullNames...)
+		header = append([]string{featureValues[keys[0]].EntityName}, featureValues[keys[0]].FeatureNames...)
 	)
 
 	if err := w.Write(header); err != nil {
@@ -94,7 +94,7 @@ func printOnlineFeaturesInASCIITable(featureValues map[string]*types.FeatureValu
 		table = tablewriter.NewWriter(os.Stdout)
 
 		keys   = entityKeys(featureValues)
-		header = append([]string{featureValues[keys[0]].EntityName}, featureValues[keys[0]].FeatureFullNames...)
+		header = append([]string{featureValues[keys[0]].EntityName}, featureValues[keys[0]].FeatureNames...)
 	)
 	table.SetAutoFormatHeaders(false)
 	table.SetHeader(header)
diff --git a/oomcli/cmd/join_helper.go b/oomcli/cmd/join_helper.go
index c2a631ab..c6d51394 100644
--- a/oomcli/cmd/join_helper.go
+++ b/oomcli/cmd/join_helper.go
@@ -25,9 +25,9 @@ func join(ctx context.Context, store *oomstore.OomStore, opt JoinOpt, output str
 	}
 
 	joinResult, err := store.ChannelJoin(ctx, types.ChannelJoinOpt{
-		FeatureFullNames: opt.FeatureNames,
-		EntityRows:       entityRows,
-		ValueNames:       header[2:],
+		FeatureNames: opt.FeatureNames,
+		EntityRows:   entityRows,
+		ValueNames:   header[2:],
 	})
 	if err != nil {
 		return err
diff --git a/oomcli/cmd/push.go b/oomcli/cmd/push.go
index 91f80723..e6323c6e 100644
--- a/oomcli/cmd/push.go
+++ b/oomcli/cmd/push.go
@@ -59,6 +59,6 @@ func init() {
 	flags.StringVarP(&pushOpt.GroupName, "group", "g", "", "feature group")
 	_ = pushCmd.MarkFlagRequired("group")
 
-	flags.StringToStringVarP(&pushOpt.FeaturePair, "features", "f", nil, "features")
+	flags.StringToStringVarP(&pushOpt.FeaturePair, "features", "f", nil, "feature name-value pairs")
 	_ = pushCmd.MarkFlagRequired("features")
 }
diff --git a/oomcli/cmd/update_feature.go b/oomcli/cmd/update_feature.go
index 282c8632..5f884fc8 100644
--- a/oomcli/cmd/update_feature.go
+++ b/oomcli/cmd/update_feature.go
@@ -14,7 +14,7 @@ var updateFeatureCmd = &cobra.Command{
 	Short: "Update a particular feature",
 	Args:  cobra.ExactArgs(1),
 	PreRun: func(cmd *cobra.Command, args []string) {
-		updateFeatureOpt.FeatureFullName = args[0]
+		updateFeatureOpt.FeatureName = args[0]
 		if !cmd.Flags().Changed("description") {
 			updateFeatureOpt.NewDescription = nil
 		}
@@ -25,7 +25,7 @@ var updateFeatureCmd = &cobra.Command{
 		defer oomStore.Close()
 
 		if err := oomStore.UpdateFeature(ctx, updateFeatureOpt); err != nil {
-			exitf("failed to update feature %s, err %+v\n", updateFeatureOpt.FeatureFullName, err)
+			exitf("failed to update feature %s, err %+v\n", updateFeatureOpt.FeatureName, err)
 		}
 	},
 }
diff --git a/pkg/oomstore/export.go b/pkg/oomstore/export.go
index edb73f10..c33da6ce 100644
--- a/pkg/oomstore/export.go
+++ b/pkg/oomstore/export.go
@@ -10,6 +10,7 @@ import (
 	"github.com/oom-ai/oomstore/internal/database/offline"
 	"github.com/oom-ai/oomstore/pkg/errdefs"
 	"github.com/oom-ai/oomstore/pkg/oomstore/types"
+	"github.com/oom-ai/oomstore/pkg/oomstore/util"
 )
 
 /*
@@ -26,11 +27,11 @@ Usage Example:
 	return exportResult.CheckStreamError()
 */
 func (s *OomStore) ChannelExport(ctx context.Context, opt types.ChannelExportOpt) (*types.ExportResult, error) {
-	if err := validateFullFeatureNames(opt.FeatureFullNames...); err != nil {
+	if err := util.ValidateFullFeatureNames(opt.FeatureNames...); err != nil {
 		return nil, err
 	}
 	features, err := s.ListFeature(ctx, types.ListFeatureOpt{
-		FeatureFullNames: &opt.FeatureFullNames,
+		FeatureNames: &opt.FeatureNames,
 	})
 	if err != nil {
 		return nil, err
@@ -82,9 +83,9 @@ func (s *OomStore) ChannelExport(ctx context.Context, opt types.ChannelExportOpt
 // feature values to the given file path.
 func (s *OomStore) Export(ctx context.Context, opt types.ExportOpt) error {
 	exportResult, err := s.ChannelExport(ctx, types.ChannelExportOpt{
-		FeatureFullNames: opt.FeatureFullNames,
-		UnixMilli:        opt.UnixMilli,
-		Limit:            opt.Limit,
+		FeatureNames: opt.FeatureNames,
+		UnixMilli:    opt.UnixMilli,
+		Limit:        opt.Limit,
 	})
 	if err != nil {
 		return err
diff --git a/pkg/oomstore/feature.go b/pkg/oomstore/feature.go
index 7181d330..a903350e 100644
--- a/pkg/oomstore/feature.go
+++ b/pkg/oomstore/feature.go
@@ -2,9 +2,6 @@ package oomstore
 
 import (
 	"context"
-	"strings"
-
-	"github.com/oom-ai/oomstore/pkg/errdefs"
 
 	"github.com/oom-ai/oomstore/internal/database/metadata"
 	"github.com/oom-ai/oomstore/internal/database/online"
@@ -52,18 +49,18 @@ func (s *OomStore) ListFeature(ctx context.Context, opt types.ListFeatureOpt) (t
 	if err != nil {
 		return nil, err
 	}
-	if opt.FeatureFullNames != nil {
-		features = features.FilterFullnames(*opt.FeatureFullNames)
+	if opt.FeatureNames != nil {
+		features = features.FilterFullnames(*opt.FeatureNames)
 	}
 	return features, nil
 }
 
 // Update metadata of a feature.
 func (s *OomStore) UpdateFeature(ctx context.Context, opt types.UpdateFeatureOpt) error {
-	if err := validateFullFeatureNames(opt.FeatureFullName); err != nil {
+	if err := util.ValidateFullFeatureNames(opt.FeatureName); err != nil {
 		return err
 	}
-	feature, err := s.GetFeatureByFullName(ctx, opt.FeatureFullName)
+	feature, err := s.GetFeatureByFullName(ctx, opt.FeatureName)
 	if err != nil {
 		return err
 	}
@@ -106,13 +103,3 @@ func (s *OomStore) CreateFeature(ctx context.Context, opt types.CreateFeatureOpt
 
 	return id, nil
 }
-
-func validateFullFeatureNames(fullnames ...string) error {
-	for _, fullname := range fullnames {
-		nameSlice := strings.Split(fullname, util.SepFullFeatureName)
-		if len(nameSlice) != 2 {
-			return errdefs.Errorf("invalid full feature name: '%s'", fullname)
-		}
-	}
-	return nil
-}
diff --git a/pkg/oomstore/join.go b/pkg/oomstore/join.go
index 9a3804b0..47876ed2 100644
--- a/pkg/oomstore/join.go
+++ b/pkg/oomstore/join.go
@@ -14,12 +14,13 @@ import (
 	"github.com/oom-ai/oomstore/internal/database/offline"
 	"github.com/oom-ai/oomstore/pkg/errdefs"
 	"github.com/oom-ai/oomstore/pkg/oomstore/types"
+	"github.com/oom-ai/oomstore/pkg/oomstore/util"
 )
 
 // ChannelJoin gets point-in-time correct feature values for each entity row.
 // Currently, this API only supports batch features.
 func (s *OomStore) ChannelJoin(ctx context.Context, opt types.ChannelJoinOpt) (*types.JoinResult, error) {
-	if err := validateFullFeatureNames(opt.FeatureFullNames...); err != nil {
+	if err := util.ValidateFullFeatureNames(opt.FeatureNames...); err != nil {
 		return nil, err
 	}
 	data := make(chan []interface{})
@@ -29,7 +30,7 @@ func (s *OomStore) ChannelJoin(ctx context.Context, opt types.ChannelJoinOpt) (*
 		Data: data,
 	}
 	features, err := s.ListFeature(ctx, types.ListFeatureOpt{
-		FeatureFullNames: &opt.FeatureFullNames,
+		FeatureNames: &opt.FeatureNames,
 	})
 	if err != nil {
 		return nil, err
@@ -74,7 +75,7 @@ func (s *OomStore) ChannelJoin(ctx context.Context, opt types.ChannelJoinOpt) (*
 // Input File should contain header, the first two columns of Input File should be
 // entity_key, unix_milli, then followed by other real-time feature values.
 func (s *OomStore) Join(ctx context.Context, opt types.JoinOpt) error {
-	if err := validateFullFeatureNames(opt.FeatureFullNames...); err != nil {
+	if err := util.ValidateFullFeatureNames(opt.FeatureNames...); err != nil {
 		return err
 	}
 	entityRows, header, err := GetEntityRowsFromInputFile(opt.InputFilePath)
@@ -83,9 +84,9 @@ func (s *OomStore) Join(ctx context.Context, opt types.JoinOpt) error {
 	}
 
 	joinResult, err := s.ChannelJoin(ctx, types.ChannelJoinOpt{
-		FeatureFullNames: opt.FeatureFullNames,
-		EntityRows:       entityRows,
-		ValueNames:       header[2:],
+		FeatureNames: opt.FeatureNames,
+		EntityRows:   entityRows,
+		ValueNames:   header[2:],
 	})
 	if err != nil {
 		return err
diff --git a/pkg/oomstore/online_query.go b/pkg/oomstore/online_query.go
index 28b1e994..88fd0d63 100644
--- a/pkg/oomstore/online_query.go
+++ b/pkg/oomstore/online_query.go
@@ -8,19 +8,20 @@ import (
 	"github.com/oom-ai/oomstore/internal/database/dbutil"
 	"github.com/oom-ai/oomstore/internal/database/online"
 	"github.com/oom-ai/oomstore/pkg/oomstore/types"
+	"github.com/oom-ai/oomstore/pkg/oomstore/util"
 )
 
 // OnlineGet gets online features of a particular entity instance.
 func (s *OomStore) OnlineGet(ctx context.Context, opt types.OnlineGetOpt) (*types.FeatureValues, error) {
-	if err := validateFullFeatureNames(opt.FeatureFullNames...); err != nil {
+	if err := util.ValidateFullFeatureNames(opt.FeatureNames...); err != nil {
 		return nil, err
 	}
 	rs := types.FeatureValues{
-		EntityKey:        opt.EntityKey,
-		FeatureFullNames: opt.FeatureFullNames,
-		FeatureValueMap:  make(map[string]interface{}),
+		EntityKey:       opt.EntityKey,
+		FeatureNames:    opt.FeatureNames,
+		FeatureValueMap: make(map[string]interface{}),
 	}
-	features := s.metadata.ListCachedFeature(ctx, &opt.FeatureFullNames)
+	features := s.metadata.ListCachedFeature(ctx, &opt.FeatureNames)
 	if len(features) == 0 {
 		return &rs, nil
 	}
@@ -58,11 +59,11 @@ func (s *OomStore) OnlineGet(ctx context.Context, opt types.OnlineGetOpt) (*type
 
 // OnlineMultiGet gets online features of multiple entity instances.
 func (s *OomStore) OnlineMultiGet(ctx context.Context, opt types.OnlineMultiGetOpt) (map[string]*types.FeatureValues, error) {
-	if err := validateFullFeatureNames(opt.FeatureFullNames...); err != nil {
+	if err := util.ValidateFullFeatureNames(opt.FeatureNames...); err != nil {
 		return nil, err
 	}
 	result := make(map[string]*types.FeatureValues)
-	features := s.metadata.ListCachedFeature(ctx, &opt.FeatureFullNames)
+	features := s.metadata.ListCachedFeature(ctx, &opt.FeatureNames)
 	if len(features) == 0 {
 		return result, nil
 	}
@@ -84,10 +85,10 @@ func (s *OomStore) OnlineMultiGet(ctx context.Context, opt types.OnlineMultiGetO
 
 	for _, entityKey := range opt.EntityKeys {
 		result[entityKey] = &types.FeatureValues{
-			EntityName:       entity.Name,
-			EntityKey:        entityKey,
-			FeatureFullNames: opt.FeatureFullNames,
-			FeatureValueMap:  make(map[string]interface{}),
+			EntityName:      entity.Name,
+			EntityKey:       entityKey,
+			FeatureNames:    opt.FeatureNames,
+			FeatureValueMap: make(map[string]interface{}),
 		}
 		for featureName, featureValue := range featureValueMap[entityKey] {
 			result[entityKey].FeatureValueMap[featureName] = featureValue
diff --git a/pkg/oomstore/types/feature_values.go b/pkg/oomstore/types/feature_values.go
index 7fdcbe57..079ab5ab 100644
--- a/pkg/oomstore/types/feature_values.go
+++ b/pkg/oomstore/types/feature_values.go
@@ -1,15 +1,15 @@
 package types
 
 type FeatureValues struct {
-	EntityName       string
-	EntityKey        string
-	FeatureFullNames []string
-	FeatureValueMap  map[string]interface{}
+	EntityName      string
+	EntityKey       string
+	FeatureNames    []string
+	FeatureValueMap map[string]interface{}
 }
 
 func (fv *FeatureValues) FeatureValueSlice() []interface{} {
-	values := make([]interface{}, 0, len(fv.FeatureFullNames))
-	for _, name := range fv.FeatureFullNames {
+	values := make([]interface{}, 0, len(fv.FeatureNames))
+	for _, name := range fv.FeatureNames {
 		values = append(values, fv.FeatureValueMap[name])
 	}
 	return values
diff --git a/pkg/oomstore/types/options.go b/pkg/oomstore/types/options.go
index 160ca927..5c505ba8 100644
--- a/pkg/oomstore/types/options.go
+++ b/pkg/oomstore/types/options.go
@@ -8,14 +8,14 @@ type CreateFeatureOpt struct {
 }
 
 type ListFeatureOpt struct {
-	EntityName       *string
-	GroupName        *string
-	FeatureFullNames *[]string
+	EntityName   *string
+	GroupName    *string
+	FeatureNames *[]string
 }
 
 type UpdateFeatureOpt struct {
-	FeatureFullName string
-	NewDescription  *string
+	FeatureName    string
+	NewDescription *string
 }
 
 type CreateEntityOpt struct {
@@ -31,38 +31,38 @@ type CreateGroupOpt struct {
 }
 
 type ChannelExportOpt struct {
-	FeatureFullNames []string
-	UnixMilli        int64
-	Limit            *uint64
+	FeatureNames []string
+	UnixMilli    int64
+	Limit        *uint64
 }
 
 type ExportOpt struct {
-	FeatureFullNames []string
-	UnixMilli        int64
-	Limit            *uint64
-	OutputFilePath   string
+	FeatureNames   []string
+	UnixMilli      int64
+	Limit          *uint64
+	OutputFilePath string
 }
 
 type OnlineGetOpt struct {
-	FeatureFullNames []string
-	EntityKey        string
+	FeatureNames []string
+	EntityKey    string
 }
 
 type OnlineMultiGetOpt struct {
-	FeatureFullNames []string
-	EntityKeys       []string
+	FeatureNames []string
+	EntityKeys   []string
 }
 
 type ChannelJoinOpt struct {
-	FeatureFullNames []string
-	EntityRows       <-chan EntityRow
-	ValueNames       []string
+	FeatureNames []string
+	EntityRows   <-chan EntityRow
+	ValueNames   []string
 }
 
 type JoinOpt struct {
-	FeatureFullNames []string
-	InputFilePath    string
-	OutputFilePath   string
+	FeatureNames   []string
+	InputFilePath  string
+	OutputFilePath string
 }
 
 type UpdateEntityOpt struct {
@@ -82,8 +82,10 @@ type SyncOpt struct {
 }
 
 type PushOpt struct {
-	EntityKey     string
-	GroupName     string
+	EntityKey string
+	GroupName string
+
+	// feature names without group prefix
 	FeatureNames  []string
 	FeatureValues []interface{}
 }


Test patch:
diff --git a/internal/database/metadata/test_impl/feature.go b/internal/database/metadata/test_impl/feature.go
index a9bed41e..b1ad9252 100644
--- a/internal/database/metadata/test_impl/feature.go
+++ b/internal/database/metadata/test_impl/feature.go
@@ -192,7 +192,7 @@ func TestListCachedFeature(t *testing.T, prepareStore PrepareStoreFn, destroySto
 	features = store.ListCachedFeature(ctx, nil)
 	assert.Equal(t, 1, features.Len())
 
-	// case 8: list features by FeatureFullNames
+	// case 8: list features by FeatureNames
 	features = store.ListCachedFeature(ctx, &[]string{"device_info.phone"})
 	assert.Equal(t, 1, len(features))
 }
diff --git a/pkg/oomstore/export_test.go b/pkg/oomstore/export_test.go
index 1123b8d8..0e8c7da9 100644
--- a/pkg/oomstore/export_test.go
+++ b/pkg/oomstore/export_test.go
@@ -46,8 +46,8 @@ func TestChannelExport(t *testing.T) {
 		{
 			description: "provide multiple features, should return multiple feature values",
 			opt: types.ChannelExportOpt{
-				FeatureFullNames: []string{"model", "price"},
-				UnixMilli:        100,
+				FeatureNames: []string{"model", "price"},
+				UnixMilli:    100,
 			},
 			features:     features,
 			exportStream: prepareTwoFeatureStream(),
@@ -56,8 +56,8 @@ func TestChannelExport(t *testing.T) {
 		{
 			description: "provide no features, should return empty values",
 			opt: types.ChannelExportOpt{
-				FeatureFullNames: []string{},
-				UnixMilli:        100,
+				FeatureNames: []string{},
+				UnixMilli:    100,
 			},
 			features:     features,
 			exportStream: prepareEmptyStream(),
@@ -67,8 +67,8 @@ func TestChannelExport(t *testing.T) {
 		{
 			description: "provide one feature, should return one feature values",
 			opt: types.ChannelExportOpt{
-				FeatureFullNames: []string{"price"},
-				UnixMilli:        100,
+				FeatureNames: []string{"price"},
+				UnixMilli:    100,
 			},
 			features:     features[1:],
 			exportStream: prepareOneFeatureStream(),
@@ -80,7 +80,7 @@ func TestChannelExport(t *testing.T) {
 			t.Skip()
 			metadataStore.EXPECT().Refresh().Return(nil).AnyTimes()
 
-			if len(tc.opt.FeatureFullNames) == 0 {
+			if len(tc.opt.FeatureNames) == 0 {
 				metadataStore.EXPECT().GetGroupByName(ctx, "device_info").Return(&types.Group{
 					Name: "device_info",
 					ID:   1,
diff --git a/pkg/oomstore/join_test.go b/pkg/oomstore/join_test.go
index 1355a418..d1784248 100644
--- a/pkg/oomstore/join_test.go
+++ b/pkg/oomstore/join_test.go
@@ -54,8 +54,8 @@ func TestChannelJoin(t *testing.T) {
 		{
 			description: "no valid features, return nil",
 			opt: types.ChannelJoinOpt{
-				FeatureFullNames: []string{},
-				EntityRows:       entityRows,
+				FeatureNames: []string{},
+				EntityRows:   entityRows,
 			},
 			features:      nil,
 			expectedError: nil,
@@ -64,8 +64,8 @@ func TestChannelJoin(t *testing.T) {
 		{
 			description: "inconsistent features, return nil",
 			opt: types.ChannelJoinOpt{
-				FeatureFullNames: inconsistentFeatures.FullNames(),
-				EntityRows:       entityRows,
+				FeatureNames: inconsistentFeatures.FullNames(),
+				EntityRows:   entityRows,
 			},
 			features:      inconsistentFeatures,
 			expectedError: fmt.Errorf("inconsistent entity type: %v", map[string]string{"device": "price", "user": "age"}),
@@ -74,8 +74,8 @@ func TestChannelJoin(t *testing.T) {
 		{
 			description: "nil joined, return nil",
 			opt: types.ChannelJoinOpt{
-				FeatureFullNames: consistentFeatures.FullNames(),
-				EntityRows:       entityRows,
+				FeatureNames: consistentFeatures.FullNames(),
+				EntityRows:   entityRows,
 			},
 			entity:   &entity,
 			features: consistentFeatures,
@@ -90,8 +90,8 @@ func TestChannelJoin(t *testing.T) {
 		{
 			description: "consistent entity type, succeed",
 			opt: types.ChannelJoinOpt{
-				FeatureFullNames: consistentFeatures.FullNames(),
-				EntityRows:       entityRows,
+				FeatureNames: consistentFeatures.FullNames(),
+				EntityRows:   entityRows,
 			},
 			entity:   &entity,
 			features: consistentFeatures,
diff --git a/pkg/oomstore/online_query_test.go b/pkg/oomstore/online_query_test.go
index ece4c4ee..534f2e5c 100644
--- a/pkg/oomstore/online_query_test.go
+++ b/pkg/oomstore/online_query_test.go
@@ -36,22 +36,22 @@ func TestOnlineGet(t *testing.T) {
 		{
 			description: "no available features",
 			opt: types.OnlineGetOpt{
-				FeatureFullNames: []string{"a.f1", "a.f2"},
-				EntityKey:        "1234",
+				FeatureNames: []string{"a.f1", "a.f2"},
+				EntityKey:    "1234",
 			},
 			features:      nil,
 			expectedError: nil,
 			expected: &types.FeatureValues{
-				EntityKey:        "1234",
-				FeatureFullNames: []string{"a.f1", "a.f2"},
-				FeatureValueMap:  map[string]interface{}{},
+				EntityKey:       "1234",
+				FeatureNames:    []string{"a.f1", "a.f2"},
+				FeatureValueMap: map[string]interface{}{},
 			},
 		},
 		{
 			description: "inconsistent entity type, fail",
 			opt: types.OnlineGetOpt{
-				FeatureFullNames: inconsistentFeatures.FullNames(),
-				EntityKey:        "1234",
+				FeatureNames: inconsistentFeatures.FullNames(),
+				EntityKey:    "1234",
 			},
 			features:      inconsistentFeatures,
 			expectedError: fmt.Errorf("expected 1 entity, got 2 entities"),
@@ -60,16 +60,16 @@ func TestOnlineGet(t *testing.T) {
 		{
 			description: "consistent entity type, succeed",
 			opt: types.OnlineGetOpt{
-				FeatureFullNames: consistentFeatures.FullNames(),
-				EntityKey:        "1234",
+				FeatureNames: consistentFeatures.FullNames(),
+				EntityKey:    "1234",
 			},
 			features:      consistentFeatures,
 			entityName:    &entityName,
 			expectedError: nil,
 			expected: &types.FeatureValues{
-				EntityName:       entityName,
-				EntityKey:        "1234",
-				FeatureFullNames: consistentFeatures.FullNames(),
+				EntityName:   entityName,
+				EntityKey:    "1234",
+				FeatureNames: consistentFeatures.FullNames(),
 				FeatureValueMap: map[string]interface{}{
 					"price": int64(100),
 					"model": "xiaomi",
@@ -80,7 +80,7 @@ func TestOnlineGet(t *testing.T) {
 
 	for _, tc := range testCases {
 		t.Run(tc.description, func(t *testing.T) {
-			metadataStore.EXPECT().ListCachedFeature(gomock.Any(), &tc.opt.FeatureFullNames).Return(tc.features)
+			metadataStore.EXPECT().ListCachedFeature(gomock.Any(), &tc.opt.FeatureNames).Return(tc.features)
 			if tc.entityName != nil {
 				onlineStore.EXPECT().Get(gomock.Any(), gomock.Any()).Return(dbutil.RowMap{
 					"price": int64(100),
@@ -123,8 +123,8 @@ func TestOnlineMultiGet(t *testing.T) {
 		{
 			description: "no available features, return nil",
 			opt: types.OnlineMultiGetOpt{
-				FeatureFullNames: []string{"a.f1", "a.f2"},
-				EntityKeys:       []string{"1234", "1235"},
+				FeatureNames: []string{"a.f1", "a.f2"},
+				EntityKeys:   []string{"1234", "1235"},
 			},
 			features:      nil,
 			expectedError: nil,
@@ -133,8 +133,8 @@ func TestOnlineMultiGet(t *testing.T) {
 		{
 			description: "inconsistent entity type, fail",
 			opt: types.OnlineMultiGetOpt{
-				FeatureFullNames: inconsistentFeatures.FullNames(),
-				EntityKeys:       []string{"1234", "1235"},
+				FeatureNames: inconsistentFeatures.FullNames(),
+				EntityKeys:   []string{"1234", "1235"},
 			},
 			features:      inconsistentFeatures,
 			expectedError: fmt.Errorf("expected 1 entity, got 2 entities"),
@@ -143,26 +143,26 @@ func TestOnlineMultiGet(t *testing.T) {
 		{
 			description: "consistent entity type, succeed",
 			opt: types.OnlineMultiGetOpt{
-				FeatureFullNames: consistentFeatures.FullNames(),
-				EntityKeys:       []string{"1234", "1235"},
+				FeatureNames: consistentFeatures.FullNames(),
+				EntityKeys:   []string{"1234", "1235"},
 			},
 			features:      consistentFeatures,
 			entityName:    &entityName,
 			expectedError: nil,
 			expected: map[string]*types.FeatureValues{
 				"1234": {
-					EntityName:       "device",
-					EntityKey:        "1234",
-					FeatureFullNames: consistentFeatures.FullNames(),
+					EntityName:   "device",
+					EntityKey:    "1234",
+					FeatureNames: consistentFeatures.FullNames(),
 					FeatureValueMap: map[string]interface{}{
 						"model": "xiaomi",
 						"price": int64(100),
 					},
 				},
 				"1235": {
-					EntityName:       "device",
-					EntityKey:        "1235",
-					FeatureFullNames: consistentFeatures.FullNames(),
+					EntityName:   "device",
+					EntityKey:    "1235",
+					FeatureNames: consistentFeatures.FullNames(),
 					FeatureValueMap: map[string]interface{}{
 						"model": "apple",
 						"price": int64(200),
@@ -174,7 +174,7 @@ func TestOnlineMultiGet(t *testing.T) {
 
 	for _, tc := range testCases {
 		t.Run(tc.description, func(t *testing.T) {
-			metadataStore.EXPECT().ListCachedFeature(gomock.Any(), &tc.opt.FeatureFullNames).Return(tc.features)
+			metadataStore.EXPECT().ListCachedFeature(gomock.Any(), &tc.opt.FeatureNames).Return(tc.features)
 			if tc.entityName != nil {
 				onlineStore.EXPECT().MultiGet(gomock.Any(), gomock.Any()).Return(map[string]dbutil.RowMap{
 					"1234": {