From 501f59dece231bb28fa4b33ef1033e9bc1bac2cf Mon Sep 17 00:00:00 2001 From: Kevin Zhang Date: Tue, 22 Mar 2022 17:56:02 -0700 Subject: [PATCH 01/43] Initial structure for go sqlite online store Signed-off-by: Kevin Zhang --- go/internal/feast/onlinestore.go | 5 +++ go/internal/feast/sqliteonlinestore.go | 47 ++++++++++++++++++++++++++ 2 files changed, 52 insertions(+) create mode 100644 go/internal/feast/sqliteonlinestore.go diff --git a/go/internal/feast/onlinestore.go b/go/internal/feast/onlinestore.go index b770cea4c6e..7914b3ec52f 100644 --- a/go/internal/feast/onlinestore.go +++ b/go/internal/feast/onlinestore.go @@ -3,6 +3,7 @@ package feast import ( "context" "fmt" + "github.com/feast-dev/feast/go/protos/feast/serving" "github.com/feast-dev/feast/go/protos/feast/types" "github.com/golang/protobuf/ptypes/timestamp" @@ -56,6 +57,10 @@ func NewOnlineStore(config *RepoConfig) (OnlineStore, error) { if onlineStoreType == "redis" { onlineStore, err := NewRedisOnlineStore(config.Project, config.OnlineStore) return onlineStore, err + } + if onlineStoreType == "sqlite" { + onlineStore, err := NewSqliteOnlineStore(config.Project, config.OnlineStore) + return onlineStore, err } else { return nil, fmt.Errorf("%s online store type is currently not supported; only Redis is supported", onlineStoreType) } diff --git a/go/internal/feast/sqliteonlinestore.go b/go/internal/feast/sqliteonlinestore.go new file mode 100644 index 00000000000..e37006e93f1 --- /dev/null +++ b/go/internal/feast/sqliteonlinestore.go @@ -0,0 +1,47 @@ +package feast + +import ( + "context" + "fmt" + + "github.com/feast-dev/feast/go/protos/feast/types" +) + +type SqliteOnlineStore struct { + // Feast project name + // TODO (woop): Should we remove project as state that is tracked at the store level? + project string + + path string +} + +func NewSqliteOnlineStore(project string, onlineStoreConfig map[string]interface{}) (*SqliteOnlineStore, error) { + store := SqliteOnlineStore{project: project} + if db_path, ok := onlineStoreConfig["path"]; !ok { + return nil, fmt.Errorf("cannot find sqlite path %s", db_path) + } else if dbPathStr, ok := db_path.(string); !ok { + return nil, fmt.Errorf("cannot find convert sqlite path to string %s", db_path) + } else { + store.path = dbPathStr + } + return &SqliteOnlineStore{ + project: project, + }, nil +} + +func (*SqliteOnlineStore) Destruct() { + +} + +func (*SqliteOnlineStore) OnlineRead(ctx context.Context, entityKeys []types.EntityKey, featureViewNames []string, featureNames []string) ([][]FeatureData, error) { + return nil, nil +} + +// feature views, entities, dataframe? +func (*SqliteOnlineStore) WriteToOnlineStore(ctx context.Context, featureViewName string, data [][]FeatureData) error { + return nil +} + +func (*SqliteOnlineStore) initializeConnection(db_path string) { + +} From 0fbd8dc8590b24801ff89a7900904b7d7e1c826c Mon Sep 17 00:00:00 2001 From: Kevin Zhang Date: Wed, 23 Mar 2022 11:47:14 -0700 Subject: [PATCH 02/43] Somewhat intermediate state Signed-off-by: Kevin Zhang --- go.mod | 3 + go.sum | 2 + go/internal/feast/foo.db | 0 go/internal/feast/sqliteonlinestore.go | 68 +++++++++++++++--- go/internal/feast/sqliteonlinestore_test.go | 54 ++++++++++++++ go/internal/test/selected_tetra/__init__.py | 0 .../selected_tetra/data/driver_stats.parquet | Bin 0 -> 34740 bytes .../test/selected_tetra/data/online_store.db | Bin 0 -> 16384 bytes go/internal/test/selected_tetra/example.py | 35 +++++++++ .../test/selected_tetra/feature_store.yaml | 5 ++ .../feature_repos/repo_configuration.py | 42 +++++------ 11 files changed, 180 insertions(+), 29 deletions(-) create mode 100644 go/internal/feast/foo.db create mode 100644 go/internal/feast/sqliteonlinestore_test.go create mode 100644 go/internal/test/selected_tetra/__init__.py create mode 100644 go/internal/test/selected_tetra/data/driver_stats.parquet create mode 100644 go/internal/test/selected_tetra/data/online_store.db create mode 100644 go/internal/test/selected_tetra/example.py create mode 100644 go/internal/test/selected_tetra/feature_store.yaml diff --git a/go.mod b/go.mod index 0cb92efeaec..7d4093df57b 100644 --- a/go.mod +++ b/go.mod @@ -24,6 +24,9 @@ require ( github.com/klauspost/compress v1.13.6 // indirect github.com/kr/pretty v0.1.0 // indirect github.com/pierrec/lz4/v4 v4.1.9 // indirect + github.com/go-sql-driver/mysql v1.6.0 // indirect + github.com/google/go-cmp v0.5.7 // indirect + github.com/mattn/go-sqlite3 v1.14.12 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect golang.org/x/exp v0.0.0-20211028214138-64b4c8e87d1a // indirect golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f // indirect diff --git a/go.sum b/go.sum index 4a428ee19e7..2bd2eedee8f 100644 --- a/go.sum +++ b/go.sum @@ -235,6 +235,8 @@ github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzE github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= +github.com/mattn/go-sqlite3 v1.14.12 h1:TJ1bhYJPV44phC+IMu1u2K/i5RriLTPe+yc68XDJ1Z0= +github.com/mattn/go-sqlite3 v1.14.12/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= diff --git a/go/internal/feast/foo.db b/go/internal/feast/foo.db new file mode 100644 index 00000000000..e69de29bb2d diff --git a/go/internal/feast/sqliteonlinestore.go b/go/internal/feast/sqliteonlinestore.go index e37006e93f1..49723cd47f9 100644 --- a/go/internal/feast/sqliteonlinestore.go +++ b/go/internal/feast/sqliteonlinestore.go @@ -1,10 +1,14 @@ package feast import ( + "database/sql" + "errors" + "context" "fmt" "github.com/feast-dev/feast/go/protos/feast/types" + _ "github.com/mattn/go-sqlite3" ) type SqliteOnlineStore struct { @@ -13,6 +17,7 @@ type SqliteOnlineStore struct { project string path string + db *sql.DB } func NewSqliteOnlineStore(project string, onlineStoreConfig map[string]interface{}) (*SqliteOnlineStore, error) { @@ -23,25 +28,72 @@ func NewSqliteOnlineStore(project string, onlineStoreConfig map[string]interface return nil, fmt.Errorf("cannot find convert sqlite path to string %s", db_path) } else { store.path = dbPathStr + db, err := initializeConnection(dbPathStr) + if err != nil { + return nil, err + } + store.db = db } - return &SqliteOnlineStore{ - project: project, - }, nil + return &store, nil } -func (*SqliteOnlineStore) Destruct() { - +func (s *SqliteOnlineStore) Destruct() { + s.db.Close() } -func (*SqliteOnlineStore) OnlineRead(ctx context.Context, entityKeys []types.EntityKey, featureViewNames []string, featureNames []string) ([][]FeatureData, error) { +func (s *SqliteOnlineStore) OnlineRead(ctx context.Context, entityKeys []types.EntityKey, featureViewNames []string, featureNames []string) ([][]FeatureData, error) { + _, err := s.getConnection() + if err != nil { + return nil, err + } return nil, nil } // feature views, entities, dataframe? -func (*SqliteOnlineStore) WriteToOnlineStore(ctx context.Context, featureViewName string, data [][]FeatureData) error { +func (s *SqliteOnlineStore) WriteToOnlineStore(ctx context.Context, featureViewName string, data [][]FeatureData) error { + return nil +} + +func (s *SqliteOnlineStore) Update(ctx context.Context, config *RepoConfig, tables_to_delete []*FeatureView, tables_to_keep []*FeatureView) error { + _, err := s.getConnection() + if err != nil { + return err + } + project := config.Project + for _, table := range tables_to_keep { + s.db.Exec( + fmt.Sprintf("CREATE TABLE IF NOT EXISTS %s (entity_key BLOB, feature_name TEXT, value BLOB, event_ts timestamp, created_ts timestamp, PRIMARY KEY(entity_key, feature_name))", tableId(project, table))) + s.db.Exec( + fmt.Sprintf("CREATE INDEX IF NOT EXISTS %s_ek ON %s (entity_key);", tableId(project, table), tableId(project, table))) + } + for _, table := range tables_to_delete { + s.db.Exec("DROP TABLE IF EXISTS %s", tableId(project, table)) + } return nil } -func (*SqliteOnlineStore) initializeConnection(db_path string) { +func (s *SqliteOnlineStore) getConnection() (*sql.DB, error) { + if s.db == nil { + if s.path == "" { + return nil, errors.New("no database path available") + } + db, err := initializeConnection(s.path) + s.db = db + if err != nil { + return nil, err + } + } + return s.db, nil +} +func tableId(project string, table *FeatureView) string { + return fmt.Sprintf("%s_%s", project, table.base.name) +} + +func initializeConnection(db_path string) (*sql.DB, error) { + db, err := sql.Open("sqlite3", "./foo.db") + if err != nil { + return nil, err + } + return db, nil } diff --git a/go/internal/feast/sqliteonlinestore_test.go b/go/internal/feast/sqliteonlinestore_test.go new file mode 100644 index 00000000000..b63ede2950a --- /dev/null +++ b/go/internal/feast/sqliteonlinestore_test.go @@ -0,0 +1,54 @@ +package feast + +import ( + "fmt" + "log" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestSqliteSetup(t *testing.T) { + dir := "../test/selected_tetra" + config, err := NewRepoConfigFromFile(dir) + assert.Nil(t, err) + assert.Equal(t, "selected_tetra", config.Project) + assert.Equal(t, "data/registry.db", config.GetRegistryConfig().Path) + assert.Equal(t, "local", config.Provider) + assert.Equal(t, map[string]interface{}{ + "path": "data/online_store.db", + }, config.OnlineStore) + assert.Empty(t, config.OfflineStore) + assert.Empty(t, config.FeatureServer) + assert.Empty(t, config.Flags) +} + +func TestSqliteUpdate(t *testing.T) { + dir := "../test/selected_tetra" + config, err := NewRepoConfigFromFile(dir) + assert.Nil(t, err) + store, err := NewSqliteOnlineStore("test", config.OnlineStore) + defer store.Destruct() + assert.Nil(t, err) + show_tables := `SELECT + * + FROM + selected_tetra_driver_hourly_stats;` + rows, err := store.db.Query(show_tables) + if err != nil { + log.Fatal(err) + } + defer rows.Close() + for rows.Next() { + fmt.Println("in") + var id int + var name string + err = rows.Scan(&id, &name) + if err != nil { + log.Fatal(err) + } + fmt.Println(id, name) + } + assert.False(t, true) + +} diff --git a/go/internal/test/selected_tetra/__init__.py b/go/internal/test/selected_tetra/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/go/internal/test/selected_tetra/data/driver_stats.parquet b/go/internal/test/selected_tetra/data/driver_stats.parquet new file mode 100644 index 0000000000000000000000000000000000000000..7f7fcf3e547a21ab25f2c104cdfcb2661b00dff3 GIT binary patch literal 34740 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 WNmA?h9{u}wk#+b2-;3{K@_zs%R>h0} literal 0 HcmV?d00001 diff --git a/go/internal/test/selected_tetra/data/online_store.db b/go/internal/test/selected_tetra/data/online_store.db new file mode 100644 index 0000000000000000000000000000000000000000..0f62a3cb8e4e81e77ab1d587ceb5b0ea962e4807 GIT binary patch literal 16384 zcmeI%O=uHA6ae7e>?UcG?2L+7L~92Gwb<%5u?MScZEe6}e@rdZ6z#eu(;Aw#y4ggF z2T?>&C{zT&yW-u8qTtPgM+HF`YwfMR-e>G&@J;pr z2MB-w2!H?xfB*=900@8p2!Oz92wd8$gqy}v@};rdY?huc&^*mJG;2E4u`M%e=StKz zPtO(Y{JdFkET>@7>H3S*gCohF(In{~>PsFY_1CkcWO(Qw7Lpxw*2y{Z<}{sey{yRL zrc_G4eAcl}Cf-) zY@YS!rd4$2%FoUEmuz;_U*q=n4esrxu@grYBzy<=qmO6-#kCh&N!t{A95Z8T^iK3p zLhKXP)TxY(w1`q?=4MNOa&O(Z-(T*Dn1JP@2g_&G*WS}n0ZTc5g#`ug`rG<= zM8M*vUSV0neciscJ<%v&`Rc*)D(EYJxde^bRE5Ai{JnO z5C8!X009sH0T2KI5C8!XSXF^`)z~Tj>--CEQ;lZ9>gA3TZC8yJ{<6Q_qsA}pF=dMJ zi&xK#qWt1D^PdR6xW_D~kzc%eGSk2>UNdD0^NV{-OhWwP)$@-azj)0oL**Cum`f=9 z;?+}z09)45=y@a6Obp8XKkAmyOY{KUMi Date: Thu, 24 Mar 2022 12:27:27 -0700 Subject: [PATCH 03/43] Add sqlite online store for go for testing Signed-off-by: Kevin Zhang --- go/internal/feast/onlinestore.go | 3 +- go/internal/feast/sqliteonlinestore.go | 96 ++++++++++++++---- go/internal/feast/sqliteonlinestore_test.go | 69 ++++++++----- .../__init__.py | 0 .../feature_repo/data/driver_stats.parquet | Bin 0 -> 34696 bytes .../data/online_store.db | Bin 16384 -> 16384 bytes .../example.py | 2 +- .../feature_store.yaml | 2 +- .../selected_tetra/data/driver_stats.parquet | Bin 34740 -> 0 bytes 9 files changed, 126 insertions(+), 46 deletions(-) rename go/internal/test/{selected_tetra => feature_repo}/__init__.py (100%) create mode 100644 go/internal/test/feature_repo/data/driver_stats.parquet rename go/internal/test/{selected_tetra => feature_repo}/data/online_store.db (87%) rename go/internal/test/{selected_tetra => feature_repo}/example.py (96%) rename go/internal/test/{selected_tetra => feature_repo}/feature_store.yaml (78%) delete mode 100644 go/internal/test/selected_tetra/data/driver_stats.parquet diff --git a/go/internal/feast/onlinestore.go b/go/internal/feast/onlinestore.go index 7914b3ec52f..fa41fa01afb 100644 --- a/go/internal/feast/onlinestore.go +++ b/go/internal/feast/onlinestore.go @@ -59,7 +59,8 @@ func NewOnlineStore(config *RepoConfig) (OnlineStore, error) { return onlineStore, err } if onlineStoreType == "sqlite" { - onlineStore, err := NewSqliteOnlineStore(config.Project, config.OnlineStore) + print("ASFDSASF!!") + onlineStore, err := NewSqliteOnlineStore(config.Project, config, config.OnlineStore) return onlineStore, err } else { return nil, fmt.Errorf("%s online store type is currently not supported; only Redis is supported", onlineStoreType) diff --git a/go/internal/feast/sqliteonlinestore.go b/go/internal/feast/sqliteonlinestore.go index 49723cd47f9..978e22ee17b 100644 --- a/go/internal/feast/sqliteonlinestore.go +++ b/go/internal/feast/sqliteonlinestore.go @@ -3,32 +3,37 @@ package feast import ( "database/sql" "errors" + "log" + "strings" + "time" "context" "fmt" + "github.com/feast-dev/feast/go/protos/feast/serving" "github.com/feast-dev/feast/go/protos/feast/types" _ "github.com/mattn/go-sqlite3" + "google.golang.org/protobuf/proto" + timestamppb "google.golang.org/protobuf/types/known/timestamppb" ) type SqliteOnlineStore struct { // Feast project name - // TODO (woop): Should we remove project as state that is tracked at the store level? project string - - path string - db *sql.DB + path string + db *sql.DB } -func NewSqliteOnlineStore(project string, onlineStoreConfig map[string]interface{}) (*SqliteOnlineStore, error) { +// Creates a new sqlite online store object. onlineStoreConfig should have relative path of database file with respect to repoConfig.repoPath. +func NewSqliteOnlineStore(project string, repoConfig *RepoConfig, onlineStoreConfig map[string]interface{}) (*SqliteOnlineStore, error) { store := SqliteOnlineStore{project: project} if db_path, ok := onlineStoreConfig["path"]; !ok { return nil, fmt.Errorf("cannot find sqlite path %s", db_path) } else if dbPathStr, ok := db_path.(string); !ok { return nil, fmt.Errorf("cannot find convert sqlite path to string %s", db_path) } else { - store.path = dbPathStr - db, err := initializeConnection(dbPathStr) + store.path = fmt.Sprintf("%s/%s", repoConfig.RepoPath, dbPathStr) + db, err := initializeConnection(store.path) if err != nil { return nil, err } @@ -41,17 +46,69 @@ func (s *SqliteOnlineStore) Destruct() { s.db.Close() } +// Returns FeatureData 2D array. Each row corresponds to one entity value and each column corresponds to a single feature where the number of columns should be +// same length as the length of featureNames. Reads from every table in featureViewNames with the entity keys described. func (s *SqliteOnlineStore) OnlineRead(ctx context.Context, entityKeys []types.EntityKey, featureViewNames []string, featureNames []string) ([][]FeatureData, error) { + featureCount := len(featureNames) _, err := s.getConnection() if err != nil { return nil, err } - return nil, nil -} -// feature views, entities, dataframe? -func (s *SqliteOnlineStore) WriteToOnlineStore(ctx context.Context, featureViewName string, data [][]FeatureData) error { - return nil + project := s.project + results := make([][]FeatureData, len(entityKeys)) + entityNameToEntityIndex := make(map[string]int) + in_query := make([]string, len(entityKeys)) + serialized_entities := make([]interface{}, len(entityKeys)) + for i := 0; i < len(entityKeys); i++ { + serKey, err := serializeEntityKey(&entityKeys[i]) + if err != nil { + return nil, err + } + // TODO: fix this, string conversion is not safe + entityNameToEntityIndex[string(*serKey)] = i + // for IN clause in read query + in_query[i] = "?" + serialized_entities[i] = *serKey + } + featureNamesToIdx := make(map[string]int) + for idx, name := range featureNames { + featureNamesToIdx[name] = idx + } + + for idx := 0; idx < len(entityKeys); idx++ { + results[idx] = make([]FeatureData, featureCount) + } + for _, featureViewName := range featureViewNames { + query_string := fmt.Sprintf(`SELECT entity_key, feature_name, value, event_ts + FROM %s + WHERE entity_key IN (%s) + ORDER BY entity_key`, tableId(project, featureViewName), strings.Join(in_query, ",")) + rows, err := s.db.Query(query_string, serialized_entities...) + if err != nil { + return nil, err + } + defer rows.Close() + for rows.Next() { + var entity_key string + var feature_name string + var valueString string + var event_ts time.Time + var value types.Value + err = rows.Scan(&entity_key, &feature_name, &valueString, &event_ts) + if err != nil { + log.Fatal(err) + } + if err := proto.Unmarshal([]byte(valueString), &value); err != nil { + return nil, errors.New("error converting parsed value to types.Value") + } + results[entityNameToEntityIndex[entity_key]][featureNamesToIdx[feature_name]] = FeatureData{reference: serving.FeatureReferenceV2{FeatureViewName: featureViewName, FeatureName: feature_name}, + timestamp: *timestamppb.New(event_ts), + value: types.Value{Val: value.Val}, + } + } + } + return results, nil } func (s *SqliteOnlineStore) Update(ctx context.Context, config *RepoConfig, tables_to_delete []*FeatureView, tables_to_keep []*FeatureView) error { @@ -62,16 +119,17 @@ func (s *SqliteOnlineStore) Update(ctx context.Context, config *RepoConfig, tabl project := config.Project for _, table := range tables_to_keep { s.db.Exec( - fmt.Sprintf("CREATE TABLE IF NOT EXISTS %s (entity_key BLOB, feature_name TEXT, value BLOB, event_ts timestamp, created_ts timestamp, PRIMARY KEY(entity_key, feature_name))", tableId(project, table))) + fmt.Sprintf("CREATE TABLE IF NOT EXISTS %s (entity_key BLOB, feature_name TEXT, value BLOB, event_ts timestamp, created_ts timestamp, PRIMARY KEY(entity_key, feature_name))", tableId(project, table.base.name))) s.db.Exec( - fmt.Sprintf("CREATE INDEX IF NOT EXISTS %s_ek ON %s (entity_key);", tableId(project, table), tableId(project, table))) + fmt.Sprintf("CREATE INDEX IF NOT EXISTS %s_ek ON %s (entity_key);", tableId(project, table.base.name), tableId(project, table.base.name))) } for _, table := range tables_to_delete { - s.db.Exec("DROP TABLE IF EXISTS %s", tableId(project, table)) + s.db.Exec("DROP TABLE IF EXISTS %s", tableId(project, table.base.name)) } return nil } +// Gets a sqlite connection and sets it to the online store and also returns a pointer to the connection. func (s *SqliteOnlineStore) getConnection() (*sql.DB, error) { if s.db == nil { if s.path == "" { @@ -86,12 +144,14 @@ func (s *SqliteOnlineStore) getConnection() (*sql.DB, error) { return s.db, nil } -func tableId(project string, table *FeatureView) string { - return fmt.Sprintf("%s_%s", project, table.base.name) +// Constructs the table id from the project and table(featureViewName) string. +func tableId(project string, featureViewName string) string { + return fmt.Sprintf("%s_%s", project, featureViewName) } +// Creates a connection to the sqlite database and returns the connection. func initializeConnection(db_path string) (*sql.DB, error) { - db, err := sql.Open("sqlite3", "./foo.db") + db, err := sql.Open("sqlite3", db_path) if err != nil { return nil, err } diff --git a/go/internal/feast/sqliteonlinestore_test.go b/go/internal/feast/sqliteonlinestore_test.go index b63ede2950a..cce65f0e048 100644 --- a/go/internal/feast/sqliteonlinestore_test.go +++ b/go/internal/feast/sqliteonlinestore_test.go @@ -1,18 +1,19 @@ package feast import ( - "fmt" - "log" + "context" + "reflect" "testing" + "github.com/feast-dev/feast/go/protos/feast/types" "github.com/stretchr/testify/assert" ) func TestSqliteSetup(t *testing.T) { - dir := "../test/selected_tetra" + dir := "../test/feature_repo" config, err := NewRepoConfigFromFile(dir) assert.Nil(t, err) - assert.Equal(t, "selected_tetra", config.Project) + assert.Equal(t, "feature_repo", config.Project) assert.Equal(t, "data/registry.db", config.GetRegistryConfig().Path) assert.Equal(t, "local", config.Provider) assert.Equal(t, map[string]interface{}{ @@ -23,32 +24,50 @@ func TestSqliteSetup(t *testing.T) { assert.Empty(t, config.Flags) } -func TestSqliteUpdate(t *testing.T) { - dir := "../test/selected_tetra" +func TestSqliteOnlineRead(t *testing.T) { + dir := "../test/feature_repo" config, err := NewRepoConfigFromFile(dir) assert.Nil(t, err) - store, err := NewSqliteOnlineStore("test", config.OnlineStore) + store, err := NewSqliteOnlineStore("feature_repo", config, config.OnlineStore) defer store.Destruct() assert.Nil(t, err) - show_tables := `SELECT - * - FROM - selected_tetra_driver_hourly_stats;` - rows, err := store.db.Query(show_tables) - if err != nil { - log.Fatal(err) + entity_key1 := types.EntityKey{ + JoinKeys: []string{"driver_id"}, + EntityValues: []*types.Value{{Val: &types.Value_Int64Val{1005}}}, } - defer rows.Close() - for rows.Next() { - fmt.Println("in") - var id int - var name string - err = rows.Scan(&id, &name) - if err != nil { - log.Fatal(err) + entity_key2 := types.EntityKey{ + JoinKeys: []string{"driver_id"}, + EntityValues: []*types.Value{{Val: &types.Value_Int64Val{1001}}}, + } + entity_key3 := types.EntityKey{ + JoinKeys: []string{"driver_id"}, + EntityValues: []*types.Value{{Val: &types.Value_Int64Val{1003}}}, + } + entityKeys := []types.EntityKey{entity_key1, entity_key2, entity_key3} + tableNames := []string{"driver_hourly_stats"} + featureNames := []string{"conv_rate", "acc_rate", "avg_daily_trips"} + featureData, err := store.OnlineRead(context.Background(), entityKeys, tableNames, featureNames) + assert.Nil(t, err) + returnedFeatureValues := make([]types.Value, 0) + returnedFeatureNames := make([]string, 0) + for _, featureVector := range featureData { + for _, feature := range featureVector { + returnedFeatureValues = append(returnedFeatureValues, feature.value) + returnedFeatureNames = append(returnedFeatureNames, feature.reference.FeatureName) } - fmt.Println(id, name) } - assert.False(t, true) - + expectedFeatureValues := []types.Value{ + {Val: &types.Value_FloatVal{0.78135854}}, + {Val: &types.Value_FloatVal{0.38527268}}, + {Val: &types.Value_Int64Val{755}}, + {Val: &types.Value_FloatVal{0.49661186}}, + {Val: &types.Value_FloatVal{0.9440974}}, + {Val: &types.Value_Int64Val{169}}, + {Val: &types.Value_FloatVal{0.80762655}}, + {Val: &types.Value_FloatVal{0.71510273}}, + {Val: &types.Value_Int64Val{545}}, + } + expectedFeatureNames := []string{"conv_rate", "acc_rate", "avg_daily_trips", "conv_rate", "acc_rate", "avg_daily_trips", "conv_rate", "acc_rate", "avg_daily_trips"} + assert.True(t, reflect.DeepEqual(expectedFeatureValues, returnedFeatureValues)) + assert.True(t, reflect.DeepEqual(expectedFeatureNames, returnedFeatureNames)) } diff --git a/go/internal/test/selected_tetra/__init__.py b/go/internal/test/feature_repo/__init__.py similarity index 100% rename from go/internal/test/selected_tetra/__init__.py rename to go/internal/test/feature_repo/__init__.py diff --git a/go/internal/test/feature_repo/data/driver_stats.parquet b/go/internal/test/feature_repo/data/driver_stats.parquet new file mode 100644 index 0000000000000000000000000000000000000000..ee4f58fdbc85a8cd9999687aa218155c4a47bbe1 GIT binary patch literal 34696 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 literal 16384 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 WNmA?h9{u}wk#+b2-;3{K@_zs%R>h0} From 01d9a1d53bd5116901297242272e3b57ce9b932e Mon Sep 17 00:00:00 2001 From: Kevin Zhang Date: Thu, 24 Mar 2022 12:30:20 -0700 Subject: [PATCH 04/43] Revert Signed-off-by: Kevin Zhang --- .../feature_repos/repo_configuration.py | 51 ++++++++++--------- 1 file changed, 27 insertions(+), 24 deletions(-) diff --git a/sdk/python/tests/integration/feature_repos/repo_configuration.py b/sdk/python/tests/integration/feature_repos/repo_configuration.py index bc4089d71a0..f37e1107de8 100644 --- a/sdk/python/tests/integration/feature_repos/repo_configuration.py +++ b/sdk/python/tests/integration/feature_repos/repo_configuration.py @@ -50,6 +50,7 @@ # Port 12345 will chosen as default for redis node configuration because Redis Cluster is started off of nodes # 6379 -> 6384. This causes conflicts in cli integration tests so we manually keep them separate. REDIS_CONFIG = {"type": "redis", "connection_string": "localhost:6379,db=0"} +SQLITE_CONFIG = {"type": "sqlite", "path": "data/online_store.db"} REDIS_CLUSTER_CONFIG = { "type": "redis", "redis_type": "redis_cluster", @@ -74,29 +75,29 @@ [ IntegrationTestRepoConfig(online_store=REDIS_CONFIG), # GCP configurations - # IntegrationTestRepoConfig( - # provider="gcp", - # offline_store_creator=BigQueryDataSourceCreator, - # online_store="datastore", - # ), - # IntegrationTestRepoConfig( - # provider="gcp", - # offline_store_creator=BigQueryDataSourceCreator, - # online_store=REDIS_CONFIG, - # ), - # AWS configurations - # IntegrationTestRepoConfig( - # provider="aws", - # offline_store_creator=RedshiftDataSourceCreator, - # online_store=DYNAMO_CONFIG, - # python_feature_server=True, - # ), - # IntegrationTestRepoConfig( - # provider="aws", - # offline_store_creator=RedshiftDataSourceCreator, - # online_store=REDIS_CONFIG, - # ), - # Snowflake configurations + IntegrationTestRepoConfig( + provider="gcp", + offline_store_creator=BigQueryDataSourceCreator, + online_store="datastore", + ), + IntegrationTestRepoConfig( + provider="gcp", + offline_store_creator=BigQueryDataSourceCreator, + online_store=REDIS_CONFIG, + ), + AWS configurations + IntegrationTestRepoConfig( + provider="aws", + offline_store_creator=RedshiftDataSourceCreator, + online_store=DYNAMO_CONFIG, + python_feature_server=True, + ), + IntegrationTestRepoConfig( + provider="aws", + offline_store_creator=RedshiftDataSourceCreator, + online_store=REDIS_CONFIG, + ), + Snowflake configurations IntegrationTestRepoConfig( provider="aws", # no list features, no feature server offline_store_creator=SnowflakeDataSourceCreator, @@ -117,7 +118,9 @@ FULL_REPO_CONFIGS = DEFAULT_FULL_REPO_CONFIGS GO_REPO_CONFIGS = [ - IntegrationTestRepoConfig(online_store=REDIS_CONFIG, go_feature_server=True,), + #IntegrationTestRepoConfig(online_store=REDIS_CONFIG, go_feature_server=True,), + IntegrationTestRepoConfig(online_store=SQLITE_CONFIG, go_feature_server=True,), + ] From 2c4fccd7dacda4098181b228d88934595380100d Mon Sep 17 00:00:00 2001 From: Kevin Zhang Date: Thu, 24 Mar 2022 12:31:05 -0700 Subject: [PATCH 05/43] Revert Signed-off-by: Kevin Zhang --- .../integration/feature_repos/repo_configuration.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/sdk/python/tests/integration/feature_repos/repo_configuration.py b/sdk/python/tests/integration/feature_repos/repo_configuration.py index f37e1107de8..9cbe11f86b9 100644 --- a/sdk/python/tests/integration/feature_repos/repo_configuration.py +++ b/sdk/python/tests/integration/feature_repos/repo_configuration.py @@ -50,7 +50,6 @@ # Port 12345 will chosen as default for redis node configuration because Redis Cluster is started off of nodes # 6379 -> 6384. This causes conflicts in cli integration tests so we manually keep them separate. REDIS_CONFIG = {"type": "redis", "connection_string": "localhost:6379,db=0"} -SQLITE_CONFIG = {"type": "sqlite", "path": "data/online_store.db"} REDIS_CLUSTER_CONFIG = { "type": "redis", "redis_type": "redis_cluster", @@ -85,7 +84,7 @@ offline_store_creator=BigQueryDataSourceCreator, online_store=REDIS_CONFIG, ), - AWS configurations + # AWS configurations IntegrationTestRepoConfig( provider="aws", offline_store_creator=RedshiftDataSourceCreator, @@ -97,7 +96,7 @@ offline_store_creator=RedshiftDataSourceCreator, online_store=REDIS_CONFIG, ), - Snowflake configurations + # Snowflake configurations IntegrationTestRepoConfig( provider="aws", # no list features, no feature server offline_store_creator=SnowflakeDataSourceCreator, @@ -118,9 +117,7 @@ FULL_REPO_CONFIGS = DEFAULT_FULL_REPO_CONFIGS GO_REPO_CONFIGS = [ - #IntegrationTestRepoConfig(online_store=REDIS_CONFIG, go_feature_server=True,), - IntegrationTestRepoConfig(online_store=SQLITE_CONFIG, go_feature_server=True,), - + IntegrationTestRepoConfig(online_store=REDIS_CONFIG, go_feature_server=True,), ] From 34dcd15133e50a9f0212d2d609b0f40c699ab964 Mon Sep 17 00:00:00 2001 From: Kevin Zhang Date: Thu, 24 Mar 2022 12:39:43 -0700 Subject: [PATCH 06/43] Clean up Signed-off-by: Kevin Zhang --- go/internal/feast/foo.db | 0 go/internal/feast/onlinestore.go | 1 - sdk/python/feast/templates/local/example.py | 2 +- .../tests/integration/online_store/test_universal_online.py | 4 ++++ 4 files changed, 5 insertions(+), 2 deletions(-) delete mode 100644 go/internal/feast/foo.db diff --git a/go/internal/feast/foo.db b/go/internal/feast/foo.db deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/go/internal/feast/onlinestore.go b/go/internal/feast/onlinestore.go index fa41fa01afb..c96164a71f0 100644 --- a/go/internal/feast/onlinestore.go +++ b/go/internal/feast/onlinestore.go @@ -59,7 +59,6 @@ func NewOnlineStore(config *RepoConfig) (OnlineStore, error) { return onlineStore, err } if onlineStoreType == "sqlite" { - print("ASFDSASF!!") onlineStore, err := NewSqliteOnlineStore(config.Project, config, config.OnlineStore) return onlineStore, err } else { diff --git a/sdk/python/feast/templates/local/example.py b/sdk/python/feast/templates/local/example.py index 6ab549b8c5b..76210d79694 100644 --- a/sdk/python/feast/templates/local/example.py +++ b/sdk/python/feast/templates/local/example.py @@ -8,7 +8,7 @@ # production, you can use your favorite DWH, such as BigQuery. See Feast documentation # for more info. driver_hourly_stats = FileSource( - path="%PARQUET_PATH%", + path="data/driver_stats.parquet", event_timestamp_column="event_timestamp", created_timestamp_column="created", ) diff --git a/sdk/python/tests/integration/online_store/test_universal_online.py b/sdk/python/tests/integration/online_store/test_universal_online.py index e146379cf1e..23c4688b960 100644 --- a/sdk/python/tests/integration/online_store/test_universal_online.py +++ b/sdk/python/tests/integration/online_store/test_universal_online.py @@ -714,6 +714,10 @@ def eventually_apply() -> Tuple[None, bool]: assert all(v is None for v in online_features["value"]) +<<<<<<< HEAD +======= +#@pytest.mark.skip +>>>>>>> e82ef9c7 (Clean up) @pytest.mark.integration @pytest.mark.goserver @pytest.mark.parametrize("full_feature_names", [True, False], ids=lambda v: str(v)) From 80f45792e291662c52c12c11f19e3f8105499950 Mon Sep 17 00:00:00 2001 From: Kevin Zhang Date: Thu, 24 Mar 2022 14:12:39 -0700 Subject: [PATCH 07/43] Address review issues Signed-off-by: Kevin Zhang --- go/internal/feast/sqliteonlinestore.go | 42 +++++++------------ .../online_store/test_universal_online.py | 5 +-- 2 files changed, 16 insertions(+), 31 deletions(-) diff --git a/go/internal/feast/sqliteonlinestore.go b/go/internal/feast/sqliteonlinestore.go index 978e22ee17b..e7a72e13864 100644 --- a/go/internal/feast/sqliteonlinestore.go +++ b/go/internal/feast/sqliteonlinestore.go @@ -3,8 +3,8 @@ package feast import ( "database/sql" "errors" - "log" "strings" + "sync" "time" "context" @@ -22,6 +22,7 @@ type SqliteOnlineStore struct { project string path string db *sql.DB + db_mu sync.Mutex } // Creates a new sqlite online store object. onlineStoreConfig should have relative path of database file with respect to repoConfig.repoPath. @@ -29,16 +30,19 @@ func NewSqliteOnlineStore(project string, repoConfig *RepoConfig, onlineStoreCon store := SqliteOnlineStore{project: project} if db_path, ok := onlineStoreConfig["path"]; !ok { return nil, fmt.Errorf("cannot find sqlite path %s", db_path) - } else if dbPathStr, ok := db_path.(string); !ok { - return nil, fmt.Errorf("cannot find convert sqlite path to string %s", db_path) } else { - store.path = fmt.Sprintf("%s/%s", repoConfig.RepoPath, dbPathStr) - db, err := initializeConnection(store.path) - if err != nil { - return nil, err + if dbPathStr, ok := db_path.(string); !ok { + return nil, fmt.Errorf("cannot find convert sqlite path to string %s", db_path) + } else { + store.path = fmt.Sprintf("%s/%s", repoConfig.RepoPath, dbPathStr) + db, err := initializeConnection(store.path) + if err != nil { + return nil, err + } + store.db = db } - store.db = db } + return &store, nil } @@ -97,7 +101,7 @@ func (s *SqliteOnlineStore) OnlineRead(ctx context.Context, entityKeys []types.E var value types.Value err = rows.Scan(&entity_key, &feature_name, &valueString, &event_ts) if err != nil { - log.Fatal(err) + return nil, errors.New("error could not resolve row in query (entity key, feature name, value, event ts)") } if err := proto.Unmarshal([]byte(valueString), &value); err != nil { return nil, errors.New("error converting parsed value to types.Value") @@ -111,26 +115,10 @@ func (s *SqliteOnlineStore) OnlineRead(ctx context.Context, entityKeys []types.E return results, nil } -func (s *SqliteOnlineStore) Update(ctx context.Context, config *RepoConfig, tables_to_delete []*FeatureView, tables_to_keep []*FeatureView) error { - _, err := s.getConnection() - if err != nil { - return err - } - project := config.Project - for _, table := range tables_to_keep { - s.db.Exec( - fmt.Sprintf("CREATE TABLE IF NOT EXISTS %s (entity_key BLOB, feature_name TEXT, value BLOB, event_ts timestamp, created_ts timestamp, PRIMARY KEY(entity_key, feature_name))", tableId(project, table.base.name))) - s.db.Exec( - fmt.Sprintf("CREATE INDEX IF NOT EXISTS %s_ek ON %s (entity_key);", tableId(project, table.base.name), tableId(project, table.base.name))) - } - for _, table := range tables_to_delete { - s.db.Exec("DROP TABLE IF EXISTS %s", tableId(project, table.base.name)) - } - return nil -} - // Gets a sqlite connection and sets it to the online store and also returns a pointer to the connection. func (s *SqliteOnlineStore) getConnection() (*sql.DB, error) { + s.db_mu.Lock() + defer s.db_mu.Unlock() if s.db == nil { if s.path == "" { return nil, errors.New("no database path available") diff --git a/sdk/python/tests/integration/online_store/test_universal_online.py b/sdk/python/tests/integration/online_store/test_universal_online.py index 23c4688b960..a947b467ce9 100644 --- a/sdk/python/tests/integration/online_store/test_universal_online.py +++ b/sdk/python/tests/integration/online_store/test_universal_online.py @@ -714,10 +714,7 @@ def eventually_apply() -> Tuple[None, bool]: assert all(v is None for v in online_features["value"]) -<<<<<<< HEAD -======= -#@pytest.mark.skip ->>>>>>> e82ef9c7 (Clean up) +@pytest.mark.skip @pytest.mark.integration @pytest.mark.goserver @pytest.mark.parametrize("full_feature_names", [True, False], ids=lambda v: str(v)) From 17acc6aec82f0ab9fc29dc432b21e462e797de98 Mon Sep 17 00:00:00 2001 From: Kevin Zhang Date: Fri, 25 Mar 2022 11:30:34 -0700 Subject: [PATCH 08/43] Fix/address issues Signed-off-by: Kevin Zhang --- go.mod | 4 +-- go/internal/feast/featurestore.go | 4 +-- go/internal/feast/onlinestore.go | 2 +- go/internal/feast/redisonlinestore.go | 11 +++--- go/internal/feast/sqliteonlinestore.go | 24 ++++++++----- go/internal/feast/sqliteonlinestore_test.go | 36 +++++++++---------- go/internal/test/feature_repo/example.py | 2 +- sdk/python/feast/templates/local/example.py | 2 +- .../online_store/test_universal_online.py | 1 - 9 files changed, 46 insertions(+), 40 deletions(-) diff --git a/go.mod b/go.mod index 7d4093df57b..a3ce2c3dc0e 100644 --- a/go.mod +++ b/go.mod @@ -9,6 +9,7 @@ require ( github.com/go-redis/redis/v8 v8.11.4 github.com/golang/protobuf v1.5.2 github.com/google/uuid v1.1.2 + github.com/mattn/go-sqlite3 v1.14.12 github.com/spaolacci/murmur3 v1.1.0 github.com/stretchr/testify v1.7.0 google.golang.org/grpc v1.44.0 @@ -24,9 +25,6 @@ require ( github.com/klauspost/compress v1.13.6 // indirect github.com/kr/pretty v0.1.0 // indirect github.com/pierrec/lz4/v4 v4.1.9 // indirect - github.com/go-sql-driver/mysql v1.6.0 // indirect - github.com/google/go-cmp v0.5.7 // indirect - github.com/mattn/go-sqlite3 v1.14.12 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect golang.org/x/exp v0.0.0-20211028214138-64b4c8e87d1a // indirect golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f // indirect diff --git a/go/internal/feast/featurestore.go b/go/internal/feast/featurestore.go index 492c158e52e..4232fa46ead 100644 --- a/go/internal/feast/featurestore.go +++ b/go/internal/feast/featurestore.go @@ -561,9 +561,9 @@ func (fs *FeatureStore) readFromOnlineStore(ctx context.Context, entityRows []*p requestedFeatureNames []string, ) ([][]FeatureData, error) { numRows := len(entityRows) - entityRowsValue := make([]prototypes.EntityKey, numRows) + entityRowsValue := make([]*prototypes.EntityKey, numRows) for index, entityKey := range entityRows { - entityRowsValue[index] = prototypes.EntityKey{JoinKeys: entityKey.JoinKeys, EntityValues: entityKey.EntityValues} + entityRowsValue[index] = &prototypes.EntityKey{JoinKeys: entityKey.JoinKeys, EntityValues: entityKey.EntityValues} } return fs.onlineStore.OnlineRead(ctx, entityRowsValue, requestedFeatureViewNames, requestedFeatureNames) } diff --git a/go/internal/feast/onlinestore.go b/go/internal/feast/onlinestore.go index c96164a71f0..094e23d16be 100644 --- a/go/internal/feast/onlinestore.go +++ b/go/internal/feast/onlinestore.go @@ -34,7 +34,7 @@ type OnlineStore interface { // Feature object as pointers in GetOnlineFeaturesResponse) // => allocate memory for each field once in OnlineRead // and reuse them in GetOnlineFeaturesResponse? - OnlineRead(ctx context.Context, entityKeys []types.EntityKey, featureViewNames []string, featureNames []string) ([][]FeatureData, error) + OnlineRead(ctx context.Context, entityKeys []*types.EntityKey, featureViewNames []string, featureNames []string) ([][]FeatureData, error) // Destruct must be call once user is done using OnlineStore // This is to comply with the Connector since we have to close the plugin Destruct() diff --git a/go/internal/feast/redisonlinestore.go b/go/internal/feast/redisonlinestore.go index c0af52c3c74..7190721496c 100644 --- a/go/internal/feast/redisonlinestore.go +++ b/go/internal/feast/redisonlinestore.go @@ -5,15 +5,16 @@ import ( "encoding/binary" "errors" "fmt" + "sort" + "strconv" + "strings" + "github.com/feast-dev/feast/go/protos/feast/serving" "github.com/feast-dev/feast/go/protos/feast/types" "github.com/go-redis/redis/v8" "github.com/golang/protobuf/proto" "github.com/spaolacci/murmur3" timestamppb "google.golang.org/protobuf/types/known/timestamppb" - "sort" - "strconv" - "strings" ) type redisType int @@ -117,7 +118,7 @@ func getRedisType(onlineStoreConfig map[string]interface{}) (redisType, error) { return t, nil } -func (r *RedisOnlineStore) OnlineRead(ctx context.Context, entityKeys []types.EntityKey, featureViewNames []string, featureNames []string) ([][]FeatureData, error) { +func (r *RedisOnlineStore) OnlineRead(ctx context.Context, entityKeys []*types.EntityKey, featureViewNames []string, featureNames []string) ([][]FeatureData, error) { featureCount := len(featureNames) index := featureCount featureViewIndices := make(map[string]int) @@ -152,7 +153,7 @@ func (r *RedisOnlineStore) OnlineRead(ctx context.Context, entityKeys []types.En redisKeyToEntityIndex := make(map[string]int) for i := 0; i < len(entityKeys); i++ { - var key, err = buildRedisKey(r.project, &entityKeys[i]) + var key, err = buildRedisKey(r.project, entityKeys[i]) if err != nil { return nil, err } diff --git a/go/internal/feast/sqliteonlinestore.go b/go/internal/feast/sqliteonlinestore.go index e7a72e13864..1ad9007b110 100644 --- a/go/internal/feast/sqliteonlinestore.go +++ b/go/internal/feast/sqliteonlinestore.go @@ -1,7 +1,9 @@ package feast import ( + "crypto/sha1" "database/sql" + "encoding/hex" "errors" "strings" "sync" @@ -52,25 +54,24 @@ func (s *SqliteOnlineStore) Destruct() { // Returns FeatureData 2D array. Each row corresponds to one entity value and each column corresponds to a single feature where the number of columns should be // same length as the length of featureNames. Reads from every table in featureViewNames with the entity keys described. -func (s *SqliteOnlineStore) OnlineRead(ctx context.Context, entityKeys []types.EntityKey, featureViewNames []string, featureNames []string) ([][]FeatureData, error) { +func (s *SqliteOnlineStore) OnlineRead(ctx context.Context, entityKeys []*types.EntityKey, featureViewNames []string, featureNames []string) ([][]FeatureData, error) { featureCount := len(featureNames) _, err := s.getConnection() if err != nil { return nil, err } - project := s.project results := make([][]FeatureData, len(entityKeys)) entityNameToEntityIndex := make(map[string]int) in_query := make([]string, len(entityKeys)) serialized_entities := make([]interface{}, len(entityKeys)) for i := 0; i < len(entityKeys); i++ { - serKey, err := serializeEntityKey(&entityKeys[i]) + serKey, err := serializeEntityKey(entityKeys[i]) if err != nil { return nil, err } // TODO: fix this, string conversion is not safe - entityNameToEntityIndex[string(*serKey)] = i + entityNameToEntityIndex[hashSerializedEntityKey(serKey)] = i // for IN clause in read query in_query[i] = "?" serialized_entities[i] = *serKey @@ -94,19 +95,19 @@ func (s *SqliteOnlineStore) OnlineRead(ctx context.Context, entityKeys []types.E } defer rows.Close() for rows.Next() { - var entity_key string + var entity_key []byte var feature_name string - var valueString string + var valueString []byte var event_ts time.Time var value types.Value err = rows.Scan(&entity_key, &feature_name, &valueString, &event_ts) if err != nil { return nil, errors.New("error could not resolve row in query (entity key, feature name, value, event ts)") } - if err := proto.Unmarshal([]byte(valueString), &value); err != nil { + if err := proto.Unmarshal(valueString, &value); err != nil { return nil, errors.New("error converting parsed value to types.Value") } - results[entityNameToEntityIndex[entity_key]][featureNamesToIdx[feature_name]] = FeatureData{reference: serving.FeatureReferenceV2{FeatureViewName: featureViewName, FeatureName: feature_name}, + results[entityNameToEntityIndex[hashSerializedEntityKey(&entity_key)]][featureNamesToIdx[feature_name]] = FeatureData{reference: serving.FeatureReferenceV2{FeatureViewName: featureViewName, FeatureName: feature_name}, timestamp: *timestamppb.New(event_ts), value: types.Value{Val: value.Val}, } @@ -145,3 +146,10 @@ func initializeConnection(db_path string) (*sql.DB, error) { } return db, nil } + +func hashSerializedEntityKey(serializedEntityKey *[]byte) string { + h := sha1.New() + h.Write(*serializedEntityKey) + sha1_hash := hex.EncodeToString(h.Sum(nil)) + return sha1_hash +} diff --git a/go/internal/feast/sqliteonlinestore_test.go b/go/internal/feast/sqliteonlinestore_test.go index cce65f0e048..cbd085eac8c 100644 --- a/go/internal/feast/sqliteonlinestore_test.go +++ b/go/internal/feast/sqliteonlinestore_test.go @@ -33,39 +33,39 @@ func TestSqliteOnlineRead(t *testing.T) { assert.Nil(t, err) entity_key1 := types.EntityKey{ JoinKeys: []string{"driver_id"}, - EntityValues: []*types.Value{{Val: &types.Value_Int64Val{1005}}}, + EntityValues: []*types.Value{{Val: &types.Value_Int64Val{Int64Val: 1005}}}, } entity_key2 := types.EntityKey{ JoinKeys: []string{"driver_id"}, - EntityValues: []*types.Value{{Val: &types.Value_Int64Val{1001}}}, + EntityValues: []*types.Value{{Val: &types.Value_Int64Val{Int64Val: 1001}}}, } entity_key3 := types.EntityKey{ JoinKeys: []string{"driver_id"}, - EntityValues: []*types.Value{{Val: &types.Value_Int64Val{1003}}}, + EntityValues: []*types.Value{{Val: &types.Value_Int64Val{Int64Val: 1003}}}, } - entityKeys := []types.EntityKey{entity_key1, entity_key2, entity_key3} + entityKeys := []*types.EntityKey{&entity_key1, &entity_key2, &entity_key3} tableNames := []string{"driver_hourly_stats"} featureNames := []string{"conv_rate", "acc_rate", "avg_daily_trips"} featureData, err := store.OnlineRead(context.Background(), entityKeys, tableNames, featureNames) assert.Nil(t, err) - returnedFeatureValues := make([]types.Value, 0) + returnedFeatureValues := make([]*types.Value, 0) returnedFeatureNames := make([]string, 0) for _, featureVector := range featureData { - for _, feature := range featureVector { - returnedFeatureValues = append(returnedFeatureValues, feature.value) - returnedFeatureNames = append(returnedFeatureNames, feature.reference.FeatureName) + for idx, _ := range featureVector { + returnedFeatureValues = append(returnedFeatureValues, &featureVector[idx].value) + returnedFeatureNames = append(returnedFeatureNames, featureVector[idx].reference.FeatureName) } } - expectedFeatureValues := []types.Value{ - {Val: &types.Value_FloatVal{0.78135854}}, - {Val: &types.Value_FloatVal{0.38527268}}, - {Val: &types.Value_Int64Val{755}}, - {Val: &types.Value_FloatVal{0.49661186}}, - {Val: &types.Value_FloatVal{0.9440974}}, - {Val: &types.Value_Int64Val{169}}, - {Val: &types.Value_FloatVal{0.80762655}}, - {Val: &types.Value_FloatVal{0.71510273}}, - {Val: &types.Value_Int64Val{545}}, + expectedFeatureValues := []*types.Value{ + {Val: &types.Value_FloatVal{FloatVal: 0.78135854}}, + {Val: &types.Value_FloatVal{FloatVal: 0.38527268}}, + {Val: &types.Value_Int64Val{Int64Val: 755}}, + {Val: &types.Value_FloatVal{FloatVal: 0.49661186}}, + {Val: &types.Value_FloatVal{FloatVal: 0.9440974}}, + {Val: &types.Value_Int64Val{Int64Val: 169}}, + {Val: &types.Value_FloatVal{FloatVal: 0.80762655}}, + {Val: &types.Value_FloatVal{FloatVal: 0.71510273}}, + {Val: &types.Value_Int64Val{Int64Val: 545}}, } expectedFeatureNames := []string{"conv_rate", "acc_rate", "avg_daily_trips", "conv_rate", "acc_rate", "avg_daily_trips", "conv_rate", "acc_rate", "avg_daily_trips"} assert.True(t, reflect.DeepEqual(expectedFeatureValues, returnedFeatureValues)) diff --git a/go/internal/test/feature_repo/example.py b/go/internal/test/feature_repo/example.py index a7ca04e319d..76210d79694 100644 --- a/go/internal/test/feature_repo/example.py +++ b/go/internal/test/feature_repo/example.py @@ -8,7 +8,7 @@ # production, you can use your favorite DWH, such as BigQuery. See Feast documentation # for more info. driver_hourly_stats = FileSource( - path="/Users/kevinzhang/tecton-ai/offline_store/feast/go/internal/test/feature_repo/data/driver_stats.parquet", + path="data/driver_stats.parquet", event_timestamp_column="event_timestamp", created_timestamp_column="created", ) diff --git a/sdk/python/feast/templates/local/example.py b/sdk/python/feast/templates/local/example.py index 76210d79694..6ab549b8c5b 100644 --- a/sdk/python/feast/templates/local/example.py +++ b/sdk/python/feast/templates/local/example.py @@ -8,7 +8,7 @@ # production, you can use your favorite DWH, such as BigQuery. See Feast documentation # for more info. driver_hourly_stats = FileSource( - path="data/driver_stats.parquet", + path="%PARQUET_PATH%", event_timestamp_column="event_timestamp", created_timestamp_column="created", ) diff --git a/sdk/python/tests/integration/online_store/test_universal_online.py b/sdk/python/tests/integration/online_store/test_universal_online.py index a947b467ce9..e146379cf1e 100644 --- a/sdk/python/tests/integration/online_store/test_universal_online.py +++ b/sdk/python/tests/integration/online_store/test_universal_online.py @@ -714,7 +714,6 @@ def eventually_apply() -> Tuple[None, bool]: assert all(v is None for v in online_features["value"]) -@pytest.mark.skip @pytest.mark.integration @pytest.mark.goserver @pytest.mark.parametrize("full_feature_names", [True, False], ids=lambda v: str(v)) From 64fa88be86c96b11808d772f8293903821191e80 Mon Sep 17 00:00:00 2001 From: Kevin Zhang Date: Fri, 25 Mar 2022 11:31:39 -0700 Subject: [PATCH 09/43] lint Signed-off-by: Kevin Zhang --- go/internal/feast/sqliteonlinestore_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go/internal/feast/sqliteonlinestore_test.go b/go/internal/feast/sqliteonlinestore_test.go index cbd085eac8c..34afffd5d7f 100644 --- a/go/internal/feast/sqliteonlinestore_test.go +++ b/go/internal/feast/sqliteonlinestore_test.go @@ -51,7 +51,7 @@ func TestSqliteOnlineRead(t *testing.T) { returnedFeatureValues := make([]*types.Value, 0) returnedFeatureNames := make([]string, 0) for _, featureVector := range featureData { - for idx, _ := range featureVector { + for idx := range featureVector { returnedFeatureValues = append(returnedFeatureValues, &featureVector[idx].value) returnedFeatureNames = append(returnedFeatureNames, featureVector[idx].reference.FeatureName) } From 0601f0af472658457031a7c2039b9d211beb28e6 Mon Sep 17 00:00:00 2001 From: Kevin Zhang Date: Fri, 25 Mar 2022 12:15:04 -0700 Subject: [PATCH 10/43] Fix Signed-off-by: Kevin Zhang --- go/cmd/server/server_test.go | 33 +++++++++++-------- go/internal/feast/onlinestore.go | 2 +- .../test/feature_repo/feature_store.yaml | 1 + 3 files changed, 21 insertions(+), 15 deletions(-) diff --git a/go/cmd/server/server_test.go b/go/cmd/server/server_test.go index 990fc79c72e..e31fbcefc0a 100644 --- a/go/cmd/server/server_test.go +++ b/go/cmd/server/server_test.go @@ -2,33 +2,38 @@ package main import ( "context" + "net" + "path/filepath" + "runtime" + "testing" + "github.com/feast-dev/feast/go/internal/feast" "github.com/feast-dev/feast/go/protos/feast/serving" "github.com/stretchr/testify/assert" "google.golang.org/grpc" "google.golang.org/grpc/test/bufconn" - "net" - "path/filepath" - "runtime" - "testing" ) // Return absolute path to the test_repo directory regardless of the working directory -func getRepoPath() string { +func getRepoPath(basePath string) string { // Get the file path of this source file, regardless of the working directory - _, filename, _, ok := runtime.Caller(0) - if !ok { - panic("couldn't find file path of the test file") + if basePath == "" { + _, filename, _, ok := runtime.Caller(0) + if !ok { + panic("couldn't find file path of the test file") + } + return filepath.Join(filename, "..", "..", "feature_repo") + } else { + return filepath.Join(basePath, "feature_repo") } - return filepath.Join(filename, "..", "..", "feature_repo") } -func getClient(ctx context.Context) (serving.ServingServiceClient, func()) { +func getClient(ctx context.Context, basePath string) (serving.ServingServiceClient, func()) { buffer := 1024 * 1024 listener := bufconn.Listen(buffer) server := grpc.NewServer() - config, err := feast.NewRepoConfigFromFile(getRepoPath()) + config, err := feast.NewRepoConfigFromFile(getRepoPath(basePath)) if err != nil { panic(err) } @@ -60,7 +65,7 @@ func getClient(ctx context.Context) (serving.ServingServiceClient, func()) { func TestGetFeastServingInfo(t *testing.T) { t.Skip("@todo(achals): feature_repo isn't checked in yet") ctx := context.Background() - client, closer := getClient(ctx) + client, closer := getClient(ctx, "") defer closer() response, err := client.GetFeastServingInfo(ctx, &serving.GetFeastServingInfoRequest{}) assert.Nil(t, err) @@ -68,9 +73,9 @@ func TestGetFeastServingInfo(t *testing.T) { } func TestGetOnlineFeatures(t *testing.T) { - t.Skip("@todo(achals): feature_repo isn't checked in yet") + //t.Skip("@todo(achals): feature_repo isn't checked in yet") ctx := context.Background() - client, closer := getClient(ctx) + client, closer := getClient(ctx, "../../internal/test") defer closer() response, err := client.GetOnlineFeatures(ctx, &serving.GetOnlineFeaturesRequest{}) assert.Nil(t, err) diff --git a/go/internal/feast/onlinestore.go b/go/internal/feast/onlinestore.go index 094e23d16be..961125d14fd 100644 --- a/go/internal/feast/onlinestore.go +++ b/go/internal/feast/onlinestore.go @@ -62,6 +62,6 @@ func NewOnlineStore(config *RepoConfig) (OnlineStore, error) { onlineStore, err := NewSqliteOnlineStore(config.Project, config, config.OnlineStore) return onlineStore, err } else { - return nil, fmt.Errorf("%s online store type is currently not supported; only Redis is supported", onlineStoreType) + return nil, fmt.Errorf("%s online store type is currently not supported; only redis and sqlite are supported", onlineStoreType) } } diff --git a/go/internal/test/feature_repo/feature_store.yaml b/go/internal/test/feature_repo/feature_store.yaml index 3b48f432875..70bb90e86b1 100644 --- a/go/internal/test/feature_repo/feature_store.yaml +++ b/go/internal/test/feature_repo/feature_store.yaml @@ -2,4 +2,5 @@ project: feature_repo registry: data/registry.db provider: local online_store: + type: sqlite path: data/online_store.db \ No newline at end of file From 2233ff823468dd0bd390a1405d8fc9bd36549f7b Mon Sep 17 00:00:00 2001 From: Kevin Zhang Date: Fri, 25 Mar 2022 12:48:38 -0700 Subject: [PATCH 11/43] Fix Signed-off-by: Kevin Zhang --- go/cmd/server/server_test.go | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/go/cmd/server/server_test.go b/go/cmd/server/server_test.go index e31fbcefc0a..f441387adeb 100644 --- a/go/cmd/server/server_test.go +++ b/go/cmd/server/server_test.go @@ -2,6 +2,7 @@ package main import ( "context" + "log" "net" "path/filepath" "runtime" @@ -9,6 +10,7 @@ import ( "github.com/feast-dev/feast/go/internal/feast" "github.com/feast-dev/feast/go/protos/feast/serving" + "github.com/feast-dev/feast/go/protos/feast/types" "github.com/stretchr/testify/assert" "google.golang.org/grpc" "google.golang.org/grpc/test/bufconn" @@ -77,7 +79,26 @@ func TestGetOnlineFeatures(t *testing.T) { ctx := context.Background() client, closer := getClient(ctx, "../../internal/test") defer closer() - response, err := client.GetOnlineFeatures(ctx, &serving.GetOnlineFeaturesRequest{}) + entities := make(map[string]*types.RepeatedValue) + entities["driver"] = &types.RepeatedValue{ + Val: []*types.Value{ + {Val: &types.Value_Int64Val{Int64Val: 1001}}, + {Val: &types.Value_Int64Val{Int64Val: 1003}}, + {Val: &types.Value_Int64Val{Int64Val: 1005}}, + }, + } + request := &serving.GetOnlineFeaturesRequest{ + Kind: &serving.GetOnlineFeaturesRequest_Features{ + Features: &serving.FeatureList{ + Val: []string{"driver_hourly_stats:conv_rate", "driver_hourly_stats:acc_rate", "driver_hourly_stats:avg_daily_trips"}, + }, + }, + Entities: entities, + } + response, err := client.GetOnlineFeatures(ctx, request) assert.Nil(t, err) assert.NotNil(t, response) + log.Println(response.Metadata.FeatureNames.Val) + assert.True(t, false) + } From 4de021bfa4539c7e2ccb96310757718e87fa0b26 Mon Sep 17 00:00:00 2001 From: Kevin Zhang Date: Fri, 25 Mar 2022 14:48:05 -0700 Subject: [PATCH 12/43] Make integration test work Signed-off-by: Kevin Zhang --- go/cmd/server/server.go | 7 +++++++ go/cmd/server/server_test.go | 21 ++++++++++++++++----- go/internal/feast/featurestore.go | 1 + 3 files changed, 24 insertions(+), 5 deletions(-) diff --git a/go/cmd/server/server.go b/go/cmd/server/server.go index 2f9edb793a0..0c90f759020 100644 --- a/go/cmd/server/server.go +++ b/go/cmd/server/server.go @@ -2,6 +2,7 @@ package main import ( "context" + "github.com/feast-dev/feast/go/internal/feast" "github.com/feast-dev/feast/go/protos/feast/serving" prototypes "github.com/feast-dev/feast/go/protos/feast/types" @@ -24,6 +25,9 @@ func (s *servingServiceServer) GetFeastServingInfo(ctx context.Context, request }, nil } +// Returns an object containing the response to GetOnlineFeatures. +// Metadata contains featurenames that corresponds to the number of rows in response.Results. +// Results contains values including the value of the feature, the event timestamp, and feature status in a columnar format. func (s *servingServiceServer) GetOnlineFeatures(ctx context.Context, request *serving.GetOnlineFeaturesRequest) (*serving.GetOnlineFeaturesResponse, error) { featuresOrService, err := s.fs.ParseFeatures(request.GetKind()) if err != nil { @@ -36,6 +40,9 @@ func (s *servingServiceServer) GetOnlineFeatures(ctx context.Context, request *s featuresOrService.FeatureService, request.GetEntities(), request.GetFullFeatureNames()) + if err != nil { + return nil, err + } resp := &serving.GetOnlineFeaturesResponse{ Results: make([]*serving.GetOnlineFeaturesResponse_FeatureVector, 0), diff --git a/go/cmd/server/server_test.go b/go/cmd/server/server_test.go index f441387adeb..8ed2c27f3c0 100644 --- a/go/cmd/server/server_test.go +++ b/go/cmd/server/server_test.go @@ -2,9 +2,9 @@ package main import ( "context" - "log" "net" "path/filepath" + "reflect" "runtime" "testing" @@ -75,12 +75,12 @@ func TestGetFeastServingInfo(t *testing.T) { } func TestGetOnlineFeatures(t *testing.T) { - //t.Skip("@todo(achals): feature_repo isn't checked in yet") ctx := context.Background() + // Pregenerated using `feast init`. client, closer := getClient(ctx, "../../internal/test") defer closer() entities := make(map[string]*types.RepeatedValue) - entities["driver"] = &types.RepeatedValue{ + entities["driver_id"] = &types.RepeatedValue{ Val: []*types.Value{ {Val: &types.Value_Int64Val{Int64Val: 1001}}, {Val: &types.Value_Int64Val{Int64Val: 1003}}, @@ -96,9 +96,20 @@ func TestGetOnlineFeatures(t *testing.T) { Entities: entities, } response, err := client.GetOnlineFeatures(ctx, request) + expectedEntityValuesResp := []*types.Value{ + {Val: &types.Value_Int64Val{Int64Val: 1001}}, + {Val: &types.Value_Int64Val{Int64Val: 1003}}, + {Val: &types.Value_Int64Val{Int64Val: 1005}}, + } + expectedFeatureNamesResp := []string{"driver_id", "conv_rate", "acc_rate", "avg_daily_trips"} assert.Nil(t, err) assert.NotNil(t, response) - log.Println(response.Metadata.FeatureNames.Val) - assert.True(t, false) + // Columnar so first column should have column names of all features + assert.True(t, reflect.DeepEqual(response.Results[0].Values, expectedEntityValuesResp)) + assert.True(t, reflect.DeepEqual(response.Metadata.FeatureNames.Val, expectedFeatureNamesResp)) + assert.Equal(t, len(response.Results), 4) + for _, row := range response.Results { + assert.Greater(t, len(row.Values), 0) + } } diff --git a/go/internal/feast/featurestore.go b/go/internal/feast/featurestore.go index 4232fa46ead..788f3780b86 100644 --- a/go/internal/feast/featurestore.go +++ b/go/internal/feast/featurestore.go @@ -198,6 +198,7 @@ func (fs *FeatureStore) GetOnlineFeatures( } result := make([]*FeatureVector, 0) arrowMemory := memory.NewGoAllocator() + for _, groupRef := range groupedRefs { featureData, err := fs.readFromOnlineStore(ctx, groupRef.entityKeys, groupRef.featureViewNames, groupRef.featureNames) if err != nil { From e3f9970156f0446a60eed04f2b6356fa83753078 Mon Sep 17 00:00:00 2001 From: Kevin Zhang Date: Fri, 25 Mar 2022 15:00:06 -0700 Subject: [PATCH 13/43] Fix tests Signed-off-by: Kevin Zhang --- go/cmd/server/server_test.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/go/cmd/server/server_test.go b/go/cmd/server/server_test.go index 8ed2c27f3c0..067b4e64b0c 100644 --- a/go/cmd/server/server_test.go +++ b/go/cmd/server/server_test.go @@ -30,6 +30,7 @@ func getRepoPath(basePath string) string { } } +// Starts a new grpc server, registers the serving service and returns a client. func getClient(ctx context.Context, basePath string) (serving.ServingServiceClient, func()) { buffer := 1024 * 1024 listener := bufconn.Listen(buffer) @@ -65,16 +66,16 @@ func getClient(ctx context.Context, basePath string) (serving.ServingServiceClie } func TestGetFeastServingInfo(t *testing.T) { - t.Skip("@todo(achals): feature_repo isn't checked in yet") ctx := context.Background() - client, closer := getClient(ctx, "") + // Pregenerated using `feast init`. + client, closer := getClient(ctx, "../../internal/test") defer closer() response, err := client.GetFeastServingInfo(ctx, &serving.GetFeastServingInfoRequest{}) assert.Nil(t, err) assert.Equal(t, feastServerVersion, response.Version) } -func TestGetOnlineFeatures(t *testing.T) { +func TestGetOnlineFeaturesSqlite(t *testing.T) { ctx := context.Background() // Pregenerated using `feast init`. client, closer := getClient(ctx, "../../internal/test") From d9f53336486ffec36c715383431fc35c07aec6e8 Mon Sep 17 00:00:00 2001 From: Kevin Zhang Date: Fri, 25 Mar 2022 15:02:11 -0700 Subject: [PATCH 14/43] Fix tests Signed-off-by: Kevin Zhang --- go/internal/feast/sqliteonlinestore_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/go/internal/feast/sqliteonlinestore_test.go b/go/internal/feast/sqliteonlinestore_test.go index 34afffd5d7f..ad884000d34 100644 --- a/go/internal/feast/sqliteonlinestore_test.go +++ b/go/internal/feast/sqliteonlinestore_test.go @@ -17,6 +17,7 @@ func TestSqliteSetup(t *testing.T) { assert.Equal(t, "data/registry.db", config.GetRegistryConfig().Path) assert.Equal(t, "local", config.Provider) assert.Equal(t, map[string]interface{}{ + "type": "sqlite", "path": "data/online_store.db", }, config.OnlineStore) assert.Empty(t, config.OfflineStore) From a631c52824249f2aacf5445e9cb953c41be42913 Mon Sep 17 00:00:00 2001 From: Kevin Zhang Date: Fri, 25 Mar 2022 15:54:03 -0700 Subject: [PATCH 15/43] debugging Signed-off-by: Kevin Zhang --- go/cmd/server/server.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/go/cmd/server/server.go b/go/cmd/server/server.go index 0c90f759020..0ca3482773d 100644 --- a/go/cmd/server/server.go +++ b/go/cmd/server/server.go @@ -2,6 +2,7 @@ package main import ( "context" + "log" "github.com/feast-dev/feast/go/internal/feast" "github.com/feast-dev/feast/go/protos/feast/serving" @@ -31,6 +32,8 @@ func (s *servingServiceServer) GetFeastServingInfo(ctx context.Context, request func (s *servingServiceServer) GetOnlineFeatures(ctx context.Context, request *serving.GetOnlineFeaturesRequest) (*serving.GetOnlineFeaturesResponse, error) { featuresOrService, err := s.fs.ParseFeatures(request.GetKind()) if err != nil { + log.Println("first error") + return nil, err } @@ -41,6 +44,7 @@ func (s *servingServiceServer) GetOnlineFeatures(ctx context.Context, request *s request.GetEntities(), request.GetFullFeatureNames()) if err != nil { + log.Println("2nd error") return nil, err } From 993ead4fd153589b2db6e58a56e1a8425d0dee34 Mon Sep 17 00:00:00 2001 From: Kevin Zhang Date: Fri, 25 Mar 2022 15:59:48 -0700 Subject: [PATCH 16/43] Debug Signed-off-by: Kevin Zhang --- go/cmd/server/server.go | 4 ---- go/internal/feast/featurestore.go | 28 ++++++++++++++++++++++++++++ 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/go/cmd/server/server.go b/go/cmd/server/server.go index 0ca3482773d..0c90f759020 100644 --- a/go/cmd/server/server.go +++ b/go/cmd/server/server.go @@ -2,7 +2,6 @@ package main import ( "context" - "log" "github.com/feast-dev/feast/go/internal/feast" "github.com/feast-dev/feast/go/protos/feast/serving" @@ -32,8 +31,6 @@ func (s *servingServiceServer) GetFeastServingInfo(ctx context.Context, request func (s *servingServiceServer) GetOnlineFeatures(ctx context.Context, request *serving.GetOnlineFeaturesRequest) (*serving.GetOnlineFeaturesResponse, error) { featuresOrService, err := s.fs.ParseFeatures(request.GetKind()) if err != nil { - log.Println("first error") - return nil, err } @@ -44,7 +41,6 @@ func (s *servingServiceServer) GetOnlineFeatures(ctx context.Context, request *s request.GetEntities(), request.GetFullFeatureNames()) if err != nil { - log.Println("2nd error") return nil, err } diff --git a/go/internal/feast/featurestore.go b/go/internal/feast/featurestore.go index 788f3780b86..d343c26ab9a 100644 --- a/go/internal/feast/featurestore.go +++ b/go/internal/feast/featurestore.go @@ -5,6 +5,7 @@ import ( "crypto/sha256" "errors" "fmt" + "log" "sort" "strings" @@ -104,6 +105,8 @@ func (fs *FeatureStore) GetOnlineFeatures( numRows, err := fs.validateEntityValues(entityProtos) if err != nil { + log.Println("1") + log.Println(err) return nil, err } @@ -121,21 +124,34 @@ func (fs *FeatureStore) GetOnlineFeatures( err = validateFeatureRefs(requestedFeatureViews, fullFeatureNames) if err != nil { + log.Println("2") + + log.Println(err) return nil, err } if len(requestedRequestFeatureViews)+len(requestedOnDemandFeatureViews) > 0 { + log.Println("3") + + log.Println(err) return nil, status.Errorf(codes.InvalidArgument, "on demand feature views are currently not supported") } entityNameToJoinKeyMap, expectedJoinKeysSet, err := fs.getEntityMaps(requestedFeatureViews) if err != nil { + log.Println("4") + + log.Println(err) + return nil, err } // TODO (Ly): This should return empty now // Expect no ODFV or Request FV passed in GetOnlineFearuresRequest neededRequestData, neededRequestODFVFeatures, err := fs.getNeededRequestData(requestedRequestFeatureViews, requestedOnDemandFeatureViews) if err != nil { + log.Println("5") + + log.Println(err) return nil, err } @@ -152,6 +168,9 @@ func (fs *FeatureStore) GetOnlineFeatures( // requestDataFeatures[joinKeyOrFeature] = vals } else { if _, ok := expectedJoinKeysSet[joinKeyOrFeature]; !ok { + log.Println("joinkey") + log.Println("6") + return nil, fmt.Errorf("JoinKey is not expected in this request: %s\n%v", joinKeyOrFeature, expectedJoinKeysSet) } else { mappedEntityProtos[joinKeyOrFeature] = vals @@ -194,6 +213,9 @@ func (fs *FeatureStore) GetOnlineFeatures( groupedRefs, err := groupFeatureRefs(requestedFeatureViews, mappedEntityProtos, entityNameToJoinKeyMap, fullFeatureNames) if err != nil { + log.Println("7") + + log.Println(err) return nil, err } result := make([]*FeatureVector, 0) @@ -202,6 +224,10 @@ func (fs *FeatureStore) GetOnlineFeatures( for _, groupRef := range groupedRefs { featureData, err := fs.readFromOnlineStore(ctx, groupRef.entityKeys, groupRef.featureViewNames, groupRef.featureNames) if err != nil { + log.Println("8") + + log.Println(err) + return nil, err } @@ -212,6 +238,8 @@ func (fs *FeatureStore) GetOnlineFeatures( numRows, ) if err != nil { + log.Println("9") + return nil, err } result = append(result, vectors...) From 63fbee66a771a83d04063b3b12b521cafb7c5965 Mon Sep 17 00:00:00 2001 From: Kevin Zhang Date: Fri, 25 Mar 2022 16:03:56 -0700 Subject: [PATCH 17/43] Debug Signed-off-by: Kevin Zhang --- go/internal/feast/featurestore.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/go/internal/feast/featurestore.go b/go/internal/feast/featurestore.go index d343c26ab9a..0955f2588ce 100644 --- a/go/internal/feast/featurestore.go +++ b/go/internal/feast/featurestore.go @@ -170,6 +170,8 @@ func (fs *FeatureStore) GetOnlineFeatures( if _, ok := expectedJoinKeysSet[joinKeyOrFeature]; !ok { log.Println("joinkey") log.Println("6") + log.Println(expectedJoinKeysSet) + log.Printf("JoinKey is not expected in this request: %s\n%v", joinKeyOrFeature, expectedJoinKeysSet) return nil, fmt.Errorf("JoinKey is not expected in this request: %s\n%v", joinKeyOrFeature, expectedJoinKeysSet) } else { From c325834a5afda112742fea21b181f38792e4230c Mon Sep 17 00:00:00 2001 From: Kevin Zhang Date: Fri, 25 Mar 2022 16:27:18 -0700 Subject: [PATCH 18/43] Debug Signed-off-by: Kevin Zhang --- go/internal/feast/featurestore.go | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/go/internal/feast/featurestore.go b/go/internal/feast/featurestore.go index 0955f2588ce..29588d8afc7 100644 --- a/go/internal/feast/featurestore.go +++ b/go/internal/feast/featurestore.go @@ -105,8 +105,6 @@ func (fs *FeatureStore) GetOnlineFeatures( numRows, err := fs.validateEntityValues(entityProtos) if err != nil { - log.Println("1") - log.Println(err) return nil, err } @@ -122,26 +120,20 @@ func (fs *FeatureStore) GetOnlineFeatures( fs.getFeatureViewsToUseByFeatureRefs(featureRefs, false) } + log.Println("requestedFeatureViews") + log.Println(requestedFeatureViews[0]) + err = validateFeatureRefs(requestedFeatureViews, fullFeatureNames) if err != nil { - log.Println("2") - - log.Println(err) return nil, err } if len(requestedRequestFeatureViews)+len(requestedOnDemandFeatureViews) > 0 { - log.Println("3") - - log.Println(err) return nil, status.Errorf(codes.InvalidArgument, "on demand feature views are currently not supported") } entityNameToJoinKeyMap, expectedJoinKeysSet, err := fs.getEntityMaps(requestedFeatureViews) if err != nil { - log.Println("4") - - log.Println(err) return nil, err } @@ -386,6 +378,8 @@ func (fs *FeatureStore) getFeatureViewsToUseByFeatureRefs(features []string, hid } for _, featureView := range featureViews { fvs[featureView.base.name] = featureView + log.Println("Asd") + log.Println(featureView.base.name) } requestFeatureViews, err := fs.registry.listRequestFeatureViews(fs.config.Project) @@ -413,6 +407,8 @@ func (fs *FeatureStore) getFeatureViewsToUseByFeatureRefs(features []string, hid if err != nil { return nil, nil, nil, nil, err } + log.Println("ASDfas") + log.Println(featureViewName) if fv, ok := fvs[featureViewName]; ok { found := false for _, group := range fvsToUse { @@ -422,6 +418,7 @@ func (fs *FeatureStore) getFeatureViewsToUseByFeatureRefs(features []string, hid } } if !found { + log.Println(featureName) fvsToUse = append(fvsToUse, &featureViewAndRefs{ view: fv, featureRefs: []string{featureName}, @@ -436,7 +433,6 @@ func (fs *FeatureStore) getFeatureViewsToUseByFeatureRefs(features []string, hid " feature view %s and that you have registered it by running \"apply\"", featureViewName, featureViewName) } } - return fvs, fvsToUse, requestFvsToUse, odFvsToUse, nil } @@ -452,10 +448,13 @@ func (fs *FeatureStore) getEntityMaps(requestedFeatureViews []*featureViewAndRef for _, entity := range entities { entitiesByName[entity.name] = entity + log.Println(entity.name) } for _, featuresAndView := range requestedFeatureViews { featureView := featuresAndView.view + log.Println("featureview") + log.Println(featureView) var joinKeyToAliasMap map[string]string if featureView.base.projection != nil && featureView.base.projection.joinKeyMap != nil { joinKeyToAliasMap = featureView.base.projection.joinKeyMap @@ -465,6 +464,7 @@ func (fs *FeatureStore) getEntityMaps(requestedFeatureViews []*featureViewAndRef for entityName := range featureView.entities { joinKey := entitiesByName[entityName].joinKey + log.Println(joinKey) entityNameToJoinKeyMap[entityName] = joinKey if alias, ok := joinKeyToAliasMap[joinKey]; ok { From f2202b8494e9afdd9b5fbbba6ad91c1ee8f4bff6 Mon Sep 17 00:00:00 2001 From: Kevin Zhang Date: Fri, 25 Mar 2022 16:31:53 -0700 Subject: [PATCH 19/43] Debug Signed-off-by: Kevin Zhang --- go/internal/feast/featurestore.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/go/internal/feast/featurestore.go b/go/internal/feast/featurestore.go index 29588d8afc7..d320a97795d 100644 --- a/go/internal/feast/featurestore.go +++ b/go/internal/feast/featurestore.go @@ -116,6 +116,7 @@ func (fs *FeatureStore) GetOnlineFeatures( fvs, requestedFeatureViews, requestedRequestFeatureViews, requestedOnDemandFeatureViews, err = fs.getFeatureViewsToUseByService(featureService, false) } else { + log.Println("in") fvs, requestedFeatureViews, requestedRequestFeatureViews, requestedOnDemandFeatureViews, err = fs.getFeatureViewsToUseByFeatureRefs(featureRefs, false) } @@ -371,8 +372,8 @@ func (fs *FeatureStore) getFeatureViewsToUseByFeatureRefs(features []string, hid fvs := make(map[string]*FeatureView) requestFvs := make(map[string]*RequestFeatureView) odFvs := make(map[string]*OnDemandFeatureView) - featureViews, err := fs.listFeatureViews(hideDummyEntity) + log.Println(featureViews) if err != nil { return nil, nil, nil, nil, err } From c24258700040a36ea24be5bc34b472d39c4bcc99 Mon Sep 17 00:00:00 2001 From: Kevin Zhang Date: Fri, 25 Mar 2022 16:42:15 -0700 Subject: [PATCH 20/43] Debug Signed-off-by: Kevin Zhang --- go/internal/feast/registry.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/go/internal/feast/registry.go b/go/internal/feast/registry.go index 8963703f514..73eeb3ee22b 100644 --- a/go/internal/feast/registry.go +++ b/go/internal/feast/registry.go @@ -3,6 +3,7 @@ package feast import ( "errors" "fmt" + "log" "net/url" "sync" "time" @@ -141,6 +142,7 @@ func (r *Registry) loadFeatureViews(registry *core.Registry) { if _, ok := r.cachedFeatureViews[featureView.Spec.Project]; !ok { r.cachedFeatureViews[featureView.Spec.Project] = make(map[string]*core.FeatureView) } + log.Println(featureView.Spec.Name) r.cachedFeatureViews[featureView.Spec.Project][featureView.Spec.Name] = featureView } } From 08e51fbba92e4095b6d33d97d703182d330054d9 Mon Sep 17 00:00:00 2001 From: Kevin Zhang Date: Fri, 25 Mar 2022 16:50:42 -0700 Subject: [PATCH 21/43] Debug Signed-off-by: Kevin Zhang --- go/internal/feast/featurestore.go | 32 ------------------------------- go/internal/feast/registry.go | 5 ++++- 2 files changed, 4 insertions(+), 33 deletions(-) diff --git a/go/internal/feast/featurestore.go b/go/internal/feast/featurestore.go index d320a97795d..867acdb7cc5 100644 --- a/go/internal/feast/featurestore.go +++ b/go/internal/feast/featurestore.go @@ -5,7 +5,6 @@ import ( "crypto/sha256" "errors" "fmt" - "log" "sort" "strings" @@ -116,14 +115,10 @@ func (fs *FeatureStore) GetOnlineFeatures( fvs, requestedFeatureViews, requestedRequestFeatureViews, requestedOnDemandFeatureViews, err = fs.getFeatureViewsToUseByService(featureService, false) } else { - log.Println("in") fvs, requestedFeatureViews, requestedRequestFeatureViews, requestedOnDemandFeatureViews, err = fs.getFeatureViewsToUseByFeatureRefs(featureRefs, false) } - log.Println("requestedFeatureViews") - log.Println(requestedFeatureViews[0]) - err = validateFeatureRefs(requestedFeatureViews, fullFeatureNames) if err != nil { return nil, err @@ -142,9 +137,6 @@ func (fs *FeatureStore) GetOnlineFeatures( // Expect no ODFV or Request FV passed in GetOnlineFearuresRequest neededRequestData, neededRequestODFVFeatures, err := fs.getNeededRequestData(requestedRequestFeatureViews, requestedOnDemandFeatureViews) if err != nil { - log.Println("5") - - log.Println(err) return nil, err } @@ -161,11 +153,6 @@ func (fs *FeatureStore) GetOnlineFeatures( // requestDataFeatures[joinKeyOrFeature] = vals } else { if _, ok := expectedJoinKeysSet[joinKeyOrFeature]; !ok { - log.Println("joinkey") - log.Println("6") - log.Println(expectedJoinKeysSet) - log.Printf("JoinKey is not expected in this request: %s\n%v", joinKeyOrFeature, expectedJoinKeysSet) - return nil, fmt.Errorf("JoinKey is not expected in this request: %s\n%v", joinKeyOrFeature, expectedJoinKeysSet) } else { mappedEntityProtos[joinKeyOrFeature] = vals @@ -208,9 +195,6 @@ func (fs *FeatureStore) GetOnlineFeatures( groupedRefs, err := groupFeatureRefs(requestedFeatureViews, mappedEntityProtos, entityNameToJoinKeyMap, fullFeatureNames) if err != nil { - log.Println("7") - - log.Println(err) return nil, err } result := make([]*FeatureVector, 0) @@ -219,10 +203,6 @@ func (fs *FeatureStore) GetOnlineFeatures( for _, groupRef := range groupedRefs { featureData, err := fs.readFromOnlineStore(ctx, groupRef.entityKeys, groupRef.featureViewNames, groupRef.featureNames) if err != nil { - log.Println("8") - - log.Println(err) - return nil, err } @@ -233,8 +213,6 @@ func (fs *FeatureStore) GetOnlineFeatures( numRows, ) if err != nil { - log.Println("9") - return nil, err } result = append(result, vectors...) @@ -373,14 +351,11 @@ func (fs *FeatureStore) getFeatureViewsToUseByFeatureRefs(features []string, hid requestFvs := make(map[string]*RequestFeatureView) odFvs := make(map[string]*OnDemandFeatureView) featureViews, err := fs.listFeatureViews(hideDummyEntity) - log.Println(featureViews) if err != nil { return nil, nil, nil, nil, err } for _, featureView := range featureViews { fvs[featureView.base.name] = featureView - log.Println("Asd") - log.Println(featureView.base.name) } requestFeatureViews, err := fs.registry.listRequestFeatureViews(fs.config.Project) @@ -408,8 +383,6 @@ func (fs *FeatureStore) getFeatureViewsToUseByFeatureRefs(features []string, hid if err != nil { return nil, nil, nil, nil, err } - log.Println("ASDfas") - log.Println(featureViewName) if fv, ok := fvs[featureViewName]; ok { found := false for _, group := range fvsToUse { @@ -419,7 +392,6 @@ func (fs *FeatureStore) getFeatureViewsToUseByFeatureRefs(features []string, hid } } if !found { - log.Println(featureName) fvsToUse = append(fvsToUse, &featureViewAndRefs{ view: fv, featureRefs: []string{featureName}, @@ -449,13 +421,10 @@ func (fs *FeatureStore) getEntityMaps(requestedFeatureViews []*featureViewAndRef for _, entity := range entities { entitiesByName[entity.name] = entity - log.Println(entity.name) } for _, featuresAndView := range requestedFeatureViews { featureView := featuresAndView.view - log.Println("featureview") - log.Println(featureView) var joinKeyToAliasMap map[string]string if featureView.base.projection != nil && featureView.base.projection.joinKeyMap != nil { joinKeyToAliasMap = featureView.base.projection.joinKeyMap @@ -465,7 +434,6 @@ func (fs *FeatureStore) getEntityMaps(requestedFeatureViews []*featureViewAndRef for entityName := range featureView.entities { joinKey := entitiesByName[entityName].joinKey - log.Println(joinKey) entityNameToJoinKeyMap[entityName] = joinKey if alias, ok := joinKeyToAliasMap[joinKey]; ok { diff --git a/go/internal/feast/registry.go b/go/internal/feast/registry.go index 73eeb3ee22b..a6eb3d55c47 100644 --- a/go/internal/feast/registry.go +++ b/go/internal/feast/registry.go @@ -38,6 +38,8 @@ type Registry struct { } func NewRegistry(registryConfig *RegistryConfig, repoPath string) (*Registry, error) { + log.Println("registry") + log.Println(registryConfig) registryStoreType := registryConfig.RegistryStoreType registryPath := registryConfig.Path r := &Registry{ @@ -89,9 +91,11 @@ func (r *Registry) refresh() error { func (r *Registry) getRegistryProto() (*core.Registry, error) { expired := r.cachedRegistry == nil || (r.cachedRegistryProtoTtl > 0 && time.Now().After(r.cachedRegistryProtoLastUpdated.Add(r.cachedRegistryProtoTtl))) if !expired { + log.Println("Expired?") return r.cachedRegistry, nil } registryProto, err := r.registryStore.GetRegistryProto() + log.Println(registryProto) if err != nil { return registryProto, err } @@ -142,7 +146,6 @@ func (r *Registry) loadFeatureViews(registry *core.Registry) { if _, ok := r.cachedFeatureViews[featureView.Spec.Project]; !ok { r.cachedFeatureViews[featureView.Spec.Project] = make(map[string]*core.FeatureView) } - log.Println(featureView.Spec.Name) r.cachedFeatureViews[featureView.Spec.Project][featureView.Spec.Name] = featureView } } From b5516390b8628bdff0553dc9ac08316375389948 Mon Sep 17 00:00:00 2001 From: Kevin Zhang Date: Mon, 28 Mar 2022 12:24:23 -0700 Subject: [PATCH 22/43] Remove feature_repo files Signed-off-by: Kevin Zhang --- go/internal/test/feature_repo/__init__.py | 0 .../feature_repo/data/driver_stats.parquet | Bin 34696 -> 0 bytes .../test/feature_repo/data/online_store.db | Bin 16384 -> 0 bytes go/internal/test/feature_repo/example.py | 35 ------------------ .../test/feature_repo/feature_store.yaml | 6 --- 5 files changed, 41 deletions(-) delete mode 100644 go/internal/test/feature_repo/__init__.py delete mode 100644 go/internal/test/feature_repo/data/driver_stats.parquet delete mode 100644 go/internal/test/feature_repo/data/online_store.db delete mode 100644 go/internal/test/feature_repo/example.py delete mode 100644 go/internal/test/feature_repo/feature_store.yaml diff --git a/go/internal/test/feature_repo/__init__.py b/go/internal/test/feature_repo/__init__.py deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/go/internal/test/feature_repo/data/driver_stats.parquet b/go/internal/test/feature_repo/data/driver_stats.parquet deleted file mode 100644 index ee4f58fdbc85a8cd9999687aa218155c4a47bbe1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 34696 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 diff --git a/go/internal/test/feature_repo/example.py b/go/internal/test/feature_repo/example.py deleted file mode 100644 index 76210d79694..00000000000 --- a/go/internal/test/feature_repo/example.py +++ /dev/null @@ -1,35 +0,0 @@ -# This is an example feature definition file - -from datetime import timedelta - -from feast import Entity, Feature, FeatureView, FileSource, ValueType - -# Read data from parquet files. Parquet is convenient for local development mode. For -# production, you can use your favorite DWH, such as BigQuery. See Feast documentation -# for more info. -driver_hourly_stats = FileSource( - path="data/driver_stats.parquet", - event_timestamp_column="event_timestamp", - created_timestamp_column="created", -) - -# Define an entity for the driver. You can think of entity as a primary key used to -# fetch features. -driver = Entity(name="driver_id", value_type=ValueType.INT64, description="driver id",) - -# Our parquet files contain sample data that includes a driver_id column, timestamps and -# three feature column. Here we define a Feature View that will allow us to serve this -# data to our model online. -driver_hourly_stats_view = FeatureView( - name="driver_hourly_stats", - entities=["driver_id"], - ttl=timedelta(days=1), - features=[ - Feature(name="conv_rate", dtype=ValueType.FLOAT), - Feature(name="acc_rate", dtype=ValueType.FLOAT), - Feature(name="avg_daily_trips", dtype=ValueType.INT64), - ], - online=True, - batch_source=driver_hourly_stats, - tags={}, -) diff --git a/go/internal/test/feature_repo/feature_store.yaml b/go/internal/test/feature_repo/feature_store.yaml deleted file mode 100644 index 70bb90e86b1..00000000000 --- a/go/internal/test/feature_repo/feature_store.yaml +++ /dev/null @@ -1,6 +0,0 @@ -project: feature_repo -registry: data/registry.db -provider: local -online_store: - type: sqlite - path: data/online_store.db \ No newline at end of file From 0755e05ac40c274ff3959b44159a80d6875fe755 Mon Sep 17 00:00:00 2001 From: Kevin Zhang Date: Mon, 28 Mar 2022 12:24:39 -0700 Subject: [PATCH 23/43] update gitignore Signed-off-by: Kevin Zhang --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 5cd8d218856..50e2ab912b3 100644 --- a/.gitignore +++ b/.gitignore @@ -105,6 +105,7 @@ coverage.xml .hypothesis/ .pytest_cache/ infra/scripts/*.conf +go/internal/test/ # Translations *.mo @@ -204,7 +205,7 @@ ui/.pnp ui/.pnp.js ui/coverage ui/build -ui/feature_repo/data/online.db +ui/feature_repo/data/online.db ui/feature_repo/registry.db ui/.vercel From eb8a3208d807da32e105fdfb74db2833719ff9bd Mon Sep 17 00:00:00 2001 From: Kevin Zhang Date: Mon, 28 Mar 2022 13:02:27 -0700 Subject: [PATCH 24/43] Clean up code Signed-off-by: Kevin Zhang --- go/cmd/server/server_test.go | 31 ++++++++++++++++----- go/internal/feast/onlinestore.go | 7 ++--- go/internal/feast/registry.go | 5 ---- go/internal/feast/sqliteonlinestore.go | 3 ++ go/internal/feast/sqliteonlinestore_test.go | 22 +++++++-------- 5 files changed, 40 insertions(+), 28 deletions(-) diff --git a/go/cmd/server/server_test.go b/go/cmd/server/server_test.go index 067b4e64b0c..e5fbf26bfe1 100644 --- a/go/cmd/server/server_test.go +++ b/go/cmd/server/server_test.go @@ -9,6 +9,7 @@ import ( "testing" "github.com/feast-dev/feast/go/internal/feast" + "github.com/feast-dev/feast/go/internal/test" "github.com/feast-dev/feast/go/protos/feast/serving" "github.com/feast-dev/feast/go/protos/feast/types" "github.com/stretchr/testify/assert" @@ -68,7 +69,8 @@ func getClient(ctx context.Context, basePath string) (serving.ServingServiceClie func TestGetFeastServingInfo(t *testing.T) { ctx := context.Background() // Pregenerated using `feast init`. - client, closer := getClient(ctx, "../../internal/test") + dir := "../../internal/test" + client, closer := getClient(ctx, dir) defer closer() response, err := client.GetFeastServingInfo(ctx, &serving.GetFeastServingInfoRequest{}) assert.Nil(t, err) @@ -78,7 +80,8 @@ func TestGetFeastServingInfo(t *testing.T) { func TestGetOnlineFeaturesSqlite(t *testing.T) { ctx := context.Background() // Pregenerated using `feast init`. - client, closer := getClient(ctx, "../../internal/test") + dir := "../../internal/test" + client, closer := getClient(ctx, dir) defer closer() entities := make(map[string]*types.RepeatedValue) entities["driver_id"] = &types.RepeatedValue{ @@ -105,12 +108,26 @@ func TestGetOnlineFeaturesSqlite(t *testing.T) { expectedFeatureNamesResp := []string{"driver_id", "conv_rate", "acc_rate", "avg_daily_trips"} assert.Nil(t, err) assert.NotNil(t, response) + rows, err := test.ReadParquet(filepath.Join(dir, "feature_repo", "data", "driver_stats.parquet")) + assert.Nil(t, err) + entityKeys := map[int64]bool{1001: true, 1003: true, 1005: true} + correctFeatures := test.GetLatestFeatures(rows, entityKeys) + expectedConvRateValues := []*types.Value{} + expectedAccRateValues := []*types.Value{} + expectedAvgDailyTripsValues := []*types.Value{} + + for _, key := range []int64{1001, 1003, 1005} { + expectedConvRateValues = append(expectedConvRateValues, &types.Value{Val: &types.Value_FloatVal{FloatVal: correctFeatures[key].Conv_rate}}) + expectedAccRateValues = append(expectedAccRateValues, &types.Value{Val: &types.Value_FloatVal{FloatVal: correctFeatures[key].Acc_rate}}) + expectedAvgDailyTripsValues = append(expectedAvgDailyTripsValues, &types.Value{Val: &types.Value_Int64Val{Int64Val: int64(correctFeatures[key].Avg_daily_trips)}}) + } + // Columnar so get in column format row by row should have column names of all features + assert.Equal(t, len(response.Results), 4) - // Columnar so first column should have column names of all features assert.True(t, reflect.DeepEqual(response.Results[0].Values, expectedEntityValuesResp)) + assert.True(t, reflect.DeepEqual(response.Results[1].Values, expectedConvRateValues)) + assert.True(t, reflect.DeepEqual(response.Results[2].Values, expectedAccRateValues)) + assert.True(t, reflect.DeepEqual(response.Results[3].Values, expectedAvgDailyTripsValues)) + assert.True(t, reflect.DeepEqual(response.Metadata.FeatureNames.Val, expectedFeatureNamesResp)) - assert.Equal(t, len(response.Results), 4) - for _, row := range response.Results { - assert.Greater(t, len(row.Values), 0) - } } diff --git a/go/internal/feast/onlinestore.go b/go/internal/feast/onlinestore.go index 961125d14fd..a4ac0a0a3e5 100644 --- a/go/internal/feast/onlinestore.go +++ b/go/internal/feast/onlinestore.go @@ -52,15 +52,12 @@ func getOnlineStoreType(onlineStoreConfig map[string]interface{}) (string, bool) func NewOnlineStore(config *RepoConfig) (OnlineStore, error) { onlineStoreType, ok := getOnlineStoreType(config.OnlineStore) if !ok { - return nil, fmt.Errorf("could not get online store type from online store config: %+v", config.OnlineStore) + onlineStore, err := NewSqliteOnlineStore(config.Project, config, config.OnlineStore) + return onlineStore, err } if onlineStoreType == "redis" { onlineStore, err := NewRedisOnlineStore(config.Project, config.OnlineStore) return onlineStore, err - } - if onlineStoreType == "sqlite" { - onlineStore, err := NewSqliteOnlineStore(config.Project, config, config.OnlineStore) - return onlineStore, err } else { return nil, fmt.Errorf("%s online store type is currently not supported; only redis and sqlite are supported", onlineStoreType) } diff --git a/go/internal/feast/registry.go b/go/internal/feast/registry.go index a6eb3d55c47..8963703f514 100644 --- a/go/internal/feast/registry.go +++ b/go/internal/feast/registry.go @@ -3,7 +3,6 @@ package feast import ( "errors" "fmt" - "log" "net/url" "sync" "time" @@ -38,8 +37,6 @@ type Registry struct { } func NewRegistry(registryConfig *RegistryConfig, repoPath string) (*Registry, error) { - log.Println("registry") - log.Println(registryConfig) registryStoreType := registryConfig.RegistryStoreType registryPath := registryConfig.Path r := &Registry{ @@ -91,11 +88,9 @@ func (r *Registry) refresh() error { func (r *Registry) getRegistryProto() (*core.Registry, error) { expired := r.cachedRegistry == nil || (r.cachedRegistryProtoTtl > 0 && time.Now().After(r.cachedRegistryProtoLastUpdated.Add(r.cachedRegistryProtoTtl))) if !expired { - log.Println("Expired?") return r.cachedRegistry, nil } registryProto, err := r.registryStore.GetRegistryProto() - log.Println(registryProto) if err != nil { return registryProto, err } diff --git a/go/internal/feast/sqliteonlinestore.go b/go/internal/feast/sqliteonlinestore.go index 1ad9007b110..e133dd162f1 100644 --- a/go/internal/feast/sqliteonlinestore.go +++ b/go/internal/feast/sqliteonlinestore.go @@ -148,6 +148,9 @@ func initializeConnection(db_path string) (*sql.DB, error) { } func hashSerializedEntityKey(serializedEntityKey *[]byte) string { + if serializedEntityKey == nil { + return "" + } h := sha1.New() h.Write(*serializedEntityKey) sha1_hash := hex.EncodeToString(h.Sum(nil)) diff --git a/go/internal/feast/sqliteonlinestore_test.go b/go/internal/feast/sqliteonlinestore_test.go index ad884000d34..27c7d728071 100644 --- a/go/internal/feast/sqliteonlinestore_test.go +++ b/go/internal/feast/sqliteonlinestore_test.go @@ -2,9 +2,11 @@ package feast import ( "context" + "path/filepath" "reflect" "testing" + "github.com/feast-dev/feast/go/internal/test" "github.com/feast-dev/feast/go/protos/feast/types" "github.com/stretchr/testify/assert" ) @@ -17,7 +19,6 @@ func TestSqliteSetup(t *testing.T) { assert.Equal(t, "data/registry.db", config.GetRegistryConfig().Path) assert.Equal(t, "local", config.Provider) assert.Equal(t, map[string]interface{}{ - "type": "sqlite", "path": "data/online_store.db", }, config.OnlineStore) assert.Empty(t, config.OfflineStore) @@ -57,16 +58,15 @@ func TestSqliteOnlineRead(t *testing.T) { returnedFeatureNames = append(returnedFeatureNames, featureVector[idx].reference.FeatureName) } } - expectedFeatureValues := []*types.Value{ - {Val: &types.Value_FloatVal{FloatVal: 0.78135854}}, - {Val: &types.Value_FloatVal{FloatVal: 0.38527268}}, - {Val: &types.Value_Int64Val{Int64Val: 755}}, - {Val: &types.Value_FloatVal{FloatVal: 0.49661186}}, - {Val: &types.Value_FloatVal{FloatVal: 0.9440974}}, - {Val: &types.Value_Int64Val{Int64Val: 169}}, - {Val: &types.Value_FloatVal{FloatVal: 0.80762655}}, - {Val: &types.Value_FloatVal{FloatVal: 0.71510273}}, - {Val: &types.Value_Int64Val{Int64Val: 545}}, + rows, err := test.ReadParquet(filepath.Join(dir, "data", "driver_stats.parquet")) + assert.Nil(t, err) + entities := map[int64]bool{1005: true, 1001: true, 1003: true} + correctFeatures := test.GetLatestFeatures(rows, entities) + expectedFeatureValues := []*types.Value{} + for _, key := range []int64{1005, 1001, 1003} { + expectedFeatureValues = append(expectedFeatureValues, &types.Value{Val: &types.Value_FloatVal{FloatVal: correctFeatures[key].Conv_rate}}) + expectedFeatureValues = append(expectedFeatureValues, &types.Value{Val: &types.Value_FloatVal{FloatVal: correctFeatures[key].Acc_rate}}) + expectedFeatureValues = append(expectedFeatureValues, &types.Value{Val: &types.Value_Int64Val{Int64Val: int64(correctFeatures[key].Avg_daily_trips)}}) } expectedFeatureNames := []string{"conv_rate", "acc_rate", "avg_daily_trips", "conv_rate", "acc_rate", "avg_daily_trips", "conv_rate", "acc_rate", "avg_daily_trips"} assert.True(t, reflect.DeepEqual(expectedFeatureValues, returnedFeatureValues)) From c3472bd44f00e0e3f9c02fb0818dc1c6b3800192 Mon Sep 17 00:00:00 2001 From: Kevin Zhang Date: Mon, 28 Mar 2022 13:03:20 -0700 Subject: [PATCH 25/43] Update go mod Signed-off-by: Kevin Zhang --- go.mod | 10 +-- go.sum | 217 +++++++++++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 216 insertions(+), 11 deletions(-) diff --git a/go.mod b/go.mod index a3ce2c3dc0e..7b74ac264ce 100644 --- a/go.mod +++ b/go.mod @@ -8,22 +8,25 @@ require ( github.com/go-python/gopy v0.4.0 github.com/go-redis/redis/v8 v8.11.4 github.com/golang/protobuf v1.5.2 - github.com/google/uuid v1.1.2 + github.com/google/uuid v1.2.0 github.com/mattn/go-sqlite3 v1.14.12 github.com/spaolacci/murmur3 v1.1.0 github.com/stretchr/testify v1.7.0 + github.com/xitongsys/parquet-go v1.6.2 + github.com/xitongsys/parquet-go-source v0.0.0-20220315005136-aec0fe3e777c google.golang.org/grpc v1.44.0 google.golang.org/protobuf v1.27.1 ) require ( + github.com/apache/thrift v0.16.0 // indirect github.com/cespare/xxhash/v2 v2.1.2 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect + github.com/golang/snappy v0.0.4 // indirect github.com/google/flatbuffers v2.0.0+incompatible // indirect github.com/google/go-cmp v0.5.7 // indirect - github.com/klauspost/compress v1.13.6 // indirect - github.com/kr/pretty v0.1.0 // indirect + github.com/klauspost/compress v1.15.1 // indirect github.com/pierrec/lz4/v4 v4.1.9 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect golang.org/x/exp v0.0.0-20211028214138-64b4c8e87d1a // indirect @@ -32,7 +35,6 @@ require ( golang.org/x/text v0.3.7 // indirect golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect google.golang.org/genproto v0.0.0-20220118154757-00ab72f36ad5 // indirect - gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect ) diff --git a/go.sum b/go.sum index 2bd2eedee8f..bccf79b3823 100644 --- a/go.sum +++ b/go.sum @@ -1,8 +1,35 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= +cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= +cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= dmitri.shuralyov.com/gpu/mtl v0.0.0-20201218220906-28db891af037/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= gioui.org v0.0.0-20210308172011-57750fc8a0a6/go.mod h1:RSH6KIUZ0p2xy5zHDxgAM4zumjgTw83q2ge/PI+yyw8= +github.com/Azure/azure-pipeline-go v0.2.3/go.mod h1:x841ezTBIMG6O3lAcl8ATHnsOPVl2bqk7S3ta6S6u4k= +github.com/Azure/azure-storage-blob-go v0.14.0/go.mod h1:SMqIBi+SuiQH32bvyjngEewEeXoPfKMgWlBDaYf6fck= +github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= +github.com/Azure/go-autorest/autorest/adal v0.9.13/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M= +github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74= +github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= +github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= +github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= @@ -17,17 +44,36 @@ github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuy github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/apache/arrow/go/arrow v0.0.0-20200730104253-651201b0f516/go.mod h1:QNYViu/X0HXDHw7m3KXzWSVXIbfUvJqBFe6Gj8/pYA0= github.com/apache/arrow/go/arrow v0.0.0-20211112161151-bc219186db40 h1:q4dksr6ICHXqG5hm0ZW5IHyeEJXoIJSOZeBLmWPNeIQ= github.com/apache/arrow/go/arrow v0.0.0-20211112161151-bc219186db40/go.mod h1:Q7yQnSMnLvcXlZ8RV+jwz/6y1rQTqbX6C82SndT52Zs= +github.com/apache/thrift v0.0.0-20181112125854-24918abba929/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= +github.com/apache/thrift v0.14.2/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= +github.com/apache/thrift v0.16.0 h1:qEy6UW60iVOlUy+b9ZR0d5WzUWYGOo4HfopoyBaNmoY= +github.com/apache/thrift v0.16.0/go.mod h1:PHK3hniurgQaNMZYaCLEqXKsYK8upmhPbmdP2FXSqgU= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A= github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU= github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/aws/aws-sdk-go v1.30.19/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0= github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g= +github.com/aws/aws-sdk-go-v2 v1.7.1/go.mod h1:L5LuPC1ZgDr2xQS7AmIec/Jlc7O/Y1u2KxJyNVab250= +github.com/aws/aws-sdk-go-v2/config v1.5.0/go.mod h1:RWlPOAW3E3tbtNAqTwvSW54Of/yP3oiZXMI0xfUdjyA= +github.com/aws/aws-sdk-go-v2/credentials v1.3.1/go.mod h1:r0n73xwsIVagq8RsxmZbGSRQFj9As3je72C2WzUIToc= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.3.0/go.mod h1:2LAuqPx1I6jNfaGDucWfA2zqQCYCOMCDHiCOciALyNw= +github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.3.2/go.mod h1:qaqQiHSrOUVOfKe6fhgQ6UzhxjwqVW8aHNegd6Ws4w4= +github.com/aws/aws-sdk-go-v2/internal/ini v1.1.1/go.mod h1:Zy8smImhTdOETZqfyn01iNOe0CNggVbPjCajyaz6Gvg= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.2.1/go.mod h1:v33JQ57i2nekYTA70Mb+O18KeH4KqhdqxTJZNK1zdRE= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.2.1/go.mod h1:zceowr5Z1Nh2WVP8bf/3ikB41IZW59E4yIYbg+pC6mw= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.5.1/go.mod h1:6EQZIwNNvHpq/2/QSJnp4+ECvqIy55w95Ofs0ze+nGQ= +github.com/aws/aws-sdk-go-v2/service/s3 v1.11.1/go.mod h1:XLAGFrEjbvMCLvAtWLLP32yTv8GpBquCApZEycDLunI= +github.com/aws/aws-sdk-go-v2/service/sso v1.3.1/go.mod h1:J3A3RGUvuCZjvSuZEcOpHDnzZP/sKbhDWV2T1EOzFIM= +github.com/aws/aws-sdk-go-v2/service/sts v1.6.0/go.mod h1:q7o0j7d7HrJk/vr9uUt3BVRASvcU7gYZB9PUgPiByXg= +github.com/aws/smithy-go v1.6.0/go.mod h1:SObp3lf9smib00L/v3U2eAKG8FyQ7iLrJnQiAmR5n+E= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= @@ -41,6 +87,9 @@ github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghf github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= @@ -52,6 +101,7 @@ github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWH github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= +github.com/colinmarc/hdfs/v2 v2.1.1/go.mod h1:M3x+k8UKKmxtFu++uAZ0OtDU8jR3jnaZIAc6yK4Ue0c= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= @@ -81,6 +131,7 @@ github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7 github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= +github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4= github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= @@ -93,6 +144,7 @@ github.com/go-fonts/latin-modern v0.2.0/go.mod h1:rQVLdDMK+mK1xscDwsqM5J8U2jrRa3 github.com/go-fonts/liberation v0.1.1/go.mod h1:K6qoJYypsmfVjWg8KOVDQhLc8UDgIK2HYqyqAO9z7GY= github.com/go-fonts/stix v0.1.0/go.mod h1:w/c1f0ldAUlJmLBvlbkvVXLAD+tAMqobIIQpmnUIzUY= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= @@ -105,6 +157,7 @@ github.com/go-logr/logr v0.4.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTg github.com/go-redis/redis/v8 v8.11.4 h1:kHoYkfZP6+pe04aFTnhDH6GDROa5yJdHJVNxV3F46Tg= github.com/go-redis/redis/v8 v8.11.4/go.mod h1:2Z2wHZXdQpCDXEGzqMockDpNyYvi2l4Pxt6RJr792+w= github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= +github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= @@ -115,7 +168,15 @@ github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGw github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= +github.com/golang/protobuf v1.1.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -133,10 +194,13 @@ github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= +github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/gonuts/commander v0.1.0/go.mod h1:qkb5mSlcWodYgo7vs8ulLnXhfinhZsZcm6+H/z1JjgY= github.com/gonuts/flag v0.1.0/go.mod h1:ZTmTGtrSPejTo/SRNhCqwLTmiAgyBdCkLYhHrAoBdz4= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/flatbuffers v1.11.0/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= github.com/google/flatbuffers v2.0.0+incompatible h1:dicJ2oXwypfwUGnB2/TYWYEKiuk9eYQlQO/AnOHl5mI= github.com/google/flatbuffers v2.0.0+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= @@ -144,15 +208,24 @@ github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o= github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs= +github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= @@ -172,6 +245,7 @@ github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHh github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= +github.com/hashicorp/go-uuid v0.0.0-20180228145832-27454136f036/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= @@ -184,26 +258,35 @@ github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2p github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= +github.com/jcmturner/gofork v0.0.0-20180107083740-2aebee971930/go.mod h1:MK8+TM0La+2rjBD4jE12Kj1pCCxK7d2LK/UM3ncEo0o= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= +github.com/jmespath/go-jmespath v0.3.0/go.mod h1:9QtRXoHjLGCJ5IBSaohpXITPlowMeeYCZ7fLUTSywik= +github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= +github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/jung-kurt/gofpdf v1.0.0/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.9.7/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= github.com/klauspost/compress v1.13.1/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= -github.com/klauspost/compress v1.13.6 h1:P76CopJELS0TiO2mebmnzgWaajssP/EszplttgQxcgc= -github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= +github.com/klauspost/compress v1.15.1 h1:y9FcTHGyrebwfP0ZZqFiaxTaiDnUrGkJkI+f583BL1A= +github.com/klauspost/compress v1.15.1/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= -github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= @@ -211,9 +294,12 @@ github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-b github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-ieproxy v0.0.1/go.mod h1:pYabZ6IHcRpFh7vIaLfK7rdcWgFEb3SFJ6/gNWuh88E= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/mattn/go-sqlite3 v1.14.12 h1:TJ1bhYJPV44phC+IMu1u2K/i5RriLTPe+yc68XDJ1Z0= +github.com/mattn/go-sqlite3 v1.14.12/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= @@ -235,8 +321,7 @@ github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzE github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= -github.com/mattn/go-sqlite3 v1.14.12 h1:TJ1bhYJPV44phC+IMu1u2K/i5RriLTPe+yc68XDJ1Z0= -github.com/mattn/go-sqlite3 v1.14.12/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= +github.com/ncw/swift v1.0.52/go.mod h1:23YIA4yWVnGwv2dQlN4bB7egfYX6YLn0Yo/S6zZO/ZM= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= @@ -264,6 +349,7 @@ github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnh github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pborman/getopt v0.0.0-20180729010549-6fdd0a2c7117/go.mod h1:85jBQOZwpVEaDAr341tbn15RS4fCAsIst0qp7i8ex1o= github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac= github.com/phpdave11/gofpdf v1.4.2/go.mod h1:zpO6xFn9yxo3YLyMvW8HcKWVdbNqgIfOOp2dXMnm1mY= @@ -321,6 +407,7 @@ github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJ github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= @@ -328,6 +415,7 @@ github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3 github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.0/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= @@ -338,13 +426,23 @@ github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1 github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= +github.com/xitongsys/parquet-go v1.5.1/go.mod h1:xUxwM8ELydxh4edHGegYq1pA8NnMKDx0K/GyB0o2bww= +github.com/xitongsys/parquet-go v1.6.2 h1:MhCaXii4eqceKPu9BwrjLqyK10oX9WF+xGhwvwbw7xM= +github.com/xitongsys/parquet-go v1.6.2/go.mod h1:IulAQyalCm0rPiZVNnCgm/PCL64X2tdSVGMQ/UeKqWA= +github.com/xitongsys/parquet-go-source v0.0.0-20190524061010-2b72cbee77d5/go.mod h1:xxCx7Wpym/3QCo6JhujJX51dzSXrwmb0oH6FQb39SEA= +github.com/xitongsys/parquet-go-source v0.0.0-20200817004010-026bad9b25d0/go.mod h1:HYhIKsdns7xz80OgkbgJYrtQY7FjHWHKH6cvN7+czGE= +github.com/xitongsys/parquet-go-source v0.0.0-20220315005136-aec0fe3e777c h1:UDtocVeACpnwauljUbeHD9UOjjcvF5kLUHruww7VT9A= +github.com/xitongsys/parquet-go-source v0.0.0-20220315005136-aec0fe3e777c/go.mod h1:qLb2Itmdcp7KPa5KZKvhE9U1q5bYSOmgeOckF/H2rQA= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg= go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opentelemetry.io/otel v0.20.0/go.mod h1:Y3ugLH2oa81t5QO+Lty+zXf8zC9L26ax4Nzoxm/dooo= go.opentelemetry.io/otel/metric v0.20.0/go.mod h1:598I5tYlH1vzBjn+BTuhzTCSb/9debfNp6R3s7Pr1eU= go.opentelemetry.io/otel/oteltest v0.20.0/go.mod h1:L7bgKf9ZB7qCwT9Up7i9/pn0PWIa9FqQ2IQ8LoxiGnw= @@ -363,20 +461,32 @@ go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9E go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= go.uber.org/zap v1.16.0/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ= +golang.org/x/crypto v0.0.0-20180723164146-c126467f60eb/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56/go.mod h1:JhuoJpWY28nO4Vef9tZUw9qufEGTyX1+7lmHxV5q5G4= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= golang.org/x/exp v0.0.0-20191002040644-a1355ae1e2c3/go.mod h1:NOZ3BPKG0ec/BKJQgnvsSFpcKLM5xXVWnvZS97DWHgE= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= +golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= golang.org/x/exp v0.0.0-20211028214138-64b4c8e87d1a h1:9kUIHyUjWEuW2MdtaYvvEXTDs2eNylNVAt/ui66QGOg= golang.org/x/exp v0.0.0-20211028214138-64b4c8e87d1a/go.mod h1:a3o/VtDNHN+dCVLEpzjjUHOzR+Ln3DHX056ZPzoZGGA= golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= @@ -392,7 +502,11 @@ golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTk golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= @@ -400,7 +514,9 @@ golang.org/x/mobile v0.0.0-20201217150744-e6ae53a27f4f/go.mod h1:skQtrUTUwhdJvXM golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.1.1-0.20191209134235-331c550502dd/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.5.1-0.20210830214625-1b1db11ec8f4/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= @@ -416,13 +532,22 @@ golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73r golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191112182307-2180aed22343/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= @@ -430,6 +555,8 @@ golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f h1:OfiFi4JbukWwe3lzw+xunroH1 golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -452,14 +579,27 @@ golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191112214154-59a1497f0cea/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200828194041-157a740278f4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -472,7 +612,9 @@ golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359 h1:2B5p2L5IfGiD7+b9BOoRMC6DgObAVZV+Fsp050NqXik= golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= @@ -480,6 +622,8 @@ golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -492,15 +636,34 @@ golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3 golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190927191325-030b2cf1153e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200117012304-6edc0a871e69/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= @@ -517,14 +680,39 @@ gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6d gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc= gonum.org/v1/plot v0.9.0/go.mod h1:3Pcqqmp6RHvJI72kgb8fThyUnav364FOsdDo2aGW5lY= google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= +google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20210630183607-d20f26d13c79/go.mod h1:yiaVoXHpRzHGyxV3o4DktVWY4mSUErTKaeEOq6C3t3U= @@ -535,12 +723,14 @@ google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZi google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.22.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= @@ -563,12 +753,18 @@ google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+Rur google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= +gopkg.in/jcmturner/aescts.v1 v1.0.1/go.mod h1:nsR8qBOg+OucoIW+WMhB3GspUQXq9XorLnQb9XtvcOo= +gopkg.in/jcmturner/dnsutils.v1 v1.0.1/go.mod h1:m3v+5svpVOhtFAP/wSz+yzh4Mc0Fg7eRhxkJMWSIz9Q= +gopkg.in/jcmturner/goidentity.v3 v3.0.0/go.mod h1:oG2kH0IvSYNIu80dVAyu/yoefjq1mNfM5bm88whjWx4= +gopkg.in/jcmturner/gokrb5.v7 v7.3.0/go.mod h1:l8VISx+WGYp+Fp7KRbsiUuXTTOnxIc3Tuvyavf11/WM= +gopkg.in/jcmturner/rpc.v1 v1.1.0/go.mod h1:YIdkC4XfD6GXbzje11McwsDuOlZQSb9W4vfLvuNnlv8= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= @@ -578,6 +774,7 @@ gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= @@ -585,9 +782,15 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.1.3/go.mod h1:NgwopIslSNH47DimFoV78dnkksY2EFtX0ajyb3K/las= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= +rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU= From a5b6607073673e961d74a6facb1eb140af70aa6d Mon Sep 17 00:00:00 2001 From: Kevin Zhang Date: Mon, 28 Mar 2022 13:15:34 -0700 Subject: [PATCH 26/43] Update makefile Signed-off-by: Kevin Zhang --- Makefile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Makefile b/Makefile index 18d6c9a0fd4..5062dfa3e56 100644 --- a/Makefile +++ b/Makefile @@ -142,7 +142,9 @@ compile-go-lib: install-go-proto-dependencies install-go-ci-dependencies cd sdk/python && python setup.py build_go_lib test-go: compile-protos-go + cd go/internal/test && feast init feature_repo && cd feature_repo && feast apply && feast materialize-incremental $(shell date -u +"%Y-%m-%dT%H:%M:%S") && cd ../../../ go test ./... + rm -rf go/internal/test/feature_repo format-go: gofmt -s -w go/ From 5f6868978896b82ce99557efc259d5e77848c07a Mon Sep 17 00:00:00 2001 From: Kevin Zhang Date: Mon, 28 Mar 2022 13:21:11 -0700 Subject: [PATCH 27/43] Fix gitignore issue Signed-off-by: Kevin Zhang --- .gitignore | 2 +- go/internal/test/utils.go | 96 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 97 insertions(+), 1 deletion(-) create mode 100644 go/internal/test/utils.go diff --git a/.gitignore b/.gitignore index 50e2ab912b3..b285234c731 100644 --- a/.gitignore +++ b/.gitignore @@ -105,7 +105,7 @@ coverage.xml .hypothesis/ .pytest_cache/ infra/scripts/*.conf -go/internal/test/ +go/internal/test/feature_repo # Translations *.mo diff --git a/go/internal/test/utils.go b/go/internal/test/utils.go new file mode 100644 index 00000000000..3926258d892 --- /dev/null +++ b/go/internal/test/utils.go @@ -0,0 +1,96 @@ +package test + +import ( + "encoding/json" + "log" + + "github.com/xitongsys/parquet-go-source/local" + "github.com/xitongsys/parquet-go/reader" + "github.com/xitongsys/parquet-go/source" +) + +var jsonSchema string = ` +{ + "Tag": "name=Schema, repetitiontype=REQUIRED", + "Fields": [ + { + "Tag": "name=Event_timestamp, type=INT64, convertedtype=TIMESTAMP_MICROS, repetitiontype=OPTIONAL" + }, + { + "Tag": "name=Driver_id, type=INT64, repetitiontype=OPTIONAL" + }, + { + "Tag": "name=Conv_rate, type=FLOAT, repetitiontype=OPTIONAL" + }, + { + "Tag": "name=Acc_rate, type=FLOAT, repetitiontype=OPTIONAL" + }, + { + "Tag": "name=Avg_daily_trips, type=INT32, repetitiontype=OPTIONAL" + }, + { + "Tag": "name=Created, type=INT64, convertedtype=TIMESTAMP_MICROS, repetitiontype=OPTIONAL" + } + ] +} +` + +type Row struct { + Event_timestamp int64 `json:"Event_timestamp"` + Driver_id int64 `json:"Driver_id"` + Conv_rate float32 `json:"Conv_rate"` + Acc_rate float32 `json:"Acc_rate"` + Avg_daily_trips int32 `json:"Avg_daily_trips"` + Created int64 `json:"Created"` +} + +func ReadParquet(filePath string) ([]*Row, error) { + var fr source.ParquetFile + + fr, err := local.NewLocalFileReader(filePath) + if err != nil { + return nil, err + } + pr, err := reader.NewParquetReader(fr, nil, 1) + if err != nil { + log.Printf("Can't create parquet reader") + return nil, err + } + if err = pr.SetSchemaHandlerFromJSON(jsonSchema); err != nil { + log.Println("Can't set schema from json", err) + return nil, err + } + + num := int(pr.GetNumRows()) + res, err := pr.ReadByNumber(num) + if err != nil { + return nil, err + } + jsonBs, err := json.Marshal(res) + if err != nil { + return nil, err + } + + Rows := []*Row{} + err = json.Unmarshal(jsonBs, &Rows) + if err != nil { + return nil, err + } + return Rows, nil +} + +func GetLatestFeatures(Rows []*Row, entities map[int64]bool) map[int64]*Row { + correctFeatureRows := make(map[int64]*Row) + for _, Row := range Rows { + if _, ok := entities[Row.Driver_id]; ok { + if _, ok := correctFeatureRows[Row.Driver_id]; ok { + if Row.Event_timestamp > correctFeatureRows[Row.Driver_id].Event_timestamp { + correctFeatureRows[Row.Driver_id] = Row + } + } else { + correctFeatureRows[Row.Driver_id] = Row + } + } + } + return correctFeatureRows +} From 3896f9921d50f933cf161c20215e81fc5a78f043 Mon Sep 17 00:00:00 2001 From: Kevin Zhang Date: Mon, 28 Mar 2022 13:24:48 -0700 Subject: [PATCH 28/43] Update makefile Signed-off-by: Kevin Zhang --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 5062dfa3e56..b394c8c9f81 100644 --- a/Makefile +++ b/Makefile @@ -141,7 +141,7 @@ compile-go-lib: install-go-proto-dependencies install-go-ci-dependencies python -m pip install pybindgen==0.22.0 cd sdk/python && python setup.py build_go_lib -test-go: compile-protos-go +test-go: install-python-ci-dependencies compile-protos-go cd go/internal/test && feast init feature_repo && cd feature_repo && feast apply && feast materialize-incremental $(shell date -u +"%Y-%m-%dT%H:%M:%S") && cd ../../../ go test ./... rm -rf go/internal/test/feature_repo From 111c2d06f5fe58144ed36d0b5acc8c7cdfc75337 Mon Sep 17 00:00:00 2001 From: Kevin Zhang Date: Mon, 28 Mar 2022 13:27:30 -0700 Subject: [PATCH 29/43] Update makefile Signed-off-by: Kevin Zhang --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index b394c8c9f81..22f948eabbe 100644 --- a/Makefile +++ b/Makefile @@ -141,7 +141,7 @@ compile-go-lib: install-go-proto-dependencies install-go-ci-dependencies python -m pip install pybindgen==0.22.0 cd sdk/python && python setup.py build_go_lib -test-go: install-python-ci-dependencies compile-protos-go +test-go: install-python compile-protos-go cd go/internal/test && feast init feature_repo && cd feature_repo && feast apply && feast materialize-incremental $(shell date -u +"%Y-%m-%dT%H:%M:%S") && cd ../../../ go test ./... rm -rf go/internal/test/feature_repo From c6197ceae4be5e120e2ff7429f517a59fabb8714 Mon Sep 17 00:00:00 2001 From: Kevin Zhang Date: Mon, 28 Mar 2022 13:30:38 -0700 Subject: [PATCH 30/43] Update makefile Signed-off-by: Kevin Zhang --- .github/workflows/unit_tests.yml | 1 + Makefile | 1 + 2 files changed, 2 insertions(+) diff --git a/.github/workflows/unit_tests.yml b/.github/workflows/unit_tests.yml index 70a8510a101..f9d4bc72f63 100644 --- a/.github/workflows/unit_tests.yml +++ b/.github/workflows/unit_tests.yml @@ -76,6 +76,7 @@ jobs: - name: Upgrade pip version run: | pip install --upgrade "pip>=21.3.1" + pip install pip-tools - name: Setup Go id: setup-go uses: actions/setup-go@v2 diff --git a/Makefile b/Makefile index 22f948eabbe..18cd9098742 100644 --- a/Makefile +++ b/Makefile @@ -142,6 +142,7 @@ compile-go-lib: install-go-proto-dependencies install-go-ci-dependencies cd sdk/python && python setup.py build_go_lib test-go: install-python compile-protos-go + pip install pip-tools cd go/internal/test && feast init feature_repo && cd feature_repo && feast apply && feast materialize-incremental $(shell date -u +"%Y-%m-%dT%H:%M:%S") && cd ../../../ go test ./... rm -rf go/internal/test/feature_repo From a5d4d28121cc5fcfb450ae83bd689c5ee0fe3bd4 Mon Sep 17 00:00:00 2001 From: Kevin Zhang Date: Mon, 28 Mar 2022 13:33:34 -0700 Subject: [PATCH 31/43] Update makefile Signed-off-by: Kevin Zhang --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 18cd9098742..ca3a23502bb 100644 --- a/Makefile +++ b/Makefile @@ -142,7 +142,7 @@ compile-go-lib: install-go-proto-dependencies install-go-ci-dependencies cd sdk/python && python setup.py build_go_lib test-go: install-python compile-protos-go - pip install pip-tools + pip install feast cd go/internal/test && feast init feature_repo && cd feature_repo && feast apply && feast materialize-incremental $(shell date -u +"%Y-%m-%dT%H:%M:%S") && cd ../../../ go test ./... rm -rf go/internal/test/feature_repo From 14d5602f5a169a5dbf78155fa336d5b2427dc462 Mon Sep 17 00:00:00 2001 From: Kevin Zhang Date: Mon, 28 Mar 2022 13:36:28 -0700 Subject: [PATCH 32/43] Update makefile Signed-off-by: Kevin Zhang --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index ca3a23502bb..8ab6cceb8e3 100644 --- a/Makefile +++ b/Makefile @@ -141,7 +141,7 @@ compile-go-lib: install-go-proto-dependencies install-go-ci-dependencies python -m pip install pybindgen==0.22.0 cd sdk/python && python setup.py build_go_lib -test-go: install-python compile-protos-go +test-go: compile-protos-go pip install feast cd go/internal/test && feast init feature_repo && cd feature_repo && feast apply && feast materialize-incremental $(shell date -u +"%Y-%m-%dT%H:%M:%S") && cd ../../../ go test ./... From 02eda7a755fa6337162ad4f8f020efe1f257d6d4 Mon Sep 17 00:00:00 2001 From: Kevin Zhang Date: Mon, 28 Mar 2022 13:44:27 -0700 Subject: [PATCH 33/43] Revert worfklow Signed-off-by: Kevin Zhang --- .github/workflows/unit_tests.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/unit_tests.yml b/.github/workflows/unit_tests.yml index f9d4bc72f63..70a8510a101 100644 --- a/.github/workflows/unit_tests.yml +++ b/.github/workflows/unit_tests.yml @@ -76,7 +76,6 @@ jobs: - name: Upgrade pip version run: | pip install --upgrade "pip>=21.3.1" - pip install pip-tools - name: Setup Go id: setup-go uses: actions/setup-go@v2 From 14876a21259df3251c5f08e93369bf9ce9c5acaa Mon Sep 17 00:00:00 2001 From: Kevin Zhang Date: Mon, 28 Mar 2022 17:33:50 -0700 Subject: [PATCH 34/43] Update build path Signed-off-by: Kevin Zhang --- Makefile | 3 +- go/cmd/server/server_test.go | 10 ++- go/internal/feast/featurestore.go | 1 - go/internal/feast/sqliteonlinestore_test.go | 17 ++-- go/internal/feast/utils.go | 96 +++++++++++++++++++++ go/internal/test/utils.go | 58 +++++++++++++ 6 files changed, 175 insertions(+), 10 deletions(-) create mode 100644 go/internal/feast/utils.go diff --git a/Makefile b/Makefile index 8ab6cceb8e3..7110658080f 100644 --- a/Makefile +++ b/Makefile @@ -141,11 +141,10 @@ compile-go-lib: install-go-proto-dependencies install-go-ci-dependencies python -m pip install pybindgen==0.22.0 cd sdk/python && python setup.py build_go_lib +# Needs feast package to setup the feature store test-go: compile-protos-go pip install feast - cd go/internal/test && feast init feature_repo && cd feature_repo && feast apply && feast materialize-incremental $(shell date -u +"%Y-%m-%dT%H:%M:%S") && cd ../../../ go test ./... - rm -rf go/internal/test/feature_repo format-go: gofmt -s -w go/ diff --git a/go/cmd/server/server_test.go b/go/cmd/server/server_test.go index e5fbf26bfe1..9ee8c87e2fe 100644 --- a/go/cmd/server/server_test.go +++ b/go/cmd/server/server_test.go @@ -69,7 +69,10 @@ func getClient(ctx context.Context, basePath string) (serving.ServingServiceClie func TestGetFeastServingInfo(t *testing.T) { ctx := context.Background() // Pregenerated using `feast init`. - dir := "../../internal/test" + dir := "." + err := test.SetupFeatureRepo(dir) + assert.Nil(t, err) + defer test.CleanUpRepo(dir) client, closer := getClient(ctx, dir) defer closer() response, err := client.GetFeastServingInfo(ctx, &serving.GetFeastServingInfoRequest{}) @@ -80,7 +83,10 @@ func TestGetFeastServingInfo(t *testing.T) { func TestGetOnlineFeaturesSqlite(t *testing.T) { ctx := context.Background() // Pregenerated using `feast init`. - dir := "../../internal/test" + dir := "." + err := test.SetupFeatureRepo(dir) + assert.Nil(t, err) + defer test.CleanUpRepo(dir) client, closer := getClient(ctx, dir) defer closer() entities := make(map[string]*types.RepeatedValue) diff --git a/go/internal/feast/featurestore.go b/go/internal/feast/featurestore.go index 867acdb7cc5..7ac2c0538f1 100644 --- a/go/internal/feast/featurestore.go +++ b/go/internal/feast/featurestore.go @@ -130,7 +130,6 @@ func (fs *FeatureStore) GetOnlineFeatures( entityNameToJoinKeyMap, expectedJoinKeysSet, err := fs.getEntityMaps(requestedFeatureViews) if err != nil { - return nil, err } // TODO (Ly): This should return empty now diff --git a/go/internal/feast/sqliteonlinestore_test.go b/go/internal/feast/sqliteonlinestore_test.go index 27c7d728071..2ce125889d2 100644 --- a/go/internal/feast/sqliteonlinestore_test.go +++ b/go/internal/feast/sqliteonlinestore_test.go @@ -12,8 +12,12 @@ import ( ) func TestSqliteSetup(t *testing.T) { - dir := "../test/feature_repo" - config, err := NewRepoConfigFromFile(dir) + dir := "../test" + feature_repo_path := filepath.Join(dir, "feature_repo") + err := test.SetupFeatureRepo(dir) + assert.Nil(t, err) + defer test.CleanUpRepo(dir) + config, err := NewRepoConfigFromFile(feature_repo_path) assert.Nil(t, err) assert.Equal(t, "feature_repo", config.Project) assert.Equal(t, "data/registry.db", config.GetRegistryConfig().Path) @@ -27,8 +31,11 @@ func TestSqliteSetup(t *testing.T) { } func TestSqliteOnlineRead(t *testing.T) { - dir := "../test/feature_repo" - config, err := NewRepoConfigFromFile(dir) + dir := "../test" + feature_repo_path := filepath.Join(dir, "feature_repo") + test.SetupFeatureRepo(dir) + defer test.CleanUpRepo(dir) + config, err := NewRepoConfigFromFile(feature_repo_path) assert.Nil(t, err) store, err := NewSqliteOnlineStore("feature_repo", config, config.OnlineStore) defer store.Destruct() @@ -58,7 +65,7 @@ func TestSqliteOnlineRead(t *testing.T) { returnedFeatureNames = append(returnedFeatureNames, featureVector[idx].reference.FeatureName) } } - rows, err := test.ReadParquet(filepath.Join(dir, "data", "driver_stats.parquet")) + rows, err := test.ReadParquet(filepath.Join(feature_repo_path, "data", "driver_stats.parquet")) assert.Nil(t, err) entities := map[int64]bool{1005: true, 1001: true, 1003: true} correctFeatures := test.GetLatestFeatures(rows, entities) diff --git a/go/internal/feast/utils.go b/go/internal/feast/utils.go new file mode 100644 index 00000000000..cddc6a636c0 --- /dev/null +++ b/go/internal/feast/utils.go @@ -0,0 +1,96 @@ +package feast + +import ( + "encoding/json" + "log" + + "github.com/xitongsys/parquet-go-source/local" + "github.com/xitongsys/parquet-go/reader" + "github.com/xitongsys/parquet-go/source" +) + +var jsonSchema string = ` +{ + "Tag": "name=Schema, repetitiontype=REQUIRED", + "Fields": [ + { + "Tag": "name=Event_timestamp, type=INT64, convertedtype=TIMESTAMP_MICROS, repetitiontype=OPTIONAL" + }, + { + "Tag": "name=Driver_id, type=INT64, repetitiontype=OPTIONAL" + }, + { + "Tag": "name=Conv_rate, type=FLOAT, repetitiontype=OPTIONAL" + }, + { + "Tag": "name=Acc_rate, type=FLOAT, repetitiontype=OPTIONAL" + }, + { + "Tag": "name=Avg_daily_trips, type=INT32, repetitiontype=OPTIONAL" + }, + { + "Tag": "name=Created, type=INT64, convertedtype=TIMESTAMP_MICROS, repetitiontype=OPTIONAL" + } + ] +} +` + +type Row struct { + Event_timestamp int64 `json:"Event_timestamp"` + Driver_id int64 `json:"Driver_id"` + Conv_rate float32 `json:"Conv_rate"` + Acc_rate float32 `json:"Acc_rate"` + Avg_daily_trips int32 `json:"Avg_daily_trips"` + Created int64 `json:"Created"` +} + +func ReadParquet(filePath string) ([]*Row, error) { + var fr source.ParquetFile + + fr, err := local.NewLocalFileReader(filePath) + if err != nil { + return nil, err + } + pr, err := reader.NewParquetReader(fr, nil, 1) + if err != nil { + log.Printf("Can't create parquet reader") + return nil, err + } + if err = pr.SetSchemaHandlerFromJSON(jsonSchema); err != nil { + log.Println("Can't set schema from json", err) + return nil, err + } + + num := int(pr.GetNumRows()) + res, err := pr.ReadByNumber(num) + if err != nil { + return nil, err + } + jsonBs, err := json.Marshal(res) + if err != nil { + return nil, err + } + + Rows := []*Row{} + err = json.Unmarshal(jsonBs, &Rows) + if err != nil { + return nil, err + } + return Rows, nil +} + +func GetLatestFeatures(Rows []*Row, entities map[int64]bool) map[int64]*Row { + correctFeatureRows := make(map[int64]*Row) + for _, Row := range Rows { + if _, ok := entities[Row.Driver_id]; ok { + if _, ok := correctFeatureRows[Row.Driver_id]; ok { + if Row.Event_timestamp > correctFeatureRows[Row.Driver_id].Event_timestamp { + correctFeatureRows[Row.Driver_id] = Row + } + } else { + correctFeatureRows[Row.Driver_id] = Row + } + } + } + return correctFeatureRows +} diff --git a/go/internal/test/utils.go b/go/internal/test/utils.go index 3926258d892..5c7f3f460a0 100644 --- a/go/internal/test/utils.go +++ b/go/internal/test/utils.go @@ -2,7 +2,12 @@ package test import ( "encoding/json" + "fmt" "log" + "os" + "os/exec" + "path/filepath" + "time" "github.com/xitongsys/parquet-go-source/local" "github.com/xitongsys/parquet-go/reader" @@ -94,3 +99,56 @@ func GetLatestFeatures(Rows []*Row, entities map[int64]bool) map[int64]*Row { } return correctFeatureRows } + +func SetupFeatureRepo(basePath string) error { + cmd := exec.Command("feast", "init", "feature_repo") + path, err := filepath.Abs(basePath) + cmd.Env = os.Environ() + + if err != nil { + return err + } + cmd.Dir = path + err = cmd.Run() + if err != nil { + log.Println("in3") + return err + } + applyCommand := exec.Command("feast", "apply") + applyCommand.Env = os.Environ() + feature_repo_path, err := filepath.Abs(filepath.Join(path, "feature_repo")) + if err != nil { + return err + } + applyCommand.Dir = feature_repo_path + err = applyCommand.Run() + if err != nil { + log.Println("in1") + return err + } + t := time.Now() + formattedTime := fmt.Sprintf("%d-%02d-%02dT%02d:%02d:%02d", + t.Year(), t.Month(), t.Day(), + t.Hour(), t.Minute(), t.Second()) + materializeCommand := exec.Command("feast", "materialize-incremental", formattedTime) + materializeCommand.Env = os.Environ() + materializeCommand.Dir = feature_repo_path + err = materializeCommand.Run() + if err != nil { + log.Println("in2") + return err + } + return nil +} + +func CleanUpRepo(basePath string) error { + feature_repo_path, err := filepath.Abs(filepath.Join(basePath, "feature_repo")) + if err != nil { + return err + } + err = os.RemoveAll(feature_repo_path) + if err != nil { + return err + } + return nil +} From 3bfcfad66943f590bd642133efe010628063e6dd Mon Sep 17 00:00:00 2001 From: Kevin Zhang Date: Tue, 29 Mar 2022 10:04:16 -0700 Subject: [PATCH 35/43] remove Signed-off-by: Kevin Zhang --- go/internal/feast/utils.go | 96 -------------------------------------- 1 file changed, 96 deletions(-) delete mode 100644 go/internal/feast/utils.go diff --git a/go/internal/feast/utils.go b/go/internal/feast/utils.go deleted file mode 100644 index cddc6a636c0..00000000000 --- a/go/internal/feast/utils.go +++ /dev/null @@ -1,96 +0,0 @@ -package feast - -import ( - "encoding/json" - "log" - - "github.com/xitongsys/parquet-go-source/local" - "github.com/xitongsys/parquet-go/reader" - "github.com/xitongsys/parquet-go/source" -) - -var jsonSchema string = ` -{ - "Tag": "name=Schema, repetitiontype=REQUIRED", - "Fields": [ - { - "Tag": "name=Event_timestamp, type=INT64, convertedtype=TIMESTAMP_MICROS, repetitiontype=OPTIONAL" - }, - { - "Tag": "name=Driver_id, type=INT64, repetitiontype=OPTIONAL" - }, - { - "Tag": "name=Conv_rate, type=FLOAT, repetitiontype=OPTIONAL" - }, - { - "Tag": "name=Acc_rate, type=FLOAT, repetitiontype=OPTIONAL" - }, - { - "Tag": "name=Avg_daily_trips, type=INT32, repetitiontype=OPTIONAL" - }, - { - "Tag": "name=Created, type=INT64, convertedtype=TIMESTAMP_MICROS, repetitiontype=OPTIONAL" - } - ] -} -` - -type Row struct { - Event_timestamp int64 `json:"Event_timestamp"` - Driver_id int64 `json:"Driver_id"` - Conv_rate float32 `json:"Conv_rate"` - Acc_rate float32 `json:"Acc_rate"` - Avg_daily_trips int32 `json:"Avg_daily_trips"` - Created int64 `json:"Created"` -} - -func ReadParquet(filePath string) ([]*Row, error) { - var fr source.ParquetFile - - fr, err := local.NewLocalFileReader(filePath) - if err != nil { - return nil, err - } - pr, err := reader.NewParquetReader(fr, nil, 1) - if err != nil { - log.Printf("Can't create parquet reader") - return nil, err - } - if err = pr.SetSchemaHandlerFromJSON(jsonSchema); err != nil { - log.Println("Can't set schema from json", err) - return nil, err - } - - num := int(pr.GetNumRows()) - res, err := pr.ReadByNumber(num) - if err != nil { - return nil, err - } - jsonBs, err := json.Marshal(res) - if err != nil { - return nil, err - } - - Rows := []*Row{} - err = json.Unmarshal(jsonBs, &Rows) - if err != nil { - return nil, err - } - return Rows, nil -} - -func GetLatestFeatures(Rows []*Row, entities map[int64]bool) map[int64]*Row { - correctFeatureRows := make(map[int64]*Row) - for _, Row := range Rows { - if _, ok := entities[Row.Driver_id]; ok { - if _, ok := correctFeatureRows[Row.Driver_id]; ok { - if Row.Event_timestamp > correctFeatureRows[Row.Driver_id].Event_timestamp { - correctFeatureRows[Row.Driver_id] = Row - } - } else { - correctFeatureRows[Row.Driver_id] = Row - } - } - } - return correctFeatureRows -} From 8739e6883d4a70c69df7621750da7a8d6cd52251 Mon Sep 17 00:00:00 2001 From: Kevin Zhang Date: Tue, 29 Mar 2022 10:10:47 -0700 Subject: [PATCH 36/43] rename Signed-off-by: Kevin Zhang --- go/internal/test/{utils.go => go_integration_test_utils.go} | 3 --- 1 file changed, 3 deletions(-) rename go/internal/test/{utils.go => go_integration_test_utils.go} (98%) diff --git a/go/internal/test/utils.go b/go/internal/test/go_integration_test_utils.go similarity index 98% rename from go/internal/test/utils.go rename to go/internal/test/go_integration_test_utils.go index 5c7f3f460a0..13c7d45f846 100644 --- a/go/internal/test/utils.go +++ b/go/internal/test/go_integration_test_utils.go @@ -111,7 +111,6 @@ func SetupFeatureRepo(basePath string) error { cmd.Dir = path err = cmd.Run() if err != nil { - log.Println("in3") return err } applyCommand := exec.Command("feast", "apply") @@ -123,7 +122,6 @@ func SetupFeatureRepo(basePath string) error { applyCommand.Dir = feature_repo_path err = applyCommand.Run() if err != nil { - log.Println("in1") return err } t := time.Now() @@ -135,7 +133,6 @@ func SetupFeatureRepo(basePath string) error { materializeCommand.Dir = feature_repo_path err = materializeCommand.Run() if err != nil { - log.Println("in2") return err } return nil From bd052a34067753eda6987d79924cdf43f0b449bc Mon Sep 17 00:00:00 2001 From: Kevin Zhang Date: Tue, 29 Mar 2022 11:06:59 -0700 Subject: [PATCH 37/43] Address review Signed-off-by: Kevin Zhang --- Makefile | 2 +- go/internal/test/go_integration_test_utils.go | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/Makefile b/Makefile index 7110658080f..9358001f3ca 100644 --- a/Makefile +++ b/Makefile @@ -143,7 +143,7 @@ compile-go-lib: install-go-proto-dependencies install-go-ci-dependencies # Needs feast package to setup the feature store test-go: compile-protos-go - pip install feast + pip install -e "sdk/python[ci]" go test ./... format-go: diff --git a/go/internal/test/go_integration_test_utils.go b/go/internal/test/go_integration_test_utils.go index 13c7d45f846..d21ca46dfb7 100644 --- a/go/internal/test/go_integration_test_utils.go +++ b/go/internal/test/go_integration_test_utils.go @@ -2,7 +2,6 @@ package test import ( "encoding/json" - "fmt" "log" "os" "os/exec" @@ -125,9 +124,8 @@ func SetupFeatureRepo(basePath string) error { return err } t := time.Now() - formattedTime := fmt.Sprintf("%d-%02d-%02dT%02d:%02d:%02d", - t.Year(), t.Month(), t.Day(), - t.Hour(), t.Minute(), t.Second()) + + formattedTime := t.Format(time.RFC3339Nano) materializeCommand := exec.Command("feast", "materialize-incremental", formattedTime) materializeCommand.Env = os.Environ() materializeCommand.Dir = feature_repo_path From 189d3d2a28a9303d89db8e936d19d1649990bef2 Mon Sep 17 00:00:00 2001 From: Kevin Zhang Date: Tue, 29 Mar 2022 11:31:48 -0700 Subject: [PATCH 38/43] fix tests Signed-off-by: Kevin Zhang --- Makefile | 2 +- sdk/python/setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 9358001f3ca..e56997783b9 100644 --- a/Makefile +++ b/Makefile @@ -146,7 +146,7 @@ test-go: compile-protos-go pip install -e "sdk/python[ci]" go test ./... -format-go: +format-go:g gofmt -s -w go/ lint-go: compile-protos-go diff --git a/sdk/python/setup.py b/sdk/python/setup.py index 931bdc3faa2..24ca5a39f6c 100644 --- a/sdk/python/setup.py +++ b/sdk/python/setup.py @@ -101,7 +101,7 @@ CI_REQUIRED = ( [ - "cryptography==3.3.2", + "cryptography==3.4.8", "flake8", "black==19.10b0", "isort>=5", From acc86bb180b9807fd216bf0617734a07a267212a Mon Sep 17 00:00:00 2001 From: Kevin Zhang Date: Tue, 29 Mar 2022 11:34:59 -0700 Subject: [PATCH 39/43] Fix Signed-off-by: Kevin Zhang --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index e56997783b9..9358001f3ca 100644 --- a/Makefile +++ b/Makefile @@ -146,7 +146,7 @@ test-go: compile-protos-go pip install -e "sdk/python[ci]" go test ./... -format-go:g +format-go: gofmt -s -w go/ lint-go: compile-protos-go From 34ab63deed25d28a1884c2d5ef494b0ef16da7c9 Mon Sep 17 00:00:00 2001 From: Kevin Zhang Date: Tue, 29 Mar 2022 11:44:39 -0700 Subject: [PATCH 40/43] see if this fixes test Signed-off-by: Kevin Zhang --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 9358001f3ca..7110658080f 100644 --- a/Makefile +++ b/Makefile @@ -143,7 +143,7 @@ compile-go-lib: install-go-proto-dependencies install-go-ci-dependencies # Needs feast package to setup the feature store test-go: compile-protos-go - pip install -e "sdk/python[ci]" + pip install feast go test ./... format-go: From fa0abf3e7adf06c3ba0efb87facb5bfd2f61fa50 Mon Sep 17 00:00:00 2001 From: Kevin Zhang Date: Tue, 29 Mar 2022 11:51:26 -0700 Subject: [PATCH 41/43] Fix Signed-off-by: Kevin Zhang --- Makefile | 2 +- go/internal/test/go_integration_test_utils.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 7110658080f..9358001f3ca 100644 --- a/Makefile +++ b/Makefile @@ -143,7 +143,7 @@ compile-go-lib: install-go-proto-dependencies install-go-ci-dependencies # Needs feast package to setup the feature store test-go: compile-protos-go - pip install feast + pip install -e "sdk/python[ci]" go test ./... format-go: diff --git a/go/internal/test/go_integration_test_utils.go b/go/internal/test/go_integration_test_utils.go index d21ca46dfb7..5567e62cf5c 100644 --- a/go/internal/test/go_integration_test_utils.go +++ b/go/internal/test/go_integration_test_utils.go @@ -125,7 +125,7 @@ func SetupFeatureRepo(basePath string) error { } t := time.Now() - formattedTime := t.Format(time.RFC3339Nano) + formattedTime := t.Format(time.RFC3339) materializeCommand := exec.Command("feast", "materialize-incremental", formattedTime) materializeCommand.Env = os.Environ() materializeCommand.Dir = feature_repo_path From 941aea8f141f910c54a269a6730bb236dd0e4dc8 Mon Sep 17 00:00:00 2001 From: Kevin Zhang Date: Tue, 29 Mar 2022 11:59:22 -0700 Subject: [PATCH 42/43] revert Signed-off-by: Kevin Zhang --- go/internal/test/go_integration_test_utils.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/go/internal/test/go_integration_test_utils.go b/go/internal/test/go_integration_test_utils.go index 5567e62cf5c..c1a5db342a9 100644 --- a/go/internal/test/go_integration_test_utils.go +++ b/go/internal/test/go_integration_test_utils.go @@ -2,6 +2,7 @@ package test import ( "encoding/json" + "fmt" "log" "os" "os/exec" @@ -125,7 +126,9 @@ func SetupFeatureRepo(basePath string) error { } t := time.Now() - formattedTime := t.Format(time.RFC3339) + formattedTime := fmt.Sprintf("%d-%02d-%02dT%02d:%02d:%02d", + t.Year(), t.Month(), t.Day(), + t.Hour(), t.Minute(), t.Second()) materializeCommand := exec.Command("feast", "materialize-incremental", formattedTime) materializeCommand.Env = os.Environ() materializeCommand.Dir = feature_repo_path From 025a7e420e5c0ef2a21e23c00cf185430abf87dd Mon Sep 17 00:00:00 2001 From: Kevin Zhang Date: Tue, 29 Mar 2022 12:42:14 -0700 Subject: [PATCH 43/43] Will add in separate pr to update cryptography Signed-off-by: Kevin Zhang --- sdk/python/setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/python/setup.py b/sdk/python/setup.py index 24ca5a39f6c..931bdc3faa2 100644 --- a/sdk/python/setup.py +++ b/sdk/python/setup.py @@ -101,7 +101,7 @@ CI_REQUIRED = ( [ - "cryptography==3.4.8", + "cryptography==3.3.2", "flake8", "black==19.10b0", "isort>=5",