From 513efa677c7cf74216dff1c6830748c95f8eb280 Mon Sep 17 00:00:00 2001 From: Chi Fujii Date: Tue, 13 Jun 2023 00:23:02 +0900 Subject: [PATCH 1/4] Add skipped tests for `find` `batchSize` (#2825) Closes #2005. --- integration/helpers.go | 11 + integration/query_test.go | 552 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 563 insertions(+) diff --git a/integration/helpers.go b/integration/helpers.go index bc2a55715b7d..94e51c1fdcd7 100644 --- a/integration/helpers.go +++ b/integration/helpers.go @@ -405,3 +405,14 @@ func FindAll(t testing.TB, ctx context.Context, collection *mongo.Collection) [] return FetchAll(t, ctx, cursor) } + +// generateDocuments generates documents with _id ranging from startID to endID. +// It returns bson.A containing bson.D documents. +func generateDocuments(startID, endID int32) bson.A { + var docs bson.A + for i := startID; i < endID; i++ { + docs = append(docs, bson.D{{"_id", i}}) + } + + return docs +} diff --git a/integration/query_test.go b/integration/query_test.go index 8c77e0ebce8c..7ab4202be163 100644 --- a/integration/query_test.go +++ b/integration/query_test.go @@ -19,6 +19,7 @@ import ( "testing" "time" + "github.com/AlekSi/pointer" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.mongodb.org/mongo-driver/bson" @@ -613,3 +614,554 @@ func TestQueryNonExistingCollection(t *testing.T) { require.NoError(t, err) require.Len(t, actual, 0) } + +func TestQueryCommandBatchSize(t *testing.T) { + t.Parallel() + ctx, collection := setup.Setup(t) + + // the number of documents is set to slightly above the default batchSize of 101 + docs := generateDocuments(0, 110) + _, err := collection.InsertMany(ctx, docs) + require.NoError(t, err) + + for name, tc := range map[string]struct { //nolint:vet // used for testing only + batchSize any // optional, nil to leave batchSize unset + firstBatch primitive.A // optional, expected firstBatch + + err *mongo.CommandError // optional, expected error from MongoDB + altMessage string // optional, alternative error message for FerretDB, ignored if empty + skip string // optional, skip test with a specified reason + }{ + "Int": { + batchSize: 1, + firstBatch: docs[:1], + skip: "https://github.com/FerretDB/FerretDB/issues/2005", + }, + "Long": { + batchSize: int64(2), + firstBatch: docs[:2], + skip: "https://github.com/FerretDB/FerretDB/issues/2005", + }, + "LongZero": { + batchSize: int64(0), + firstBatch: bson.A{}, + }, + "LongNegative": { + batchSize: int64(-1), + err: &mongo.CommandError{ + Code: 51024, + Name: "Location51024", + Message: "BSON field 'batchSize' value must be >= 0, actual value '-1'", + }, + altMessage: "BSON field 'batchSize' value must be >= 0, actual value '-1'", + }, + "DoubleNegative": { + batchSize: -1.1, + err: &mongo.CommandError{ + Code: 51024, + Name: "Location51024", + Message: "BSON field 'batchSize' value must be >= 0, actual value '-1'", + }, + }, + "DoubleFloor": { + batchSize: 1.9, + firstBatch: docs[:1], + skip: "https://github.com/FerretDB/FerretDB/issues/2005", + }, + "Bool": { + batchSize: true, + firstBatch: docs[:1], + err: &mongo.CommandError{ + Code: 14, + Name: "TypeMismatch", + Message: "BSON field 'FindCommandRequest.batchSize' is the wrong type 'bool', expected types '[long, int, decimal, double']", + }, + skip: "https://github.com/FerretDB/FerretDB/issues/2005", + }, + "Unset": { + // default batchSize is 101 when unset + batchSize: nil, + firstBatch: docs[:101], + skip: "https://github.com/FerretDB/FerretDB/issues/2005", + }, + "LargeBatchSize": { + batchSize: 102, + firstBatch: docs[:102], + skip: "https://github.com/FerretDB/FerretDB/issues/2005", + }, + } { + name, tc := name, tc + t.Run(name, func(t *testing.T) { + if tc.skip != "" { + t.Skip(tc.skip) + } + + t.Parallel() + + var rest bson.D + if tc.batchSize != nil { + rest = append(rest, bson.E{Key: "batchSize", Value: tc.batchSize}) + } + + command := append( + bson.D{{"find", collection.Name()}}, + rest..., + ) + + var res bson.D + err := collection.Database().RunCommand(ctx, command).Decode(&res) + if tc.err != nil { + assert.Nil(t, res) + AssertEqualAltCommandError(t, *tc.err, tc.altMessage, err) + + return + } + + require.NoError(t, err) + + v, ok := res.Map()["cursor"] + require.True(t, ok) + + cursor, ok := v.(bson.D) + require.True(t, ok) + + // Do not check the value of cursor id, FerretDB has a different id. + cursorID := cursor.Map()["id"] + assert.NotNil(t, cursorID) + + firstBatch, ok := cursor.Map()["firstBatch"] + require.True(t, ok) + require.Equal(t, tc.firstBatch, firstBatch) + }) + } +} + +func TestQueryBatchSize(t *testing.T) { + t.Parallel() + ctx, collection := setup.Setup(t) + + // the number of documents is set to much bigger than the default batchSize of 101 + docs := generateDocuments(0, 220) + _, err := collection.InsertMany(ctx, docs) + require.NoError(t, err) + + t.Run("SetBatchSize", func(t *testing.T) { + t.Skip("https://github.com/FerretDB/FerretDB/issues/2005") + + t.Parallel() + + // set BatchSize to 2 + cursor, err := collection.Find(ctx, bson.D{}, &options.FindOptions{BatchSize: pointer.ToInt32(2)}) + require.NoError(t, err) + + defer cursor.Close(ctx) + + // firstBatch has remaining 2 documents + require.Equal(t, 2, cursor.RemainingBatchLength()) + + // get first document from firstBatch + ok := cursor.Next(ctx) + require.True(t, ok, "expected to have next document") + require.Equal(t, 1, cursor.RemainingBatchLength()) + + // get second document from firstBatch + ok = cursor.Next(ctx) + require.True(t, ok, "expected to have next document") + require.Equal(t, 0, cursor.RemainingBatchLength()) + + // get first document from secondBatch + ok = cursor.Next(ctx) + require.True(t, ok, "expected to have next document") + require.Equal(t, 1, cursor.RemainingBatchLength()) + + // get second document from secondBatch + ok = cursor.Next(ctx) + require.True(t, ok, "expected to have next document") + require.Equal(t, 0, cursor.RemainingBatchLength()) + + // increase batchSize + cursor.SetBatchSize(5) + + // get first document from thirdBatch + ok = cursor.Next(ctx) + require.True(t, ok, "expected to have next document") + require.Equal(t, 4, cursor.RemainingBatchLength()) + + // get rest of documents from the cursor + var res bson.D + err = cursor.All(ctx, &res) + require.NoError(t, err) + + // cursor is exhausted + ok = cursor.Next(ctx) + require.False(t, ok, "cursor exhausted, not expecting next document") + }) + + t.Run("DefaultBatchSize", func(t *testing.T) { + t.Skip("https://github.com/FerretDB/FerretDB/issues/2005") + + t.Parallel() + + // leave batchSize unset, firstBatch uses default batchSize 101 + cursor, err := collection.Find(ctx, bson.D{}) + require.NoError(t, err) + + defer cursor.Close(ctx) + + // firstBatch has remaining 101 documents + require.Equal(t, 101, cursor.RemainingBatchLength()) + + // get 101 documents from firstBatch + for i := 0; i < 101; i++ { + ok := cursor.Next(ctx) + require.True(t, ok, "expected to have next document") + } + + require.Equal(t, 0, cursor.RemainingBatchLength()) + + // secondBatch has the rest of the documents, not only 109 documents + // TODO: 16MB batchSize limit https://github.com/FerretDB/FerretDB/issues/2824 + ok := cursor.Next(ctx) + require.True(t, ok, "expected to have next document") + require.Equal(t, 118, cursor.RemainingBatchLength()) + }) + + t.Run("SingleBatch", func(t *testing.T) { + t.Parallel() + + // set limit to negative, it ignores batchSize and returns single document in the firstBatch. + cursor, err := collection.Find(ctx, bson.D{}, &options.FindOptions{ + Limit: pointer.ToInt64(-1), + BatchSize: pointer.ToInt32(10), + }) + require.NoError(t, err) + + defer cursor.Close(ctx) + + // firstBatch has remaining 1 document + require.Equal(t, 1, cursor.RemainingBatchLength()) + + // firstBatch contains single document + ok := cursor.Next(ctx) + require.True(t, ok, "expected to have next document") + require.Equal(t, 0, cursor.RemainingBatchLength()) + + // there is no remaining batch, cursor is exhausted + ok = cursor.Next(ctx) + require.False(t, ok, "cursor exhausted, not expecting next document") + require.Equal(t, 0, cursor.RemainingBatchLength()) + }) +} + +func TestQueryCommandGetMore(t *testing.T) { + t.Skip("https://github.com/FerretDB/FerretDB/issues/2005") + + t.Parallel() + ctx, collection := setup.Setup(t) + + // the number of documents is set to slightly above the default batchSize of 101 + docs := generateDocuments(0, 110) + _, err := collection.InsertMany(ctx, docs) + require.NoError(t, err) + + for name, tc := range map[string]struct { //nolint:vet // used for testing only + findBatchSize any // optional, nil to leave findBatchSize unset + getMoreBatchSize any // optional, nil to leave getMoreBatchSize unset + collection any // optional, nil to leave collection unset + cursorID any // optional, defaults to cursorID from find() + firstBatch primitive.A // required, expected find firstBatch + nextBatch primitive.A // optional, expected getMore nextBatch + + err *mongo.CommandError // optional, expected error from MongoDB + altMessage string // optional, alternative error message for FerretDB, ignored if empty + skip string // optional, skip test with a specified reason + }{ + "Int": { + findBatchSize: 1, + getMoreBatchSize: int32(1), + collection: collection.Name(), + firstBatch: docs[:1], + nextBatch: docs[1:2], + }, + "IntNegative": { + findBatchSize: 1, + getMoreBatchSize: int32(-1), + collection: collection.Name(), + firstBatch: docs[:1], + err: &mongo.CommandError{ + Code: 51024, + Name: "Location51024", + Message: "BSON field 'batchSize' value must be >= 0, actual value '-1'", + }, + altMessage: "BSON field 'batchSize' value must be >= 0, actual value '-1'", + }, + "IntZero": { + findBatchSize: 1, + getMoreBatchSize: int32(0), + collection: collection.Name(), + firstBatch: docs[:1], + nextBatch: docs[1:], + }, + "Long": { + findBatchSize: 1, + getMoreBatchSize: int64(1), + collection: collection.Name(), + firstBatch: docs[:1], + nextBatch: docs[1:2], + }, + "LongNegative": { + findBatchSize: 1, + getMoreBatchSize: int64(-1), + collection: collection.Name(), + firstBatch: docs[:1], + err: &mongo.CommandError{ + Code: 51024, + Name: "Location51024", + Message: "BSON field 'batchSize' value must be >= 0, actual value '-1'", + }, + }, + "LongZero": { + findBatchSize: 1, + getMoreBatchSize: int64(0), + collection: collection.Name(), + firstBatch: docs[:1], + nextBatch: docs[1:], + }, + "Double": { + findBatchSize: 1, + getMoreBatchSize: float64(1), + collection: collection.Name(), + firstBatch: docs[:1], + nextBatch: docs[1:2], + }, + "DoubleNegative": { + findBatchSize: 1, + getMoreBatchSize: float64(-1), + collection: collection.Name(), + firstBatch: docs[:1], + err: &mongo.CommandError{ + Code: 51024, + Name: "Location51024", + Message: "BSON field 'batchSize' value must be >= 0, actual value '-1'", + }, + }, + "DoubleZero": { + findBatchSize: 1, + getMoreBatchSize: float64(0), + collection: collection.Name(), + firstBatch: docs[:1], + nextBatch: docs[1:], + }, + "DoubleFloor": { + findBatchSize: 1, + getMoreBatchSize: 1.9, + collection: collection.Name(), + firstBatch: docs[:1], + nextBatch: docs[1:2], + }, + "GetMoreCursorExhausted": { + findBatchSize: 200, + getMoreBatchSize: int32(1), + collection: collection.Name(), + firstBatch: docs[:110], + err: &mongo.CommandError{ + Code: 43, + Name: "CursorNotFound", + Message: "cursor id 0 not found", + }, + }, + "Bool": { + findBatchSize: 1, + getMoreBatchSize: false, + collection: collection.Name(), + firstBatch: docs[:1], + err: &mongo.CommandError{ + Code: 14, + Name: "TypeMismatch", + Message: "BSON field 'getMore.batchSize' is the wrong type 'bool', expected types '[long, int, decimal, double']", + }, + }, + "Unset": { + findBatchSize: 1, + // unset getMore batchSize gets all remaining documents + getMoreBatchSize: nil, + collection: collection.Name(), + firstBatch: docs[:1], + nextBatch: docs[1:], + }, + "LargeBatchSize": { + findBatchSize: 1, + getMoreBatchSize: 105, + collection: collection.Name(), + firstBatch: docs[:1], + nextBatch: docs[1:106], + }, + "StringCursorID": { + findBatchSize: 1, + getMoreBatchSize: 1, + collection: collection.Name(), + cursorID: "invalid", + firstBatch: docs[:1], + err: &mongo.CommandError{ + Code: 14, + Name: "TypeMismatch", + Message: "BSON field 'getMore.getMore' is the wrong type 'string', expected type 'long'", + }, + }, + "NotFoundCursorID": { + findBatchSize: 1, + getMoreBatchSize: 1, + collection: collection.Name(), + cursorID: int64(1234), + firstBatch: docs[:1], + err: &mongo.CommandError{ + Code: 43, + Name: "CursorNotFound", + Message: "cursor id 1234 not found", + }, + }, + "WrongTypeNamespace": { + findBatchSize: 1, + getMoreBatchSize: 1, + collection: bson.D{}, + firstBatch: docs[:1], + err: &mongo.CommandError{ + Code: 14, + Name: "TypeMismatch", + Message: "BSON field 'getMore.collection' is the wrong type 'object', expected type 'string'", + }, + }, + "InvalidNamespace": { + findBatchSize: 1, + getMoreBatchSize: 1, + collection: "invalid", + firstBatch: docs[:1], + err: &mongo.CommandError{ + Code: 13, + Name: "Unauthorized", + Message: "Requested getMore on namespace 'TestQueryCommandGetMore.invalid'," + + " but cursor belongs to a different namespace TestQueryCommandGetMore.TestQueryCommandGetMore", + }, + }, + "EmptyCollectionName": { + findBatchSize: 1, + getMoreBatchSize: 1, + collection: "", + firstBatch: docs[:1], + err: &mongo.CommandError{ + Code: 73, + Name: "InvalidNamespace", + Message: "Collection names cannot be empty", + }, + }, + "MissingCollectionName": { + findBatchSize: 1, + getMoreBatchSize: 1, + collection: nil, + firstBatch: docs[:1], + err: &mongo.CommandError{ + Code: 40414, + Name: "Location40414", + Message: "BSON field 'getMore.collection' is missing but a required field", + }, + }, + } { + name, tc := name, tc + t.Run(name, func(t *testing.T) { + if tc.skip != "" { + t.Skip(tc.skip) + } + + // Do not run tests in parallel, MongoDB throws error that session and cursor does not match. + // > Location50738 + // > Cannot run getMore on cursor 2053655655200551971, + // > which was created in session 2926eea5-9775-41a3-a563-096969f1c7d5 - 47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU= - - , + // > in session 774d9ac6-b24a-4fd8-9874-f92ab1c9c8f5 - 47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU= - - + + require.NotNil(t, tc.firstBatch, "firstBatch must not be nil") + + var findRest bson.D + if tc.findBatchSize != nil { + findRest = append(findRest, bson.E{Key: "batchSize", Value: tc.findBatchSize}) + } + + findCommand := append( + bson.D{{"find", collection.Name()}}, + findRest..., + ) + + var res bson.D + err := collection.Database().RunCommand(ctx, findCommand).Decode(&res) + require.NoError(t, err) + + v, ok := res.Map()["cursor"] + require.True(t, ok) + + cursor, ok := v.(bson.D) + require.True(t, ok) + + cursorID := cursor.Map()["id"] + assert.NotNil(t, cursorID) + + firstBatch, ok := cursor.Map()["firstBatch"] + require.True(t, ok) + require.Equal(t, tc.firstBatch, firstBatch) + + if tc.cursorID != nil { + cursorID = tc.cursorID + } + + var getMoreRest bson.D + if tc.getMoreBatchSize != nil { + getMoreRest = append(getMoreRest, bson.E{Key: "batchSize", Value: tc.getMoreBatchSize}) + } + + if tc.collection != nil { + getMoreRest = append(getMoreRest, bson.E{Key: "collection", Value: tc.collection}) + } + + getMoreCommand := append( + bson.D{ + {"getMore", cursorID}, + }, + getMoreRest..., + ) + + err = collection.Database().RunCommand(ctx, getMoreCommand).Decode(&res) + if tc.err != nil { + AssertEqualAltCommandError(t, *tc.err, tc.altMessage, err) + + // upon error response contains firstBatch field. + v, ok = res.Map()["cursor"] + require.True(t, ok) + + cursor, ok = v.(bson.D) + require.True(t, ok) + + cursorID = cursor.Map()["id"] + assert.NotNil(t, cursorID) + + firstBatch, ok = cursor.Map()["firstBatch"] + require.True(t, ok) + require.Equal(t, tc.firstBatch, firstBatch) + + return + } + + require.NoError(t, err) + + v, ok = res.Map()["cursor"] + require.True(t, ok) + + cursor, ok = v.(bson.D) + require.True(t, ok) + + cursorID = cursor.Map()["id"] + assert.NotNil(t, cursorID) + + nextBatch, ok := cursor.Map()["nextBatch"] + require.True(t, ok) + require.Equal(t, tc.nextBatch, nextBatch) + }) + } +} From 8ad2489606937b15fd61d651db90189cb7f1e374 Mon Sep 17 00:00:00 2001 From: Elena Grahovac Date: Tue, 13 Jun 2023 08:41:31 +0200 Subject: [PATCH 2/4] Add more test cases for index validation (#2752) Closes #2311. --- integration/indexes_compat_test.go | 64 ++++++++-- integration/indexes_test.go | 140 ++++++++++++++++++++++ internal/handlers/pg/msg_createindexes.go | 2 - 3 files changed, 192 insertions(+), 14 deletions(-) diff --git a/integration/indexes_compat_test.go b/integration/indexes_compat_test.go index d45b07c95a92..c3ad7e96c8fe 100644 --- a/integration/indexes_compat_test.go +++ b/integration/indexes_compat_test.go @@ -27,7 +27,7 @@ import ( "github.com/FerretDB/FerretDB/integration/shareddata" ) -func TestIndexesCompatList(t *testing.T) { +func TestListIndexesCompat(t *testing.T) { t.Parallel() s := setup.SetupCompatWithOpts(t, &setup.SetupCompatOpts{ @@ -67,7 +67,7 @@ func TestIndexesCompatList(t *testing.T) { } } -func TestIndexesCompatCreate(t *testing.T) { +func TestCreateIndexesCompat(t *testing.T) { setup.SkipForTigrisWithReason(t, "Indexes creation is not supported for Tigris") t.Parallel() @@ -304,55 +304,62 @@ func TestCreateIndexesCommandCompat(t *testing.T) { resultType compatTestCaseResultType // defaults to nonEmptyResult skip string // optional, skip test with a specified reason }{ - "invalid-collection-name": { + "InvalidCollectionName": { collectionName: 42, key: bson.D{{"v", -1}}, indexName: "custom-name", resultType: emptyResult, }, - "nil-collection-name": { + "NilCollectionName": { collectionName: nil, key: bson.D{{"v", -1}}, indexName: "custom-name", resultType: emptyResult, }, - "index-name-not-set": { + "EmptyCollectionName": { + collectionName: "", + key: bson.D{{"v", -1}}, + indexName: "custom-name", + resultType: emptyResult, + skip: "https://github.com/FerretDB/FerretDB/issues/2311", + }, + "IndexNameNotSet": { collectionName: "test", key: bson.D{{"v", -1}}, indexName: nil, resultType: emptyResult, skip: "https://github.com/FerretDB/FerretDB/issues/2311", }, - "empty-index-name": { + "EmptyIndexName": { collectionName: "test", key: bson.D{{"v", -1}}, indexName: "", resultType: emptyResult, skip: "https://github.com/FerretDB/FerretDB/issues/2311", }, - "non-string-index-name": { + "NonStringIndexName": { collectionName: "test", key: bson.D{{"v", -1}}, indexName: 42, resultType: emptyResult, }, - "existing-name-different-key-length": { + "ExistingNameDifferentKeyLength": { collectionName: "test", key: bson.D{{"_id", 1}, {"v", 1}}, indexName: "_id_", // the same name as the default index skip: "https://github.com/FerretDB/FerretDB/issues/2311", }, - "invalid-key": { + "InvalidKey": { collectionName: "test", key: 42, resultType: emptyResult, }, - "empty-key": { + "EmptyKey": { collectionName: "test", key: bson.D{}, resultType: emptyResult, }, - "key-not-set": { + "KeyNotSet": { collectionName: "test", resultType: emptyResult, skip: "https://github.com/FerretDB/FerretDB/issues/2311", @@ -436,7 +443,7 @@ func TestCreateIndexesCommandCompat(t *testing.T) { } } -func TestIndexesCompatDrop(t *testing.T) { +func TestDropIndexesCompat(t *testing.T) { setup.SkipForTigrisWithReason(t, "Indexes are not supported for Tigris") t.Parallel() @@ -483,6 +490,10 @@ func TestIndexesCompatDrop(t *testing.T) { dropIndexName: "nonexistent_1", resultType: emptyResult, }, + "Empty": { + dropIndexName: "", + resultType: emptyResult, + }, } { name, tc := name, tc t.Run(name, func(t *testing.T) { @@ -579,6 +590,22 @@ func TestDropIndexesCommandCompat(t *testing.T) { }, toDrop: bson.A{"v_-1", "v_1_foo_1"}, }, + "MultipleIndexesByKey": { + toCreate: []mongo.IndexModel{ + {Keys: bson.D{{"v", -1}}}, + {Keys: bson.D{{"v.foo", -1}}}, + }, + toDrop: bson.A{bson.D{{"v", -1}}, bson.D{{"v.foo", -1}}}, + resultType: emptyResult, + }, + "NonExistentMultipleIndexes": { + toDrop: bson.A{"non-existent", "invalid"}, + resultType: emptyResult, + }, + "InvalidMultipleIndexType": { + toDrop: bson.A{1}, + resultType: emptyResult, + }, "DocumentIndex": { toCreate: []mongo.IndexModel{ {Keys: bson.D{{"v", -1}}}, @@ -593,6 +620,19 @@ func TestDropIndexesCommandCompat(t *testing.T) { }, toDrop: "*", }, + "WrongExpression": { + toCreate: []mongo.IndexModel{ + {Keys: bson.D{{"v", -1}}}, + {Keys: bson.D{{"foo.bar", 1}}}, + {Keys: bson.D{{"foo", 1}, {"bar", 1}}}, + }, + toDrop: "***", + resultType: emptyResult, + }, + "NonExistentDescendingID": { + toDrop: bson.D{{"_id", -1}}, + resultType: emptyResult, + }, "MultipleKeyIndex": { toCreate: []mongo.IndexModel{ {Keys: bson.D{{"_id", -1}, {"v", 1}}}, diff --git a/integration/indexes_test.go b/integration/indexes_test.go index d7016bea4ee3..00cad97cabe8 100644 --- a/integration/indexes_test.go +++ b/integration/indexes_test.go @@ -172,3 +172,143 @@ func TestIndexesDropCommandErrors(t *testing.T) { }) } } + +func TestCreateIndexesInvalidSpec(t *testing.T) { + setup.SkipForTigrisWithReason(t, "Indexes are not supported for Tigris") + + t.Parallel() + + for name, tc := range map[string]struct { + indexes any + err *mongo.CommandError + altMessage string + skip string + }{ + "EmptyIndexes": { + indexes: bson.A{}, + err: &mongo.CommandError{ + Code: 2, + Name: "BadValue", + Message: "Must specify at least one index to create", + }, + }, + "NilIndexes": { + indexes: nil, + err: &mongo.CommandError{ + Code: 10065, + Name: "Location10065", + Message: "invalid parameter: expected an object (indexes)", + }, + skip: "https://github.com/FerretDB/FerretDB/issues/2311", + }, + "InvalidType": { + indexes: 42, + err: &mongo.CommandError{ + Code: 14, + Name: "TypeMismatch", + Message: "BSON field 'createIndexes.indexes' is the wrong type 'int', expected type 'array'", + }, + skip: "https://github.com/FerretDB/FerretDB/issues/2311", + }, + } { + name, tc := name, tc + t.Run(name, func(t *testing.T) { + if tc.skip != "" { + t.Skip(tc.skip) + } + + t.Parallel() + + provider := shareddata.ArrayDocuments // one provider is enough to check for errors + ctx, collection := setup.Setup(t, provider) + + command := bson.D{ + {"createIndexes", collection.Name()}, + {"indexes", tc.indexes}, + } + + var res bson.D + err := collection.Database().RunCommand(ctx, command).Decode(&res) + + require.Nil(t, res) + AssertEqualAltCommandError(t, *tc.err, tc.altMessage, err) + }) + } +} + +func TestDropIndexesInvalidCollection(t *testing.T) { + setup.SkipForTigrisWithReason(t, "Indexes are not supported for Tigris") + + t.Parallel() + + for name, tc := range map[string]struct { + collectionName any + indexName any + err *mongo.CommandError + altMessage string + skip string + }{ + "NonExistentCollection": { + collectionName: "non-existent", + indexName: "index", + err: &mongo.CommandError{ + Code: 26, + Name: "NamespaceNotFound", + Message: "ns not found TestDropIndexesInvalidCollection-NonExistentCollection.non-existent", + }, + }, + "InvalidTypeCollection": { + collectionName: 42, + indexName: "index", + err: &mongo.CommandError{ + Code: 2, + Name: "BadValue", + Message: "collection name has invalid type int", + }, + skip: "https://github.com/FerretDB/FerretDB/issues/2311", + }, + "NilCollection": { + collectionName: nil, + indexName: "index", + err: &mongo.CommandError{ + Code: 2, + Name: "BadValue", + Message: "collection name has invalid type null", + }, + skip: "https://github.com/FerretDB/FerretDB/issues/2311", + }, + "EmptyCollection": { + collectionName: "", + indexName: "index", + err: &mongo.CommandError{ + Code: 73, + Name: "InvalidNamespace", + Message: "Invalid namespace specified 'TestIndexesDropInvalidCollection-EmptyCollection.'", + }, + skip: "https://github.com/FerretDB/FerretDB/issues/2311", + }, + } { + name, tc := name, tc + t.Run(name, func(t *testing.T) { + if tc.skip != "" { + t.Skip(tc.skip) + } + + t.Parallel() + + provider := shareddata.ArrayDocuments // one provider is enough to check for errors + ctx, collection := setup.Setup(t, provider) + + command := bson.D{ + {"dropIndexes", tc.collectionName}, + {"index", tc.indexName}, + } + + var res bson.D + err := collection.Database().RunCommand(ctx, command).Decode(&res) + + require.Nil(t, res) + AssertEqualAltCommandError(t, *tc.err, tc.altMessage, err) + }) + } +} diff --git a/internal/handlers/pg/msg_createindexes.go b/internal/handlers/pg/msg_createindexes.go index f4194af59298..2fd7c9941c17 100644 --- a/internal/handlers/pg/msg_createindexes.go +++ b/internal/handlers/pg/msg_createindexes.go @@ -273,7 +273,6 @@ func processIndexKey(keyDoc *types.Document) (pgdb.IndexKey, error) { var orderParam int64 if orderParam, err = commonparams.GetWholeNumberParam(order); err != nil { - // TODO Add better validation and return proper error: https://github.com/FerretDB/FerretDB/issues/2311 return nil, commonerrors.NewCommandErrorMsgWithArgument( commonerrors.ErrNotImplemented, fmt.Sprintf("Index key value %q is not implemented yet", order), @@ -289,7 +288,6 @@ func processIndexKey(keyDoc *types.Document) (pgdb.IndexKey, error) { case -1: indexOrder = types.Descending default: - // TODO Add better validation: https://github.com/FerretDB/FerretDB/issues/2311 return nil, commonerrors.NewCommandErrorMsgWithArgument( commonerrors.ErrNotImplemented, fmt.Sprintf("Index key value %q is not implemented yet", orderParam), From 9536babb87ab2873de3383c684b837d30db49a75 Mon Sep 17 00:00:00 2001 From: Patryk Kwiatek Date: Tue, 13 Jun 2023 13:08:12 +0000 Subject: [PATCH 3/4] Implement `$type` aggregation operator (#2789) --- .../aggregate_documents_compat_test.go | 92 +++++++++++++- .../common/aggregations/expression.go | 19 ++- .../operators/accumulators/sum.go | 8 +- .../aggregations/operators/operators.go | 42 ++++++- .../common/aggregations/operators/type.go | 116 ++++++++++++++---- .../common/aggregations/stages/group.go | 7 +- .../stages/projection/projection.go | 1 + .../common/aggregations/stages/unwind.go | 7 +- 8 files changed, 248 insertions(+), 44 deletions(-) diff --git a/integration/aggregate_documents_compat_test.go b/integration/aggregate_documents_compat_test.go index 74d3c56d46f2..46112cb400c3 100644 --- a/integration/aggregate_documents_compat_test.go +++ b/integration/aggregate_documents_compat_test.go @@ -604,7 +604,7 @@ func TestAggregateCompatGroup(t *testing.T) { resultType: emptyResult, skip: "https://github.com/FerretDB/FerretDB/issues/2123", }, - "IDTypeOperator": { + "IDType": { pipeline: bson.A{bson.D{{"$group", bson.D{ {"_id", bson.D{{"$type", "_id"}}}, }}}}, @@ -1597,12 +1597,98 @@ func TestAggregateCompatProject(t *testing.T) { }, resultType: emptyResult, }, - "TypeOperator": { + "Type": { pipeline: bson.A{ bson.D{{"$sort", bson.D{{"_id", -1}}}}, bson.D{{"$project", bson.D{{"type", bson.D{{"$type", "$v"}}}}}}, }, - skip: "https://github.com/FerretDB/FerretDB/issues/2679", + }, + "TypeNonExistent": { + pipeline: bson.A{ + bson.D{{"$sort", bson.D{{"_id", -1}}}}, + bson.D{{"$project", bson.D{{"type", bson.D{{"$type", "$foo"}}}}}}, + }, + }, + "TypeDotNotation": { + pipeline: bson.A{ + bson.D{{"$sort", bson.D{{"_id", -1}}}}, + bson.D{{"$project", bson.D{{"type", bson.D{{"$type", "$v.foo"}}}}}}, + }, + }, + "TypeRecursive": { + pipeline: bson.A{ + bson.D{{"$sort", bson.D{{"_id", -1}}}}, + bson.D{{"$project", bson.D{{"type", bson.D{{"$type", bson.D{{"$type", "$v"}}}}}}}}, + }, + }, + "TypeRecursiveNonExistent": { + pipeline: bson.A{ + bson.D{{"$sort", bson.D{{"_id", -1}}}}, + bson.D{{"$project", bson.D{{"type", bson.D{{"$type", bson.D{{"$non-existent", "$v"}}}}}}}}, + }, + skip: "https://github.com/FerretDB/FerretDB/issues/2678", + }, + "TypeRecursiveInvalid": { + pipeline: bson.A{ + bson.D{{"$sort", bson.D{{"_id", -1}}}}, + bson.D{{"$project", bson.D{{"type", bson.D{{"$type", bson.D{{"v", "$v"}}}}}}}}, + }, + }, + + "TypeInt": { + pipeline: bson.A{ + bson.D{{"$sort", bson.D{{"_id", -1}}}}, + bson.D{{"$project", bson.D{{"type", bson.D{{"$type", int32(42)}}}}}}, + }, + }, + "TypeLong": { + pipeline: bson.A{ + bson.D{{"$sort", bson.D{{"_id", -1}}}}, + bson.D{{"$project", bson.D{{"type", bson.D{{"$type", int64(42)}}}}}}, + }, + }, + "TypeString": { + pipeline: bson.A{ + bson.D{{"$sort", bson.D{{"_id", -1}}}}, + bson.D{{"$project", bson.D{{"type", bson.D{{"$type", "42"}}}}}}, + }, + }, + "TypeDocument": { + pipeline: bson.A{ + bson.D{{"$sort", bson.D{{"_id", -1}}}}, + bson.D{{"$project", bson.D{{"type", bson.D{{"$type", bson.D{{"foo", "bar"}}}}}}}}, + }, + }, + "TypeArraySingleItem": { + pipeline: bson.A{ + bson.D{{"$sort", bson.D{{"_id", -1}}}}, + bson.D{{"$project", bson.D{{"type", bson.D{{"$type", bson.A{int32(42)}}}}}}}, + }, + }, + "TypeArray": { + pipeline: bson.A{ + bson.D{{"$sort", bson.D{{"_id", -1}}}}, + bson.D{{"$project", bson.D{{"type", bson.D{{"$type", bson.A{"foo", "bar"}}}}}}}, + }, + skip: "https://github.com/FerretDB/FerretDB/issues/2678", + }, + "TypeNestedArray": { + pipeline: bson.A{ + bson.D{{"$sort", bson.D{{"_id", -1}}}}, + bson.D{{"$project", bson.D{{"type", bson.D{{"$type", bson.A{bson.A{"foo", "bar"}}}}}}}}, + }, + }, + "TypeObjectID": { + pipeline: bson.A{ + bson.D{{"$sort", bson.D{{"_id", -1}}}}, + bson.D{{"$project", bson.D{{"type", bson.D{{"$type", primitive.NewObjectID()}}}}}}, + }, + }, + "TypeBool": { + pipeline: bson.A{ + bson.D{{"$sort", bson.D{{"_id", -1}}}}, + bson.D{{"$project", bson.D{{"type", bson.D{{"$type", true}}}}}}, + }, }, } diff --git a/internal/handlers/common/aggregations/expression.go b/internal/handlers/common/aggregations/expression.go index eedad104b45e..69ba5e8d7ed7 100644 --- a/internal/handlers/common/aggregations/expression.go +++ b/internal/handlers/common/aggregations/expression.go @@ -15,6 +15,7 @@ package aggregations import ( + "fmt" "strings" "github.com/FerretDB/FerretDB/internal/types" @@ -132,18 +133,17 @@ func NewExpression(expression string) (*Expression, error) { } // Evaluate gets the value at the path. -// It returns `types.Null` if the path does not exists. -func (e *Expression) Evaluate(doc *types.Document) any { +// It returns error if the path does not exists. +func (e *Expression) Evaluate(doc *types.Document) (any, error) { path := e.path if path.Len() == 1 { val, err := doc.Get(path.String()) if err != nil { - // $group stage groups non-existent paths with `Null` - return types.Null + return nil, err } - return val + return val, nil } var isPrefixArray bool @@ -160,16 +160,15 @@ func (e *Expression) Evaluate(doc *types.Document) any { if len(vals) == 0 { if isPrefixArray { // when the prefix is array, return empty array. - return must.NotFail(types.NewArray()) + return must.NotFail(types.NewArray()), nil } - // $group stage groups non-existent paths with `Null` - return types.Null + return nil, fmt.Errorf("no document found under %s path", path) } if len(vals) == 1 && !isPrefixArray { // when the prefix is not array, return the value - return vals[0] + return vals[0], nil } // when the prefix is array, return an array of value. @@ -178,7 +177,7 @@ func (e *Expression) Evaluate(doc *types.Document) any { arr.Append(v) } - return arr + return arr, nil } // GetExpressionSuffix returns suffix of pathExpression. diff --git a/internal/handlers/common/aggregations/operators/accumulators/sum.go b/internal/handlers/common/aggregations/operators/accumulators/sum.go index 0aa82e12e555..565b4c6d9ba9 100644 --- a/internal/handlers/common/aggregations/operators/accumulators/sum.go +++ b/internal/handlers/common/aggregations/operators/accumulators/sum.go @@ -80,7 +80,13 @@ func (s *sum) Accumulate(iter types.DocumentsIterator) (any, error) { } if s.expression != nil { - numbers = append(numbers, s.expression.Evaluate(doc)) + value, err := s.expression.Evaluate(doc) + + // sum fields that exist + if err == nil { + numbers = append(numbers, value) + } + continue } diff --git a/internal/handlers/common/aggregations/operators/operators.go b/internal/handlers/common/aggregations/operators/operators.go index 0ad29392cd48..aa581fb4fdf6 100644 --- a/internal/handlers/common/aggregations/operators/operators.go +++ b/internal/handlers/common/aggregations/operators/operators.go @@ -22,9 +22,13 @@ package operators import ( + "errors" "fmt" + "strings" "github.com/FerretDB/FerretDB/internal/types" + "github.com/FerretDB/FerretDB/internal/util/iterator" + "github.com/FerretDB/FerretDB/internal/util/lazyerrors" ) var ( @@ -39,6 +43,9 @@ var ( // ErrNotImplemented indicates that given operator is not implemented yet. ErrNotImplemented = fmt.Errorf("The operator is not implemented yet") + + // ErrNoOperator indicates that given document does not contain any operator. + ErrNoOperator = fmt.Errorf("No operator in document") ) // newOperatorFunc is a type for a function that creates a standard aggregation operator. @@ -59,12 +66,39 @@ type Operator interface { // The document should look like: `{<$operator>: }`. func NewOperator(doc any) (Operator, error) { operatorDoc, ok := doc.(*types.Document) - - switch { - case !ok: + if !ok { + // TODO: https://github.com/FerretDB/FerretDB/pull/2789 return nil, ErrWrongType - case operatorDoc.Len() == 0: + } + + if operatorDoc.Len() == 0 { return nil, ErrEmptyField + } + + iter := operatorDoc.Iterator() + defer iter.Close() + + var operatorExists bool + + for { + k, _, err := iter.Next() + if errors.Is(err, iterator.ErrIteratorDone) { + break + } + + if err != nil { + return nil, lazyerrors.Error(err) + } + + if strings.HasPrefix(k, "$") { + operatorExists = true + break + } + } + + switch { + case !operatorExists: + return nil, ErrNoOperator case operatorDoc.Len() > 1: return nil, ErrTooManyFields } diff --git a/internal/handlers/common/aggregations/operators/type.go b/internal/handlers/common/aggregations/operators/type.go index 77d998148782..dcea5daabff2 100644 --- a/internal/handlers/common/aggregations/operators/type.go +++ b/internal/handlers/common/aggregations/operators/type.go @@ -16,37 +16,105 @@ package operators import ( - "github.com/FerretDB/FerretDB/internal/handlers/commonerrors" + "errors" + "fmt" + "strings" + "time" + + "github.com/FerretDB/FerretDB/internal/handlers/common/aggregations" + "github.com/FerretDB/FerretDB/internal/handlers/commonparams" "github.com/FerretDB/FerretDB/internal/types" + "github.com/FerretDB/FerretDB/internal/util/lazyerrors" "github.com/FerretDB/FerretDB/internal/util/must" ) -// typeOp represent $type operator. -type typeOp struct{} +// typeOp represents `$type` operator. +type typeOp struct { + param any +} -// newType creates a new $type aggregation operator. -func newType(expression *types.Document) (Operator, error) { - // TODO https://github.com/FerretDB/FerretDB/issues/2678 - must.NotFail(expression.Get("$type")) +// newType returns `$type` operator. +func newType(operation *types.Document) (Operator, error) { + param := must.NotFail(operation.Get("$type")) - return nil, commonerrors.NewCommandErrorMsgWithArgument( - commonerrors.ErrNotImplemented, - "$type aggregation operator is not implemented yet", - "$type", - ) + return &typeOp{ + param: param, + }, nil } // Process implements Operator interface. -func (t *typeOp) Process(in *types.Document) (any, error) { - // TODO https://github.com/FerretDB/FerretDB/issues/2678 - return nil, commonerrors.NewCommandErrorMsgWithArgument( - commonerrors.ErrNotImplemented, - "$type aggregation operator is not implemented yet", - "$type", - ) -} +func (t *typeOp) Process(doc *types.Document) (any, error) { + typeParam := t.param -// check interfaces -var ( - _ Operator = (*typeOp)(nil) -) + var paramEvaluated bool + + var res any + + for !paramEvaluated { + paramEvaluated = true + + switch param := typeParam.(type) { + case *types.Document: + operator, err := NewOperator(param) + if errors.Is(err, ErrNoOperator) { + res = param + continue + } + + if err != nil { + // TODO https://github.com/FerretDB/FerretDB/issues/2678 + return nil, err + } + + if typeParam, err = operator.Process(doc); err != nil { + // TODO https://github.com/FerretDB/FerretDB/issues/2678 + return nil, lazyerrors.Error(err) + } + + // the result of nested operator needs to be evaluated + paramEvaluated = false + + case *types.Array: + if param.Len() != 1 { + // TODO https://github.com/FerretDB/FerretDB/issues/2678 + return nil, fmt.Errorf("Expression $type takes exactly 1 arguments. %d were passed in.", param.Len()) + } + + value, err := param.Get(0) + if err != nil { + return nil, lazyerrors.Error(err) + } + + res = value + + case float64, types.Binary, types.ObjectID, bool, time.Time, + types.NullType, types.Regex, int32, types.Timestamp, int64: + res = param + + case string: + if strings.HasPrefix(param, "$") { + expression, err := aggregations.NewExpression(param) + if err != nil { + // TODO https://github.com/FerretDB/FerretDB/issues/2678 + return nil, err + } + + value, err := expression.Evaluate(doc) + if err != nil { + return "missing", nil + } + + res = value + + continue + } + + res = param + + default: + panic(fmt.Sprint("wrong type of value: ", typeParam)) + } + } + + return commonparams.AliasFromType(res), nil +} diff --git a/internal/handlers/common/aggregations/stages/group.go b/internal/handlers/common/aggregations/stages/group.go index 13a8be36ee0d..359db26a52af 100644 --- a/internal/handlers/common/aggregations/stages/group.go +++ b/internal/handlers/common/aggregations/stages/group.go @@ -219,7 +219,12 @@ func (g *group) groupDocuments(ctx context.Context, in []*types.Document) ([]gro var group groupMap for _, doc := range in { - val := expression.Evaluate(doc) + val, err := expression.Evaluate(doc) + if err != nil { + // $group treats non-existent fields as nulls + val = types.Null + } + group.addOrAppend(val, doc) } diff --git a/internal/handlers/common/aggregations/stages/projection/projection.go b/internal/handlers/common/aggregations/stages/projection/projection.go index 784eb5658116..9fc8cf3b39eb 100644 --- a/internal/handlers/common/aggregations/stages/projection/projection.go +++ b/internal/handlers/common/aggregations/stages/projection/projection.go @@ -143,6 +143,7 @@ func ValidateProjection(projection *types.Document) (*types.Document, bool, erro case *types.Document: // validate operators later validated.Set(key, value) + result = true case *types.Array, string, types.Binary, types.ObjectID, time.Time, types.NullType, types.Regex, types.Timestamp: // all this types are treated as new fields value diff --git a/internal/handlers/common/aggregations/stages/unwind.go b/internal/handlers/common/aggregations/stages/unwind.go index b7c37ab6e7d5..aeb8d581177a 100644 --- a/internal/handlers/common/aggregations/stages/unwind.go +++ b/internal/handlers/common/aggregations/stages/unwind.go @@ -121,7 +121,12 @@ func (u *unwind) Process(ctx context.Context, iter types.DocumentsIterator, clos key := u.field.GetExpressionSuffix() for _, doc := range docs { - d := u.field.Evaluate(doc) + d, err := u.field.Evaluate(doc) + if err != nil { + // Ignore non-existent values + continue + } + switch d := d.(type) { case *types.Array: iter := d.Iterator() From 89d65626200aab2fd5997930fb23117f8618884b Mon Sep 17 00:00:00 2001 From: Dmitry Date: Tue, 13 Jun 2023 15:47:52 +0100 Subject: [PATCH 4/4] Fix running single test with `task` (#2832) --- CONTRIBUTING.md | 5 ++++- Taskfile.yml | 21 +++++++++++---------- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 4757fee7bfb9..e4c8f58b117c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -167,6 +167,9 @@ If tests fail and the output is too confusing, try running them sequentially by You can also run `task -C 1` to limit the number of concurrent tasks, which is useful for debugging. +To run a single test case, you may want to use the predefined variable `TEST_RUN`. +For example, to run a single test case for in-process FerretDB with `pg` handler you may use `task test-integration-pg TEST_RUN='TestName/TestCaseName'`. + Finally, since all tests just run `go test` with various arguments and flags under the hood, you may also use all standard `go` tool facilities, including [`GOFLAGS` environment variable](https://pkg.go.dev/cmd/go#hdr-Environment_variables). @@ -174,7 +177,7 @@ For example: - to run a single test case for in-process FerretDB with `pg` handler with all subtests running sequentially, - you may use `env GOFLAGS='-run=TestName/TestCaseName -parallel=1' task test-integration-pg`; + you may use `env GOFLAGS='-parallel=1' task test-integration-pg TEST_RUN='TestName/TestCaseName'`; - to run all tests for in-process FerretDB with `tigris` handler with [Go execution tracer](https://pkg.go.dev/runtime/trace) enabled, you may use `env GOFLAGS='-trace=trace.out' task test-integration-tigris`. diff --git a/Taskfile.yml b/Taskfile.yml index 2bdc0a42b9ba..c8d1bfdccaac 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -13,6 +13,7 @@ vars: UNIXSOCKETFLAG: -target-unix-socket={{ne OS "windows"}} BUILDTAGS: ferretdb_debug,ferretdb_tigris,ferretdb_hana SERVICES: postgres postgres_secured tigris tigris1 tigris2 tigris3 tigris4 mongodb mongodb_secured jaeger + TEST_RUN: "" tasks: # invoked when `task` is run without arguments @@ -189,14 +190,14 @@ tasks: dir: integration cmds: - > - go test -count=1 -run='{{.RUN}}' -timeout={{.INTEGRATIONTIME}} {{.RACEFLAG}} -tags={{.BUILDTAGS}} -shuffle=on -coverpkg=../... + go test -count=1 -run='{{or .TEST_RUN .SHARD_RUN}}' -timeout={{.INTEGRATIONTIME}} {{.RACEFLAG}} -tags={{.BUILDTAGS}} -shuffle=on -coverpkg=../... -coverprofile=integration-pg.txt . -target-backend=ferretdb-pg -target-tls -postgresql-url=postgres://username@127.0.0.1:5432/ferretdb -compat-url='mongodb://username:password@127.0.0.1:47018/?tls=true&tlsCertificateKeyFile=../build/certs/client.pem&tlsCaFile=../build/certs/rootCA-cert.pem' vars: - RUN: + SHARD_RUN: sh: go run -C .. ./cmd/envtool tests shard --index={{.SHARD_INDEX | default 1}} --total={{.SHARD_TOTAL | default 1}} test-integration-sqlite: @@ -204,14 +205,14 @@ tasks: dir: integration cmds: - > - go test -count=1 -run='{{.RUN}}' -timeout={{.INTEGRATIONTIME}} {{.RACEFLAG}} -tags={{.BUILDTAGS}} -shuffle=on -coverpkg=../... + go test -count=1 -run='{{or .TEST_RUN .SHARD_RUN}}' -timeout={{.INTEGRATIONTIME}} {{.RACEFLAG}} -tags={{.BUILDTAGS}} -shuffle=on -coverpkg=../... -coverprofile=integration-sqlite.txt . -target-backend=ferretdb-sqlite -target-tls -compat-url='mongodb://username:password@127.0.0.1:47018/?tls=true&tlsCertificateKeyFile=../build/certs/client.pem&tlsCaFile=../build/certs/rootCA-cert.pem' -disable-filter-pushdown vars: - RUN: + SHARD_RUN: sh: go run -C .. ./cmd/envtool tests shard --index={{.SHARD_INDEX | default 1}} --total={{.SHARD_TOTAL | default 1}} test-integration-tigris: @@ -219,14 +220,14 @@ tasks: dir: integration cmds: - > - go test -count=1 -run='{{.RUN}}' -timeout={{.INTEGRATIONTIME}} {{.RACEFLAG}} -tags={{.BUILDTAGS}} -shuffle=on -coverpkg=../... + go test -count=1 -run='{{or .TEST_RUN .SHARD_RUN}}' -timeout={{.INTEGRATIONTIME}} {{.RACEFLAG}} -tags={{.BUILDTAGS}} -shuffle=on -coverpkg=../... -coverprofile=integration-tigris.txt . -target-backend=ferretdb-tigris {{.UNIXSOCKETFLAG}} -tigris-urls=127.0.0.1:8081,127.0.0.1:8091,127.0.0.1:8092,127.0.0.1:8093,127.0.0.1:8094 -compat-url=mongodb://127.0.0.1:47017/ vars: - RUN: + SHARD_RUN: sh: go run -C .. ./cmd/envtool tests shard --index={{.SHARD_INDEX | default 1}} --total={{.SHARD_TOTAL | default 1}} test-integration-hana: @@ -234,14 +235,14 @@ tasks: dir: integration cmds: - > - go test -count=1 -run='{{.RUN}}' -timeout={{.INTEGRATIONTIME}} {{.RACEFLAG}} -tags={{.BUILDTAGS}} -shuffle=on -coverpkg=../... + go test -count=1 -run='{{or .TEST_RUN .SHARD_RUN}}' -timeout={{.INTEGRATIONTIME}} {{.RACEFLAG}} -tags={{.BUILDTAGS}} -shuffle=on -coverpkg=../... -coverprofile=integration-hana.txt . -target-backend=ferretdb-hana {{.UNIXSOCKETFLAG}} -hana-url=$FERRETDB_HANA_URL -compat-url=mongodb://127.0.0.1:47017/ vars: - RUN: + SHARD_RUN: sh: go run -C .. ./cmd/envtool tests shard --index={{.SHARD_INDEX | default 1}} --total={{.SHARD_TOTAL | default 1}} test-integration-mongodb: @@ -249,12 +250,12 @@ tasks: dir: integration cmds: - > - go test -count=1 -run='{{.RUN}}' -timeout={{.INTEGRATIONTIME}} {{.RACEFLAG}} -tags={{.BUILDTAGS}} -shuffle=on -coverpkg=../... + go test -count=1 -run='{{or .TEST_RUN .SHARD_RUN}}' -timeout={{.INTEGRATIONTIME}} {{.RACEFLAG}} -tags={{.BUILDTAGS}} -shuffle=on -coverpkg=../... -coverprofile=integration-mongodb.txt . -target-url=mongodb://127.0.0.1:47017/ -target-backend=mongodb vars: - RUN: + SHARD_RUN: sh: go run -C .. ./cmd/envtool tests shard --index={{.SHARD_INDEX | default 1}} --total={{.SHARD_TOTAL | default 1}} bench-unit-short: