Refactor indexes

This commit is contained in:
Anton Zadvorny 2024-01-12 07:13:30 +03:00
parent 2dd1fb91c7
commit f1c6820d66
5 changed files with 156 additions and 43 deletions

4
go.mod
View File

@ -3,7 +3,6 @@ module go.pkg.cx/leaf
go 1.21 go 1.21
require ( require (
github.com/rs/zerolog v1.31.0
github.com/stretchr/testify v1.8.4 github.com/stretchr/testify v1.8.4
go.mongodb.org/mongo-driver v1.13.1 go.mongodb.org/mongo-driver v1.13.1
) )
@ -12,8 +11,6 @@ require (
github.com/davecgh/go-spew v1.1.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect
github.com/golang/snappy v0.0.1 // indirect github.com/golang/snappy v0.0.1 // indirect
github.com/klauspost/compress v1.13.6 // indirect github.com/klauspost/compress v1.13.6 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.19 // indirect
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe // indirect github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/xdg-go/pbkdf2 v1.0.0 // indirect github.com/xdg-go/pbkdf2 v1.0.0 // indirect
@ -22,7 +19,6 @@ require (
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d // indirect golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d // indirect
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 // indirect golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 // indirect
golang.org/x/sys v0.12.0 // indirect
golang.org/x/text v0.7.0 // indirect golang.org/x/text v0.7.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
) )

15
go.sum
View File

@ -1,26 +1,15 @@
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4=
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM= github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/klauspost/compress v1.13.6 h1:P76CopJELS0TiO2mebmnzgWaajssP/EszplttgQxcgc= 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.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe h1:iruDEfMl2E6fbMZ9s0scYfZQ84/6SPL6zC8ACM2oIL0= github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe h1:iruDEfMl2E6fbMZ9s0scYfZQ84/6SPL6zC8ACM2oIL0=
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
github.com/rs/zerolog v1.31.0 h1:FcTR3NnLWW+NnTwwhFWiJSZr4ECLpqCm6QsEnyvbV4A=
github.com/rs/zerolog v1.31.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c= github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c=
@ -52,10 +41,6 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=

View File

