diff --git a/go.mod b/go.mod index dfd393d2557a..ee2b368ccac7 100644 --- a/go.mod +++ b/go.mod @@ -68,7 +68,7 @@ require ( golang.org/x/net v0.21.0 // indirect golang.org/x/sync v0.6.0 // indirect golang.org/x/text v0.14.0 // indirect - golang.org/x/tools v0.18.0 // indirect + golang.org/x/tools v0.18.0 // indirect; https://github.com/golang/go/issues/66259 google.golang.org/genproto/googleapis/api v0.0.0-20240102182953-50ed04b92917 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917 // indirect google.golang.org/grpc v1.61.0 // indirect diff --git a/integration/basic_test.go b/integration/basic_test.go index f99665f59566..f82fee9cc1e9 100644 --- a/integration/basic_test.go +++ b/integration/basic_test.go @@ -552,58 +552,6 @@ func TestDebugError(t *testing.T) { }) } -func TestCheckingNestedDocuments(t *testing.T) { - t.Skip("https://github.com/FerretDB/FerretDB/issues/3759") - - for name, tc := range map[string]struct { - doc any - err *mongo.CommandError - }{ - "1ok": { - doc: CreateNestedDocument(1), - }, - "10ok": { - doc: CreateNestedDocument(10), - }, - "100ok": { - doc: CreateNestedDocument(100), - }, - "179ok": { - doc: CreateNestedDocument(179), - }, - "180fail": { - doc: CreateNestedDocument(180), - err: &mongo.CommandError{ - Message: "bson.Array.ReadFrom (document has exceeded the max supported nesting: 179.", - }, - }, - "180endedWithDocumentFail": { - doc: bson.D{{"v", CreateNestedDocument(179)}}, - err: &mongo.CommandError{ - Message: "bson.Document.ReadFrom (document has exceeded the max supported nesting: 179.", - }, - }, - "1000fail": { - doc: CreateNestedDocument(1000), - err: &mongo.CommandError{ - Message: "bson.Document.ReadFrom (document has exceeded the max supported nesting: 179.", - }, - }, - } { - t.Run(name, func(t *testing.T) { - ctx, collection := setup.Setup(t) - - _, err := collection.InsertOne(ctx, tc.doc) - if tc.err != nil { - AssertEqualCommandError(t, *tc.err, err) - return - } - - require.NoError(t, err) - }) - } -} - func TestPingCommand(t *testing.T) { t.Parallel() diff --git a/integration/benchmarks_test.go b/integration/benchmarks_test.go index 033d470dbe94..f73c09a18157 100644 --- a/integration/benchmarks_test.go +++ b/integration/benchmarks_test.go @@ -54,7 +54,7 @@ func BenchmarkFind(b *testing.B) { b.Run(name, func(b *testing.B) { var firstDocs, docs int - for i := 0; i < b.N; i++ { + for range b.N { cursor, err := s.Collection.Find(s.Ctx, bc.filter) require.NoError(b, err) @@ -112,7 +112,7 @@ func BenchmarkReplaceOne(b *testing.B) { filter := bson.D{{"_id", doc[0].Value}} var res *mongo.UpdateResult - for i := 0; i < b.N; i++ { + for i := range b.N { doc[1].Value = int64(i + 1) res, err = collection.ReplaceOne(ctx, filter, doc) @@ -148,7 +148,7 @@ func BenchmarkInsertMany(b *testing.B) { b.Run(fmt.Sprintf("%s/Batch%d", provider.Name(), batchSize), func(b *testing.B) { b.StopTimer() - for i := 0; i < b.N; i++ { + for range b.N { require.NoError(b, collection.Drop(ctx)) iter := provider.NewIterator() diff --git a/integration/go.mod b/integration/go.mod index dcb4b78950ab..8cdaed2cebf5 100644 --- a/integration/go.mod +++ b/integration/go.mod @@ -68,7 +68,7 @@ require ( golang.org/x/sync v0.6.0 // indirect golang.org/x/sys v0.17.0 // indirect golang.org/x/text v0.14.0 // indirect - golang.org/x/tools v0.18.0 // indirect + golang.org/x/tools v0.18.0 // indirect; https://github.com/golang/go/issues/66259 google.golang.org/genproto/googleapis/api v0.0.0-20240102182953-50ed04b92917 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917 // indirect google.golang.org/grpc v1.61.0 // indirect diff --git a/integration/integration.go b/integration/integration.go index d03ab9bbff5b..206d9c3fd4f8 100644 --- a/integration/integration.go +++ b/integration/integration.go @@ -458,29 +458,3 @@ func GenerateDocuments(startID, endID int32) (bson.A, []bson.D) { return arr, docs } - -// CreateNestedDocument creates a mock BSON document that consists of nested arrays and documents. -// The nesting level is based on integer parameter. -func CreateNestedDocument(n int) bson.D { - return createNestedDocument(n, false).(bson.D) -} - -// createNestedDocument creates the nested n times object that consists of -// documents and arrays. If the arr is true, the root value will be array. -// -// This function should be used only internally. -// To generate values for tests please use -// exported CreateNestedDocument function. -func createNestedDocument(n int, arr bool) any { - var child any - - if n > 0 { - child = createNestedDocument(n-1, !arr) - } - - if arr { - return bson.A{child} - } - - return bson.D{{"v", child}} -} diff --git a/integration/nested_document_test.go b/integration/nested_document_test.go deleted file mode 100644 index ebd39bb4d795..000000000000 --- a/integration/nested_document_test.go +++ /dev/null @@ -1,44 +0,0 @@ -// 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" - "go.mongodb.org/mongo-driver/bson" -) - -func TestCreateNestedDocument(t *testing.T) { - t.Parallel() - - t.Run("0", func(t *testing.T) { - expected := bson.D{{"v", nil}} - actual := CreateNestedDocument(0) - assert.Equal(t, expected, actual) - }) - - t.Run("1", func(t *testing.T) { - expected := bson.D{{"v", bson.A{nil}}} - actual := CreateNestedDocument(1) - assert.Equal(t, expected, actual) - }) - - t.Run("2", func(t *testing.T) { - expected := bson.D{{"v", bson.A{bson.D{{"v", nil}}}}} - actual := CreateNestedDocument(2) - assert.Equal(t, expected, actual) - }) -} diff --git a/internal/bson/bson_test.go b/internal/bson/bson_test.go index ac5da42290b9..b681992075cf 100644 --- a/internal/bson/bson_test.go +++ b/internal/bson/bson_test.go @@ -228,7 +228,7 @@ func benchmark(b *testing.B, testCases []testCase, newFunc func() bsontype) { b.SetBytes(br.Size()) b.ResetTimer() - for i := 0; i < b.N; i++ { + for range b.N { _, seekErr = br.Seek(0, io.SeekStart) v = newFunc() diff --git a/internal/bson2/array.go b/internal/bson2/array.go index be18204a71d2..156c812fbfa8 100644 --- a/internal/bson2/array.go +++ b/internal/bson2/array.go @@ -141,7 +141,7 @@ func (arr *Array) Encode() (RawArray, error) { // LogValue implements slog.LogValuer interface. func (arr *Array) LogValue() slog.Value { - return slogValue(arr) + return slogValue(arr, 1) } // LogMessage returns an indented representation as a string, diff --git a/internal/bson2/bson2_test.go b/internal/bson2/bson2_test.go index d9826f73eae6..f29205d15369 100644 --- a/internal/bson2/bson2_test.go +++ b/internal/bson2/bson2_test.go @@ -285,6 +285,19 @@ var normalTestCases = []normalTestCase{ "timestamp": [Timestamp(42), Timestamp(0)], }`, }, + { + name: "nested", + raw: testutil.MustParseDumpFile("testdata", "nested.hex"), + tdoc: must.NotFail(makeNested(false, 150).(*Document).Convert()), + m: ` + { + "f": [ + { + "f": [{"f": [{"f": [{"f": [{"f": [{"f": [{"f": [{"f": [{"f": [{...}]}]}]}]}]}]}]}]}], + }, + ], + }`, + }, { name: "float64Doc", raw: RawDocument{ diff --git a/internal/bson2/document.go b/internal/bson2/document.go index a1644c6f7e0f..84e962f06bac 100644 --- a/internal/bson2/document.go +++ b/internal/bson2/document.go @@ -175,7 +175,7 @@ func (doc *Document) Encode() (RawDocument, error) { // LogValue implements slog.LogValuer interface. func (doc *Document) LogValue() slog.Value { - return slogValue(doc) + return slogValue(doc, 1) } // LogMessage returns an indented representation as a string, diff --git a/internal/bson2/logging.go b/internal/bson2/logging.go index 29a823fd8d15..61022698e117 100644 --- a/internal/bson2/logging.go +++ b/internal/bson2/logging.go @@ -25,9 +25,12 @@ import ( "time" ) -// flowLimit is the maximum length of a flow/inline/compact representation of a BSON value. +// logMaxFlowLength is the maximum length of a flow/inline/compact representation of a BSON value. // It may be set to 0 to disable flow representation. -const flowLimit = 80 +const logMaxFlowLength = 80 + +// logMaxDepth is the maximum depth of a recursive representation of a BSON value. +const logMaxDepth = 20 // nanBits is the most common pattern of a NaN float64 value, the same as math.Float64bits(math.NaN()). const nanBits = 0b111111111111000000000000000000000000000000000000000000000000001 @@ -42,13 +45,17 @@ const nanBits = 0b11111111111100000000000000000000000000000000000000000000000000 // More information is subsequently lost in handlers output; // for example, float64(42), int32(42), and int64(42) values would all look the same // (`f64=42 i32=42 i64=42` or `{"f64":42,"i32":42,"i64":42}`). -func slogValue(v any) slog.Value { +func slogValue(v any, depth int) slog.Value { switch v := v.(type) { case *Document: + if depth > logMaxDepth { + return slog.StringValue("Document<...>") + } + var attrs []slog.Attr for _, f := range v.fields { - attrs = append(attrs, slog.Attr{Key: f.name, Value: slogValue(f.value)}) + attrs = append(attrs, slog.Attr{Key: f.name, Value: slogValue(f.value, depth+1)}) } return slog.GroupValue(attrs...) @@ -61,10 +68,14 @@ func slogValue(v any) slog.Value { return slog.StringValue("RawDocument<" + strconv.Itoa(len(v)) + ">") case *Array: + if depth > logMaxDepth { + return slog.StringValue("Array<...>") + } + var attrs []slog.Attr for i, v := range v.elements { - attrs = append(attrs, slog.Attr{Key: strconv.Itoa(i), Value: slogValue(v)}) + attrs = append(attrs, slog.Attr{Key: strconv.Itoa(i), Value: slogValue(v, depth+1)}) } return slog.GroupValue(attrs...) @@ -131,11 +142,11 @@ func slogValue(v any) slog.Value { // The result is optimized for large values such as full request documents. // All information is preserved. func logMessage(v any) string { - return logMessageIndent(v, "") + return logMessageIndent(v, "", 1) } -// logMessageIndent is a variant of [logMessage] with an indentation for recursive calls. -func logMessageIndent(v any, indent string) string { +// logMessageIndent is a variant of [logMessage] with an indentation and depth for recursive calls. +func logMessageIndent(v any, indent string, depth int) string { switch v := v.(type) { case *Document: l := len(v.fields) @@ -143,12 +154,16 @@ func logMessageIndent(v any, indent string) string { return "{}" } - if flowLimit > 0 { + if depth > logMaxDepth { + return "{...}" + } + + if logMaxFlowLength > 0 { res := "{" for i, f := range v.fields { res += strconv.Quote(f.name) + `: ` - res += logMessageIndent(f.value, "") + res += logMessageIndent(f.value, "", depth+1) if i != l-1 { res += ", " @@ -157,7 +172,7 @@ func logMessageIndent(v any, indent string) string { res += `}` - if len(res) < flowLimit { + if len(res) < logMaxFlowLength { return res } } @@ -167,7 +182,7 @@ func logMessageIndent(v any, indent string) string { for _, f := range v.fields { res += indent + " " res += strconv.Quote(f.name) + `: ` - res += logMessageIndent(f.value, indent+" ") + ",\n" + res += logMessageIndent(f.value, indent+" ", depth+1) + ",\n" } res += indent + `}` @@ -183,11 +198,15 @@ func logMessageIndent(v any, indent string) string { return "[]" } - if flowLimit > 0 { + if depth > logMaxDepth { + return "[...]" + } + + if logMaxFlowLength > 0 { res := "[" for i, e := range v.elements { - res += logMessageIndent(e, "") + res += logMessageIndent(e, "", depth+1) if i != l-1 { res += ", " @@ -196,7 +215,7 @@ func logMessageIndent(v any, indent string) string { res += `]` - if len(res) < flowLimit { + if len(res) < logMaxFlowLength { return res } } @@ -205,7 +224,7 @@ func logMessageIndent(v any, indent string) string { for _, e := range v.elements { res += indent + " " - res += logMessageIndent(e, indent+" ") + ",\n" + res += logMessageIndent(e, indent+" ", depth+1) + ",\n" } res += indent + `]` diff --git a/internal/bson2/logging_test.go b/internal/bson2/logging_test.go index 62c7ef172660..338cdad5c32b 100644 --- a/internal/bson2/logging_test.go +++ b/internal/bson2/logging_test.go @@ -129,6 +129,21 @@ func TestLogging(t *testing.T) { "array": ["foo", "bar", ["baz", "qux"]], }`, }, + { + name: "Nested", + v: makeNested(false, 20).(*Document), + t: `v.f.0.f.0.f.0.f.0.f.0.f.0.f.0.f.0.f.0.f.0=`, + j: `{"v":{"f":{"0":{"f":{"0":{"f":{"0":{"f":{"0":{"f":{"0":{"f":{"0":` + + `{"f":{"0":{"f":{"0":{"f":{"0":{"f":{"0":null}}}}}}}}}}}}}}}}}}}}}`, + m: ` + { + "f": [ + { + "f": [{"f": [{"f": [{"f": [{"f": [{"f": [{"f": [{"f": [{"f": [null]}]}]}]}]}]}]}]}], + }, + ], + }`, + }, } { t.Run(tc.name, func(t *testing.T) { tlog.InfoContext(ctx, "", slog.Any("v", tc.v)) @@ -143,3 +158,22 @@ func TestLogging(t *testing.T) { }) } } + +// makeNested creates a nested document or array with the given depth. +func makeNested(array bool, depth int) any { + if depth < 1 { + panic("depth must be at least 1") + } + + var child any = Null + + if depth > 1 { + child = makeNested(!array, depth-1) + } + + if array { + return must.NotFail(NewArray(child)) + } + + return must.NotFail(NewDocument("f", child)) +} diff --git a/internal/bson2/raw_array.go b/internal/bson2/raw_array.go index 5e0067a09d2a..569527b97825 100644 --- a/internal/bson2/raw_array.go +++ b/internal/bson2/raw_array.go @@ -92,7 +92,7 @@ func (raw RawArray) decode(mode decodeMode) (*Array, error) { // LogValue implements slog.LogValuer interface. func (doc RawArray) LogValue() slog.Value { - return slogValue(doc) + return slogValue(doc, 1) } // LogMessage returns a representation as a string. diff --git a/internal/bson2/raw_document.go b/internal/bson2/raw_document.go index 19d2eb126aaf..37d479e8bb01 100644 --- a/internal/bson2/raw_document.go +++ b/internal/bson2/raw_document.go @@ -163,7 +163,7 @@ func (raw RawDocument) decode(mode decodeMode) (*Document, error) { // LogValue implements slog.LogValuer interface. func (doc RawDocument) LogValue() slog.Value { - return slogValue(doc) + return slogValue(doc, 1) } // LogMessage returns a representation as a string. diff --git a/internal/bson2/testdata/nested.hex b/internal/bson2/testdata/nested.hex new file mode 100644 index 000000000000..ede9bb7cdf15 --- /dev/null +++ b/internal/bson2/testdata/nested.hex @@ -0,0 +1,75 @@ +00000000 b0 04 00 00 04 66 00 a8 04 00 00 03 30 00 a0 04 |.....f......0...| +00000010 00 00 04 66 00 98 04 00 00 03 30 00 90 04 00 00 |...f......0.....| +00000020 04 66 00 88 04 00 00 03 30 00 80 04 00 00 04 66 |.f......0......f| +00000030 00 78 04 00 00 03 30 00 70 04 00 00 04 66 00 68 |.x....0.p....f.h| +00000040 04 00 00 03 30 00 60 04 00 00 04 66 00 58 04 00 |....0.`....f.X..| +00000050 00 03 30 00 50 04 00 00 04 66 00 48 04 00 00 03 |..0.P....f.H....| +00000060 30 00 40 04 00 00 04 66 00 38 04 00 00 03 30 00 |0.@....f.8....0.| +00000070 30 04 00 00 04 66 00 28 04 00 00 03 30 00 20 04 |0....f.(....0. .| +00000080 00 00 04 66 00 18 04 00 00 03 30 00 10 04 00 00 |...f......0.....| +00000090 04 66 00 08 04 00 00 03 30 00 00 04 00 00 04 66 |.f......0......f| +000000a0 00 f8 03 00 00 03 30 00 f0 03 00 00 04 66 00 e8 |......0......f..| +000000b0 03 00 00 03 30 00 e0 03 00 00 04 66 00 d8 03 00 |....0......f....| +000000c0 00 03 30 00 d0 03 00 00 04 66 00 c8 03 00 00 03 |..0......f......| +000000d0 30 00 c0 03 00 00 04 66 00 b8 03 00 00 03 30 00 |0......f......0.| +000000e0 b0 03 00 00 04 66 00 a8 03 00 00 03 30 00 a0 03 |.....f......0...| +000000f0 00 00 04 66 00 98 03 00 00 03 30 00 90 03 00 00 |...f......0.....| +00000100 04 66 00 88 03 00 00 03 30 00 80 03 00 00 04 66 |.f......0......f| +00000110 00 78 03 00 00 03 30 00 70 03 00 00 04 66 00 68 |.x....0.p....f.h| +00000120 03 00 00 03 30 00 60 03 00 00 04 66 00 58 03 00 |....0.`....f.X..| +00000130 00 03 30 00 50 03 00 00 04 66 00 48 03 00 00 03 |..0.P....f.H....| +00000140 30 00 40 03 00 00 04 66 00 38 03 00 00 03 30 00 |0.@....f.8....0.| +00000150 30 03 00 00 04 66 00 28 03 00 00 03 30 00 20 03 |0....f.(....0. .| +00000160 00 00 04 66 00 18 03 00 00 03 30 00 10 03 00 00 |...f......0.....| +00000170 04 66 00 08 03 00 00 03 30 00 00 03 00 00 04 66 |.f......0......f| +00000180 00 f8 02 00 00 03 30 00 f0 02 00 00 04 66 00 e8 |......0......f..| +00000190 02 00 00 03 30 00 e0 02 00 00 04 66 00 d8 02 00 |....0......f....| +000001a0 00 03 30 00 d0 02 00 00 04 66 00 c8 02 00 00 03 |..0......f......| +000001b0 30 00 c0 02 00 00 04 66 00 b8 02 00 00 03 30 00 |0......f......0.| +000001c0 b0 02 00 00 04 66 00 a8 02 00 00 03 30 00 a0 02 |.....f......0...| +000001d0 00 00 04 66 00 98 02 00 00 03 30 00 90 02 00 00 |...f......0.....| +000001e0 04 66 00 88 02 00 00 03 30 00 80 02 00 00 04 66 |.f......0......f| +000001f0 00 78 02 00 00 03 30 00 70 02 00 00 04 66 00 68 |.x....0.p....f.h| +00000200 02 00 00 03 30 00 60 02 00 00 04 66 00 58 02 00 |....0.`....f.X..| +00000210 00 03 30 00 50 02 00 00 04 66 00 48 02 00 00 03 |..0.P....f.H....| +00000220 30 00 40 02 00 00 04 66 00 38 02 00 00 03 30 00 |0.@....f.8....0.| +00000230 30 02 00 00 04 66 00 28 02 00 00 03 30 00 20 02 |0....f.(....0. .| +00000240 00 00 04 66 00 18 02 00 00 03 30 00 10 02 00 00 |...f......0.....| +00000250 04 66 00 08 02 00 00 03 30 00 00 02 00 00 04 66 |.f......0......f| +00000260 00 f8 01 00 00 03 30 00 f0 01 00 00 04 66 00 e8 |......0......f..| +00000270 01 00 00 03 30 00 e0 01 00 00 04 66 00 d8 01 00 |....0......f....| +00000280 00 03 30 00 d0 01 00 00 04 66 00 c8 01 00 00 03 |..0......f......| +00000290 30 00 c0 01 00 00 04 66 00 b8 01 00 00 03 30 00 |0......f......0.| +000002a0 b0 01 00 00 04 66 00 a8 01 00 00 03 30 00 a0 01 |.....f......0...| +000002b0 00 00 04 66 00 98 01 00 00 03 30 00 90 01 00 00 |...f......0.....| +000002c0 04 66 00 88 01 00 00 03 30 00 80 01 00 00 04 66 |.f......0......f| +000002d0 00 78 01 00 00 03 30 00 70 01 00 00 04 66 00 68 |.x....0.p....f.h| +000002e0 01 00 00 03 30 00 60 01 00 00 04 66 00 58 01 00 |....0.`....f.X..| +000002f0 00 03 30 00 50 01 00 00 04 66 00 48 01 00 00 03 |..0.P....f.H....| +00000300 30 00 40 01 00 00 04 66 00 38 01 00 00 03 30 00 |0.@....f.8....0.| +00000310 30 01 00 00 04 66 00 28 01 00 00 03 30 00 20 01 |0....f.(....0. .| +00000320 00 00 04 66 00 18 01 00 00 03 30 00 10 01 00 00 |...f......0.....| +00000330 04 66 00 08 01 00 00 03 30 00 00 01 00 00 04 66 |.f......0......f| +00000340 00 f8 00 00 00 03 30 00 f0 00 00 00 04 66 00 e8 |......0......f..| +00000350 00 00 00 03 30 00 e0 00 00 00 04 66 00 d8 00 00 |....0......f....| +00000360 00 03 30 00 d0 00 00 00 04 66 00 c8 00 00 00 03 |..0......f......| +00000370 30 00 c0 00 00 00 04 66 00 b8 00 00 00 03 30 00 |0......f......0.| +00000380 b0 00 00 00 04 66 00 a8 00 00 00 03 30 00 a0 00 |.....f......0...| +00000390 00 00 04 66 00 98 00 00 00 03 30 00 90 00 00 00 |...f......0.....| +000003a0 04 66 00 88 00 00 00 03 30 00 80 00 00 00 04 66 |.f......0......f| +000003b0 00 78 00 00 00 03 30 00 70 00 00 00 04 66 00 68 |.x....0.p....f.h| +000003c0 00 00 00 03 30 00 60 00 00 00 04 66 00 58 00 00 |....0.`....f.X..| +000003d0 00 03 30 00 50 00 00 00 04 66 00 48 00 00 00 03 |..0.P....f.H....| +000003e0 30 00 40 00 00 00 04 66 00 38 00 00 00 03 30 00 |0.@....f.8....0.| +000003f0 30 00 00 00 04 66 00 28 00 00 00 03 30 00 20 00 |0....f.(....0. .| +00000400 00 00 04 66 00 18 00 00 00 03 30 00 10 00 00 00 |...f......0.....| +00000410 04 66 00 08 00 00 00 0a 30 00 00 00 00 00 00 00 |.f......0.......| +00000420 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| +00000430 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| +00000440 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| +00000450 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| +00000460 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| +00000470 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| +00000480 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| +00000490 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| +000004a0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| diff --git a/internal/handler/sjson/sjson_test.go b/internal/handler/sjson/sjson_test.go index bf58fbc65f24..70c8991cfd8f 100644 --- a/internal/handler/sjson/sjson_test.go +++ b/internal/handler/sjson/sjson_test.go @@ -380,7 +380,7 @@ func benchmark(b *testing.B, testCases []testCase, newFunc func() sjsontype) { b.SetBytes(int64(len(data))) b.ResetTimer() - for i := 0; i < b.N; i++ { + for range b.N { v = newFunc() err = unmarshalJSON(v, &tc) } diff --git a/internal/util/lazyerrors/lazyerrors_test.go b/internal/util/lazyerrors/lazyerrors_test.go index 79deafd3107c..07141405b24b 100644 --- a/internal/util/lazyerrors/lazyerrors_test.go +++ b/internal/util/lazyerrors/lazyerrors_test.go @@ -130,7 +130,7 @@ func TestPC(t *testing.T) { var drain any func BenchmarkNew(b *testing.B) { - for i := 0; i < b.N; i++ { + for range b.N { drain = New("err") } @@ -140,7 +140,7 @@ func BenchmarkNew(b *testing.B) { } func BenchmarkStatic(b *testing.B) { - for i := 0; i < b.N; i++ { + for range b.N { drain = errors.New("[lazyerrors_test.go:144 lazyerrors.BenchmarkStatic] err") } diff --git a/internal/util/password/plain_test.go b/internal/util/password/plain_test.go index e5310fac79ab..9733269e8ccb 100644 --- a/internal/util/password/plain_test.go +++ b/internal/util/password/plain_test.go @@ -158,7 +158,7 @@ func BenchmarkPlain(b *testing.B) { b.Run("Exported", func(b *testing.B) { b.ReportAllocs() - for i := 0; i < b.N; i++ { + for range b.N { _, err = PlainHash("password") } }) diff --git a/internal/util/password/scramsha1_test.go b/internal/util/password/scramsha1_test.go index 80cdc6c2c9b7..2e3f5b33421c 100644 --- a/internal/util/password/scramsha1_test.go +++ b/internal/util/password/scramsha1_test.go @@ -16,7 +16,6 @@ package password import ( "encoding/base64" - "fmt" "testing" "github.com/stretchr/testify/assert" @@ -161,8 +160,8 @@ func BenchmarkSCRAMSHA1(b *testing.B) { b.Run("Exported", func(b *testing.B) { b.ReportAllocs() - for i := 0; i < b.N; i++ { - _, err = SCRAMSHA1Hash(fmt.Sprintf("user%d", i), "password") + for range b.N { + _, err = SCRAMSHA1Hash("user", "password") } }) diff --git a/internal/util/password/scramsha256_test.go b/internal/util/password/scramsha256_test.go index d45cc41783c3..b3e380ac135b 100644 --- a/internal/util/password/scramsha256_test.go +++ b/internal/util/password/scramsha256_test.go @@ -267,7 +267,7 @@ func BenchmarkSCRAMSHA256(b *testing.B) { b.Run("Exported", func(b *testing.B) { b.ReportAllocs() - for i := 0; i < b.N; i++ { + for range b.N { _, err = SCRAMSHA256Hash("password") } }) diff --git a/internal/wire/op_msg.go b/internal/wire/op_msg.go index 85019bc16d42..4bf7b1da2562 100644 --- a/internal/wire/op_msg.go +++ b/internal/wire/op_msg.go @@ -25,13 +25,16 @@ import ( "github.com/FerretDB/FerretDB/internal/util/must" ) +// typesValidation, when true, enables validation of types in wire messages. +const typesValidation = true + // OpMsgSection is one or more sections contained in an OpMsg. type OpMsgSection struct { // The order of fields is weird to make the struct smaller due to alignment. // The wire order is: kind, identifier, documents. Identifier string - documents []bson2.RawDocument + Documents []bson2.RawDocument Kind byte } @@ -40,7 +43,7 @@ func MakeOpMsgSection(doc *types.Document) OpMsgSection { raw := must.NotFail(must.NotFail(bson2.ConvertDocument(doc)).Encode()) return OpMsgSection{ - documents: []bson2.RawDocument{raw}, + Documents: []bson2.RawDocument{raw}, } } @@ -57,7 +60,7 @@ type OpMsg struct { // NewOpMsg creates a message with a single section of kind 0 with a single raw document. func NewOpMsg(raw bson2.RawDocument) (*OpMsg, error) { var msg OpMsg - if err := msg.SetSections(OpMsgSection{documents: []bson2.RawDocument{raw}}); err != nil { + if err := msg.SetSections(OpMsgSection{Documents: []bson2.RawDocument{raw}}); err != nil { return nil, lazyerrors.Error(err) } @@ -84,8 +87,8 @@ func checkSections(sections []OpMsgSection) error { return lazyerrors.New("kind 0 section has identifier") } - if len(s.documents) != 1 { - return lazyerrors.Errorf("kind 0 section has %d documents", len(s.documents)) + if len(s.Documents) != 1 { + return lazyerrors.Errorf("kind 0 section has %d documents", len(s.Documents)) } case 1: @@ -120,10 +123,10 @@ func (msg *OpMsg) SetSections(sections ...OpMsgSection) error { } } - // for validation - _, err := msg.Document() - if err != nil { - return lazyerrors.Error(err) + if typesValidation { + if _, err := msg.Document(); err != nil { + return lazyerrors.Error(err) + } } return nil @@ -148,7 +151,7 @@ func (msg *OpMsg) Document() (*types.Document, error) { continue } - doc, err := section.documents[0].Convert() + doc, err := section.Documents[0].Convert() if err != nil { return nil, lazyerrors.Error(err) } @@ -161,9 +164,9 @@ func (msg *OpMsg) Document() (*types.Document, error) { continue } - a := types.MakeArray(len(section.documents)) + a := types.MakeArray(len(section.Documents)) - for _, d := range section.documents { + for _, d := range section.Documents { doc, err := d.Convert() if err != nil { return nil, lazyerrors.Error(err) @@ -204,10 +207,10 @@ func (msg *OpMsg) RawSections() (bson2.RawDocument, []byte) { for _, s := range msg.Sections() { switch s.Kind { case 0: - spec = s.documents[0] + spec = s.Documents[0] case 1: - for _, d := range s.documents { + for _, d := range s.Documents { seq = append(seq, d...) } } @@ -230,7 +233,7 @@ func (msg *OpMsg) RawDocument() (bson2.RawDocument, error) { return nil, lazyerrors.Errorf(`expected section 0/"", got %d/%q`, s.Kind, s.Identifier) } - return s.documents[0], nil + return s.Documents[0], nil } func (msg *OpMsg) msgbody() {} @@ -238,7 +241,7 @@ func (msg *OpMsg) msgbody() {} // check implements [MsgBody] interface. func (msg *OpMsg) check() error { for _, s := range msg.sections { - for _, d := range s.documents { + for _, d := range s.Documents { if _, err := d.DecodeDeep(); err != nil { return lazyerrors.Error(err) } @@ -270,7 +273,7 @@ func (msg *OpMsg) UnmarshalBinaryNocopy(b []byte) error { return lazyerrors.Error(err) } - section.documents = []bson2.RawDocument{b[offset : offset+l]} + section.Documents = []bson2.RawDocument{b[offset : offset+l]} offset += l case 1: @@ -313,7 +316,7 @@ func (msg *OpMsg) UnmarshalBinaryNocopy(b []byte) error { return lazyerrors.Error(err) } - section.documents = append(section.documents, b[offset:offset+l]) + section.Documents = append(section.Documents, b[offset:offset+l]) offset += l secSize -= l } @@ -351,9 +354,10 @@ func (msg *OpMsg) UnmarshalBinaryNocopy(b []byte) error { } } - // for validation - if _, err := msg.Document(); err != nil { - return lazyerrors.Error(err) + if typesValidation { + if _, err := msg.Document(); err != nil { + return lazyerrors.Error(err) + } } return nil @@ -371,9 +375,10 @@ func (msg *OpMsg) MarshalBinary() ([]byte, error) { } } - // for validation - if _, err := msg.Document(); err != nil { - return nil, lazyerrors.Error(err) + if typesValidation { + if _, err := msg.Document(); err != nil { + return nil, lazyerrors.Error(err) + } } b := make([]byte, 4, 16) @@ -385,13 +390,13 @@ func (msg *OpMsg) MarshalBinary() ([]byte, error) { switch section.Kind { case 0: - b = append(b, section.documents[0]...) + b = append(b, section.Documents[0]...) case 1: sec := make([]byte, bson2.SizeCString(section.Identifier)) bson2.EncodeCString(sec, section.Identifier) - for _, doc := range section.documents { + for _, doc := range section.Documents { sec = append(sec, doc...) } @@ -435,7 +440,7 @@ func (msg *OpMsg) String() string { switch section.Kind { case 0: - doc, err := section.documents[0].DecodeDeep() + doc, err := section.Documents[0].DecodeDeep() if err == nil { must.NoError(s.Add("Document", doc)) } else { @@ -444,9 +449,9 @@ func (msg *OpMsg) String() string { case 1: must.NoError(s.Add("Identifier", section.Identifier)) - docs := bson2.MakeArray(len(section.documents)) + docs := bson2.MakeArray(len(section.Documents)) - for _, d := range section.documents { + for _, d := range section.Documents { doc, err := d.DecodeDeep() if err == nil { must.NoError(docs.Add(doc)) diff --git a/internal/wire/op_msg_test.go b/internal/wire/op_msg_test.go index ba9c09ff128e..041d6913f277 100644 --- a/internal/wire/op_msg_test.go +++ b/internal/wire/op_msg_test.go @@ -39,7 +39,7 @@ var msgTestCases = []testCase{ }, msgBody: &OpMsg{ sections: []OpMsgSection{{ - documents: []bson2.RawDocument{makeRawDocument( + Documents: []bson2.RawDocument{makeRawDocument( "buildInfo", int32(1), "lsid", must.NotFail(types.NewDocument( "id", types.Binary{ @@ -83,7 +83,7 @@ var msgTestCases = []testCase{ }, msgBody: &OpMsg{ sections: []OpMsgSection{{ - documents: []bson2.RawDocument{makeRawDocument( + Documents: []bson2.RawDocument{makeRawDocument( "version", "5.0.0", "gitVersion", "1184f004a99660de6f5e745573419bda8a28c0e9", "modules", must.NotFail(types.NewArray()), @@ -180,7 +180,7 @@ var msgTestCases = []testCase{ msgBody: &OpMsg{ sections: []OpMsgSection{ { - documents: []bson2.RawDocument{makeRawDocument( + Documents: []bson2.RawDocument{makeRawDocument( "insert", "actor", "ordered", true, "writeConcern", must.NotFail(types.NewDocument( @@ -192,7 +192,7 @@ var msgTestCases = []testCase{ { Kind: 1, Identifier: "documents", - documents: []bson2.RawDocument{ + Documents: []bson2.RawDocument{ makeRawDocument( "_id", types.ObjectID{0x61, 0x2e, 0xc2, 0x80, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01}, "actor_id", int32(1), @@ -290,7 +290,7 @@ var msgTestCases = []testCase{ }, msgBody: &OpMsg{ sections: []OpMsgSection{{ - documents: []bson2.RawDocument{makeRawDocument( + Documents: []bson2.RawDocument{makeRawDocument( "insert", "values", "documents", must.NotFail(types.NewArray( must.NotFail(types.NewDocument( @@ -343,7 +343,7 @@ var msgTestCases = []testCase{ msgBody: &OpMsg{ sections: []OpMsgSection{ { - documents: []bson2.RawDocument{makeRawDocument( + Documents: []bson2.RawDocument{makeRawDocument( "insert", "TestInsertSimple", "ordered", true, "$db", "testinsertsimple", @@ -352,7 +352,7 @@ var msgTestCases = []testCase{ { Kind: 1, Identifier: "documents", - documents: []bson2.RawDocument{makeRawDocument( + Documents: []bson2.RawDocument{makeRawDocument( "_id", types.ObjectID{0x63, 0x7c, 0xfa, 0xd8, 0x8d, 0xc3, 0xce, 0xcd, 0xe3, 0x8e, 0x1e, 0x6b}, "v", math.Copysign(0, -1), )}, @@ -421,13 +421,13 @@ var msgTestCases = []testCase{ { Kind: 1, Identifier: "documents", - documents: []bson2.RawDocument{makeRawDocument( + Documents: []bson2.RawDocument{makeRawDocument( "_id", types.ObjectID{0x63, 0x8c, 0xec, 0x46, 0xaa, 0x77, 0x8b, 0xf3, 0x70, 0x10, 0x54, 0x29}, "a", float64(3), )}, }, { - documents: []bson2.RawDocument{makeRawDocument( + Documents: []bson2.RawDocument{makeRawDocument( "insert", "foo", "ordered", true, "$db", "test", @@ -510,7 +510,7 @@ var msgTestCases = []testCase{ { Kind: 1, Identifier: "updates", - documents: []bson2.RawDocument{makeRawDocument( + Documents: []bson2.RawDocument{makeRawDocument( "q", must.NotFail(types.NewDocument( "a", float64(20), )), @@ -524,7 +524,7 @@ var msgTestCases = []testCase{ )}, }, { - documents: []bson2.RawDocument{makeRawDocument( + Documents: []bson2.RawDocument{makeRawDocument( "update", "foo", "ordered", true, "$db", "test", @@ -594,13 +594,13 @@ var msgTestCases = []testCase{ { Kind: 1, Identifier: "documents", - documents: []bson2.RawDocument{makeRawDocument( + Documents: []bson2.RawDocument{makeRawDocument( "_id", types.ObjectID{0x63, 0x8c, 0xec, 0x46, 0xaa, 0x77, 0x8b, 0xf3, 0x70, 0x10, 0x54, 0x29}, "a", float64(3), )}, }, { - documents: []bson2.RawDocument{makeRawDocument( + Documents: []bson2.RawDocument{makeRawDocument( "insert", "fooo", "ordered", true, "$db", "test", diff --git a/internal/wire/wire_test.go b/internal/wire/wire_test.go index 57d19645be48..d28e31d374ef 100644 --- a/internal/wire/wire_test.go +++ b/internal/wire/wire_test.go @@ -128,7 +128,10 @@ func testMessages(t *testing.T, testCases []testCase) { require.NoError(t, err) assert.Equal(t, tc.command, d.Command()) - assert.NotPanics(t, func() { _, _ = msg.RawDocument() }) + assert.NotPanics(t, func() { + _, _ = msg.RawSections() + _, _ = msg.RawDocument() + }) } }) @@ -209,6 +212,7 @@ func fuzzMessages(f *testing.F, testCases []testCase) { if msg, ok := msgBody.(*OpMsg); ok { assert.NotPanics(t, func() { _, _ = msg.Document() + _, _ = msg.RawSections() _, _ = msg.RawDocument() }) } diff --git a/tools/checkcomments/checkcomments.go b/tools/checkcomments/checkcomments.go index 0f521243229a..9140af662dbd 100644 --- a/tools/checkcomments/checkcomments.go +++ b/tools/checkcomments/checkcomments.go @@ -29,7 +29,7 @@ import ( ) // todoRE represents correct // TODO comment format. -var todoRE = regexp.MustCompile(`^// TODO (\Qhttps://github.com/FerretDB/\E\w+/issues/(\d+))$`) +var todoRE = regexp.MustCompile(`^// TODO (\Qhttps://github.com/FerretDB/\E([-\w]+)/issues/(\d+))$`) // analyzer represents the checkcomments analyzer. var analyzer = &analysis.Analyzer{ @@ -92,23 +92,28 @@ func run(pass *analysis.Pass) (any, error) { match := todoRE.FindStringSubmatch(line) - if len(match) != 3 { + if len(match) != 4 { pass.Reportf(c.Pos(), "invalid TODO: incorrect format") continue } - if client == nil { - continue - } - url := match[1] - - num, err := strconv.Atoi(match[2]) + repo := match[2] + num, err := strconv.Atoi(match[3]) if err != nil { log.Panic(err) } - status, err := client.IssueStatus(context.TODO(), num) + if num <= 0 { + pass.Reportf(c.Pos(), "invalid TODO: incorrect issue number") + continue + } + + if client == nil { + continue + } + + status, err := client.IssueStatus(context.TODO(), url, repo, num) if err != nil { log.Panic(err) } diff --git a/tools/checkcomments/checkcomments_test.go b/tools/checkcomments/checkcomments_test.go index c2045612d8c3..4ac04a506662 100644 --- a/tools/checkcomments/checkcomments_test.go +++ b/tools/checkcomments/checkcomments_test.go @@ -61,19 +61,15 @@ func TestClient(t *testing.T) { c, err := newClient(cacheFilePath, t.Logf, t.Logf, t.Logf) require.NoError(t, err) - actual, err := c.checkIssueStatus(ctx, 10) + actual, err := c.checkIssueStatus(ctx, "FerretDB", 10) require.NoError(t, err) assert.Equal(t, issueOpen, actual) - actual, err = c.checkIssueStatus(ctx, 1) + actual, err = c.checkIssueStatus(ctx, "FerretDB", 1) require.NoError(t, err) assert.Equal(t, issueClosed, actual) - actual, err = c.checkIssueStatus(ctx, 999999) - require.NoError(t, err) - assert.Equal(t, issueNotFound, actual) - - actual, err = c.checkIssueStatus(ctx, -1) + actual, err = c.checkIssueStatus(ctx, "FerretDB", 999999) require.NoError(t, err) assert.Equal(t, issueNotFound, actual) }) @@ -84,19 +80,15 @@ func TestClient(t *testing.T) { c, err := newClient(cacheFilePath, t.Logf, t.Logf, t.Logf) require.NoError(t, err) - actual, err := c.IssueStatus(ctx, 10) + actual, err := c.IssueStatus(ctx, "https://github.com/FerretDB/FerretDB/issues/10", "FerretDB", 10) require.NoError(t, err) assert.Equal(t, issueOpen, actual) - actual, err = c.IssueStatus(ctx, 1) + actual, err = c.IssueStatus(ctx, "https://github.com/FerretDB/FerretDB/issues/1", "FerretDB", 1) require.NoError(t, err) assert.Equal(t, issueClosed, actual) - actual, err = c.IssueStatus(ctx, 999999) - require.NoError(t, err) - assert.Equal(t, issueNotFound, actual) - - actual, err = c.IssueStatus(ctx, -1) + actual, err = c.IssueStatus(ctx, "https://github.com/FerretDB/FerretDB/issues/999999", "FerretDB", 999999) require.NoError(t, err) assert.Equal(t, issueNotFound, actual) @@ -109,19 +101,15 @@ func TestClient(t *testing.T) { c.c = nil - actual, err = c.IssueStatus(ctx, 10) + actual, err = c.IssueStatus(ctx, "https://github.com/FerretDB/FerretDB/issues/10", "FerretDB", 10) require.NoError(t, err) assert.Equal(t, issueOpen, actual) - actual, err = c.IssueStatus(ctx, 1) + actual, err = c.IssueStatus(ctx, "https://github.com/FerretDB/FerretDB/issues/1", "FerretDB", 1) require.NoError(t, err) assert.Equal(t, issueClosed, actual) - actual, err = c.IssueStatus(ctx, 999999) - require.NoError(t, err) - assert.Equal(t, issueNotFound, actual) - - actual, err = c.IssueStatus(ctx, -1) + actual, err = c.IssueStatus(ctx, "https://github.com/FerretDB/FerretDB/issues/999999", "FerretDB", 999999) require.NoError(t, err) assert.Equal(t, issueNotFound, actual) }) diff --git a/tools/checkcomments/client.go b/tools/checkcomments/client.go index 696efbf0d3e3..505869aa6752 100644 --- a/tools/checkcomments/client.go +++ b/tools/checkcomments/client.go @@ -98,11 +98,9 @@ func newClient(cacheFilePath string, logf, cacheDebugF, clientDebugf gh.Printf) // // Returned error is something fatal. // On rate limit, the error is logged once and (issueOpen, nil) is returned. -func (c *client) IssueStatus(ctx context.Context, num int) (issueStatus, error) { +func (c *client) IssueStatus(ctx context.Context, url, repo string, num int) (issueStatus, error) { start := time.Now() - url := fmt.Sprintf("https://github.com/FerretDB/FerretDB/issues/%d", num) - cache := &cacheFile{ Issues: make(map[string]issue), } @@ -138,7 +136,7 @@ func (c *client) IssueStatus(ctx context.Context, num int) (issueStatus, error) return nil, noUpdate } - if res, err = c.checkIssueStatus(ctx, num); err != nil { + if res, err = c.checkIssueStatus(ctx, repo, num); err != nil { var rle *github.RateLimitError if !errors.As(err, &rle) { return nil, fmt.Errorf("%s: %s", url, err) @@ -188,8 +186,8 @@ func (c *client) IssueStatus(ctx context.Context, num int) (issueStatus, error) // checkIssueStatus checks issue status via GitHub API. // It does not use cache. -func (c *client) checkIssueStatus(ctx context.Context, num int) (issueStatus, error) { - issue, resp, err := c.c.Issues.Get(ctx, "FerretDB", "FerretDB", num) +func (c *client) checkIssueStatus(ctx context.Context, repo string, num int) (issueStatus, error) { + issue, resp, err := c.c.Issues.Get(ctx, "FerretDB", repo, num) if err != nil { if resp.StatusCode == http.StatusNotFound { return issueNotFound, nil diff --git a/tools/go.mod b/tools/go.mod index 9ea80bf51ffe..e085c4da200c 100644 --- a/tools/go.mod +++ b/tools/go.mod @@ -16,7 +16,7 @@ require ( golang.org/x/oauth2 v0.17.0 golang.org/x/perf v0.0.0-20240208143119-b26761745961 golang.org/x/pkgsite v0.0.0-20240214170749-fa961d12411c - golang.org/x/tools v0.18.0 + golang.org/x/tools v0.18.0 // https://github.com/golang/go/issues/66259 golang.org/x/vuln v1.0.4 mvdan.cc/gofumpt v0.6.0 ) diff --git a/tools/golangci/go.mod b/tools/golangci/go.mod index 8a0dcab50ded..7c4cc18aca8f 100644 --- a/tools/golangci/go.mod +++ b/tools/golangci/go.mod @@ -180,7 +180,7 @@ require ( golang.org/x/sync v0.6.0 // indirect golang.org/x/sys v0.17.0 // indirect golang.org/x/text v0.14.0 // indirect - golang.org/x/tools v0.18.0 // indirect + golang.org/x/tools v0.18.0 // indirect; https://github.com/golang/go/issues/66259 google.golang.org/protobuf v1.31.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect