diff --git a/integration/commands_administration_test.go b/integration/commands_administration_test.go index 51fd819a6be..b3827173b3e 100644 --- a/integration/commands_administration_test.go +++ b/integration/commands_administration_test.go @@ -986,3 +986,75 @@ func TestCommandsAdministrationCurrentOp(t *testing.T) { _, ok := must.NotFail(doc.Get("inprog")).(*types.Array) assert.True(t, ok) } + +func TestCommandsAdministrationListIndexes(t *testing.T) { + t.Parallel() + + ctx, targetCollections, compatCollections := setup.SetupCompat(t) + + for i := range targetCollections { + targetCollection := targetCollections[i] + compatCollection := compatCollections[i] + + t.Run(targetCollection.Name(), func(t *testing.T) { + t.Parallel() + + targetCur, targetErr := targetCollection.Indexes().List(ctx) + compatCur, compatErr := compatCollection.Indexes().List(ctx) + + require.NoError(t, compatErr) + assert.Equal(t, compatErr, targetErr) + + targetRes := FetchAll(t, ctx, targetCur) + compatRes := FetchAll(t, ctx, compatCur) + + // TODO Use simple assert.Equal after https://github.com/FerretDB/FerretDB/issues/1384 + // assert.Equal(t, compatRes, targetRes) + assert.Empty(t, targetRes) + assert.NotEmpty(t, compatRes) + }) + } +} + +// TestCommandsAdministrationRunCommandListIndexes tests the behavior when listIndexes is called through RunCommand. +// It's handy to use it to test the correctness of errors. +func TestCommandsAdministrationRunCommandListIndexes(t *testing.T) { + t.Parallel() + + ctx, targetCollections, compatCollections := setup.SetupCompat(t) + targetCollection := targetCollections[0] + compatCollection := compatCollections[0] + + for name, tc := range map[string]struct { + collectionName any + expectedError *mongo.CommandError + }{ + "non-existent-collection": { + collectionName: "non-existent-collection", + }, + "invalid-collection-name": { + collectionName: 42, + }, + } { + name, tc := name, tc + + t.Run(name, func(t *testing.T) { + t.Parallel() + + var targetRes bson.D + targetErr := targetCollection.Database().RunCommand( + ctx, bson.D{{"listIndexes", tc.collectionName}}, + ).Decode(&targetRes) + + var compatRes bson.D + compatErr := compatCollection.Database().RunCommand( + ctx, bson.D{{"listIndexes", tc.collectionName}}, + ).Decode(&targetRes) + + require.Nil(t, targetRes) + require.Nil(t, compatRes) + + AssertMatchesCommandError(t, compatErr, targetErr) + }) + } +} diff --git a/internal/handlers/pg/msg_listindexes.go b/internal/handlers/pg/msg_listindexes.go index be968b03c37..dc9b801496b 100644 --- a/internal/handlers/pg/msg_listindexes.go +++ b/internal/handlers/pg/msg_listindexes.go @@ -16,8 +16,13 @@ package pg import ( "context" + "fmt" + + "github.com/jackc/pgx/v4" "github.com/FerretDB/FerretDB/internal/handlers/common" + "github.com/FerretDB/FerretDB/internal/handlers/commonerrors" + "github.com/FerretDB/FerretDB/internal/handlers/pg/pgdb" "github.com/FerretDB/FerretDB/internal/types" "github.com/FerretDB/FerretDB/internal/util/lazyerrors" "github.com/FerretDB/FerretDB/internal/util/must" @@ -26,7 +31,10 @@ import ( // MsgListIndexes implements HandlerInterface. func (h *Handler) MsgListIndexes(ctx context.Context, msg *wire.OpMsg) (*wire.OpMsg, error) { - // TODO https://github.com/FerretDB/FerretDB/issues/278 + dbPool, err := h.DBPool(ctx) + if err != nil { + return nil, lazyerrors.Error(err) + } document, err := msg.Document() if err != nil { @@ -35,22 +43,61 @@ func (h *Handler) MsgListIndexes(ctx context.Context, msg *wire.OpMsg) (*wire.Op common.Ignored(document, h.L, "comment", "cursor") - firstBatch := must.NotFail(types.NewArray( - must.NotFail(types.NewDocument( - "v", float64(2), - "key", must.NotFail(types.NewDocument( - "_id", float64(1), - )), - "name", "_id_", - )), - )) + var db string + + if db, err = common.GetRequiredParam[string](document, "$db"); err != nil { + return nil, err + } + + var collectionParam any + + if collectionParam, err = document.Get(document.Command()); err != nil { + return nil, err + } + + collection, ok := collectionParam.(string) + if !ok { + return nil, commonerrors.NewCommandErrorMsgWithArgument( + commonerrors.ErrBadValue, + fmt.Sprintf("collection name has invalid type %s", common.AliasFromType(collectionParam)), + document.Command(), + ) + } + + var exists bool + + if err = dbPool.InTransactionRetry(ctx, func(tx pgx.Tx) error { + exists, err = pgdb.CollectionExists(ctx, tx, db, collection) + return err + }); err != nil { + return nil, err + } + + if !exists { + return nil, commonerrors.NewCommandErrorMsg( + commonerrors.ErrNamespaceNotFound, + fmt.Sprintf("ns does not exist: %s.%s", db, collection), + ) + } + + // TODO Uncomment this response when we support indexes for _id: https://github.com/FerretDB/FerretDB/issues/1384. + //firstBatch := must.NotFail(types.NewArray( + // must.NotFail(types.NewDocument( + // "v", float64(2), + // "key", must.NotFail(types.NewDocument( + // "_id", float64(1), + // )), + // "name", "_id_", + // )), + //)) + firstBatch := must.NotFail(types.NewArray()) var reply wire.OpMsg must.NoError(reply.SetSections(wire.OpMsgSection{ Documents: []*types.Document{must.NotFail(types.NewDocument( "cursor", must.NotFail(types.NewDocument( - // TODO "ns" field "id", int64(0), + "ns", fmt.Sprintf("%s.%s", db, collection), "firstBatch", firstBatch, )), "ok", float64(1), diff --git a/internal/handlers/tigris/msg_listindexes.go b/internal/handlers/tigris/msg_listindexes.go index 3c048f8f2d3..bb0c95cc38b 100644 --- a/internal/handlers/tigris/msg_listindexes.go +++ b/internal/handlers/tigris/msg_listindexes.go @@ -16,8 +16,10 @@ package tigris import ( "context" + "fmt" "github.com/FerretDB/FerretDB/internal/handlers/common" + "github.com/FerretDB/FerretDB/internal/handlers/commonerrors" "github.com/FerretDB/FerretDB/internal/types" "github.com/FerretDB/FerretDB/internal/util/lazyerrors" "github.com/FerretDB/FerretDB/internal/util/must" @@ -26,7 +28,10 @@ import ( // MsgListIndexes implements HandlerInterface. func (h *Handler) MsgListIndexes(ctx context.Context, msg *wire.OpMsg) (*wire.OpMsg, error) { - // TODO https://github.com/FerretDB/FerretDB/issues/278 + dbPool, err := h.DBPool(ctx) + if err != nil { + return nil, lazyerrors.Error(err) + } document, err := msg.Document() if err != nil { @@ -35,22 +40,57 @@ func (h *Handler) MsgListIndexes(ctx context.Context, msg *wire.OpMsg) (*wire.Op common.Ignored(document, h.L, "comment", "cursor") - firstBatch := must.NotFail(types.NewArray( - must.NotFail(types.NewDocument( - "v", float64(2), - "key", must.NotFail(types.NewDocument( - "_id", float64(1), - )), - "name", "_id_", - )), - )) + var db string + + if db, err = common.GetRequiredParam[string](document, "$db"); err != nil { + return nil, err + } + + var collectionParam any + + if collectionParam, err = document.Get(document.Command()); err != nil { + return nil, err + } + + collection, ok := collectionParam.(string) + if !ok { + return nil, commonerrors.NewCommandErrorMsgWithArgument( + commonerrors.ErrBadValue, + fmt.Sprintf("collection name has invalid type %s", common.AliasFromType(collectionParam)), + document.Command(), + ) + } + + exists, err := dbPool.CollectionExists(ctx, db, collection) + if err != nil { + return nil, lazyerrors.Error(err) + } + + if !exists { + return nil, commonerrors.NewCommandErrorMsg( + commonerrors.ErrNamespaceNotFound, + fmt.Sprintf("ns does not exist: %s.%s", db, collection), + ) + } + + // TODO Uncomment this response when we support indexes for _id: https://github.com/FerretDB/FerretDB/issues/1384. + //firstBatch := must.NotFail(types.NewArray( + // must.NotFail(types.NewDocument( + // "v", float64(2), + // "key", must.NotFail(types.NewDocument( + // "_id", float64(1), + // )), + // "name", "_id_", + // )), + //)) + firstBatch := must.NotFail(types.NewArray()) var reply wire.OpMsg must.NoError(reply.SetSections(wire.OpMsgSection{ Documents: []*types.Document{must.NotFail(types.NewDocument( "cursor", must.NotFail(types.NewDocument( - // TODO "ns" field "id", int64(0), + "ns", fmt.Sprintf("%s.%s", db, collection), "firstBatch", firstBatch, )), "ok", float64(1), diff --git a/internal/handlers/tigris/tigrisdb/collections.go b/internal/handlers/tigris/tigrisdb/collections.go index 67693a1e249..003cfc56139 100644 --- a/internal/handlers/tigris/tigrisdb/collections.go +++ b/internal/handlers/tigris/tigrisdb/collections.go @@ -33,7 +33,7 @@ func (tdb *TigrisDB) CreateCollectionIfNotExist(ctx context.Context, db, collect return false, lazyerrors.Error(err) } - exists, err := tdb.collectionExists(ctx, db, collection) + exists, err := tdb.CollectionExists(ctx, db, collection) if err != nil { return false, lazyerrors.Error(err) } @@ -64,8 +64,8 @@ func (tdb *TigrisDB) CreateCollectionIfNotExist(ctx context.Context, db, collect } } -// collectionExists returns true if collection exists. -func (tdb *TigrisDB) collectionExists(ctx context.Context, db, collection string) (bool, error) { +// CollectionExists returns true if collection exists. +func (tdb *TigrisDB) CollectionExists(ctx context.Context, db, collection string) (bool, error) { _, err := tdb.Driver.UseDatabase(db).DescribeCollection(ctx, collection) switch err := err.(type) { case nil: diff --git a/internal/handlers/tigris/tigrisdb/insert.go b/internal/handlers/tigris/tigrisdb/insert.go index dffd564d690..68255eb3e1b 100644 --- a/internal/handlers/tigris/tigrisdb/insert.go +++ b/internal/handlers/tigris/tigrisdb/insert.go @@ -37,7 +37,7 @@ func (tdb *TigrisDB) InsertManyDocuments(ctx context.Context, db, collection str return nil } - if ok, _ := tdb.collectionExists(ctx, db, collection); !ok { + if ok, _ := tdb.CollectionExists(ctx, db, collection); !ok { doc := must.NotFail(docs.Get(0)).(*types.Document) schema, err := tjson.DocumentSchema(doc) diff --git a/website/docs/reference/supported_commands.md b/website/docs/reference/supported_commands.md index fe79b6e03cc..292db2f505d 100644 --- a/website/docs/reference/supported_commands.md +++ b/website/docs/reference/supported_commands.md @@ -654,10 +654,10 @@ db.aggregate() | | `nameOnly` | | ✅ | | | | `authorizedDatabases` | | ⚠️ | Ingored | | | `comment` | | ⚠️ | Ingored | -| `listIndexes` | | | ❌ | [Issue](https://github.com/FerretDB/FerretDB/issues/278) | -| | `cursor.batchSize` | | ⚠️ | | -| | `comment` | | ⚠️ | | -| `logRotate` | | | ❌ | [Issue](https://github.com/FerretDB/FerretDB/issues/278) | +| `listIndexes` | | | ✅ | | +| | `cursor.batchSize` | | ⚠️ | Ignored | +| | `comment` | | ⚠️ | Ignored | +| `logRotate` | | | ❌ | [Issue](https://github.com/FerretDB/FerretDB/issues/1959) | | | `` | | ⚠️ | | | | `comment` | | ⚠️ | | | `reIndex` | | | ❌ | [Issue](https://github.com/FerretDB/FerretDB/issues/1516) |