1415 lines
43 KiB
Go
1415 lines
43 KiB
Go
// 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 (
|
|
"math"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
"go.mongodb.org/mongo-driver/bson"
|
|
"go.mongodb.org/mongo-driver/bson/primitive"
|
|
"go.mongodb.org/mongo-driver/mongo"
|
|
|
|
"github.com/FerretDB/FerretDB/v2/internal/util/must"
|
|
|
|
"github.com/FerretDB/FerretDB/v2/integration/setup"
|
|
"github.com/FerretDB/FerretDB/v2/integration/shareddata"
|
|
)
|
|
|
|
func TestAggregateAddFieldsErrors(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
ctx, collection := setup.Setup(t)
|
|
|
|
for name, tc := range map[string]struct { //nolint:vet // used for test only
|
|
pipeline bson.A // required, aggregation pipeline stages
|
|
|
|
err *mongo.CommandError // required
|
|
altMessage string // optional, alternative error message
|
|
failsForFerretDB string
|
|
}{
|
|
"NotDocument": {
|
|
pipeline: bson.A{
|
|
bson.D{{"$addFields", "not-document"}},
|
|
},
|
|
err: &mongo.CommandError{
|
|
Code: 40272,
|
|
Name: "Location40272",
|
|
Message: "$addFields specification stage must be an object, got string",
|
|
},
|
|
altMessage: "$addFields specification stage must be an object",
|
|
},
|
|
"InvalidFieldPath": {
|
|
pipeline: bson.A{
|
|
bson.D{{"$addFields", bson.D{{"$foo", "v"}}}},
|
|
},
|
|
err: &mongo.CommandError{
|
|
Code: 16410,
|
|
Name: "Location16410",
|
|
Message: "Invalid $addFields :: caused by :: FieldPath field names may not start with '$'. Consider using $getField or $setField.",
|
|
},
|
|
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/354",
|
|
},
|
|
} {
|
|
t.Run(name, func(tt *testing.T) {
|
|
tt.Parallel()
|
|
|
|
var t testing.TB = tt
|
|
|
|
if tc.failsForFerretDB != "" {
|
|
t = setup.FailsForFerretDB(tt, tc.failsForFerretDB)
|
|
}
|
|
|
|
require.NotNil(t, tc.pipeline, "pipeline must not be nil")
|
|
require.NotNil(t, tc.err, "err must not be nil")
|
|
|
|
_, err := collection.Aggregate(ctx, tc.pipeline)
|
|
|
|
if tc.altMessage != "" {
|
|
AssertEqualAltCommandError(t, *tc.err, tc.altMessage, err)
|
|
return
|
|
}
|
|
|
|
AssertEqualCommandError(t, *tc.err, err)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestAggregateGroupSumDecimalDouble(tt *testing.T) {
|
|
tt.Parallel()
|
|
|
|
t := setup.FailsForFerretDB(tt, "https://github.com/FerretDB/FerretDB-DocumentDB/issues/1056")
|
|
|
|
ctx, collection := setup.Setup(t)
|
|
|
|
_, err := collection.InsertMany(ctx, bson.A{
|
|
bson.D{{"_id", "decimal128"}, {"v", must.NotFail(primitive.ParseDecimal128("42.1"))}},
|
|
bson.D{{"_id", "double"}, {"v", float64(42.1)}},
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
cursor, err := collection.Aggregate(ctx, bson.A{
|
|
bson.D{{"$group", bson.D{{"_id", nil}, {"sum", bson.D{{"$sum", "$v"}}}}}},
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
var res bson.A
|
|
require.NoError(t, cursor.All(ctx, &res))
|
|
|
|
expected := bson.A{bson.D{
|
|
{"_id", nil},
|
|
{"sum", primitive.NewDecimal128(3459220962935157325, 6906845732440572485)}, // 84.20000000000000142108547152020037
|
|
}}
|
|
|
|
assert.Equal(t, expected, res)
|
|
}
|
|
|
|
func TestAggregateGroupErrors(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
ctx, collection := setup.Setup(t)
|
|
|
|
for name, tc := range map[string]struct {
|
|
pipeline bson.A // required, aggregation pipeline stages
|
|
|
|
err *mongo.CommandError // required, expected error from MongoDB
|
|
altMessage string // optional, alternative error message for FerretDB, ignored if empty
|
|
skip string // TODO https://github.com/FerretDB/FerretDB-DocumentDB/issues/1086
|
|
failsForFerretDB string
|
|
}{
|
|
"UnaryOperatorSum": {
|
|
pipeline: bson.A{
|
|
bson.D{{"$group", bson.D{{"sum", bson.D{{"$sum", bson.A{}}}}}}},
|
|
},
|
|
err: &mongo.CommandError{
|
|
Code: 40237,
|
|
Name: "Location40237",
|
|
Message: "The $sum accumulator is a unary operator",
|
|
},
|
|
altMessage: "The $sum accumulator is a unary operator",
|
|
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/389",
|
|
},
|
|
"TypeEmpty": {
|
|
pipeline: bson.A{
|
|
bson.D{{"$group", bson.D{{"v", bson.D{}}}}},
|
|
},
|
|
err: &mongo.CommandError{
|
|
Code: 40234,
|
|
Name: "Location40234",
|
|
Message: "The field 'v' must be an accumulator object",
|
|
},
|
|
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/389",
|
|
},
|
|
"TwoOperators": {
|
|
pipeline: bson.A{
|
|
bson.D{{"$group", bson.D{{"_id", bson.D{{"$type", int32(42)}, {"$op", int32(42)}}}}}},
|
|
},
|
|
err: &mongo.CommandError{
|
|
Code: 15983,
|
|
Name: "Location15983",
|
|
Message: "An object representing an expression must have exactly one field: { $type: 42, $op: 42 }",
|
|
},
|
|
altMessage: "An object representing an expression must have exactly one field",
|
|
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/390",
|
|
},
|
|
"TypeInvalidLen": {
|
|
pipeline: bson.A{
|
|
bson.D{{"$group", bson.D{{"_id", bson.D{{"$type", bson.A{"foo", "bar"}}}}}}},
|
|
},
|
|
err: &mongo.CommandError{
|
|
Code: 16020,
|
|
Name: "Location16020",
|
|
Message: "Expression $type takes exactly 1 arguments. 2 were passed in.",
|
|
},
|
|
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/390",
|
|
},
|
|
"NonExistentOperator": {
|
|
pipeline: bson.A{
|
|
bson.D{{"$group", bson.D{{"_id", bson.D{{"$non-existent", "foo"}}}}}},
|
|
},
|
|
err: &mongo.CommandError{
|
|
Code: 168,
|
|
Name: "InvalidPipelineOperator",
|
|
Message: "Unrecognized expression '$non-existent'",
|
|
},
|
|
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/390",
|
|
},
|
|
"SumEmptyExpression": {
|
|
pipeline: bson.A{
|
|
bson.D{{"$group", bson.D{
|
|
{"_id", bson.D{{"$sum", "$"}}},
|
|
}}},
|
|
},
|
|
err: &mongo.CommandError{
|
|
Code: 16872,
|
|
Name: "Location16872",
|
|
Message: "'$' by itself is not a valid FieldPath",
|
|
},
|
|
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/390",
|
|
},
|
|
"SumEmptyVariable": {
|
|
pipeline: bson.A{
|
|
bson.D{{"$group", bson.D{
|
|
{"_id", bson.D{{"$sum", "$$"}}},
|
|
}}},
|
|
},
|
|
err: &mongo.CommandError{
|
|
Code: 9,
|
|
Name: "FailedToParse",
|
|
Message: "empty variable names are not allowed",
|
|
},
|
|
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/390",
|
|
},
|
|
"SumDollarVariable": {
|
|
pipeline: bson.A{
|
|
bson.D{{"$group", bson.D{
|
|
{"_id", bson.D{{"$sum", "$$$"}}},
|
|
}}},
|
|
},
|
|
err: &mongo.CommandError{
|
|
Code: 9,
|
|
Name: "FailedToParse",
|
|
Message: "'$' starts with an invalid character for a user variable name",
|
|
},
|
|
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/390",
|
|
},
|
|
"RecursiveNonExistentOperator": {
|
|
pipeline: bson.A{
|
|
bson.D{{"$group", bson.D{{"_id", bson.D{{"$type", bson.D{{"$non-existent", "foo"}}}}}}}},
|
|
},
|
|
err: &mongo.CommandError{
|
|
Code: 168,
|
|
Name: "InvalidPipelineOperator",
|
|
Message: "Unrecognized expression '$non-existent'",
|
|
},
|
|
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/390",
|
|
},
|
|
"IDExpressionDuplicateFields": {
|
|
pipeline: bson.A{
|
|
bson.D{{"$group", bson.D{
|
|
{"_id", bson.D{
|
|
{"v", "$v"},
|
|
{"v", "$non-existent"},
|
|
}},
|
|
}}},
|
|
},
|
|
err: &mongo.CommandError{
|
|
Code: 16406,
|
|
Name: "Location16406",
|
|
Message: "duplicate field name specified in object literal: { v: \"$v\", v: \"$non-existent\" }",
|
|
},
|
|
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/390",
|
|
},
|
|
"IDExpressionEmptyPath": {
|
|
pipeline: bson.A{
|
|
bson.D{{"$group", bson.D{
|
|
{"_id", bson.D{{"v", "$"}}},
|
|
}}},
|
|
},
|
|
err: &mongo.CommandError{
|
|
Code: 16872,
|
|
Name: "Location16872",
|
|
Message: "'$' by itself is not a valid FieldPath",
|
|
},
|
|
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/390",
|
|
},
|
|
"IDExpressionEmptyVariable": {
|
|
pipeline: bson.A{
|
|
bson.D{{"$group", bson.D{
|
|
{"_id", bson.D{{"v", "$$"}}},
|
|
}}},
|
|
},
|
|
err: &mongo.CommandError{
|
|
Code: 9,
|
|
Name: "FailedToParse",
|
|
Message: "empty variable names are not allowed",
|
|
},
|
|
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/390",
|
|
},
|
|
"IDExpressionInvalidVariable$": {
|
|
pipeline: bson.A{
|
|
bson.D{{"$group", bson.D{
|
|
{"_id", bson.D{{"v", "$$$"}}},
|
|
}}},
|
|
},
|
|
err: &mongo.CommandError{
|
|
Code: 9,
|
|
Name: "FailedToParse",
|
|
Message: "'$' starts with an invalid character for a user variable name",
|
|
},
|
|
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/390",
|
|
},
|
|
"IDExpressionInvalidVariable$s": {
|
|
pipeline: bson.A{
|
|
bson.D{{"$group", bson.D{
|
|
{"_id", bson.D{{"v", "$$$s"}}},
|
|
}}},
|
|
},
|
|
err: &mongo.CommandError{
|
|
Code: 9,
|
|
Name: "FailedToParse",
|
|
Message: "'$s' starts with an invalid character for a user variable name",
|
|
},
|
|
altMessage: "'$' starts with an invalid character for a user variable name",
|
|
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/390",
|
|
},
|
|
"IDExpressionNonExistingVariable": {
|
|
pipeline: bson.A{
|
|
bson.D{{"$group", bson.D{
|
|
{"_id", bson.D{{"v", "$$s"}}},
|
|
}}},
|
|
},
|
|
err: &mongo.CommandError{
|
|
Code: 17276,
|
|
Name: "Location17276",
|
|
Message: "Use of undefined variable: s",
|
|
},
|
|
skip: "https://github.com/FerretDB/FerretDB/issues/2275",
|
|
},
|
|
} {
|
|
t.Run(name, func(tt *testing.T) {
|
|
if tc.skip != "" {
|
|
tt.Skip(tc.skip)
|
|
}
|
|
|
|
tt.Parallel()
|
|
|
|
var t testing.TB = tt
|
|
|
|
if tc.failsForFerretDB != "" {
|
|
t = setup.FailsForFerretDB(tt, tc.failsForFerretDB)
|
|
}
|
|
|
|
require.NotNil(t, tc.pipeline, "pipeline must not be nil")
|
|
require.NotNil(t, tc.err, "err must not be nil")
|
|
|
|
res, err := collection.Aggregate(ctx, tc.pipeline)
|
|
|
|
assert.Nil(t, res)
|
|
AssertEqualAltCommandError(t, *tc.err, tc.altMessage, err)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestAggregateProjectErrors(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
ctx, collection := setup.Setup(t)
|
|
|
|
for name, tc := range map[string]struct {
|
|
pipeline bson.A // required, aggregation pipeline stages
|
|
|
|
err *mongo.CommandError // required
|
|
altMessage string // optional, alternative error message
|
|
skip string // TODO https://github.com/FerretDB/FerretDB-DocumentDB/issues/1086
|
|
failsForFerretDB string
|
|
}{
|
|
"EmptyPipeline": {
|
|
pipeline: bson.A{
|
|
bson.D{{"$project", bson.D{}}},
|
|
},
|
|
err: &mongo.CommandError{
|
|
Code: 51272,
|
|
Name: "Location51272",
|
|
Message: "Invalid $project :: caused by :: projection specification must have at least one field",
|
|
},
|
|
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/369",
|
|
},
|
|
"EmptyProjectionField": {
|
|
pipeline: bson.A{
|
|
bson.D{{"$project", bson.D{{"v", bson.D{}}}}},
|
|
},
|
|
err: &mongo.CommandError{
|
|
Code: 51270,
|
|
Name: "Location51270",
|
|
Message: "Invalid $project :: caused by :: An empty sub-projection is not a valid value. Found empty object at path",
|
|
},
|
|
skip: "https://github.com/FerretDB/FerretDB/issues/2633",
|
|
},
|
|
"EmptyKey": {
|
|
pipeline: bson.A{
|
|
bson.D{{"$project", bson.D{{"", true}}}},
|
|
},
|
|
err: &mongo.CommandError{
|
|
Code: 40352,
|
|
Name: "Location40352",
|
|
Message: "Invalid $project :: caused by :: " +
|
|
"FieldPath cannot be constructed with empty string",
|
|
},
|
|
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/369",
|
|
},
|
|
"EmptyPath": {
|
|
pipeline: bson.A{
|
|
bson.D{{"$project", bson.D{{"v..d", true}}}},
|
|
},
|
|
err: &mongo.CommandError{
|
|
Code: 15998,
|
|
Name: "Location15998",
|
|
Message: "Invalid $project :: caused by :: FieldPath field names may not be empty strings.",
|
|
},
|
|
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/369",
|
|
},
|
|
"ExcludeInclude": {
|
|
pipeline: bson.A{
|
|
bson.D{{"$project", bson.D{{"foo", false}, {"bar", true}}}},
|
|
},
|
|
err: &mongo.CommandError{
|
|
Code: 31253,
|
|
Name: "Location31253",
|
|
Message: "Invalid $project :: caused by :: Cannot do inclusion on field bar in exclusion projection",
|
|
},
|
|
altMessage: "Cannot do inclusion on field bar in exclusion projection",
|
|
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/369",
|
|
},
|
|
"IncludeExclude": {
|
|
pipeline: bson.A{
|
|
bson.D{{"$project", bson.D{{"foo", true}, {"bar", false}}}},
|
|
},
|
|
err: &mongo.CommandError{
|
|
Code: 31254,
|
|
Name: "Location31254",
|
|
Message: "Invalid $project :: caused by :: Cannot do exclusion on field bar in inclusion projection",
|
|
},
|
|
altMessage: "Cannot do exclusion on field bar in inclusion projection",
|
|
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/369",
|
|
},
|
|
"PositionalOperatorMultiple": {
|
|
pipeline: bson.A{
|
|
bson.D{{"$project", bson.D{{"v.$.foo.$", true}}}},
|
|
},
|
|
err: &mongo.CommandError{
|
|
Code: 31324,
|
|
Name: "Location31324",
|
|
Message: "Invalid $project :: caused by :: " +
|
|
"Cannot use positional projection in aggregation projection",
|
|
},
|
|
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/369",
|
|
},
|
|
"PositionalOperatorMiddle": {
|
|
pipeline: bson.A{
|
|
bson.D{{"$project", bson.D{{"v.$.foo", true}}}},
|
|
},
|
|
err: &mongo.CommandError{
|
|
Code: 31394,
|
|
Name: "Location31394",
|
|
Message: "Invalid $project :: caused by :: " +
|
|
"As of 4.4, it's illegal to specify positional operator " +
|
|
"in the middle of a path.Positional projection may only be " +
|
|
"used at the end, for example: a.b.$. If the query previously " +
|
|
"used a form like a.b.$.d, remove the parts following the '$' and " +
|
|
"the results will be equivalent.",
|
|
},
|
|
altMessage: "Invalid $project :: caused by :: " +
|
|
"Positional projection may only be used at the end, " +
|
|
"for example: a.b.$. If the query previously used a form " +
|
|
"like a.b.$.d, remove the parts following the '$' and " +
|
|
"the results will be equivalent.",
|
|
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/369",
|
|
},
|
|
"PositionalOperatorWrongLocations": {
|
|
pipeline: bson.A{
|
|
bson.D{{"$project", bson.D{{"$.v.$.foo", true}}}},
|
|
},
|
|
err: &mongo.CommandError{
|
|
Code: 31394,
|
|
Name: "Location31394",
|
|
Message: "Invalid $project :: caused by :: " +
|
|
"As of 4.4, it's illegal to specify positional operator " +
|
|
"in the middle of a path.Positional projection may only be " +
|
|
"used at the end, for example: a.b.$. If the query previously " +
|
|
"used a form like a.b.$.d, remove the parts following the '$' and " +
|
|
"the results will be equivalent.",
|
|
},
|
|
altMessage: "Invalid $project :: caused by :: " +
|
|
"Positional projection may only be used at the end, " +
|
|
"for example: a.b.$. If the query previously used a form " +
|
|
"like a.b.$.d, remove the parts following the '$' and " +
|
|
"the results will be equivalent.",
|
|
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/369",
|
|
},
|
|
"PositionalOperatorEmptyPath": {
|
|
pipeline: bson.A{
|
|
bson.D{{"$project", bson.D{{"v..$", true}}}},
|
|
},
|
|
err: &mongo.CommandError{
|
|
Code: 31324,
|
|
Name: "Location31324",
|
|
Message: "Invalid $project :: caused by :: " +
|
|
"Cannot use positional projection in aggregation projection",
|
|
},
|
|
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/369",
|
|
},
|
|
"PositionalOperatorDollarKey": {
|
|
pipeline: bson.A{
|
|
bson.D{{"$project", bson.D{{"$", true}}}},
|
|
},
|
|
err: &mongo.CommandError{
|
|
Code: 16410,
|
|
Name: "Location16410",
|
|
Message: "Invalid $project :: caused by :: " +
|
|
"FieldPath field names may not start with '$'. Consider using $getField or $setField.",
|
|
},
|
|
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/369",
|
|
},
|
|
"PositionalOperatorDollarInKey": {
|
|
pipeline: bson.A{
|
|
bson.D{{"$project", bson.D{{"$v", true}}}},
|
|
},
|
|
err: &mongo.CommandError{
|
|
Code: 16410,
|
|
Name: "Location16410",
|
|
Message: "Invalid $project :: caused by :: " +
|
|
"FieldPath field names may not start with '$'. Consider using $getField or $setField.",
|
|
},
|
|
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/369",
|
|
},
|
|
"PositionalOperatorDollarPrefix": {
|
|
pipeline: bson.A{
|
|
bson.D{{"$project", bson.D{{"$.foo", true}}}},
|
|
},
|
|
err: &mongo.CommandError{
|
|
Code: 16410,
|
|
Name: "Location16410",
|
|
Message: "Invalid $project :: caused by :: " +
|
|
"FieldPath field names may not start with '$'. Consider using $getField or $setField.",
|
|
},
|
|
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/369",
|
|
},
|
|
"PositionalOperatorDotDollarInKey": {
|
|
pipeline: bson.A{
|
|
bson.D{{"$project", bson.D{{"v.$foo", true}}}},
|
|
},
|
|
err: &mongo.CommandError{
|
|
Code: 16410,
|
|
Name: "Location16410",
|
|
Message: "Invalid $project :: caused by :: " +
|
|
"FieldPath field names may not start with '$'. Consider using $getField or $setField.",
|
|
},
|
|
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/369",
|
|
},
|
|
"PositionalOperatorPrefixSuffix": {
|
|
pipeline: bson.A{
|
|
bson.D{{"$project", bson.D{{"$.v.$", true}}}},
|
|
},
|
|
err: &mongo.CommandError{
|
|
Code: 31324,
|
|
Name: "Location31324",
|
|
Message: "Invalid $project :: caused by :: " +
|
|
"Cannot use positional projection in aggregation projection",
|
|
},
|
|
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/369",
|
|
},
|
|
"PositionalOperatorExclusion": {
|
|
pipeline: bson.A{
|
|
bson.D{{"$project", bson.D{{"v.$", false}}}},
|
|
},
|
|
err: &mongo.CommandError{
|
|
Code: 31324,
|
|
Name: "Location31324",
|
|
Message: "Invalid $project :: caused by :: " +
|
|
"Cannot use positional projection in aggregation projection",
|
|
},
|
|
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/369",
|
|
},
|
|
"ProjectPositionalOperator": {
|
|
pipeline: bson.A{
|
|
bson.D{{"$project", bson.D{{"v.$", true}}}},
|
|
},
|
|
err: &mongo.CommandError{
|
|
Code: 31324,
|
|
Name: "Location31324",
|
|
Message: "Invalid $project :: caused by :: Cannot use positional projection in aggregation projection",
|
|
},
|
|
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/369",
|
|
},
|
|
"ProjectTypeEmpty": {
|
|
pipeline: bson.A{
|
|
bson.D{{"$project", bson.D{{"v", bson.D{}}}}},
|
|
},
|
|
err: &mongo.CommandError{
|
|
Code: 51270,
|
|
Name: "Location51270",
|
|
Message: "Invalid $project :: caused by :: An empty sub-projection is not a valid value." + " Found empty object at path",
|
|
},
|
|
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/369",
|
|
},
|
|
"ProjectTwoOperators": {
|
|
pipeline: bson.A{
|
|
bson.D{{"$project", bson.D{{"v", bson.D{{"$type", int32(42)}, {"$op", int32(42)}}}}}},
|
|
},
|
|
err: &mongo.CommandError{
|
|
Code: 16410,
|
|
Name: "Location16410",
|
|
Message: "Invalid $project :: caused by :: FieldPath field names may not start with '$'. Consider using $getField or $setField.",
|
|
},
|
|
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/369",
|
|
},
|
|
"ProjectTypeInvalidLen": {
|
|
pipeline: bson.A{
|
|
bson.D{{"$project", bson.D{{"v", bson.D{{"$type", bson.A{"foo", "bar"}}}}}}},
|
|
},
|
|
err: &mongo.CommandError{
|
|
Code: 16020,
|
|
Name: "Location16020",
|
|
Message: "Invalid $project :: caused by :: Expression $type takes exactly 1 arguments. 2 were passed in.",
|
|
},
|
|
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/369",
|
|
},
|
|
"ProjectNonExistentOperator": {
|
|
pipeline: bson.A{
|
|
bson.D{{"$project", bson.D{{"v", bson.D{{"$non-existent", "foo"}}}}}},
|
|
},
|
|
altMessage: "Invalid $project :: caused by :: Unrecognized expression '$non-existent'",
|
|
err: &mongo.CommandError{
|
|
Code: 31325,
|
|
Name: "Location31325",
|
|
Message: "Invalid $project :: caused by :: Unknown expression $non-existent",
|
|
},
|
|
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/369",
|
|
},
|
|
"ProjectRecursiveNonExistentOperator": {
|
|
pipeline: bson.A{
|
|
bson.D{{"$project", bson.D{{"v", bson.D{{"$type", bson.D{{"$non-existent", "foo"}}}}}}}},
|
|
},
|
|
err: &mongo.CommandError{
|
|
Code: 168,
|
|
Name: "InvalidPipelineOperator",
|
|
Message: "Invalid $project :: caused by :: Unrecognized expression '$non-existent'",
|
|
},
|
|
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/369",
|
|
},
|
|
"SumEmptyExpression": {
|
|
pipeline: bson.A{
|
|
bson.D{{"$project", bson.D{
|
|
{"sum", bson.D{{"$sum", "$"}}},
|
|
}}},
|
|
},
|
|
err: &mongo.CommandError{
|
|
Code: 16872,
|
|
Name: "Location16872",
|
|
Message: "Invalid $project :: caused by :: '$' by itself is not a valid FieldPath",
|
|
},
|
|
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/369",
|
|
},
|
|
"SumEmptyVariable": {
|
|
pipeline: bson.A{
|
|
bson.D{{"$project", bson.D{
|
|
{"sum", bson.D{{"$sum", "$$"}}},
|
|
}}},
|
|
},
|
|
err: &mongo.CommandError{
|
|
Code: 9,
|
|
Name: "FailedToParse",
|
|
Message: "Invalid $project :: caused by :: empty variable names are not allowed",
|
|
},
|
|
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/369",
|
|
},
|
|
"SumDollarVariable": {
|
|
pipeline: bson.A{
|
|
bson.D{{"$project", bson.D{
|
|
{"sum", bson.D{{"$sum", "$$$"}}},
|
|
}}},
|
|
},
|
|
err: &mongo.CommandError{
|
|
Code: 9,
|
|
Name: "FailedToParse",
|
|
Message: "Invalid $project :: caused by :: '$' starts with an invalid character for a user variable name",
|
|
},
|
|
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/369",
|
|
},
|
|
} {
|
|
t.Run(name, func(tt *testing.T) {
|
|
if tc.skip != "" {
|
|
tt.Skip(tc.skip)
|
|
}
|
|
|
|
tt.Parallel()
|
|
|
|
var t testing.TB = tt
|
|
|
|
if tc.failsForFerretDB != "" {
|
|
t = setup.FailsForFerretDB(tt, tc.failsForFerretDB)
|
|
}
|
|
|
|
require.NotNil(t, tc.pipeline, "pipeline must not be nil")
|
|
require.NotNil(t, tc.err, "err must not be nil")
|
|
|
|
_, err := collection.Aggregate(ctx, tc.pipeline)
|
|
AssertEqualAltCommandError(t, *tc.err, tc.altMessage, err)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestAggregateProject(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
ctx, collection := setup.Setup(t, shareddata.Scalars)
|
|
|
|
for name, tc := range map[string]struct { //nolint:vet // used for testing only
|
|
pipeline bson.A // required, aggregation pipeline stages
|
|
|
|
res []bson.D // required, expected response
|
|
}{
|
|
"IDFalseValueTrue": {
|
|
pipeline: bson.A{
|
|
bson.D{{"$match", bson.D{{"_id", "int32"}}}},
|
|
bson.D{{"$project", bson.D{{"_id", false}, {"v", true}}}},
|
|
},
|
|
res: []bson.D{{{"v", int32(42)}}},
|
|
},
|
|
"ValueTrueIDFalse": {
|
|
pipeline: bson.A{
|
|
bson.D{{"$match", bson.D{{"_id", "int32"}}}},
|
|
bson.D{{"$project", bson.D{{"v", true}, {"_id", false}}}},
|
|
},
|
|
res: []bson.D{{{"v", int32(42)}}},
|
|
},
|
|
} {
|
|
t.Run(name, func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
require.NotNil(t, tc.pipeline, "pipeline must not be nil")
|
|
require.NotNil(t, tc.res, "res must not be nil")
|
|
|
|
cursor, err := collection.Aggregate(ctx, tc.pipeline)
|
|
require.NoError(t, err)
|
|
defer cursor.Close(ctx)
|
|
|
|
var res []bson.D
|
|
err = cursor.All(ctx, &res)
|
|
require.NoError(t, err)
|
|
require.Equal(t, tc.res, res)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestAggregateSetErrors(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
ctx, collection := setup.Setup(t)
|
|
|
|
for name, tc := range map[string]struct { //nolint:vet // used for test only
|
|
pipeline bson.A // required, aggregation pipeline stages
|
|
|
|
err *mongo.CommandError // required
|
|
altMessage string // optional, alternative error message
|
|
failsForFerretDB string
|
|
}{
|
|
"NotDocument": {
|
|
pipeline: bson.A{
|
|
bson.D{{"$set", "not-document"}},
|
|
},
|
|
err: &mongo.CommandError{
|
|
Code: 40272,
|
|
Name: "Location40272",
|
|
Message: "$set specification stage must be an object, got string",
|
|
},
|
|
altMessage: "$addFields specification stage must be an object",
|
|
},
|
|
"InvalidFieldPath": {
|
|
pipeline: bson.A{
|
|
bson.D{{"$set", bson.D{{"$foo", "v"}}}},
|
|
},
|
|
err: &mongo.CommandError{
|
|
Code: 16410,
|
|
Name: "Location16410",
|
|
Message: "Invalid $set :: caused by :: FieldPath field names may not start with '$'. Consider using $getField or $setField.",
|
|
},
|
|
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/354",
|
|
},
|
|
} {
|
|
t.Run(name, func(tt *testing.T) {
|
|
tt.Parallel()
|
|
|
|
var t testing.TB = tt
|
|
|
|
if tc.failsForFerretDB != "" {
|
|
t = setup.FailsForFerretDB(tt, tc.failsForFerretDB)
|
|
}
|
|
|
|
require.NotNil(t, tc.pipeline, "pipeline must not be nil")
|
|
require.NotNil(t, tc.err, "err must not be nil")
|
|
|
|
_, err := collection.Aggregate(ctx, tc.pipeline)
|
|
AssertEqualAltCommandError(t, *tc.err, tc.altMessage, err)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestAggregateUnsetErrors(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
ctx, collection := setup.Setup(t)
|
|
|
|
for name, tc := range map[string]struct { //nolint:vet // used for test only
|
|
pipeline bson.A // required, aggregation pipeline stages
|
|
|
|
err *mongo.CommandError // required
|
|
altMessage string // optional, alternative error message
|
|
failsForFerretDB string
|
|
}{
|
|
"EmptyString": {
|
|
pipeline: bson.A{
|
|
bson.D{{"$unset", ""}},
|
|
},
|
|
err: &mongo.CommandError{
|
|
Code: 40352,
|
|
Name: "Location40352",
|
|
Message: "Invalid $unset :: caused by :: FieldPath cannot be constructed with empty string",
|
|
},
|
|
altMessage: "FieldPath cannot be constructed with empty string",
|
|
},
|
|
"InvalidType": {
|
|
pipeline: bson.A{
|
|
bson.D{{"$unset", bson.D{}}},
|
|
},
|
|
err: &mongo.CommandError{
|
|
Code: 31002,
|
|
Name: "Location31002",
|
|
Message: "$unset specification must be a string or an array",
|
|
},
|
|
},
|
|
"PathEmptyKey": {
|
|
pipeline: bson.A{
|
|
bson.D{{"$unset", "v..foo"}},
|
|
},
|
|
err: &mongo.CommandError{
|
|
Code: 15998,
|
|
Name: "Location15998",
|
|
Message: "Invalid $unset :: caused by :: FieldPath field names may not be empty strings.",
|
|
},
|
|
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/354",
|
|
},
|
|
"PathEmptySuffixKey": {
|
|
pipeline: bson.A{
|
|
bson.D{{"$unset", "v."}},
|
|
},
|
|
err: &mongo.CommandError{
|
|
Code: 40353,
|
|
Name: "Location40353",
|
|
Message: "Invalid $unset :: caused by :: FieldPath must not end with a '.'.",
|
|
},
|
|
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/354",
|
|
},
|
|
"PathEmptyPrefixKey": {
|
|
pipeline: bson.A{
|
|
bson.D{{"$unset", ".v"}},
|
|
},
|
|
err: &mongo.CommandError{
|
|
Code: 15998,
|
|
Name: "Location15998",
|
|
Message: "Invalid $unset :: caused by :: FieldPath field names may not be empty strings.",
|
|
},
|
|
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/354",
|
|
},
|
|
"PathDollarPrefix": {
|
|
pipeline: bson.A{
|
|
bson.D{{"$unset", "$v"}},
|
|
},
|
|
err: &mongo.CommandError{
|
|
Code: 16410,
|
|
Name: "Location16410",
|
|
Message: "Invalid $unset :: caused by :: FieldPath field names may not start with '$'. Consider using $getField or $setField.",
|
|
},
|
|
altMessage: "FieldPath field names may not start with '$'",
|
|
},
|
|
"ArrayEmpty": {
|
|
pipeline: bson.A{
|
|
bson.D{{"$unset", bson.A{}}},
|
|
},
|
|
err: &mongo.CommandError{
|
|
Code: 31119,
|
|
Name: "Location31119",
|
|
Message: "$unset specification must be a string or an array with at least one field",
|
|
},
|
|
altMessage: "$unset specification must be a string or an array with at least one field",
|
|
},
|
|
"ArrayInvalidType": {
|
|
pipeline: bson.A{
|
|
bson.D{{"$unset", bson.A{"field1", 1}}},
|
|
},
|
|
err: &mongo.CommandError{
|
|
Code: 31120,
|
|
Name: "Location31120",
|
|
Message: "$unset specification must be a string or an array containing only string values",
|
|
},
|
|
},
|
|
"ArrayEmptyString": {
|
|
pipeline: bson.A{
|
|
bson.D{{"$unset", bson.A{""}}},
|
|
},
|
|
err: &mongo.CommandError{
|
|
Code: 40352,
|
|
Name: "Location40352",
|
|
Message: "Invalid $unset :: caused by :: FieldPath cannot be constructed with empty string",
|
|
},
|
|
altMessage: "FieldPath cannot be constructed with empty string",
|
|
},
|
|
"ArrayPathDuplicate": {
|
|
pipeline: bson.A{
|
|
bson.D{{"$unset", bson.A{"v", "v"}}},
|
|
},
|
|
err: &mongo.CommandError{
|
|
Code: 31250,
|
|
Name: "Location31250",
|
|
Message: "Invalid $unset :: caused by :: Path collision at v",
|
|
},
|
|
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/354",
|
|
},
|
|
"ArrayPathOverwrites": {
|
|
pipeline: bson.A{
|
|
bson.D{{"$unset", bson.A{"v", "v.foo"}}},
|
|
},
|
|
err: &mongo.CommandError{
|
|
Code: 31249,
|
|
Name: "Location31249",
|
|
Message: "Invalid $unset :: caused by :: Path collision at v.foo remaining portion foo",
|
|
},
|
|
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/354",
|
|
},
|
|
"ArrayPathOverwritesRemaining": {
|
|
pipeline: bson.A{
|
|
bson.D{{"$unset", bson.A{"v", "v.foo.bar"}}},
|
|
},
|
|
err: &mongo.CommandError{
|
|
Code: 31249,
|
|
Name: "Location31249",
|
|
Message: "Invalid $unset :: caused by :: Path collision at v.foo.bar remaining portion foo.bar",
|
|
},
|
|
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/354",
|
|
},
|
|
"ArrayPathCollision": {
|
|
pipeline: bson.A{
|
|
bson.D{{"$unset", bson.A{"v.foo", "v"}}},
|
|
},
|
|
err: &mongo.CommandError{
|
|
Code: 31250,
|
|
Name: "Location31250",
|
|
Message: "Invalid $unset :: caused by :: Path collision at v",
|
|
},
|
|
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/354",
|
|
},
|
|
"ArrayPathEmptyKey": {
|
|
pipeline: bson.A{
|
|
bson.D{{"$unset", bson.A{"v..foo"}}},
|
|
},
|
|
err: &mongo.CommandError{
|
|
Code: 15998,
|
|
Name: "Location15998",
|
|
Message: "Invalid $unset :: caused by :: FieldPath field names may not be empty strings.",
|
|
},
|
|
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/354",
|
|
},
|
|
"ArrayPathEmptySuffixKey": {
|
|
pipeline: bson.A{
|
|
bson.D{{"$unset", bson.A{"v."}}},
|
|
},
|
|
err: &mongo.CommandError{
|
|
Code: 40353,
|
|
Name: "Location40353",
|
|
Message: "Invalid $unset :: caused by :: FieldPath must not end with a '.'.",
|
|
},
|
|
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/354",
|
|
},
|
|
"ArrayPathEmptyPrefixKey": {
|
|
pipeline: bson.A{
|
|
bson.D{{"$unset", bson.A{".v"}}},
|
|
},
|
|
err: &mongo.CommandError{
|
|
Code: 15998,
|
|
Name: "Location15998",
|
|
Message: "Invalid $unset :: caused by :: FieldPath field names may not be empty strings.",
|
|
},
|
|
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/354",
|
|
},
|
|
"ArrayPathDollarPrefix": {
|
|
pipeline: bson.A{
|
|
bson.D{{"$unset", bson.A{"$v"}}},
|
|
},
|
|
err: &mongo.CommandError{
|
|
Code: 16410,
|
|
Name: "Location16410",
|
|
Message: "Invalid $unset :: caused by :: FieldPath field names may not start with '$'. Consider using $getField or $setField.",
|
|
},
|
|
altMessage: "FieldPath field names may not start with '$'",
|
|
},
|
|
} {
|
|
t.Run(name, func(tt *testing.T) {
|
|
tt.Parallel()
|
|
|
|
var t testing.TB = tt
|
|
|
|
if tc.failsForFerretDB != "" {
|
|
t = setup.FailsForFerretDB(tt, tc.failsForFerretDB)
|
|
}
|
|
|
|
require.NotNil(t, tc.pipeline, "pipeline must not be nil")
|
|
require.NotNil(t, tc.err, "err must not be nil")
|
|
|
|
_, err := collection.Aggregate(ctx, tc.pipeline)
|
|
AssertEqualAltCommandError(t, *tc.err, tc.altMessage, err)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestAggregateSortErrors(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
ctx, collection := setup.Setup(t)
|
|
|
|
for name, tc := range map[string]struct { //nolint:vet // used for test only
|
|
pipeline bson.A // required, aggregation pipeline stages
|
|
|
|
err *mongo.CommandError // required
|
|
altMessage string // optional, alternative error message
|
|
failsForFerretDB string
|
|
}{
|
|
"DotNotationMissingField": {
|
|
pipeline: bson.A{bson.D{{"$sort", bson.D{
|
|
{"v..foo", 1},
|
|
}}}},
|
|
err: &mongo.CommandError{
|
|
Code: 15998,
|
|
Name: "Location15998",
|
|
Message: "FieldPath field names may not be empty strings.",
|
|
},
|
|
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/349",
|
|
},
|
|
} {
|
|
t.Run(name, func(tt *testing.T) {
|
|
tt.Parallel()
|
|
|
|
var t testing.TB = tt
|
|
|
|
if tc.failsForFerretDB != "" {
|
|
t = setup.FailsForFerretDB(tt, tc.failsForFerretDB)
|
|
}
|
|
|
|
require.NotNil(t, tc.pipeline, "pipeline must not be nil")
|
|
require.NotNil(t, tc.err, "err must not be nil")
|
|
|
|
_, err := collection.Aggregate(ctx, tc.pipeline)
|
|
AssertEqualAltCommandError(t, *tc.err, tc.altMessage, err)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestAggregateCommandMaxTimeMSErrors(t *testing.T) {
|
|
t.Parallel()
|
|
ctx, collection := setup.Setup(t)
|
|
|
|
for name, tc := range map[string]struct { //nolint:vet // used for testing only
|
|
command bson.D // required, command to run
|
|
|
|
err *mongo.CommandError // required, expected error from MongoDB
|
|
altMessage string // optional, alternative error message for FerretDB, ignored if empty
|
|
failsForFerretDB string
|
|
}{
|
|
"NegativeLong": {
|
|
command: bson.D{
|
|
{"aggregate", collection.Name()},
|
|
{"pipeline", bson.A{}},
|
|
{"cursor", bson.D{}},
|
|
{"maxTimeMS", int64(-1)},
|
|
},
|
|
err: &mongo.CommandError{
|
|
Code: 51024,
|
|
Name: "Location51024",
|
|
Message: "BSON field 'maxTimeMS' value must be >= 0, actual value '-1'",
|
|
},
|
|
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/354",
|
|
},
|
|
"MaxLong": {
|
|
command: bson.D{
|
|
{"aggregate", collection.Name()},
|
|
{"pipeline", bson.A{}},
|
|
{"cursor", bson.D{}},
|
|
{"maxTimeMS", math.MaxInt64},
|
|
},
|
|
err: &mongo.CommandError{
|
|
Code: 2,
|
|
Name: "BadValue",
|
|
Message: "9223372036854775807 value for maxTimeMS is out of range " + shareddata.Int32Interval,
|
|
},
|
|
altMessage: "9223372036854775807 value for maxTimeMS is out of range",
|
|
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/354",
|
|
},
|
|
"Double": {
|
|
command: bson.D{
|
|
{"aggregate", collection.Name()},
|
|
{"pipeline", bson.A{}},
|
|
{"cursor", bson.D{}},
|
|
{"maxTimeMS", 1000.5},
|
|
},
|
|
err: &mongo.CommandError{
|
|
Code: 2,
|
|
Name: "BadValue",
|
|
Message: "maxTimeMS has non-integral value",
|
|
},
|
|
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/354",
|
|
},
|
|
"NegativeDouble": {
|
|
command: bson.D{
|
|
{"aggregate", collection.Name()},
|
|
{"pipeline", bson.A{}},
|
|
{"cursor", bson.D{}},
|
|
{"maxTimeMS", -14245345234123245.55},
|
|
},
|
|
err: &mongo.CommandError{
|
|
Code: 51024,
|
|
Name: "Location51024",
|
|
Message: "BSON field 'maxTimeMS' value must be >= 0, actual value '-14245345234123246'",
|
|
},
|
|
altMessage: "BSON field 'maxTimeMS' value must be >= 0, actual value '-1.424534523412325e+16'",
|
|
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/354",
|
|
},
|
|
"BigDouble": {
|
|
command: bson.D{
|
|
{"aggregate", collection.Name()},
|
|
{"pipeline", bson.A{}},
|
|
{"cursor", bson.D{}},
|
|
{"maxTimeMS", math.MaxFloat64},
|
|
},
|
|
err: &mongo.CommandError{
|
|
Code: 2,
|
|
Name: "BadValue",
|
|
Message: "9223372036854775807 value for maxTimeMS is out of range " + shareddata.Int32Interval,
|
|
},
|
|
altMessage: "1.797693134862316e+308 value for maxTimeMS is out of range",
|
|
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/354",
|
|
},
|
|
"BigNegativeDouble": {
|
|
command: bson.D{
|
|
{"aggregate", collection.Name()},
|
|
{"pipeline", bson.A{}},
|
|
{"cursor", bson.D{}},
|
|
{"maxTimeMS", -math.MaxFloat64},
|
|
},
|
|
err: &mongo.CommandError{
|
|
Code: 51024,
|
|
Name: "Location51024",
|
|
Message: "BSON field 'maxTimeMS' value must be >= 0, actual value '-9223372036854775808'",
|
|
},
|
|
altMessage: "BSON field 'maxTimeMS' value must be >= 0, actual value '-1.797693134862316e+308'",
|
|
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/354",
|
|
},
|
|
"NegativeInt32": {
|
|
command: bson.D{
|
|
{"aggregate", collection.Name()},
|
|
{"pipeline", bson.A{}},
|
|
{"cursor", bson.D{}},
|
|
{"maxTimeMS", -1123123},
|
|
},
|
|
err: &mongo.CommandError{
|
|
Code: 51024,
|
|
Name: "Location51024",
|
|
Message: "BSON field 'maxTimeMS' value must be >= 0, actual value '-1123123'",
|
|
},
|
|
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/354",
|
|
},
|
|
"MaxIntPlus": {
|
|
command: bson.D{
|
|
{"aggregate", collection.Name()},
|
|
{"pipeline", bson.A{}},
|
|
{"cursor", bson.D{}},
|
|
{"maxTimeMS", math.MaxInt32 + 1},
|
|
},
|
|
err: &mongo.CommandError{
|
|
Code: 2,
|
|
Name: "BadValue",
|
|
Message: "2147483648 value for maxTimeMS is out of range " + shareddata.Int32Interval,
|
|
},
|
|
altMessage: "2147483648 value for maxTimeMS is out of range",
|
|
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/354",
|
|
},
|
|
"Null": {
|
|
command: bson.D{
|
|
{"aggregate", collection.Name()},
|
|
{"pipeline", bson.A{}},
|
|
{"cursor", bson.D{}},
|
|
{"maxTimeMS", nil},
|
|
},
|
|
err: &mongo.CommandError{
|
|
Code: 2,
|
|
Name: "BadValue",
|
|
Message: "maxTimeMS must be a number",
|
|
},
|
|
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/354",
|
|
},
|
|
"String": {
|
|
command: bson.D{
|
|
{"aggregate", collection.Name()},
|
|
{"pipeline", bson.A{}},
|
|
{"cursor", bson.D{}},
|
|
{"maxTimeMS", "string"},
|
|
},
|
|
err: &mongo.CommandError{
|
|
Code: 14,
|
|
Name: "TypeMismatch",
|
|
Message: "BSON field 'aggregate.maxTimeMS' is the wrong type 'string', expected types '[long, int, decimal, double']",
|
|
},
|
|
altMessage: "BSON field 'aggregate.maxTimeMS' is the wrong type 'string', expected types '[long, int, decimal, double]'",
|
|
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/354",
|
|
},
|
|
"Array": {
|
|
command: bson.D{
|
|
{"aggregate", collection.Name()},
|
|
{"pipeline", bson.A{}},
|
|
{"cursor", bson.D{}},
|
|
{"maxTimeMS", bson.A{int32(42), "foo", nil}},
|
|
},
|
|
err: &mongo.CommandError{
|
|
Code: 14,
|
|
Name: "TypeMismatch",
|
|
Message: "BSON field 'aggregate.maxTimeMS' is the wrong type 'array', expected types '[long, int, decimal, double']",
|
|
},
|
|
altMessage: "BSON field 'aggregate.maxTimeMS' is the wrong type 'array', expected types '[long, int, decimal, double]'",
|
|
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/354",
|
|
},
|
|
"Document": {
|
|
command: bson.D{
|
|
{"aggregate", collection.Name()},
|
|
{"pipeline", bson.A{}},
|
|
{"cursor", bson.D{}},
|
|
{"maxTimeMS", bson.D{{"foo", int32(42)}}},
|
|
},
|
|
err: &mongo.CommandError{
|
|
Code: 14,
|
|
Name: "TypeMismatch",
|
|
Message: "BSON field 'aggregate.maxTimeMS' is the wrong type 'object', expected types '[long, int, decimal, double']",
|
|
},
|
|
altMessage: "BSON field 'aggregate.maxTimeMS' is the wrong type 'object', expected types '[long, int, decimal, double]'",
|
|
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/354",
|
|
},
|
|
} {
|
|
t.Run(name, func(tt *testing.T) {
|
|
tt.Parallel()
|
|
|
|
var t testing.TB = tt
|
|
|
|
if tc.failsForFerretDB != "" {
|
|
t = setup.FailsForFerretDB(tt, tc.failsForFerretDB)
|
|
}
|
|
|
|
require.NotNil(t, tc.err, "err must not be nil")
|
|
|
|
var res bson.D
|
|
err := collection.Database().RunCommand(ctx, tc.command).Decode(&res)
|
|
AssertEqualAltCommandError(t, *tc.err, tc.altMessage, err)
|
|
require.Nil(t, res)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestAggregateCommandCursor(t *testing.T) {
|
|
t.Parallel()
|
|
ctx, collection := setup.Setup(t)
|
|
|
|
// the number of documents is set above the default batchSize of 101
|
|
// for testing unset batchSize returning default batchSize
|
|
arr := GenerateDocuments(0, 110)
|
|
_, err := collection.InsertMany(ctx, arr)
|
|
require.NoError(t, err)
|
|
|
|
for name, tc := range map[string]struct { //nolint:vet // used for testing only
|
|
pipeline any // optional, defaults to bson.A{}
|
|
cursor any // optional, nil to leave cursor 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
|
|
failsForFerretDB string
|
|
}{
|
|
"Int": {
|
|
cursor: bson.D{{"batchSize", 1}},
|
|
firstBatch: arr[:1],
|
|
},
|
|
"Long": {
|
|
cursor: bson.D{{"batchSize", int64(2)}},
|
|
firstBatch: arr[:2],
|
|
},
|
|
"LongZero": {
|
|
cursor: bson.D{{"batchSize", int64(0)}},
|
|
firstBatch: bson.A{},
|
|
},
|
|
"LongNegative": {
|
|
cursor: bson.D{{"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'",
|
|
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/349",
|
|
},
|
|
"DoubleZero": {
|
|
cursor: bson.D{{"batchSize", float64(0)}},
|
|
firstBatch: bson.A{},
|
|
},
|
|
"DoubleNegative": {
|
|
cursor: bson.D{{"batchSize", -1.1}},
|
|
err: &mongo.CommandError{
|
|
Code: 51024,
|
|
Name: "Location51024",
|
|
Message: "BSON field 'batchSize' value must be >= 0, actual value '-1'",
|
|
},
|
|
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/349",
|
|
},
|
|
"DoubleFloor": {
|
|
cursor: bson.D{{"batchSize", 1.9}},
|
|
firstBatch: arr[:1],
|
|
},
|
|
"Bool": {
|
|
cursor: bson.D{{"batchSize", true}},
|
|
firstBatch: arr[:1],
|
|
err: &mongo.CommandError{
|
|
Code: 14,
|
|
Name: "TypeMismatch",
|
|
Message: "BSON field 'cursor.batchSize' is the wrong type 'bool', expected types '[long, int, decimal, double']",
|
|
},
|
|
altMessage: "BSON field 'cursor.batchSize' is the wrong type 'bool', expected type 'number'",
|
|
},
|
|
"Unset": {
|
|
cursor: nil,
|
|
firstBatch: arr[:101],
|
|
err: &mongo.CommandError{
|
|
Code: 9,
|
|
Name: "FailedToParse",
|
|
Message: "The 'cursor' option is required, except for aggregate with the explain argument",
|
|
},
|
|
},
|
|
"Empty": {
|
|
cursor: bson.D{},
|
|
firstBatch: arr[:101],
|
|
},
|
|
"String": {
|
|
cursor: "invalid",
|
|
firstBatch: arr[:101],
|
|
err: &mongo.CommandError{
|
|
Code: 14,
|
|
Name: "TypeMismatch",
|
|
Message: "cursor field must be missing or an object",
|
|
},
|
|
altMessage: "BSON field 'cursor' is the wrong type 'string', expected type 'object'",
|
|
},
|
|
"LargeBatchSize": {
|
|
cursor: bson.D{{"batchSize", 102}},
|
|
firstBatch: arr[:102],
|
|
},
|
|
"LargeBatchSizeMatch": {
|
|
pipeline: bson.A{
|
|
bson.D{{"$match", bson.D{{"_id", bson.D{{"$in", bson.A{0, 1, 2, 3, 4, 5}}}}}}},
|
|
},
|
|
cursor: bson.D{{"batchSize", 102}},
|
|
firstBatch: arr[:6],
|
|
},
|
|
} {
|
|
t.Run(name, func(tt *testing.T) {
|
|
tt.Parallel()
|
|
|
|
var t testing.TB = tt
|
|
|
|
if tc.failsForFerretDB != "" {
|
|
t = setup.FailsForFerretDB(tt, tc.failsForFerretDB)
|
|
}
|
|
|
|
var pipeline any = bson.A{}
|
|
if tc.pipeline != nil {
|
|
pipeline = tc.pipeline
|
|
}
|
|
|
|
var rest bson.D
|
|
if tc.cursor != nil {
|
|
rest = append(rest, bson.E{Key: "cursor", Value: tc.cursor})
|
|
}
|
|
|
|
command := append(
|
|
bson.D{
|
|
{"aggregate", collection.Name()},
|
|
{"pipeline", pipeline},
|
|
},
|
|
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)
|
|
})
|
|
}
|
|
}
|