@ -3,45 +3,49 @@ package leaf
import ( import (
"context" "context"
"github.com/rs/zerolog"
"go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive" "go.mongodb.org/mongo-driver/mongo"
driver "go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options" "go.mongodb.org/mongo-driver/mongo/options"
) )
func ensureIndexes(ctx context.Context, collection *driver.Collection, models []driver.IndexModel, createIndexes bool, logger *zerolog.Logger) error { type IndexMessage struct {
Name string
Keys interface{}
Message string
}
func ensureIndexes(ctx context.Context, collection *mongo.Collection, models []mongo.IndexModel, createIndexes bool) ([]IndexMessage, error) {
existing, err := indexNames(ctx, collection) existing, err := indexNames(ctx, collection)
if err != nil { if err != nil {
return err return []IndexMessage{}, err
} }
indexes := filterIndexes(models, existing, logger) indexes, messages := filterIndexes(models, existing, []IndexMessage{})
if len(indexes) < 1 { if len(indexes) < 1 {
return nil return messages, nil
} }
if !createIndexes { if !createIndexes {
for _, im := range indexes { for _, im := range indexes {
logger.Warn().Str("name", *im.Options.Name).Msg("index declared but not present") messages = append(messages, IndexMessage{Name: *im.Options.Name, Message: "index declared but not present"})
} }
return nil return messages, nil
} }
_, err = collection.Indexes().CreateMany(ctx, indexes) _, err = collection.Indexes().CreateMany(ctx, indexes)
if err != nil { if err != nil {
return err return messages, err
} }
for _, im := range indexes { for _, im := range indexes {
logger.Info().Str("name", *im.Options.Name).Msg("index created") messages = append(messages, IndexMessage{Name: *im.Options.Name, Keys: im.Keys, Message: "index created"})
} }
return nil return messages, nil
} }
func indexNames(ctx context.Context, collection *driver.Collection) (map[string]struct{}, error) { func indexNames(ctx context.Context, collection *mongo.Collection) (map[string]struct{}, error) {
cursor, err := collection.Indexes().List(ctx) cursor, err := collection.Indexes().List(ctx)
if err != nil { if err != nil {
return nil, err return nil, err
@ -66,11 +70,11 @@ func indexNames(ctx context.Context, collection *driver.Collection) (map[string]
return names, nil return names, nil
} }
func filterIndexes(models []driver.IndexModel, names map[string]struct{}, logger *zerolog.Logger) []driver.IndexModel { func filterIndexes(models []mongo.IndexModel, names map[string]struct{}, messages []IndexMessage) ([]mongo.IndexModel, []IndexMessage) {
var filtered []driver.IndexModel var filtered []mongo.IndexModel
for _, im := range models { for _, im := range models {
if im.Options.Name == nil { if im.Options.Name == nil {
logger.Warn().Interface("keys", im.Keys).Msg("must specify index name explicitly") messages = append(messages, IndexMessage{Keys: im.Keys, Message: "must specify index name explicitly"})
continue continue
} }
@ -79,17 +83,17 @@ func filterIndexes(models []driver.IndexModel, names map[string]struct{}, logger
} }
} }
return filtered return filtered, messages
} }
func withTimestampIndexes(models []driver.IndexModel) []driver.IndexModel { func withTimestampIndexes(models []mongo.IndexModel) []mongo.IndexModel {
tsModels := []driver.IndexModel{ tsModels := []mongo.IndexModel{
{ {
Keys: bson.D{primitive.E{Key: "created_at", Value: 1}}, Keys: bson.D{bson.E{Key: "created_at", Value: 1}},
Options: options.Index().SetName("created_at_1"), Options: options.Index().SetName("created_at_1"),
}, },
{ {
Keys: bson.D{primitive.E{Key: "updated_at", Value: 1}}, Keys: bson.D{bson.E{Key: "updated_at", Value: 1}},
Options: options.Index().SetName("updated_at_1"), Options: options.Index().SetName("updated_at_1"),
}, },
} }

129
index_test.go Normal file
View File

@ -0,0 +1,129 @@
package leaf
import (
"context"
"testing"
"github.com/stretchr/testify/assert"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/integration/mtest"
"go.mongodb.org/mongo-driver/mongo/options"
)
func TestEnsureIndexes(t *testing.T) {
mt := mtest.New(t, mtest.NewOptions().ClientType(mtest.Mock))
ctx := context.Background()
mt.Run("error getting index names", func(mt *mtest.T) {
mt.AddMockResponses(bson.D{{Key: "ok", Value: 0}})
_, err := ensureIndexes(ctx, mt.Coll, nil, false)
assert.Error(t, err)
})
mt.Run("no indexes to create", func(mt *mtest.T) {
mt.AddMockResponses(mtest.CreateCursorResponse(0, "test.foo", mtest.FirstBatch))
messages, err := ensureIndexes(ctx, mt.Coll, nil, false)
assert.NoError(t, err)
assert.Empty(t, messages)
})
mt.Run("createIndexes is false", func(mt *mtest.T) {
indexModel := mongo.IndexModel{
Keys: bson.D{bson.E{Key: "test", Value: 1}},
Options: options.Index().SetName("test_1"),
}
mt.AddMockResponses(mtest.CreateCursorResponse(0, "test.foo", mtest.FirstBatch))
messages, err := ensureIndexes(ctx, mt.Coll, []mongo.IndexModel{indexModel}, false)
assert.NoError(t, err)
assert.Equal(t, 1, len(messages))
assert.Equal(t, "test_1", messages[0].Name)
assert.Equal(t, "index declared but not present", messages[0].Message)
})
mt.Run("createIndexes is true", func(mt *mtest.T) {
indexModel := mongo.IndexModel{
Keys: bson.D{bson.E{Key: "test", Value: 1}},
Options: options.Index().SetName("test_1"),
}
mt.AddMockResponses(mtest.CreateCursorResponse(0, "test.foo", mtest.FirstBatch))
mt.AddMockResponses(mtest.CreateSuccessResponse(bson.E{Key: "ok", Value: 1}))
messages, err := ensureIndexes(ctx, mt.Coll, []mongo.IndexModel{indexModel}, true)
assert.NoError(t, err)
assert.Equal(t, 1, len(messages))
assert.Equal(t, "test_1", messages[0].Name)
assert.Equal(t, "index created", messages[0].Message)
})
}
func TestIndexNames(t *testing.T) {
mt := mtest.New(t, mtest.NewOptions().ClientType(mtest.Mock))
ctx := context.Background()
mt.Run("test index names", func(mt *mtest.T) {
collection := mt.Coll
indexModel := mongo.IndexModel{
Keys: bson.D{bson.E{Key: "test", Value: 1}},
Options: options.Index().SetName("test_1"),
}
indexReposne := bson.D{
{Key: "v", Value: 2},
{Key: "key", Value: bson.M{"test": 1}},
{Key: "name", Value: "test_1"},
}
mt.AddMockResponses(mtest.CreateSuccessResponse(bson.E{Key: "ok", Value: 1}))
_, err := collection.Indexes().CreateOne(ctx, indexModel)
assert.NoError(t, err)
mt.AddMockResponses(mtest.CreateCursorResponse(0, "test.foo", mtest.FirstBatch, indexReposne))
names, err := indexNames(ctx, collection)
assert.NoError(t, err)
assert.Contains(t, names, "test_1")
})
}
func TestFilterIndexes(t *testing.T) {
models := []mongo.IndexModel{
{
Keys: bson.D{bson.E{Key: "test1", Value: 1}},
Options: options.Index().SetName("test_1"),
},
{
Keys: bson.D{bson.E{Key: "test2", Value: 1}},
Options: options.Index(),
},
{
Keys: bson.D{bson.E{Key: "test3", Value: 1}},
Options: options.Index().SetName("test_3"),
},
}
names := map[string]struct{}{"test_1": {}}
filtered, messages := filterIndexes(models, names, []IndexMessage{})
assert.Equal(t, 1, len(filtered))
assert.Equal(t, "test_3", *filtered[0].Options.Name)
assert.Equal(t, 1, len(messages))
assert.Equal(t, "must specify index name explicitly", messages[0].Message)
}
func TestWithTimestampIndexes(t *testing.T) {
models := []mongo.IndexModel{
{
Keys: bson.D{bson.E{Key: "test1", Value: 1}},
Options: options.Index().SetName("test_1"),
},
}
result := withTimestampIndexes(models)
assert.Equal(t, 3, len(result))
assert.Equal(t, "test_1", *result[0].Options.Name)
assert.Equal(t, "created_at_1", *result[1].Options.Name)
assert.Equal(t, "updated_at_1", *result[2].Options.Name)
}

View File

@ -5,7 +5,6 @@ import (
"fmt" "fmt"
"time" "time"
"github.com/rs/zerolog"
"go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive" "go.mongodb.org/mongo-driver/bson/primitive"
driver "go.mongodb.org/mongo-driver/mongo" driver "go.mongodb.org/mongo-driver/mongo"
@ -109,8 +108,8 @@ func (s *Repository[T]) CountDocuments(ctx context.Context, filter interface{},
return int(count), err return int(count), err
} }
func (s *Repository[T]) EnsureIndexes(ctx context.Context, createIndexes bool, logger *zerolog.Logger) error { func (s *Repository[T]) EnsureIndexes(ctx context.Context, createIndexes bool) ([]IndexMessage, error) {
return ensureIndexes(ctx, s.collection, s.indexes, createIndexes, logger) return ensureIndexes(ctx, s.collection, s.indexes, createIndexes)
} }
func repositoryError(collection *driver.Collection, op string, err error) error { func repositoryError(collection *driver.Collection, op string, err error) error {