Skip to content

Commit

Permalink
Allow filtering by name in ListDatabases and ListCollections (#3851)
Browse files Browse the repository at this point in the history
Closes #3601.

Co-authored-by: Henrique Vicente <henriquevicente@gmail.com>
Co-authored-by: Alexey Palazhchenko <alexey.palazhchenko@ferretdb.io>
Co-authored-by: Elena Grahovac <elena.grahovac@ferretdb.io>
Co-authored-by: Patryk Kwiatek <patryk@kwiatek.xyz>
  • Loading branch information
5 people authored Jan 4, 2024
1 parent c8f8c68 commit 6a17ca1
Show file tree
Hide file tree
Showing 13 changed files with 252 additions and 63 deletions.
3 changes: 1 addition & 2 deletions internal/backends/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ func (bc *backendContract) Database(name string) (Database, error) {

// ListDatabasesParams represents the parameters of Backend.ListDatabases method.
type ListDatabasesParams struct {
Name string // TODO https://github.com/FerretDB/FerretDB/issues/3601
Name string
}

// ListDatabasesResult represents the results of Backend.ListDatabases method.
Expand All @@ -139,7 +139,6 @@ type DatabaseInfo struct {

// ListDatabases returns a list of databases sorted by name.
//
// TODO https://github.com/FerretDB/FerretDB/issues/3601
// If ListDatabasesParams' Name is not empty, then only the database with that name should be returned (or an empty list).
//
// Database may not exist; that's not an error.
Expand Down
67 changes: 67 additions & 0 deletions internal/backends/backend_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,3 +64,70 @@ func TestVersion(t *testing.T) {
})
}
}

func TestListDatabases(t *testing.T) {
t.Parallel()

ctx := conninfo.Ctx(testutil.Ctx(t), conninfo.New())

for name, b := range testBackends(t) {
name, b := name, b
t.Run(name, func(t *testing.T) {
t.Parallel()
// setup 3 DB with 1 collections each. random order of db name also ensure sorting test.
dbNames := []string{"testDB2", "testDB1", "testDB3"}
collectionName := "testCollection"

testDB, err := b.Database(dbNames[0])
require.NoError(t, err)
err = testDB.CreateCollection(ctx, &backends.CreateCollectionParams{Name: collectionName})
require.NoError(t, err)

testDB, err = b.Database(dbNames[1])
require.NoError(t, err)
err = testDB.CreateCollection(ctx, &backends.CreateCollectionParams{Name: collectionName})
require.NoError(t, err)

testDB, err = b.Database(dbNames[2])
require.NoError(t, err)
err = testDB.CreateCollection(ctx, &backends.CreateCollectionParams{Name: collectionName})
require.NoError(t, err)

t.Run("ListDatabasesWithGivenName", func(t *testing.T) {
t.Parallel()
dbRes, err := b.ListDatabases(ctx, &backends.ListDatabasesParams{Name: dbNames[2]})
require.NoError(t, err)
require.Equal(t, 1, len(dbRes.Databases), "expected len 1 , since only 1 db with name testDB3")
require.Equal(t, dbNames[2], dbRes.Databases[0].Name, "expected name testDB3")
})

t.Run("ListDatabaseWithDummyName", func(t *testing.T) {
t.Parallel()
dbRes, err := b.ListDatabases(ctx, &backends.ListDatabasesParams{Name: "dummy"})
require.NoError(t, err)
require.Equal(t, 0, len(dbRes.Databases), "expected len 0 since no db with name dummy")
})

t.Run("ListDatabasesWithNilParam", func(t *testing.T) {
t.Parallel()
dbRes, err := b.ListDatabases(ctx, nil)
require.NoError(t, err)
require.Equal(t, 3, len(dbRes.Databases), "expected full list len 3")
require.Equal(t, dbNames[1], dbRes.Databases[0].Name, "expected name testDB1")
require.Equal(t, dbNames[0], dbRes.Databases[1].Name, "expected name testDB2")
require.Equal(t, dbNames[2], dbRes.Databases[2].Name, "expected name testDB3")
})

t.Run("ListDatabasesWithEMptyParam", func(t *testing.T) {
t.Parallel()
var param backends.ListDatabasesParams
dbRes, err := b.ListDatabases(ctx, &param)
require.NoError(t, err)
require.Equal(t, 3, len(dbRes.Databases), "expected full list len 3")
require.Equal(t, dbNames[1], dbRes.Databases[0].Name, "expected name testDB1")
require.Equal(t, dbNames[0], dbRes.Databases[1].Name, "expected name testDB2")
require.Equal(t, dbNames[2], dbRes.Databases[2].Name, "expected name testDB3")
})
})
}
}
69 changes: 69 additions & 0 deletions internal/backends/collection_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -541,3 +541,72 @@ func TestCollectionCompact(t *testing.T) {
})
}
}

