From f0f7b798a4bbe31e58314d7c2e6fa7ad3fcbcdf7 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Mon, 26 Jun 2023 10:23:14 +0100 Subject: [PATCH 01/38] WIP --- internal/backends/sqlite/collection.go | 28 ++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/internal/backends/sqlite/collection.go b/internal/backends/sqlite/collection.go index 244de22084c3..1be3b404633a 100644 --- a/internal/backends/sqlite/collection.go +++ b/internal/backends/sqlite/collection.go @@ -164,9 +164,33 @@ func (c *collection) Update(ctx context.Context, params *backends.UpdateParams) // Delete implements backends.Collection interface. func (c *collection) Delete(ctx context.Context, params *backends.DeleteParams) (*backends.DeleteResult, error) { - // TODO https://github.com/FerretDB/FerretDB/issues/2751 + db := c.r.DatabaseGetExisting(ctx, c.dbName) + if db == nil { + return nil, lazyerrors.Errorf("no database %q", c.dbName) + } + + tableName := c.r.CollectionToTable(c.name) + + query := fmt.Sprintf(`DELETE FROM %q WHERE json_extract(_ferretdb_sjson, '$._id') = ?`, tableName) + + var deleted int64 + + for _, id := range params.IDs { + res, err := db.ExecContext(ctx, query, id) + if err != nil { + return nil, lazyerrors.Error(err) + } + + rowsAffected, err := res.RowsAffected() + if err != nil { + return nil, lazyerrors.Error(err) + } + + deleted += rowsAffected + } + return &backends.DeleteResult{ - Deleted: 0, + Deleted: deleted, }, nil } From bf4fd41f966401629bc51182d866b70c62acf417 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Mon, 26 Jun 2023 11:12:40 +0100 Subject: [PATCH 02/38] WIP --- internal/handlers/sqlite/msg_delete.go | 134 ++++++++++++++++++++++++- 1 file changed, 133 insertions(+), 1 deletion(-) diff --git a/internal/handlers/sqlite/msg_delete.go b/internal/handlers/sqlite/msg_delete.go index 261b588d9f51..e3da3faa3268 100644 --- a/internal/handlers/sqlite/msg_delete.go +++ b/internal/handlers/sqlite/msg_delete.go @@ -16,12 +16,144 @@ package sqlite import ( "context" + "errors" + "github.com/FerretDB/FerretDB/internal/backends" + "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/iterator" + "github.com/FerretDB/FerretDB/internal/util/lazyerrors" "github.com/FerretDB/FerretDB/internal/util/must" "github.com/FerretDB/FerretDB/internal/wire" ) // MsgDelete implements HandlerInterface. func (h *Handler) MsgDelete(ctx context.Context, msg *wire.OpMsg) (*wire.OpMsg, error) { - return nil, notImplemented(must.NotFail(msg.Document()).Command()) + document, err := msg.Document() + if err != nil { + return nil, lazyerrors.Error(err) + } + + params, err := common.GetDeleteParams(document, h.L) + if err != nil { + return nil, lazyerrors.Error(err) + } + + var deleted int32 + var delErrors commonerrors.WriteErrors + + db := h.b.Database(params.DB) + defer db.Close() + + coll := db.Collection(params.Collection) + + // process every delete filter + for i, deleteParams := range params.Deletes { + del, err := execDelete(ctx, &execDeleteParams{ + coll, + deleteParams.Filter, + params.Collection, + deleteParams.Limited, + }) + if err == nil { + deleted += del + continue + } + + delErrors.Append(err, int32(i)) + + if params.Ordered { + break + } + } + + replyDoc := must.NotFail(types.NewDocument( + "ok", float64(1), + )) + + if delErrors.Len() > 0 { + replyDoc = delErrors.Document() + } + + replyDoc.Set("n", deleted) + + var reply wire.OpMsg + must.NoError(reply.SetSections(wire.OpMsgSection{ + Documents: []*types.Document{replyDoc}, + })) + + return &reply, nil +} + +// execDeleteParams contains parameters for execDelete function. +type execDeleteParams struct { + coll backends.Collection + filter *types.Document + collection string + limited bool +} + +// execDelete fetches documents, filters them out, limits them (if needed) and deletes them. +// If limit is true, only the first matched document is chosen for deletion, otherwise all matched documents are chosen. +// It returns the number of deleted documents or an error. +func execDelete(ctx context.Context, dp *execDeleteParams) (int32, error) { + var deleted int32 + + // filter is used to filter documents on the FerretDB side, + // qp.Filter is used to filter documents on the PostgreSQL side (query pushdown). + filter := dp.filter + + // query documents here + res, err := dp.coll.Query(ctx, nil) + if err != nil { + return 0, err + } + + iter := res.Iter + + defer iter.Close() + + ids := make([]any, 0, 16) + + for { + var doc *types.Document + if _, doc, err = iter.Next(); err != nil { + if errors.Is(err, iterator.ErrIteratorDone) { + break + } + + return 0, err + } + + var matches bool + if matches, err = common.FilterDocument(doc, filter); err != nil { + return 0, err + } + + if !matches { + continue + } + + ids = append(ids, must.NotFail(doc.Get("_id"))) + + // if limit is set, no need to fetch all the documents + if dp.limited { + break + } + } + + // if no documents matched, there is nothing to delete + if len(ids) == 0 { + return 0, nil + } + + deleteRes, err := dp.coll.Delete(ctx, &backends.DeleteParams{IDs: ids}) + if err != nil { + return 0, err + } + + deleted = int32(deleteRes.Deleted) + + return deleted, nil } From 687e56603ec7849d07a301ca190ab4dc227f1d22 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Mon, 26 Jun 2023 11:53:39 +0100 Subject: [PATCH 03/38] WIP --- internal/backends/sqlite/collection.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/internal/backends/sqlite/collection.go b/internal/backends/sqlite/collection.go index 1be3b404633a..e660eb69a996 100644 --- a/internal/backends/sqlite/collection.go +++ b/internal/backends/sqlite/collection.go @@ -19,6 +19,8 @@ import ( "errors" "fmt" + "modernc.org/sqlite" + "github.com/FerretDB/FerretDB/internal/backends" "github.com/FerretDB/FerretDB/internal/backends/sqlite/metadata" "github.com/FerretDB/FerretDB/internal/handlers/sjson" @@ -59,6 +61,14 @@ func (c *collection) Query(ctx context.Context, params *backends.QueryParams) (* rows, err := db.QueryContext(ctx, query) if err != nil { + var serr *sqlite.Error + // No such table returned, so we can return empty result. + if errors.As(err, &serr) && serr.Code() == 1 { // TODO: use constant to check error code + return &backends.QueryResult{ + Iter: newQueryIterator(ctx, nil), + }, nil + } + return nil, lazyerrors.Error(err) } From ec75f20334c8878d2b63fe819984f7f58e1569dc Mon Sep 17 00:00:00 2001 From: Dmitry Date: Mon, 26 Jun 2023 12:36:57 +0100 Subject: [PATCH 04/38] WIP --- internal/backends/sqlite/collection.go | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/internal/backends/sqlite/collection.go b/internal/backends/sqlite/collection.go index e660eb69a996..c48da98981da 100644 --- a/internal/backends/sqlite/collection.go +++ b/internal/backends/sqlite/collection.go @@ -20,6 +20,7 @@ import ( "fmt" "modernc.org/sqlite" + sqlitelib "modernc.org/sqlite/lib" "github.com/FerretDB/FerretDB/internal/backends" "github.com/FerretDB/FerretDB/internal/backends/sqlite/metadata" @@ -61,12 +62,10 @@ func (c *collection) Query(ctx context.Context, params *backends.QueryParams) (* rows, err := db.QueryContext(ctx, query) if err != nil { - var serr *sqlite.Error - // No such table returned, so we can return empty result. - if errors.As(err, &serr) && serr.Code() == 1 { // TODO: use constant to check error code - return &backends.QueryResult{ - Iter: newQueryIterator(ctx, nil), - }, nil + // No such table, return empty result. + var e *sqlite.Error + if errors.As(err, &e) && e.Code() == sqlitelib.SQLITE_ERROR { + return &backends.QueryResult{Iter: newQueryIterator(ctx, nil)}, nil } return nil, lazyerrors.Error(err) From 3466a13f37458dc79936d1a8b1273ea25bf6e3f9 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Mon, 26 Jun 2023 12:46:54 +0100 Subject: [PATCH 05/38] WIP --- internal/handlers/sqlite/msg_delete.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/internal/handlers/sqlite/msg_delete.go b/internal/handlers/sqlite/msg_delete.go index e3da3faa3268..e1acdc747f63 100644 --- a/internal/handlers/sqlite/msg_delete.go +++ b/internal/handlers/sqlite/msg_delete.go @@ -148,6 +148,9 @@ func execDelete(ctx context.Context, dp *execDeleteParams) (int32, error) { return 0, nil } + // close iterator to free db connection. + iter.Close() + deleteRes, err := dp.coll.Delete(ctx, &backends.DeleteParams{IDs: ids}) if err != nil { return 0, err From 6002aeb4562424c92fff35537ed77a35a3714676 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Mon, 26 Jun 2023 12:46:54 +0100 Subject: [PATCH 06/38] WIP --- internal/backends/sqlite/collection.go | 5 ++++- internal/handlers/sqlite/msg_delete.go | 3 +++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/internal/backends/sqlite/collection.go b/internal/backends/sqlite/collection.go index c48da98981da..fb9ea5c11916 100644 --- a/internal/backends/sqlite/collection.go +++ b/internal/backends/sqlite/collection.go @@ -18,6 +18,7 @@ import ( "context" "errors" "fmt" + "strings" "modernc.org/sqlite" sqlitelib "modernc.org/sqlite/lib" @@ -185,7 +186,9 @@ func (c *collection) Delete(ctx context.Context, params *backends.DeleteParams) var deleted int64 for _, id := range params.IDs { - res, err := db.ExecContext(ctx, query, id) + idArg := strings.ReplaceAll(string(must.NotFail(sjson.MarshalSingleValue(id))), "\"", "") + + res, err := db.ExecContext(ctx, query, idArg) if err != nil { return nil, lazyerrors.Error(err) } diff --git a/internal/handlers/sqlite/msg_delete.go b/internal/handlers/sqlite/msg_delete.go index e3da3faa3268..e1acdc747f63 100644 --- a/internal/handlers/sqlite/msg_delete.go +++ b/internal/handlers/sqlite/msg_delete.go @@ -148,6 +148,9 @@ func execDelete(ctx context.Context, dp *execDeleteParams) (int32, error) { return 0, nil } + // close iterator to free db connection. + iter.Close() + deleteRes, err := dp.coll.Delete(ctx, &backends.DeleteParams{IDs: ids}) if err != nil { return 0, err From d99302abcd8c5f1c775975af643849a48599d797 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Mon, 26 Jun 2023 17:14:19 +0100 Subject: [PATCH 07/38] WIP --- internal/handlers/sqlite/msg_delete.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/internal/handlers/sqlite/msg_delete.go b/internal/handlers/sqlite/msg_delete.go index e1acdc747f63..d489b6e05a0c 100644 --- a/internal/handlers/sqlite/msg_delete.go +++ b/internal/handlers/sqlite/msg_delete.go @@ -118,6 +118,7 @@ func execDelete(ctx context.Context, dp *execDeleteParams) (int32, error) { for { var doc *types.Document + if _, doc, err = iter.Next(); err != nil { if errors.Is(err, iterator.ErrIteratorDone) { break @@ -127,6 +128,7 @@ func execDelete(ctx context.Context, dp *execDeleteParams) (int32, error) { } var matches bool + if matches, err = common.FilterDocument(doc, filter); err != nil { return 0, err } From f7edb5ab4cc77a97ccef4b74d85cb337f984e1d9 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Mon, 26 Jun 2023 17:42:00 +0100 Subject: [PATCH 08/38] WIP --- internal/handlers/sqlite/msg_delete.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/internal/handlers/sqlite/msg_delete.go b/internal/handlers/sqlite/msg_delete.go index d489b6e05a0c..04ec865bccc9 100644 --- a/internal/handlers/sqlite/msg_delete.go +++ b/internal/handlers/sqlite/msg_delete.go @@ -141,6 +141,8 @@ func execDelete(ctx context.Context, dp *execDeleteParams) (int32, error) { // if limit is set, no need to fetch all the documents if dp.limited { + iter.Close() + break } } @@ -150,9 +152,6 @@ func execDelete(ctx context.Context, dp *execDeleteParams) (int32, error) { return 0, nil } - // close iterator to free db connection. - iter.Close() - deleteRes, err := dp.coll.Delete(ctx, &backends.DeleteParams{IDs: ids}) if err != nil { return 0, err From b957c63a33f22e9260c1a76678824a9474fc11cf Mon Sep 17 00:00:00 2001 From: Dmitry Date: Mon, 26 Jun 2023 17:43:12 +0100 Subject: [PATCH 09/38] WIP --- internal/backends/sqlite/query_iterator.go | 44 ++++++++++++---------- internal/handlers/sqlite/msg_delete.go | 8 ++-- 2 files changed, 27 insertions(+), 25 deletions(-) diff --git a/internal/backends/sqlite/query_iterator.go b/internal/backends/sqlite/query_iterator.go index 022068ecb447..3a87e1ffd15b 100644 --- a/internal/backends/sqlite/query_iterator.go +++ b/internal/backends/sqlite/query_iterator.go @@ -23,26 +23,29 @@ import ( "github.com/FerretDB/FerretDB/internal/types" "github.com/FerretDB/FerretDB/internal/util/iterator" "github.com/FerretDB/FerretDB/internal/util/lazyerrors" + "github.com/FerretDB/FerretDB/internal/util/observability" "github.com/FerretDB/FerretDB/internal/util/resource" ) // queryIterator implements iterator.Interface to fetch documents from the database. -// -//nolint:vet // for readability type queryIterator struct { - ctx context.Context - - m sync.Mutex - rows *sql.Rows + // the order of fields is weird to make the struct smaller due to alignment + ctx context.Context + rows *sql.Rows // protected by m token *resource.Token + m sync.Mutex } // newQueryIterator returns a new queryIterator for the given *sql.Rows. // // Iterator's Close method closes rows. +// They are also closed by the Next method on any error, including context cancellation, +// to make sure that the database connection is released as early as possible. +// In that case, the iterator's Close method should still be called. // // Nil rows are possible and return already done iterator. +// It still should be Close'd. func newQueryIterator(ctx context.Context, rows *sql.Rows) types.DocumentsIterator { iter := &queryIterator{ ctx: ctx, @@ -55,15 +58,9 @@ func newQueryIterator(ctx context.Context, rows *sql.Rows) types.DocumentsIterat } // Next implements iterator.Interface. -// -// Errors (possibly wrapped) are: -// - iterator.ErrIteratorDone; -// - context.Canceled; -// - context.DeadlineExceeded; -// - something else. -// -// Otherwise, the next document is returned. func (iter *queryIterator) Next() (struct{}, *types.Document, error) { + defer observability.FuncCall(iter.ctx)() + iter.m.Lock() defer iter.m.Unlock() @@ -75,28 +72,31 @@ func (iter *queryIterator) Next() (struct{}, *types.Document, error) { } if err := context.Cause(iter.ctx); err != nil { + iter.close() return unused, nil, lazyerrors.Error(err) } if !iter.rows.Next() { - if err := iter.rows.Err(); err != nil { - return unused, nil, lazyerrors.Error(err) - } + err := iter.rows.Err() - // to avoid context cancellation changing the next `Next()` error - // from `iterator.ErrIteratorDone` to `context.Canceled` iter.close() - return unused, nil, iterator.ErrIteratorDone + if err == nil { + err = iterator.ErrIteratorDone + } + + return unused, nil, lazyerrors.Error(err) } var b []byte if err := iter.rows.Scan(&b); err != nil { + iter.close() return unused, nil, lazyerrors.Error(err) } doc, err := sjson.Unmarshal(b) if err != nil { + iter.close() return unused, nil, lazyerrors.Error(err) } @@ -105,6 +105,8 @@ func (iter *queryIterator) Next() (struct{}, *types.Document, error) { // Close implements iterator.Interface. func (iter *queryIterator) Close() { + defer observability.FuncCall(iter.ctx)() + iter.m.Lock() defer iter.m.Unlock() @@ -115,6 +117,8 @@ func (iter *queryIterator) Close() { // // This should be called only when the caller already holds the mutex. func (iter *queryIterator) close() { + defer observability.FuncCall(iter.ctx)() + if iter.rows != nil { iter.rows.Close() iter.rows = nil diff --git a/internal/handlers/sqlite/msg_delete.go b/internal/handlers/sqlite/msg_delete.go index 04ec865bccc9..5afea5911426 100644 --- a/internal/handlers/sqlite/msg_delete.go +++ b/internal/handlers/sqlite/msg_delete.go @@ -110,16 +110,14 @@ func execDelete(ctx context.Context, dp *execDeleteParams) (int32, error) { return 0, err } - iter := res.Iter - - defer iter.Close() + defer res.Iter.Close() ids := make([]any, 0, 16) for { var doc *types.Document - if _, doc, err = iter.Next(); err != nil { + if _, doc, err = res.Iter.Next(); err != nil { if errors.Is(err, iterator.ErrIteratorDone) { break } @@ -141,7 +139,7 @@ func execDelete(ctx context.Context, dp *execDeleteParams) (int32, error) { // if limit is set, no need to fetch all the documents if dp.limited { - iter.Close() + res.Iter.Close() break } From feb245c6842ab160365cb5305fec39ee7d953b4a Mon Sep 17 00:00:00 2001 From: Dmitry Date: Mon, 26 Jun 2023 17:46:34 +0100 Subject: [PATCH 10/38] WIP --- internal/handlers/sqlite/msg_delete.go | 27 +++++--------------------- 1 file changed, 5 insertions(+), 22 deletions(-) diff --git a/internal/handlers/sqlite/msg_delete.go b/internal/handlers/sqlite/msg_delete.go index 5afea5911426..75ce39bf9539 100644 --- a/internal/handlers/sqlite/msg_delete.go +++ b/internal/handlers/sqlite/msg_delete.go @@ -50,12 +50,7 @@ func (h *Handler) MsgDelete(ctx context.Context, msg *wire.OpMsg) (*wire.OpMsg, // process every delete filter for i, deleteParams := range params.Deletes { - del, err := execDelete(ctx, &execDeleteParams{ - coll, - deleteParams.Filter, - params.Collection, - deleteParams.Limited, - }) + del, err := execDelete(ctx, coll, deleteParams.Filter, deleteParams.Limited) if err == nil { deleted += del continue @@ -86,26 +81,14 @@ func (h *Handler) MsgDelete(ctx context.Context, msg *wire.OpMsg) (*wire.OpMsg, return &reply, nil } -// execDeleteParams contains parameters for execDelete function. -type execDeleteParams struct { - coll backends.Collection - filter *types.Document - collection string - limited bool -} - // execDelete fetches documents, filters them out, limits them (if needed) and deletes them. // If limit is true, only the first matched document is chosen for deletion, otherwise all matched documents are chosen. // It returns the number of deleted documents or an error. -func execDelete(ctx context.Context, dp *execDeleteParams) (int32, error) { +func execDelete(ctx context.Context, coll backends.Collection, filter *types.Document, limited bool) (int32, error) { var deleted int32 - // filter is used to filter documents on the FerretDB side, - // qp.Filter is used to filter documents on the PostgreSQL side (query pushdown). - filter := dp.filter - // query documents here - res, err := dp.coll.Query(ctx, nil) + res, err := coll.Query(ctx, nil) if err != nil { return 0, err } @@ -138,7 +121,7 @@ func execDelete(ctx context.Context, dp *execDeleteParams) (int32, error) { ids = append(ids, must.NotFail(doc.Get("_id"))) // if limit is set, no need to fetch all the documents - if dp.limited { + if limited { res.Iter.Close() break @@ -150,7 +133,7 @@ func execDelete(ctx context.Context, dp *execDeleteParams) (int32, error) { return 0, nil } - deleteRes, err := dp.coll.Delete(ctx, &backends.DeleteParams{IDs: ids}) + deleteRes, err := coll.Delete(ctx, &backends.DeleteParams{IDs: ids}) if err != nil { return 0, err } From 7d847361404e240cff1347693e755eb129217a6a Mon Sep 17 00:00:00 2001 From: Dmitry Date: Tue, 27 Jun 2023 10:33:45 +0100 Subject: [PATCH 11/38] WIP --- internal/backends/sqlite/collection.go | 4 ++-- internal/handlers/sqlite/msg_delete.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/internal/backends/sqlite/collection.go b/internal/backends/sqlite/collection.go index fb9ea5c11916..44c65e178720 100644 --- a/internal/backends/sqlite/collection.go +++ b/internal/backends/sqlite/collection.go @@ -154,7 +154,7 @@ func (c *collection) Update(ctx context.Context, params *backends.UpdateParams) id, _ := doc.Get("_id") must.NotBeZero(id) docArg := must.NotFail(sjson.Marshal(doc)) - idArg := string(must.NotFail(sjson.MarshalSingleValue(id))) + idArg := strings.ReplaceAll(string(must.NotFail(sjson.MarshalSingleValue(id))), "\"", "") r, err := db.ExecContext(ctx, query, docArg, idArg) if err != nil { @@ -176,7 +176,7 @@ func (c *collection) Update(ctx context.Context, params *backends.UpdateParams) func (c *collection) Delete(ctx context.Context, params *backends.DeleteParams) (*backends.DeleteResult, error) { db := c.r.DatabaseGetExisting(ctx, c.dbName) if db == nil { - return nil, lazyerrors.Errorf("no database %q", c.dbName) + return nil, nil } tableName := c.r.CollectionToTable(c.name) diff --git a/internal/handlers/sqlite/msg_delete.go b/internal/handlers/sqlite/msg_delete.go index 75ce39bf9539..f9d3964311d9 100644 --- a/internal/handlers/sqlite/msg_delete.go +++ b/internal/handlers/sqlite/msg_delete.go @@ -95,7 +95,7 @@ func execDelete(ctx context.Context, coll backends.Collection, filter *types.Doc defer res.Iter.Close() - ids := make([]any, 0, 16) + ids := make([]any, 0, 0) for { var doc *types.Document From 164be8d1963fbe7f3904a4cc3bbfff012d97d94a Mon Sep 17 00:00:00 2001 From: Dmitry Date: Tue, 27 Jun 2023 11:36:45 +0100 Subject: [PATCH 12/38] WIP --- internal/backends/sqlite/collection.go | 3 ++- internal/handlers/sqlite/msg_delete.go | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/internal/backends/sqlite/collection.go b/internal/backends/sqlite/collection.go index 44c65e178720..d4d77ade8a2a 100644 --- a/internal/backends/sqlite/collection.go +++ b/internal/backends/sqlite/collection.go @@ -186,7 +186,8 @@ func (c *collection) Delete(ctx context.Context, params *backends.DeleteParams) var deleted int64 for _, id := range params.IDs { - idArg := strings.ReplaceAll(string(must.NotFail(sjson.MarshalSingleValue(id))), "\"", "") + //idArg := strings.ReplaceAll(string(must.NotFail(sjson.MarshalSingleValue(id))), "\"", "") + idArg := string(must.NotFail(sjson.MarshalSingleValue(id))) res, err := db.ExecContext(ctx, query, idArg) if err != nil { diff --git a/internal/handlers/sqlite/msg_delete.go b/internal/handlers/sqlite/msg_delete.go index f9d3964311d9..936a4e7c2ea0 100644 --- a/internal/handlers/sqlite/msg_delete.go +++ b/internal/handlers/sqlite/msg_delete.go @@ -95,7 +95,7 @@ func execDelete(ctx context.Context, coll backends.Collection, filter *types.Doc defer res.Iter.Close() - ids := make([]any, 0, 0) + ids := make([]any, 0) for { var doc *types.Document From f8da6424ccff649e8109f07ff441679e808004ed Mon Sep 17 00:00:00 2001 From: Dmitry Date: Tue, 27 Jun 2023 11:37:07 +0100 Subject: [PATCH 13/38] WIP --- internal/backends/sqlite/collection.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/internal/backends/sqlite/collection.go b/internal/backends/sqlite/collection.go index d4d77ade8a2a..44c65e178720 100644 --- a/internal/backends/sqlite/collection.go +++ b/internal/backends/sqlite/collection.go @@ -186,8 +186,7 @@ func (c *collection) Delete(ctx context.Context, params *backends.DeleteParams) var deleted int64 for _, id := range params.IDs { - //idArg := strings.ReplaceAll(string(must.NotFail(sjson.MarshalSingleValue(id))), "\"", "") - idArg := string(must.NotFail(sjson.MarshalSingleValue(id))) + idArg := strings.ReplaceAll(string(must.NotFail(sjson.MarshalSingleValue(id))), "\"", "") res, err := db.ExecContext(ctx, query, idArg) if err != nil { From ac4e4731f3efc1b790bd895cf5c8847e39220175 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Tue, 27 Jun 2023 11:46:57 +0100 Subject: [PATCH 14/38] WIP --- internal/backends/collection.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/internal/backends/collection.go b/internal/backends/collection.go index 957261b98c31..5de821cd0ceb 100644 --- a/internal/backends/collection.go +++ b/internal/backends/collection.go @@ -68,6 +68,8 @@ type QueryResult struct { // Query executes a query against the collection. // +// If the collection is not exist it returns empty iterator. +// // The passed context should be used for canceling the initial query. // It also can be used to close the returned iterator and free underlying resources, // but doing so is not necessary - the handler will do that anyway. From a95761ebb78258badc97e0e25136805a88f7bb55 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Tue, 27 Jun 2023 11:48:34 +0100 Subject: [PATCH 15/38] WIP --- internal/backends/sqlite/collection.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/backends/sqlite/collection.go b/internal/backends/sqlite/collection.go index 44c65e178720..7c0cd47751b6 100644 --- a/internal/backends/sqlite/collection.go +++ b/internal/backends/sqlite/collection.go @@ -176,7 +176,7 @@ func (c *collection) Update(ctx context.Context, params *backends.UpdateParams) func (c *collection) Delete(ctx context.Context, params *backends.DeleteParams) (*backends.DeleteResult, error) { db := c.r.DatabaseGetExisting(ctx, c.dbName) if db == nil { - return nil, nil + return &backends.DeleteResult{Deleted: 0}, nil } tableName := c.r.CollectionToTable(c.name) From 73ec4959eaacc1ef0a841e1e427acf3d4ab4106f Mon Sep 17 00:00:00 2001 From: Dmitry Date: Tue, 27 Jun 2023 11:49:43 +0100 Subject: [PATCH 16/38] WIP --- internal/backends/collection.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/internal/backends/collection.go b/internal/backends/collection.go index 5de821cd0ceb..392b4b0c3c7a 100644 --- a/internal/backends/collection.go +++ b/internal/backends/collection.go @@ -134,6 +134,8 @@ type DeleteResult struct { } // Delete deletes documents in collection. +// +// If request database is not exist it returns 0 deleted documents. func (cc *collectionContract) Delete(ctx context.Context, params *DeleteParams) (res *DeleteResult, err error) { defer observability.FuncCall(ctx)() defer checkError(err) From 81b95957b78627e1a85676a068363235a31ddfe7 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Tue, 27 Jun 2023 14:47:32 +0100 Subject: [PATCH 17/38] WIP --- internal/handlers/sqlite/msg_delete.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/handlers/sqlite/msg_delete.go b/internal/handlers/sqlite/msg_delete.go index 936a4e7c2ea0..c077c5e5fa34 100644 --- a/internal/handlers/sqlite/msg_delete.go +++ b/internal/handlers/sqlite/msg_delete.go @@ -95,7 +95,7 @@ func execDelete(ctx context.Context, coll backends.Collection, filter *types.Doc defer res.Iter.Close() - ids := make([]any, 0) + var ids []any for { var doc *types.Document From 3788becca4af36934cbca1a01f83c092000a423d Mon Sep 17 00:00:00 2001 From: Dmitry Date: Tue, 27 Jun 2023 17:55:04 +0100 Subject: [PATCH 18/38] Apply suggestions from code review Co-authored-by: Elena Grahovac --- internal/backends/collection.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/backends/collection.go b/internal/backends/collection.go index 392b4b0c3c7a..e8cb1e9f8824 100644 --- a/internal/backends/collection.go +++ b/internal/backends/collection.go @@ -68,7 +68,7 @@ type QueryResult struct { // Query executes a query against the collection. // -// If the collection is not exist it returns empty iterator. +// If the collection does not exist it returns empty iterator. // // The passed context should be used for canceling the initial query. // It also can be used to close the returned iterator and free underlying resources, @@ -135,7 +135,7 @@ type DeleteResult struct { // Delete deletes documents in collection. // -// If request database is not exist it returns 0 deleted documents. +// If request database does not exist it returns 0 deleted documents. func (cc *collectionContract) Delete(ctx context.Context, params *DeleteParams) (res *DeleteResult, err error) { defer observability.FuncCall(ctx)() defer checkError(err) From 3b713728850bdda50fc6a3c0a28a0c2dc8744e9d Mon Sep 17 00:00:00 2001 From: Dmitry Date: Wed, 28 Jun 2023 10:21:33 +0100 Subject: [PATCH 19/38] WIP --- internal/backends/sqlite/collection.go | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/internal/backends/sqlite/collection.go b/internal/backends/sqlite/collection.go index 7c0cd47751b6..3dacc3b953f4 100644 --- a/internal/backends/sqlite/collection.go +++ b/internal/backends/sqlite/collection.go @@ -18,7 +18,6 @@ import ( "context" "errors" "fmt" - "strings" "modernc.org/sqlite" sqlitelib "modernc.org/sqlite/lib" @@ -129,7 +128,7 @@ func (c *collection) Update(ctx context.Context, params *backends.UpdateParams) tableName := c.r.CollectionToTable(c.name) - query := fmt.Sprintf(`UPDATE %q SET _ferretdb_sjson = ? WHERE json_extract(_ferretdb_sjson, '$._id') = ?`, tableName) + query := fmt.Sprintf(`UPDATE %q SET _ferretdb_sjson = ? WHERE _ferretdb_sjson -> '$._id' = ?`, tableName) var res backends.UpdateResult @@ -154,7 +153,7 @@ func (c *collection) Update(ctx context.Context, params *backends.UpdateParams) id, _ := doc.Get("_id") must.NotBeZero(id) docArg := must.NotFail(sjson.Marshal(doc)) - idArg := strings.ReplaceAll(string(must.NotFail(sjson.MarshalSingleValue(id))), "\"", "") + idArg := string(must.NotFail(sjson.MarshalSingleValue(id))) r, err := db.ExecContext(ctx, query, docArg, idArg) if err != nil { @@ -181,12 +180,12 @@ func (c *collection) Delete(ctx context.Context, params *backends.DeleteParams) tableName := c.r.CollectionToTable(c.name) - query := fmt.Sprintf(`DELETE FROM %q WHERE json_extract(_ferretdb_sjson, '$._id') = ?`, tableName) + query := fmt.Sprintf(`DELETE FROM %q WHERE _ferretdb_sjson -> '$._id' = ?`, tableName) var deleted int64 for _, id := range params.IDs { - idArg := strings.ReplaceAll(string(must.NotFail(sjson.MarshalSingleValue(id))), "\"", "") + idArg := string(must.NotFail(sjson.MarshalSingleValue(id))) res, err := db.ExecContext(ctx, query, idArg) if err != nil { From 3d03875319f2550dc82081bcbf71c4411a9fc23e Mon Sep 17 00:00:00 2001 From: Dmitry Date: Wed, 28 Jun 2023 10:40:32 +0100 Subject: [PATCH 20/38] WIP --- integration/delete_test.go | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/integration/delete_test.go b/integration/delete_test.go index a45e6af3edd8..c20fa5b946fb 100644 --- a/integration/delete_test.go +++ b/integration/delete_test.go @@ -138,3 +138,21 @@ func TestDelete(t *testing.T) { }) } } + +func TestDeleteNotExistingDatabase(t *testing.T) { + t.Parallel() + + ctx, collection := setup.Setup(t) + + db := collection.Database().Client().Database("doesnotexist") + + var res bson.D + err := db.RunCommand(ctx, bson.D{ + {"delete", "test"}, + {"deletes", bson.A{bson.D{{"q", bson.D{{"v", "foo"}}}, {"limit", 1}}}}, + }).Decode(&res) + + assert.NoError(t, err) + + assert.Equal(t, int32(0), res.Map()["n"]) +} From 73fed900c2a12b1fe6ad430b2b04356bcd2bf701 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Wed, 28 Jun 2023 11:55:18 +0100 Subject: [PATCH 21/38] WIP --- integration/basic_test.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/integration/basic_test.go b/integration/basic_test.go index 7906d97f4dc0..a1df4ec00975 100644 --- a/integration/basic_test.go +++ b/integration/basic_test.go @@ -15,6 +15,7 @@ package integration import ( + "errors" "fmt" "math" "strings" @@ -25,7 +26,6 @@ import ( "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/mongo" "go.mongodb.org/mongo-driver/mongo/options" - "go.mongodb.org/mongo-driver/x/mongo/driver" "github.com/FerretDB/FerretDB/integration/setup" "github.com/FerretDB/FerretDB/integration/shareddata" @@ -524,8 +524,7 @@ func TestDatabaseName(t *testing.T) { ctx, collection := setup.Setup(t) err := collection.Database().Client().Database("").CreateCollection(ctx, collection.Name()) - expectedErr := driver.InvalidOperationError(driver.InvalidOperationError{MissingField: "Database"}) - assert.Equal(t, expectedErr, err) + assert.Equal(t, errors.New("database name cannot be empty"), err) }) t.Run("63ok", func(t *testing.T) { From 98de1cff801162bf9eb439aa42cd8da8a555b7d1 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Wed, 28 Jun 2023 12:03:10 +0100 Subject: [PATCH 22/38] WIP --- integration/delete_test.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/integration/delete_test.go b/integration/delete_test.go index c20fa5b946fb..218207b99d5a 100644 --- a/integration/delete_test.go +++ b/integration/delete_test.go @@ -154,5 +154,7 @@ func TestDeleteNotExistingDatabase(t *testing.T) { assert.NoError(t, err) - assert.Equal(t, int32(0), res.Map()["n"]) + expectedRes := bson.D{{"ok", float64(1)}, {"n", int32(0)}} + + assert.Equal(t, expectedRes, res) } From bebad9b9baa75599473631b07528f7706eb7d347 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Wed, 28 Jun 2023 12:22:00 +0100 Subject: [PATCH 23/38] WIP --- integration/delete_compat_test.go | 30 ++++++++++++++++++++++++++ integration/delete_test.go | 5 ++++- internal/handlers/sqlite/msg_delete.go | 23 +++++++++----------- 3 files changed, 44 insertions(+), 14 deletions(-) diff --git a/integration/delete_compat_test.go b/integration/delete_compat_test.go index 6b9b74a9054b..7324c3cfa85c 100644 --- a/integration/delete_compat_test.go +++ b/integration/delete_compat_test.go @@ -73,6 +73,36 @@ func TestDeleteCompat(t *testing.T) { }, ordered: true, }, + "TwoDuplicateFilter": { + filters: []bson.D{ + {{"v", int32(42)}}, + {{"v", int32(42)}}, + }, + }, + + {v: 42}, + {v: 42}, + {v: 42}, + + /* + + db.runCommand( + { + delete: "test", + deletes: [ + { + q : {v: 42}, + limit : 1 + }, + { + q : {v: 42}, + limit : 1 + }, + ] + } + ) + + */ "OrderedError": { filters: []bson.D{ diff --git a/integration/delete_test.go b/integration/delete_test.go index 218207b99d5a..de4f01dd25a8 100644 --- a/integration/delete_test.go +++ b/integration/delete_test.go @@ -149,7 +149,10 @@ func TestDeleteNotExistingDatabase(t *testing.T) { var res bson.D err := db.RunCommand(ctx, bson.D{ {"delete", "test"}, - {"deletes", bson.A{bson.D{{"q", bson.D{{"v", "foo"}}}, {"limit", 1}}}}, + {"deletes", bson.A{ + bson.D{{"q", bson.D{{"v", "foo"}}}, {"limit", 1}}, + bson.D{{"q", bson.D{{"v", "foo"}}}, {"limit", 1}}, + }}, }).Decode(&res) assert.NoError(t, err) diff --git a/internal/handlers/sqlite/msg_delete.go b/internal/handlers/sqlite/msg_delete.go index c077c5e5fa34..6745a1bead1b 100644 --- a/internal/handlers/sqlite/msg_delete.go +++ b/internal/handlers/sqlite/msg_delete.go @@ -82,36 +82,33 @@ func (h *Handler) MsgDelete(ctx context.Context, msg *wire.OpMsg) (*wire.OpMsg, } // execDelete fetches documents, filters them out, limits them (if needed) and deletes them. -// If limit is true, only the first matched document is chosen for deletion, otherwise all matched documents are chosen. +// If limited is true, only the first matched document is chosen for deletion, otherwise all matched documents are chosen. // It returns the number of deleted documents or an error. func execDelete(ctx context.Context, coll backends.Collection, filter *types.Document, limited bool) (int32, error) { - var deleted int32 - // query documents here res, err := coll.Query(ctx, nil) if err != nil { - return 0, err + return 0, lazyerrors.Error(err) } defer res.Iter.Close() var ids []any + var deleted int32 for { - var doc *types.Document - - if _, doc, err = res.Iter.Next(); err != nil { + _, doc, err := res.Iter.Next() + if err != nil { if errors.Is(err, iterator.ErrIteratorDone) { break } - return 0, err + return 0, lazyerrors.Error(err) } - var matches bool - - if matches, err = common.FilterDocument(doc, filter); err != nil { - return 0, err + matches, err := common.FilterDocument(doc, filter) + if err != nil { + return 0, lazyerrors.Error(err) } if !matches { @@ -122,7 +119,7 @@ func execDelete(ctx context.Context, coll backends.Collection, filter *types.Doc // if limit is set, no need to fetch all the documents if limited { - res.Iter.Close() + res.Iter.Close() // good comment break } From 1c2ca9a2095b80476b88fa367eca61f5d6aefa5d Mon Sep 17 00:00:00 2001 From: Dmitry Date: Wed, 28 Jun 2023 13:54:07 +0100 Subject: [PATCH 24/38] WIP --- integration/delete_command_compat_test.go | 113 ++++++++++++++++++++++ integration/delete_compat_test.go | 30 ------ integration/delete_test.go | 1 - internal/handlers/pg/msg_delete.go | 6 +- internal/handlers/sqlite/msg_delete.go | 8 +- 5 files changed, 121 insertions(+), 37 deletions(-) create mode 100644 integration/delete_command_compat_test.go diff --git a/integration/delete_command_compat_test.go b/integration/delete_command_compat_test.go new file mode 100644 index 000000000000..d011641c37a6 --- /dev/null +++ b/integration/delete_command_compat_test.go @@ -0,0 +1,113 @@ +// Copyright 2021 FerretDB Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package integration + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.mongodb.org/mongo-driver/bson" + + "github.com/FerretDB/FerretDB/integration/setup" +) + +// deleteCommandCompatTestCase describes delete compatibility test case. +type deleteCommandCompatTestCase struct { + deletes bson.A // required + resultType compatTestCaseResultType // defaults to nonEmptyResult + + skip string // optional, skip test with a specified reason +} + +func testDeleteCommandCompat(t *testing.T, testCases map[string]deleteCommandCompatTestCase) { + t.Helper() + + for name, tc := range testCases { + name, tc := name, tc + t.Run(name, func(t *testing.T) { + t.Helper() + + t.Parallel() + + // Use per-test setup because deletes modify data set. + ctx, targetCollections, compatCollections := setup.SetupCompat(t) + + var nonEmptyResults bool + for i := range targetCollections { + targetCollection := targetCollections[i] + compatCollection := compatCollections[i] + t.Run(targetCollection.Name(), func(t *testing.T) { + t.Helper() + + var targetRes, compatRes bson.D + targetErr := targetCollection.Database().RunCommand(ctx, + bson.D{ + {"delete", targetCollection.Name()}, + {"deletes", tc.deletes}, + }).Decode(&targetRes) + compatErr := compatCollection.Database().RunCommand(ctx, + bson.D{ + {"delete", compatCollection.Name()}, + {"deletes", tc.deletes}, + }).Decode(&compatRes) + + if targetErr != nil { + t.Logf("Target error: %v", targetErr) + targetErr = UnsetRaw(t, targetErr) + compatErr = UnsetRaw(t, compatErr) + assert.Equal(t, compatErr, targetErr) + } else { + require.NoError(t, compatErr, "compat error; target returned no error") + } + + assert.Equal(t, compatRes, targetRes) + + targetDocs := FindAll(t, ctx, targetCollection) + compatDocs := FindAll(t, ctx, compatCollection) + + t.Logf("Compat (expected) IDs: %v", CollectIDs(t, compatDocs)) + t.Logf("Target (actual) IDs: %v", CollectIDs(t, targetDocs)) + AssertEqualDocumentsSlice(t, compatDocs, targetDocs) + }) + } + + switch tc.resultType { + case nonEmptyResult: + assert.True(t, nonEmptyResults, "expected non-empty results (some documents should be deleted)") + case emptyResult: + assert.False(t, nonEmptyResults, "expected empty results (no documents should be deleted)") + default: + t.Fatalf("unknown result type %v", tc.resultType) + } + }) + } +} + +func TestDeleteCompatCommand(t *testing.T) { + t.Parallel() + + testCases := map[string]deleteCommandCompatTestCase{ + "DuplicateFilter": { + deletes: bson.A{ + bson.D{{"q", bson.D{{"v", "foo"}}}, {"limit", 1}}, + bson.D{{"q", bson.D{{"v", "foo"}}}, {"limit", 1}}, + }, + skip: "https://github.com/FerretDB/FerretDB/issues/2935", + }, + } + + testDeleteCommandCompat(t, testCases) +} diff --git a/integration/delete_compat_test.go b/integration/delete_compat_test.go index 7324c3cfa85c..6b9b74a9054b 100644 --- a/integration/delete_compat_test.go +++ b/integration/delete_compat_test.go @@ -73,36 +73,6 @@ func TestDeleteCompat(t *testing.T) { }, ordered: true, }, - "TwoDuplicateFilter": { - filters: []bson.D{ - {{"v", int32(42)}}, - {{"v", int32(42)}}, - }, - }, - - {v: 42}, - {v: 42}, - {v: 42}, - - /* - - db.runCommand( - { - delete: "test", - deletes: [ - { - q : {v: 42}, - limit : 1 - }, - { - q : {v: 42}, - limit : 1 - }, - ] - } - ) - - */ "OrderedError": { filters: []bson.D{ diff --git a/integration/delete_test.go b/integration/delete_test.go index de4f01dd25a8..113dac07abea 100644 --- a/integration/delete_test.go +++ b/integration/delete_test.go @@ -151,7 +151,6 @@ func TestDeleteNotExistingDatabase(t *testing.T) { {"delete", "test"}, {"deletes", bson.A{ bson.D{{"q", bson.D{{"v", "foo"}}}, {"limit", 1}}, - bson.D{{"q", bson.D{{"v", "foo"}}}, {"limit", 1}}, }}, }).Decode(&res) diff --git a/internal/handlers/pg/msg_delete.go b/internal/handlers/pg/msg_delete.go index 0b61ae78f91e..1d32def49729 100644 --- a/internal/handlers/pg/msg_delete.go +++ b/internal/handlers/pg/msg_delete.go @@ -81,15 +81,15 @@ func (h *Handler) MsgDelete(ctx context.Context, msg *wire.OpMsg) (*wire.OpMsg, } replyDoc := must.NotFail(types.NewDocument( - "ok", float64(1), + "n", deleted, )) if delErrors.Len() > 0 { replyDoc = delErrors.Document() + } else { + replyDoc.Set("ok", float64(1)) } - replyDoc.Set("n", deleted) - var reply wire.OpMsg must.NoError(reply.SetSections(wire.OpMsgSection{ Documents: []*types.Document{replyDoc}, diff --git a/internal/handlers/sqlite/msg_delete.go b/internal/handlers/sqlite/msg_delete.go index 6745a1bead1b..e3597aea49b3 100644 --- a/internal/handlers/sqlite/msg_delete.go +++ b/internal/handlers/sqlite/msg_delete.go @@ -64,14 +64,16 @@ func (h *Handler) MsgDelete(ctx context.Context, msg *wire.OpMsg) (*wire.OpMsg, } replyDoc := must.NotFail(types.NewDocument( - "ok", float64(1), + "n", deleted, )) if delErrors.Len() > 0 { replyDoc = delErrors.Document() + } else { + replyDoc.Set("ok", float64(1)) } - replyDoc.Set("n", deleted) + //replyDoc.Set("n", deleted) var reply wire.OpMsg must.NoError(reply.SetSections(wire.OpMsgSection{ @@ -119,7 +121,7 @@ func execDelete(ctx context.Context, coll backends.Collection, filter *types.Doc // if limit is set, no need to fetch all the documents if limited { - res.Iter.Close() // good comment + res.Iter.Close() // call Close() to release the underlying connection early break } From 70e68a8cec550b3c5209ac0d9b3b1708f6507e51 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Wed, 28 Jun 2023 14:11:50 +0100 Subject: [PATCH 25/38] WIP --- integration/delete_command_compat_test.go | 2 +- internal/handlers/sqlite/msg_delete.go | 15 +++++---------- 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/integration/delete_command_compat_test.go b/integration/delete_command_compat_test.go index d011641c37a6..54fbd2c94a87 100644 --- a/integration/delete_command_compat_test.go +++ b/integration/delete_command_compat_test.go @@ -25,7 +25,7 @@ import ( ) // deleteCommandCompatTestCase describes delete compatibility test case. -type deleteCommandCompatTestCase struct { +type deleteCommandCompatTestCase struct { //nolint:vet // for readability deletes bson.A // required resultType compatTestCaseResultType // defaults to nonEmptyResult diff --git a/internal/handlers/sqlite/msg_delete.go b/internal/handlers/sqlite/msg_delete.go index e3597aea49b3..badbaa9e2f95 100644 --- a/internal/handlers/sqlite/msg_delete.go +++ b/internal/handlers/sqlite/msg_delete.go @@ -73,8 +73,6 @@ func (h *Handler) MsgDelete(ctx context.Context, msg *wire.OpMsg) (*wire.OpMsg, replyDoc.Set("ok", float64(1)) } - //replyDoc.Set("n", deleted) - var reply wire.OpMsg must.NoError(reply.SetSections(wire.OpMsgSection{ Documents: []*types.Document{replyDoc}, @@ -96,11 +94,11 @@ func execDelete(ctx context.Context, coll backends.Collection, filter *types.Doc defer res.Iter.Close() var ids []any - var deleted int32 + var doc *types.Document + var matches bool for { - _, doc, err := res.Iter.Next() - if err != nil { + if _, doc, err = res.Iter.Next(); err != nil { if errors.Is(err, iterator.ErrIteratorDone) { break } @@ -108,8 +106,7 @@ func execDelete(ctx context.Context, coll backends.Collection, filter *types.Doc return 0, lazyerrors.Error(err) } - matches, err := common.FilterDocument(doc, filter) - if err != nil { + if matches, err = common.FilterDocument(doc, filter); err != nil { return 0, lazyerrors.Error(err) } @@ -137,7 +134,5 @@ func execDelete(ctx context.Context, coll backends.Collection, filter *types.Doc return 0, err } - deleted = int32(deleteRes.Deleted) - - return deleted, nil + return int32(deleteRes.Deleted), nil } From d13635b2f1b91f7cccdcbf5f9e5382425de17b9c Mon Sep 17 00:00:00 2001 From: Dmitry Date: Wed, 28 Jun 2023 14:19:32 +0100 Subject: [PATCH 26/38] WIP --- internal/handlers/tigris/msg_delete.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/internal/handlers/tigris/msg_delete.go b/internal/handlers/tigris/msg_delete.go index 2b13a158c251..24b5cf9e5fea 100644 --- a/internal/handlers/tigris/msg_delete.go +++ b/internal/handlers/tigris/msg_delete.go @@ -80,15 +80,15 @@ func (h *Handler) MsgDelete(ctx context.Context, msg *wire.OpMsg) (*wire.OpMsg, } replyDoc := must.NotFail(types.NewDocument( - "ok", float64(1), + "n", deleted, )) if delErrors.Len() > 0 { replyDoc = delErrors.Document() + } else { + replyDoc.Set("ok", float64(1)) } - replyDoc.Set("n", deleted) - var reply wire.OpMsg must.NoError(reply.SetSections(wire.OpMsgSection{ Documents: []*types.Document{replyDoc}, From 6353c9331de0509b0bc08e7b333e426cd4d99d93 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Wed, 28 Jun 2023 14:20:16 +0100 Subject: [PATCH 27/38] WIP --- integration/basic_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/integration/basic_test.go b/integration/basic_test.go index 7815db13b7ea..e86a4cad95fc 100644 --- a/integration/basic_test.go +++ b/integration/basic_test.go @@ -15,7 +15,6 @@ package integration import ( - "errors" "fmt" "math" "strings" From 19bb6087b1db9abd2016165ea74c752f700ae97d Mon Sep 17 00:00:00 2001 From: Dmitry Date: Wed, 28 Jun 2023 14:31:31 +0100 Subject: [PATCH 28/38] WIP --- integration/delete_command_compat_test.go | 34 +++++++++++++---------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/integration/delete_command_compat_test.go b/integration/delete_command_compat_test.go index 54fbd2c94a87..50ed841d7bea 100644 --- a/integration/delete_command_compat_test.go +++ b/integration/delete_command_compat_test.go @@ -25,11 +25,9 @@ import ( ) // deleteCommandCompatTestCase describes delete compatibility test case. -type deleteCommandCompatTestCase struct { //nolint:vet // for readability - deletes bson.A // required - resultType compatTestCaseResultType // defaults to nonEmptyResult - - skip string // optional, skip test with a specified reason +type deleteCommandCompatTestCase struct { + skip string // optional, skip test with a specified reason + deletes bson.A // required } func testDeleteCommandCompat(t *testing.T, testCases map[string]deleteCommandCompatTestCase) { @@ -40,12 +38,15 @@ func testDeleteCommandCompat(t *testing.T, testCases map[string]deleteCommandCom t.Run(name, func(t *testing.T) { t.Helper() + if tc.skip != "" { + t.Skip(tc.skip) + } + t.Parallel() // Use per-test setup because deletes modify data set. ctx, targetCollections, compatCollections := setup.SetupCompat(t) - var nonEmptyResults bool for i := range targetCollections { targetCollection := targetCollections[i] compatCollection := compatCollections[i] @@ -83,15 +84,6 @@ func testDeleteCommandCompat(t *testing.T, testCases map[string]deleteCommandCom AssertEqualDocumentsSlice(t, compatDocs, targetDocs) }) } - - switch tc.resultType { - case nonEmptyResult: - assert.True(t, nonEmptyResults, "expected non-empty results (some documents should be deleted)") - case emptyResult: - assert.False(t, nonEmptyResults, "expected empty results (no documents should be deleted)") - default: - t.Fatalf("unknown result type %v", tc.resultType) - } }) } } @@ -100,6 +92,18 @@ func TestDeleteCompatCommand(t *testing.T) { t.Parallel() testCases := map[string]deleteCommandCompatTestCase{ + "OneLimited": { + deletes: bson.A{ + bson.D{{"q", bson.D{{"v", int32(0)}}}, {"limit", 1}}, + }, + }, + "TwoLimited": { + deletes: bson.A{ + bson.D{{"q", bson.D{{"v", int32(42)}}}, {"limit", 1}}, + bson.D{{"q", bson.D{{"v", "foo"}}}, {"limit", 1}}, + }, + skip: "https://github.com/FerretDB/FerretDB/issues/2935", + }, "DuplicateFilter": { deletes: bson.A{ bson.D{{"q", bson.D{{"v", "foo"}}}, {"limit", 1}}, From 398d51aeefe5fe2c74a50c65a71f159da54315c6 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Wed, 28 Jun 2023 14:53:15 +0100 Subject: [PATCH 29/38] WIP --- integration/delete_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integration/delete_test.go b/integration/delete_test.go index 113dac07abea..af231ef80739 100644 --- a/integration/delete_test.go +++ b/integration/delete_test.go @@ -156,7 +156,7 @@ func TestDeleteNotExistingDatabase(t *testing.T) { assert.NoError(t, err) - expectedRes := bson.D{{"ok", float64(1)}, {"n", int32(0)}} + expectedRes := bson.D{{"n", int32(0)}, {"ok", float64(1)}} assert.Equal(t, expectedRes, res) } From d8dc923bc76cc4a625eae5bb5c16b9863e969311 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Wed, 28 Jun 2023 17:12:47 +0100 Subject: [PATCH 30/38] WIP --- integration/delete_command_compat_test.go | 2 +- internal/handlers/pg/msg_delete.go | 6 +++--- internal/handlers/sqlite/msg_delete.go | 6 +++--- internal/handlers/tigris/msg_delete.go | 6 +++--- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/integration/delete_command_compat_test.go b/integration/delete_command_compat_test.go index 50ed841d7bea..1e20ce48b73e 100644 --- a/integration/delete_command_compat_test.go +++ b/integration/delete_command_compat_test.go @@ -88,7 +88,7 @@ func testDeleteCommandCompat(t *testing.T, testCases map[string]deleteCommandCom } } -func TestDeleteCompatCommand(t *testing.T) { +func TestDeleteCommandCompat(t *testing.T) { t.Parallel() testCases := map[string]deleteCommandCompatTestCase{ diff --git a/internal/handlers/pg/msg_delete.go b/internal/handlers/pg/msg_delete.go index 1d32def49729..0b61ae78f91e 100644 --- a/internal/handlers/pg/msg_delete.go +++ b/internal/handlers/pg/msg_delete.go @@ -81,15 +81,15 @@ func (h *Handler) MsgDelete(ctx context.Context, msg *wire.OpMsg) (*wire.OpMsg, } replyDoc := must.NotFail(types.NewDocument( - "n", deleted, + "ok", float64(1), )) if delErrors.Len() > 0 { replyDoc = delErrors.Document() - } else { - replyDoc.Set("ok", float64(1)) } + replyDoc.Set("n", deleted) + var reply wire.OpMsg must.NoError(reply.SetSections(wire.OpMsgSection{ Documents: []*types.Document{replyDoc}, diff --git a/internal/handlers/sqlite/msg_delete.go b/internal/handlers/sqlite/msg_delete.go index badbaa9e2f95..390a7fdfbd1e 100644 --- a/internal/handlers/sqlite/msg_delete.go +++ b/internal/handlers/sqlite/msg_delete.go @@ -64,15 +64,15 @@ func (h *Handler) MsgDelete(ctx context.Context, msg *wire.OpMsg) (*wire.OpMsg, } replyDoc := must.NotFail(types.NewDocument( - "n", deleted, + "ok", float64(1), )) if delErrors.Len() > 0 { replyDoc = delErrors.Document() - } else { - replyDoc.Set("ok", float64(1)) } + replyDoc.Set("n", deleted) + var reply wire.OpMsg must.NoError(reply.SetSections(wire.OpMsgSection{ Documents: []*types.Document{replyDoc}, diff --git a/internal/handlers/tigris/msg_delete.go b/internal/handlers/tigris/msg_delete.go index 24b5cf9e5fea..2b13a158c251 100644 --- a/internal/handlers/tigris/msg_delete.go +++ b/internal/handlers/tigris/msg_delete.go @@ -80,15 +80,15 @@ func (h *Handler) MsgDelete(ctx context.Context, msg *wire.OpMsg) (*wire.OpMsg, } replyDoc := must.NotFail(types.NewDocument( - "n", deleted, + "ok", float64(1), )) if delErrors.Len() > 0 { replyDoc = delErrors.Document() - } else { - replyDoc.Set("ok", float64(1)) } + replyDoc.Set("n", deleted) + var reply wire.OpMsg must.NoError(reply.SetSections(wire.OpMsgSection{ Documents: []*types.Document{replyDoc}, From 2f929a3d6400add22d20ca7479f767c34f751893 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Wed, 28 Jun 2023 17:35:25 +0100 Subject: [PATCH 31/38] WIP --- integration/delete_command_compat_test.go | 33 +++++++++++++++++++++++ integration/delete_test.go | 22 --------------- 2 files changed, 33 insertions(+), 22 deletions(-) diff --git a/integration/delete_command_compat_test.go b/integration/delete_command_compat_test.go index 1e20ce48b73e..44fa1417025b 100644 --- a/integration/delete_command_compat_test.go +++ b/integration/delete_command_compat_test.go @@ -22,6 +22,7 @@ import ( "go.mongodb.org/mongo-driver/bson" "github.com/FerretDB/FerretDB/integration/setup" + "github.com/FerretDB/FerretDB/integration/shareddata" ) // deleteCommandCompatTestCase describes delete compatibility test case. @@ -115,3 +116,35 @@ func TestDeleteCommandCompat(t *testing.T) { testDeleteCommandCompat(t, testCases) } + +func TestDeleteCommandCompatNotExistingDatabase(t *testing.T) { + t.Parallel() + + res := setup.SetupCompatWithOpts(t, &setup.SetupCompatOpts{ + Providers: []shareddata.Provider{shareddata.Int32s}, + }) + ctx, targetColl, compatColl := res.Ctx, res.TargetCollections[0], res.CompatCollections[0] + + targetDB := targetColl.Database().Client().Database("doesnotexist") + compatDB := compatColl.Database().Client().Database("doesnotexist") + + var targetRes, compatRes bson.D + + targetErr := targetDB.RunCommand(ctx, bson.D{ + {"delete", "test"}, + {"deletes", bson.A{ + bson.D{{"q", bson.D{{"v", "foo"}}}, {"limit", 1}}, + }}, + }).Decode(&targetRes) + compatErr := compatDB.RunCommand(ctx, bson.D{ + {"delete", "test"}, + {"deletes", bson.A{ + bson.D{{"q", bson.D{{"v", "foo"}}}, {"limit", 1}}, + }}, + }).Decode(&compatRes) + + assert.NoError(t, targetErr) + assert.NoError(t, compatErr) + + assert.Equal(t, compatRes, targetRes) +} diff --git a/integration/delete_test.go b/integration/delete_test.go index af231ef80739..a45e6af3edd8 100644 --- a/integration/delete_test.go +++ b/integration/delete_test.go @@ -138,25 +138,3 @@ func TestDelete(t *testing.T) { }) } } - -func TestDeleteNotExistingDatabase(t *testing.T) { - t.Parallel() - - ctx, collection := setup.Setup(t) - - db := collection.Database().Client().Database("doesnotexist") - - var res bson.D - err := db.RunCommand(ctx, bson.D{ - {"delete", "test"}, - {"deletes", bson.A{ - bson.D{{"q", bson.D{{"v", "foo"}}}, {"limit", 1}}, - }}, - }).Decode(&res) - - assert.NoError(t, err) - - expectedRes := bson.D{{"n", int32(0)}, {"ok", float64(1)}} - - assert.Equal(t, expectedRes, res) -} From cab52dab2ac746fe96305fb4f27b6e729027505d Mon Sep 17 00:00:00 2001 From: Dmitry Date: Wed, 28 Jun 2023 18:00:55 +0100 Subject: [PATCH 32/38] WIP --- internal/handlers/common/delete.go | 1 + internal/handlers/pg/msg_delete.go | 4 ++-- internal/handlers/sqlite/msg_delete.go | 4 ++-- internal/handlers/tigris/msg_delete.go | 4 ++-- 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/internal/handlers/common/delete.go b/internal/handlers/common/delete.go index ba8f1faf5370..47cbb95e5abb 100644 --- a/internal/handlers/common/delete.go +++ b/internal/handlers/common/delete.go @@ -33,6 +33,7 @@ type DeleteParams struct { Let *types.Document `ferretdb:"let,unimplemented"` WriteConcern *types.Document `ferretdb:"writeConcern,ignored"` + LSID any `ferretdb:"lsid,ignored"` } // Delete represents single delete operation parameters. diff --git a/internal/handlers/pg/msg_delete.go b/internal/handlers/pg/msg_delete.go index 0b61ae78f91e..40c3e7142aaa 100644 --- a/internal/handlers/pg/msg_delete.go +++ b/internal/handlers/pg/msg_delete.go @@ -81,14 +81,14 @@ func (h *Handler) MsgDelete(ctx context.Context, msg *wire.OpMsg) (*wire.OpMsg, } replyDoc := must.NotFail(types.NewDocument( - "ok", float64(1), + "n", deleted, )) if delErrors.Len() > 0 { replyDoc = delErrors.Document() } - replyDoc.Set("n", deleted) + replyDoc.Set("ok", float64(1)) var reply wire.OpMsg must.NoError(reply.SetSections(wire.OpMsgSection{ diff --git a/internal/handlers/sqlite/msg_delete.go b/internal/handlers/sqlite/msg_delete.go index 390a7fdfbd1e..5252f8e79071 100644 --- a/internal/handlers/sqlite/msg_delete.go +++ b/internal/handlers/sqlite/msg_delete.go @@ -64,14 +64,14 @@ func (h *Handler) MsgDelete(ctx context.Context, msg *wire.OpMsg) (*wire.OpMsg, } replyDoc := must.NotFail(types.NewDocument( - "ok", float64(1), + "n", deleted, )) if delErrors.Len() > 0 { replyDoc = delErrors.Document() } - replyDoc.Set("n", deleted) + replyDoc.Set("ok", float64(1)) var reply wire.OpMsg must.NoError(reply.SetSections(wire.OpMsgSection{ diff --git a/internal/handlers/tigris/msg_delete.go b/internal/handlers/tigris/msg_delete.go index 2b13a158c251..4d0b96ec9e16 100644 --- a/internal/handlers/tigris/msg_delete.go +++ b/internal/handlers/tigris/msg_delete.go @@ -80,14 +80,14 @@ func (h *Handler) MsgDelete(ctx context.Context, msg *wire.OpMsg) (*wire.OpMsg, } replyDoc := must.NotFail(types.NewDocument( - "ok", float64(1), + "n", deleted, )) if delErrors.Len() > 0 { replyDoc = delErrors.Document() } - replyDoc.Set("n", deleted) + replyDoc.Set("ok", float64(1)) var reply wire.OpMsg must.NoError(reply.SetSections(wire.OpMsgSection{ From f2b14ce5164c43f8fbdc3eed20f8dae3910f450e Mon Sep 17 00:00:00 2001 From: Dmitry Date: Wed, 28 Jun 2023 18:24:28 +0100 Subject: [PATCH 33/38] WIP --- internal/handlers/pg/msg_delete.go | 2 +- internal/handlers/sqlite/msg_delete.go | 2 +- internal/handlers/tigris/msg_delete.go | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/internal/handlers/pg/msg_delete.go b/internal/handlers/pg/msg_delete.go index 40c3e7142aaa..5bf39c5f5b84 100644 --- a/internal/handlers/pg/msg_delete.go +++ b/internal/handlers/pg/msg_delete.go @@ -85,7 +85,7 @@ func (h *Handler) MsgDelete(ctx context.Context, msg *wire.OpMsg) (*wire.OpMsg, )) if delErrors.Len() > 0 { - replyDoc = delErrors.Document() + replyDoc.Set("writeErrors", must.NotFail(delErrors.Document().Get("writeErrors"))) } replyDoc.Set("ok", float64(1)) diff --git a/internal/handlers/sqlite/msg_delete.go b/internal/handlers/sqlite/msg_delete.go index 5252f8e79071..640a2310f283 100644 --- a/internal/handlers/sqlite/msg_delete.go +++ b/internal/handlers/sqlite/msg_delete.go @@ -68,7 +68,7 @@ func (h *Handler) MsgDelete(ctx context.Context, msg *wire.OpMsg) (*wire.OpMsg, )) if delErrors.Len() > 0 { - replyDoc = delErrors.Document() + replyDoc.Set("writeErrors", must.NotFail(delErrors.Document().Get("writeErrors"))) } replyDoc.Set("ok", float64(1)) diff --git a/internal/handlers/tigris/msg_delete.go b/internal/handlers/tigris/msg_delete.go index 4d0b96ec9e16..b4b3ec1a10ce 100644 --- a/internal/handlers/tigris/msg_delete.go +++ b/internal/handlers/tigris/msg_delete.go @@ -84,7 +84,7 @@ func (h *Handler) MsgDelete(ctx context.Context, msg *wire.OpMsg) (*wire.OpMsg, )) if delErrors.Len() > 0 { - replyDoc = delErrors.Document() + replyDoc.Set("writeErrors", must.NotFail(delErrors.Document().Get("writeErrors"))) } replyDoc.Set("ok", float64(1)) From 311d6fcfa546e680ce90b2b6bbb5f9ae4fd98970 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Wed, 28 Jun 2023 19:20:54 +0100 Subject: [PATCH 34/38] WIP --- internal/handlers/pg/msg_delete.go | 1 + internal/handlers/sqlite/msg_delete.go | 1 + internal/handlers/tigris/msg_delete.go | 1 + 3 files changed, 3 insertions(+) diff --git a/internal/handlers/pg/msg_delete.go b/internal/handlers/pg/msg_delete.go index 5bf39c5f5b84..4d3d7babf157 100644 --- a/internal/handlers/pg/msg_delete.go +++ b/internal/handlers/pg/msg_delete.go @@ -85,6 +85,7 @@ func (h *Handler) MsgDelete(ctx context.Context, msg *wire.OpMsg) (*wire.OpMsg, )) if delErrors.Len() > 0 { + // "writeErrors" should be after "n" field replyDoc.Set("writeErrors", must.NotFail(delErrors.Document().Get("writeErrors"))) } diff --git a/internal/handlers/sqlite/msg_delete.go b/internal/handlers/sqlite/msg_delete.go index 640a2310f283..ad4cf4df2707 100644 --- a/internal/handlers/sqlite/msg_delete.go +++ b/internal/handlers/sqlite/msg_delete.go @@ -68,6 +68,7 @@ func (h *Handler) MsgDelete(ctx context.Context, msg *wire.OpMsg) (*wire.OpMsg, )) if delErrors.Len() > 0 { + // "writeErrors" should be after "n" field replyDoc.Set("writeErrors", must.NotFail(delErrors.Document().Get("writeErrors"))) } diff --git a/internal/handlers/tigris/msg_delete.go b/internal/handlers/tigris/msg_delete.go index b4b3ec1a10ce..2ce511ce37fa 100644 --- a/internal/handlers/tigris/msg_delete.go +++ b/internal/handlers/tigris/msg_delete.go @@ -84,6 +84,7 @@ func (h *Handler) MsgDelete(ctx context.Context, msg *wire.OpMsg) (*wire.OpMsg, )) if delErrors.Len() > 0 { + // "writeErrors" should be after "n" field replyDoc.Set("writeErrors", must.NotFail(delErrors.Document().Get("writeErrors"))) } From 28fffde1539652bf10fe86436adcfc9b45f9bfc2 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Thu, 29 Jun 2023 09:27:32 +0100 Subject: [PATCH 35/38] WIP --- integration/delete_command_compat_test.go | 14 +++++++++----- integration/delete_compat_test.go | 11 +++++++---- internal/backends/collection.go | 2 +- 3 files changed, 17 insertions(+), 10 deletions(-) diff --git a/integration/delete_command_compat_test.go b/integration/delete_command_compat_test.go index 44fa1417025b..a9dea51611a9 100644 --- a/integration/delete_command_compat_test.go +++ b/integration/delete_command_compat_test.go @@ -68,13 +68,17 @@ func testDeleteCommandCompat(t *testing.T, testCases map[string]deleteCommandCom if targetErr != nil { t.Logf("Target error: %v", targetErr) - targetErr = UnsetRaw(t, targetErr) - compatErr = UnsetRaw(t, compatErr) - assert.Equal(t, compatErr, targetErr) - } else { - require.NoError(t, compatErr, "compat error; target returned no error") + t.Logf("Compat error: %v", compatErr) + + // error messages are intentionally not compared + AssertMatchesCommandError(t, compatErr, targetErr) + + return } + require.NoError(t, compatErr, "compat error; target returned no error") + t.Logf("Compat (expected) result: %v", compatRes) + t.Logf("Target (actual) result: %v", targetRes) assert.Equal(t, compatRes, targetRes) targetDocs := FindAll(t, ctx, targetCollection) diff --git a/integration/delete_compat_test.go b/integration/delete_compat_test.go index 6b9b74a9054b..8a8833f28f29 100644 --- a/integration/delete_compat_test.go +++ b/integration/delete_compat_test.go @@ -166,10 +166,11 @@ func testDeleteCompat(t *testing.T, testCases map[string]deleteCompatTestCase) { if targetErr != nil { t.Logf("Target error: %v", targetErr) - targetErr = UnsetRaw(t, targetErr) - compatErr = UnsetRaw(t, compatErr) - assert.Equal(t, compatErr, targetErr) - } else { + t.Logf("Compat error: %v", compatErr) + + // error messages are intentionally not compared + AssertMatchesCommandError(t, compatErr, targetErr) + } else { // we have to check the results in case of error because some documents may be deleted require.NoError(t, compatErr, "compat error; target returned no error") } @@ -177,6 +178,8 @@ func testDeleteCompat(t *testing.T, testCases map[string]deleteCompatTestCase) { nonEmptyResults = true } + t.Logf("Compat (expected) result: %v", compatRes) + t.Logf("Target (actual) result: %v", targetRes) assert.Equal(t, compatRes, targetRes) targetDocs := FindAll(t, ctx, targetCollection) diff --git a/internal/backends/collection.go b/internal/backends/collection.go index e8cb1e9f8824..33de8d5ecd22 100644 --- a/internal/backends/collection.go +++ b/internal/backends/collection.go @@ -135,7 +135,7 @@ type DeleteResult struct { // Delete deletes documents in collection. // -// If request database does not exist it returns 0 deleted documents. +// If requested database or collection does not exist it returns 0 deleted documents. func (cc *collectionContract) Delete(ctx context.Context, params *DeleteParams) (res *DeleteResult, err error) { defer observability.FuncCall(ctx)() defer checkError(err) From 296029dcc2e6c2610e3bf5f13546bfc8b0474858 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Thu, 29 Jun 2023 10:45:55 +0100 Subject: [PATCH 36/38] WIP --- integration/delete_command_compat_test.go | 2 +- integration/delete_compat_test.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/integration/delete_command_compat_test.go b/integration/delete_command_compat_test.go index a9dea51611a9..dd8e24a2d377 100644 --- a/integration/delete_command_compat_test.go +++ b/integration/delete_command_compat_test.go @@ -71,7 +71,7 @@ func testDeleteCommandCompat(t *testing.T, testCases map[string]deleteCommandCom t.Logf("Compat error: %v", compatErr) // error messages are intentionally not compared - AssertMatchesCommandError(t, compatErr, targetErr) + AssertMatchesWriteError(t, compatErr, targetErr) return } diff --git a/integration/delete_compat_test.go b/integration/delete_compat_test.go index 8a8833f28f29..02e8e9575756 100644 --- a/integration/delete_compat_test.go +++ b/integration/delete_compat_test.go @@ -169,7 +169,7 @@ func testDeleteCompat(t *testing.T, testCases map[string]deleteCompatTestCase) { t.Logf("Compat error: %v", compatErr) // error messages are intentionally not compared - AssertMatchesCommandError(t, compatErr, targetErr) + AssertMatchesWriteError(t, compatErr, targetErr) } else { // we have to check the results in case of error because some documents may be deleted require.NoError(t, compatErr, "compat error; target returned no error") } From f527db43416688aa9d8749bce84671696c75f2c3 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Thu, 29 Jun 2023 11:08:18 +0100 Subject: [PATCH 37/38] WIP --- integration/delete_command_compat_test.go | 2 +- integration/delete_compat_test.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/integration/delete_command_compat_test.go b/integration/delete_command_compat_test.go index dd8e24a2d377..aaa9ec33695b 100644 --- a/integration/delete_command_compat_test.go +++ b/integration/delete_command_compat_test.go @@ -71,7 +71,7 @@ func testDeleteCommandCompat(t *testing.T, testCases map[string]deleteCommandCom t.Logf("Compat error: %v", compatErr) // error messages are intentionally not compared - AssertMatchesWriteError(t, compatErr, targetErr) + AssertMatchesBulkException(t, compatErr, targetErr) return } diff --git a/integration/delete_compat_test.go b/integration/delete_compat_test.go index 02e8e9575756..b40ad213464e 100644 --- a/integration/delete_compat_test.go +++ b/integration/delete_compat_test.go @@ -169,7 +169,7 @@ func testDeleteCompat(t *testing.T, testCases map[string]deleteCompatTestCase) { t.Logf("Compat error: %v", compatErr) // error messages are intentionally not compared - AssertMatchesWriteError(t, compatErr, targetErr) + AssertMatchesBulkException(t, compatErr, targetErr) } else { // we have to check the results in case of error because some documents may be deleted require.NoError(t, compatErr, "compat error; target returned no error") } From fe07ff2f35d2e32993127e01a5132de62fc09812 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Thu, 29 Jun 2023 11:17:06 +0100 Subject: [PATCH 38/38] WIP --- integration/delete_command_compat_test.go | 1 + integration/delete_compat_test.go | 5 ----- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/integration/delete_command_compat_test.go b/integration/delete_command_compat_test.go index aaa9ec33695b..6a5e8f6a79b1 100644 --- a/integration/delete_command_compat_test.go +++ b/integration/delete_command_compat_test.go @@ -101,6 +101,7 @@ func TestDeleteCommandCompat(t *testing.T) { deletes: bson.A{ bson.D{{"q", bson.D{{"v", int32(0)}}}, {"limit", 1}}, }, + skip: "https://github.com/FerretDB/FerretDB/issues/2935", }, "TwoLimited": { deletes: bson.A{ diff --git a/integration/delete_compat_test.go b/integration/delete_compat_test.go index b40ad213464e..752b50f06450 100644 --- a/integration/delete_compat_test.go +++ b/integration/delete_compat_test.go @@ -38,11 +38,6 @@ func TestDeleteCompat(t *testing.T) { t.Parallel() testCases := map[string]deleteCompatTestCase{ - "Empty": { - filters: []bson.D{}, - resultType: emptyResult, - }, - "One": { filters: []bson.D{ {{"v", int32(42)}},