func TestListCollections(t *testing.T) {
t.Parallel()

ctx := conninfo.Ctx(testutil.Ctx(t), conninfo.New())

for name, b := range testBackends(t) {
name, b := name, b
t.Run(name, func(t *testing.T) {
t.Parallel()
// setup 1 DB with 3 collections respectively
dbName := "testDB"
collectionNames := []string{"testCollection2", "testCollection1", "testCollection3"}

testDB, err := b.Database(dbName)
require.NoError(t, err)
for _, collectionName := range collectionNames {
err = testDB.CreateCollection(ctx, &backends.CreateCollectionParams{Name: collectionName})
require.NoError(t, err)
}

// retrieve database details
dbRes, err := b.ListDatabases(ctx, &backends.ListDatabasesParams{Name: dbName})
require.NoError(t, err)
require.Equal(t, 1, len(dbRes.Databases), "expected len 1 , since only 1 db with name testDB")
require.Equal(t, dbName, dbRes.Databases[0].Name, "expected name testDB")

db, err := b.Database(dbRes.Databases[0].Name)
require.NoError(t, err)

// test ListCollections with 4 different params
t.Run("ListCollectionWithGivenName", func(t *testing.T) {
t.Parallel()
collRes, err := db.ListCollections(ctx, &backends.ListCollectionsParams{Name: collectionNames[2]})
require.NoError(t, err)
require.Equal(t, 1, len(collRes.Collections), "expected len 1 , with name testCollection3")
require.Equal(t, collectionNames[2], collRes.Collections[0].Name, "expected name testCollection3")
})

t.Run("ListCollectionWithDummyName", func(t *testing.T) {
t.Parallel()
collRes, err := db.ListCollections(ctx, &backends.ListCollectionsParams{Name: "dummy"})
require.NoError(t, err)
require.Equal(t, 0, len(collRes.Collections), "expected len 0 since no collection with name dummy")
})

t.Run("ListCollectionWithNilParams", func(t *testing.T) {
t.Parallel()
collRes, err := db.ListCollections(ctx, nil)
require.NoError(t, err)
require.Equal(t, 3, len(collRes.Collections), "expected full list len 3")
require.Equal(t, collectionNames[1], collRes.Collections[0].Name, "expected name testCollection1")
require.Equal(t, collectionNames[0], collRes.Collections[1].Name, "expected name testCollection2")
require.Equal(t, collectionNames[2], collRes.Collections[2].Name, "expected name testCollection3")
})

t.Run("ListCollectionWithEMptyParams", func(t *testing.T) {
t.Parallel()
var param backends.ListCollectionsParams
collRes, err := db.ListCollections(ctx, &param)
require.NoError(t, err)
require.Equal(t, 3, len(collRes.Collections), "expected full list len 3")
require.Equal(t, collectionNames[1], collRes.Collections[0].Name, "expected name testCollection1")
require.Equal(t, collectionNames[0], collRes.Collections[1].Name, "expected name testCollection2")
require.Equal(t, collectionNames[2], collRes.Collections[2].Name, "expected name testCollection3")
})
})
}
}
3 changes: 1 addition & 2 deletions internal/backends/database.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ func (dbc *databaseContract) Collection(name string) (Collection, error) {

// ListCollectionsParams represents the parameters of Database.ListCollections method.
type ListCollectionsParams struct {
Name string // TODO https://github.com/FerretDB/FerretDB/issues/3601
Name string
}

// ListCollectionsResult represents the results of Database.ListCollections method.
Expand All @@ -102,7 +102,6 @@ func (ci *CollectionInfo) Capped() bool {

// ListCollections returns a list collections in the database sorted by name.
//
// TODO https://github.com/FerretDB/FerretDB/issues/3601
// If ListCollectionsParams' Name is not empty, then only the collection with that name should be returned (or an empty list).
//
// Database may not exist; that's not an error.
Expand Down
10 changes: 2 additions & 8 deletions internal/backends/decorators/oplog/collection.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,7 @@
package oplog

import (
"cmp"
"context"
"slices"
"time"

"go.uber.org/zap"
Expand Down Expand Up @@ -224,17 +222,13 @@ func (c *collection) DropIndexes(ctx context.Context, params *backends.DropIndex
func (c *collection) oplogCollection(ctx context.Context) backends.Collection {
db := must.NotFail(c.origB.Database(oplogDatabase))

cList, err := db.ListCollections(ctx, nil)
cList, err := db.ListCollections(ctx, &backends.ListCollectionsParams{Name: oplogCollection})
if err != nil {
c.l.Error("Failed to list collections", zap.Error(err))
return nil
}

// TODO https://github.com/FerretDB/FerretDB/issues/3601
_, found := slices.BinarySearchFunc(cList.Collections, oplogCollection, func(e backends.CollectionInfo, t string) int {
return cmp.Compare(e.Name, t)
})
if !found {
if len(cList.Collections) == 0 {
c.l.Debug("Collection not found")
return nil
}
Expand Down
29 changes: 23 additions & 6 deletions internal/backends/postgresql/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@
package postgresql

import (
"cmp"
"context"
"slices"

"github.com/prometheus/client_golang/prometheus"
"go.uber.org/zap"
Expand Down Expand Up @@ -126,16 +128,31 @@ func (b *backend) ListDatabases(ctx context.Context, params *backends.ListDataba
return nil, err
}

res := &backends.ListDatabasesResult{
Databases: make([]backends.DatabaseInfo, len(list)),
}
var res *backends.ListDatabasesResult

for i, dbName := range list {
res.Databases[i] = backends.DatabaseInfo{
Name: dbName,
if params != nil && len(params.Name) > 0 {
i, found := slices.BinarySearchFunc(list, params.Name, func(dbName, t string) int {
return cmp.Compare(dbName, t)
})

var filteredList []string

if found {
filteredList = append(filteredList, list[i])
}

list = filteredList
}

res = &backends.ListDatabasesResult{
Databases: make([]backends.DatabaseInfo, 0, len(list)),
}

for _, dbName := range list {
res.Databases = append(res.Databases, backends.DatabaseInfo{
Name: dbName,
})
}
return res, nil
}

Expand Down
24 changes: 23 additions & 1 deletion internal/backends/postgresql/database.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@
package postgresql

import (
"cmp"
"context"
"slices"

"github.com/FerretDB/FerretDB/internal/backends"
"github.com/FerretDB/FerretDB/internal/backends/postgresql/metadata"
Expand Down Expand Up @@ -50,7 +52,27 @@ func (db *database) ListCollections(ctx context.Context, params *backends.ListCo
return nil, lazyerrors.Error(err)
}

res := make([]backends.CollectionInfo, len(list))
var res []backends.CollectionInfo

if params != nil && len(params.Name) > 0 {
nameList := make([]string, len(list))
for i, c := range list {
nameList[i] = c.Name
}

i, found := slices.BinarySearchFunc(nameList, params.Name, func(collectionName, t string) int {
return cmp.Compare(collectionName, t)
})
var filteredList []*metadata.Collection

if found {
filteredList = append(filteredList, list[i])
}
list = filteredList
}

res = make([]backends.CollectionInfo, len(list))

for i, c := range list {
res[i] = backends.CollectionInfo{
Name: c.Name,
Expand Down
19 changes: 18 additions & 1 deletion internal/backends/sqlite/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@
package sqlite

import (
"cmp"
"context"
"slices"

"github.com/prometheus/client_golang/prometheus"
"go.uber.org/zap"
Expand Down Expand Up @@ -102,7 +104,22 @@ func (b *backend) Database(name string) (backends.Database, error) {
func (b *backend) ListDatabases(ctx context.Context, params *backends.ListDatabasesParams) (*backends.ListDatabasesResult, error) {
list := b.r.DatabaseList(ctx)

res := &backends.ListDatabasesResult{
var res *backends.ListDatabasesResult

if params != nil && len(params.Name) > 0 {
i, found := slices.BinarySearchFunc(list, params.Name, func(dbName, t string) int {
return cmp.Compare(dbName, t)
})
var filteredList []string

if found {
filteredList = append(filteredList, list[i])
}

list = filteredList
}

res = &backends.ListDatabasesResult{
Databases: make([]backends.DatabaseInfo, len(list)),
}

Expand Down
23 changes: 22 additions & 1 deletion internal/backends/sqlite/database.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@
package sqlite

import (
"cmp"
"context"
"slices"

"github.com/FerretDB/FerretDB/internal/backends"
"github.com/FerretDB/FerretDB/internal/backends/sqlite/metadata"
Expand Down Expand Up @@ -50,7 +52,26 @@ func (db *database) ListCollections(ctx context.Context, params *backends.ListCo
return nil, lazyerrors.Error(err)
}

res := make([]backends.CollectionInfo, len(list))
var res []backends.CollectionInfo

if params != nil && len(params.Name) > 0 {
nameList := make([]string, len(list))
for i, c := range list {
nameList[i] = c.Name
}

i, found := slices.BinarySearchFunc(nameList, params.Name, func(collectionName, t string) int {
return cmp.Compare(collectionName, t)
})

var filteredList []*metadata.Collection
if found {
filteredList = append(filteredList, list[i])
}
list = filteredList
}

res = make([]backends.CollectionInfo, len(list))
for i, c := range list {
res[i] = backends.CollectionInfo{
Name: c.Name,
Expand Down
Loading

0 comments on commit 6a17ca1

Please sign in to comment.