From 09782c0a79812d8b972aab19281d6874d461ca5c Mon Sep 17 00:00:00 2001 From: Rafael Cenzano Date: Tue, 2 Dec 2025 14:39:23 -0800 Subject: [PATCH 01/10] remove uneeded var ok bool from AsInt functions --- x/bsonx/bsoncore/value.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/x/bsonx/bsoncore/value.go b/x/bsonx/bsoncore/value.go index 4dbf44ed49..fbfedbd815 100644 --- a/x/bsonx/bsoncore/value.go +++ b/x/bsonx/bsoncore/value.go @@ -137,7 +137,6 @@ func (v Value) AsInt64() int64 { } i64 = int64(f64) case TypeInt32: - var ok bool i32, _, ok := ReadInt32(v.Data) if !ok { panic(NewInsufficientBytesError(v.Data, v.Data)) @@ -170,7 +169,6 @@ func (v Value) AsInt64OK() (int64, bool) { } i64 = int64(f64) case TypeInt32: - var ok bool i32, _, ok := ReadInt32(v.Data) if !ok { return 0, false From 476bc7b273984fb049b26883cbe1129e8625a974 Mon Sep 17 00:00:00 2001 From: Rafael Cenzano Date: Tue, 2 Dec 2025 14:49:58 -0800 Subject: [PATCH 02/10] Create AsFloat64 functions --- x/bsonx/bsoncore/value.go | 64 +++++++++++++++++++++++++++++++++++---- 1 file changed, 58 insertions(+), 6 deletions(-) diff --git a/x/bsonx/bsoncore/value.go b/x/bsonx/bsoncore/value.go index fbfedbd815..2d394cc0a0 100644 --- a/x/bsonx/bsoncore/value.go +++ b/x/bsonx/bsoncore/value.go @@ -188,15 +188,67 @@ func (v Value) AsInt64OK() (int64, bool) { // AsFloat64 returns a BSON number as an float64. If the BSON type is not a numeric one, this method // will panic. -// -// TODO(GODRIVER-2751): Implement AsFloat64. -// func (v Value) AsFloat64() float64 +func (v Value) AsFloat64() float64 { + if !v.IsNumber() { + return 0 + } + var f64 float64 + switch v.Type { + case TypeDouble: + var ok bool + f64, _, ok = ReadDouble(v.Data) + if !ok { + panic(NewInsufficientBytesError(v.Data, v.Data)) + } + case TypeInt32: + i32, _, ok := ReadInt32(v.Data) + if !ok { + panic(NewInsufficientBytesError(v.Data, v.Data)) + } + f64 = float64(i32) + case TypeInt64: + i64, _, ok := ReadInt64(v.Data) + if !ok { + panic(NewInsufficientBytesError(v.Data, v.Data)) + } + f64 = float64(i64) + case TypeDecimal128: + panic(ElementTypeError{"bsoncore.Value.AsFloat64", v.Type}) + } + return f64 +} // AsFloat64OK functions the same as AsFloat64 but returns a boolean instead of panicking. False // indicates an error. -// -// TODO(GODRIVER-2751): Implement AsFloat64OK. -// func (v Value) AsFloat64OK() (float64, bool) +func (v Value) AsFloat64OK() (float64, bool) { + if !v.IsNumber() { + return 0, false + } + var f64 float64 + switch v.Type { + case TypeDouble: + var ok bool + f64, _, ok = ReadDouble(v.Data) + if !ok { + return 0, false + } + case TypeInt32: + i32, _, ok := ReadInt32(v.Data) + if !ok { + return 0, false + } + f64 = float64(i32) + case TypeInt64: + i64, _, ok := ReadInt64(v.Data) + if !ok { + return 0, false + } + f64 = float64(i64) + case TypeDecimal128: + return 0, false + } + return f64, true +} // Equal compaes v to v2 and returns true if they are equal. func (v Value) Equal(v2 Value) bool { From 33768423f5145cf410fd6211587481143348e1b6 Mon Sep 17 00:00:00 2001 From: Rafael Cenzano Date: Tue, 2 Dec 2025 15:04:41 -0800 Subject: [PATCH 03/10] Add AsFloat functions to handle raw values --- bson/raw_value.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/bson/raw_value.go b/bson/raw_value.go index 24283a24c5..0b658e2b2d 100644 --- a/bson/raw_value.go +++ b/bson/raw_value.go @@ -294,6 +294,14 @@ func (rv RawValue) AsInt64() int64 { return convertToCoreValue(rv).AsInt64() } // panicking. func (rv RawValue) AsInt64OK() (int64, bool) { return convertToCoreValue(rv).AsInt64OK() } +// AsFloat64 returns a BSON number as a float64. If the BSON type is not a numeric one, this method +// will panic. +func (rv RawValue) AsFloat64() float64 { return convertToCoreValue(rv).AsFloat64() } + +// AsFloat64OK is the same as AsFloat64, except that it returns a boolean instead of +// panicking. +func (rv RawValue) AsFloat64OK() (float64, bool) { return convertToCoreValue(rv).AsFloat64OK() } + // Decimal128 returns the decimal the Value represents. It panics if the value is a BSON type other than // decimal. func (rv RawValue) Decimal128() Decimal128 { return NewDecimal128(convertToCoreValue(rv).Decimal128()) } From 524a05c78b4a5380c652602a426ad25266e087bf Mon Sep 17 00:00:00 2001 From: Rafael Cenzano Date: Tue, 2 Dec 2025 15:05:23 -0800 Subject: [PATCH 04/10] update $$lte to use new AsFloat64 function --- internal/integration/unified/matches.go | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/internal/integration/unified/matches.go b/internal/integration/unified/matches.go index 6d34df1d95..6629386c85 100644 --- a/internal/integration/unified/matches.go +++ b/internal/integration/unified/matches.go @@ -251,19 +251,17 @@ func evaluateSpecialComparison(ctx context.Context, assertionDoc bson.Raw, actua // Numeric values can be compared even if their types are different (e.g. if expected is an int32 and actual // is an int64). - - // TODO(GODRIVER-3594): If we decide to add AsDoubleOK() as a method to RawValue, this following conversion should be updated. var expectedF64 float64 if assertionVal.Type == bson.TypeDouble { expectedF64 = assertionVal.Double() } else { - expectedF64 = float64(assertionVal.AsInt64()) + expectedF64 = assertionVal.AsFloat64() } var actualF64 float64 if actual.Type == bson.TypeDouble { actualF64 = actual.Double() } else { - actualF64 = float64(actual.AsInt64()) + actualF64 = actual.AsFloat64() } if actualF64 > expectedF64 { From 5ac0f9f9004a8be71be8eb85238b7e9c4fb1d14c Mon Sep 17 00:00:00 2001 From: Rafael Cenzano Date: Wed, 3 Dec 2025 11:44:37 -0800 Subject: [PATCH 05/10] Remove uneeded check and conversion logic --- internal/integration/unified/matches.go | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/internal/integration/unified/matches.go b/internal/integration/unified/matches.go index 6629386c85..d1feb89dad 100644 --- a/internal/integration/unified/matches.go +++ b/internal/integration/unified/matches.go @@ -251,18 +251,8 @@ func evaluateSpecialComparison(ctx context.Context, assertionDoc bson.Raw, actua // Numeric values can be compared even if their types are different (e.g. if expected is an int32 and actual // is an int64). - var expectedF64 float64 - if assertionVal.Type == bson.TypeDouble { - expectedF64 = assertionVal.Double() - } else { - expectedF64 = assertionVal.AsFloat64() - } - var actualF64 float64 - if actual.Type == bson.TypeDouble { - actualF64 = actual.Double() - } else { - actualF64 = actual.AsFloat64() - } + var expectedF64 = assertionVal.AsFloat64() + var actualF64 = actual.AsFloat64() if actualF64 > expectedF64 { return fmt.Errorf("expected numeric value %f to be less than or equal %f", actualF64, expectedF64) From 8b3929d7c53b5f559ca49065c564a50ae775f331 Mon Sep 17 00:00:00 2001 From: Rafael Cenzano Date: Wed, 3 Dec 2025 13:29:12 -0800 Subject: [PATCH 06/10] correct to a panic instead of returning 0 --- x/bsonx/bsoncore/value.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x/bsonx/bsoncore/value.go b/x/bsonx/bsoncore/value.go index 2d394cc0a0..bb16f361b8 100644 --- a/x/bsonx/bsoncore/value.go +++ b/x/bsonx/bsoncore/value.go @@ -190,7 +190,7 @@ func (v Value) AsInt64OK() (int64, bool) { // will panic. func (v Value) AsFloat64() float64 { if !v.IsNumber() { - return 0 + panic(ElementTypeError{"bsoncore.Value.AsFloat64", v.Type}) } var f64 float64 switch v.Type { From 88472f93c9332272557ecdca7cb59aff464c9058 Mon Sep 17 00:00:00 2001 From: Rafael Cenzano Date: Wed, 3 Dec 2025 13:40:19 -0800 Subject: [PATCH 07/10] correct check using assertionVal instead of actual --- internal/integration/unified/matches.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/integration/unified/matches.go b/internal/integration/unified/matches.go index d1feb89dad..82df12cac5 100644 --- a/internal/integration/unified/matches.go +++ b/internal/integration/unified/matches.go @@ -245,7 +245,7 @@ func evaluateSpecialComparison(ctx context.Context, assertionDoc bson.Raw, actua if assertionVal.Type != bson.TypeInt32 && assertionVal.Type != bson.TypeInt64 && assertionVal.Type != bson.TypeDouble { return fmt.Errorf("expected assertionVal to be an Int32, Int64, or Double but got a %s", assertionVal.Type) } - if actual.Type != bson.TypeInt32 && actual.Type != bson.TypeInt64 && assertionVal.Type != bson.TypeDouble { + if actual.Type != bson.TypeInt32 && actual.Type != bson.TypeInt64 && actual.Type != bson.TypeDouble { return fmt.Errorf("expected value to be an Int32, Int64, or Double but got a %s", actual.Type) } From 49c077baf0771abcbcc82c3775ff69c278f266cd Mon Sep 17 00:00:00 2001 From: Rafael Cenzano Date: Wed, 3 Dec 2025 13:40:54 -0800 Subject: [PATCH 08/10] Added tests for AsType functions --- x/bsonx/bsoncore/value_test.go | 240 +++++++++++++++++++++++++++++++++ 1 file changed, 240 insertions(+) diff --git a/x/bsonx/bsoncore/value_test.go b/x/bsonx/bsoncore/value_test.go index 08ce9dc1e4..e86d650115 100644 --- a/x/bsonx/bsoncore/value_test.go +++ b/x/bsonx/bsoncore/value_test.go @@ -634,6 +634,246 @@ func TestValue(t *testing.T) { nil, []any{uint64(12345), uint64(67890), true}, }, + { + "AsInt32/Not Number", Value.AsInt32, Value{Type: TypeString}, + ElementTypeError{"bsoncore.Value.AsInt32", TypeString}, + nil, + }, + { + "AsInt32/Double/Insufficient Bytes", Value.AsInt32, Value{Type: TypeDouble, Data: []byte{0x01, 0x02, 0x03}}, + NewInsufficientBytesError([]byte{0x01, 0x02, 0x03}, []byte{0x01, 0x02, 0x03}), + nil, + }, + { + "AsInt32/Int32/Insufficient Bytes", Value.AsInt32, Value{Type: TypeInt32, Data: []byte{0x01, 0x02}}, + NewInsufficientBytesError([]byte{0x01, 0x02}, []byte{0x01, 0x02}), + nil, + }, + { + "AsInt32/Int64/Insufficient Bytes", Value.AsInt32, Value{Type: TypeInt64, Data: []byte{0x01, 0x02, 0x03}}, + NewInsufficientBytesError([]byte{0x01, 0x02, 0x03}, []byte{0x01, 0x02, 0x03}), + nil, + }, + { + "AsInt32/Decimal128", Value.AsInt32, Value{Type: TypeDecimal128, Data: AppendDecimal128(nil, 12345, 67890)}, + ElementTypeError{"bsoncore.Value.AsInt32", TypeDecimal128}, + nil, + }, + { + "AsInt32/From Double", Value.AsInt32, Value{Type: TypeDouble, Data: AppendDouble(nil, 42.7)}, + nil, + []any{int32(42)}, + }, + { + "AsInt32/From Int32", Value.AsInt32, Value{Type: TypeInt32, Data: AppendInt32(nil, 12345)}, + nil, + []any{int32(12345)}, + }, + { + "AsInt32/From Int64", Value.AsInt32, Value{Type: TypeInt64, Data: AppendInt64(nil, 98765)}, + nil, + []any{int32(98765)}, + }, + { + "AsInt32OK/Not Number", Value.AsInt32OK, Value{Type: TypeString}, + nil, + []any{int32(0), false}, + }, + { + "AsInt32OK/Double/Insufficient Bytes", Value.AsInt32OK, Value{Type: TypeDouble, Data: []byte{0x01, 0x02, 0x03}}, + nil, + []any{int32(0), false}, + }, + { + "AsInt32OK/Int32/Insufficient Bytes", Value.AsInt32OK, Value{Type: TypeInt32, Data: []byte{0x01, 0x02}}, + nil, + []any{int32(0), false}, + }, + { + "AsInt32OK/Int64/Insufficient Bytes", Value.AsInt32OK, Value{Type: TypeInt64, Data: []byte{0x01, 0x02, 0x03}}, + nil, + []any{int32(0), false}, + }, + { + "AsInt32OK/Decimal128", Value.AsInt32OK, Value{Type: TypeDecimal128, Data: AppendDecimal128(nil, 12345, 67890)}, + nil, + []any{int32(0), false}, + }, + { + "AsInt32OK/From Double", Value.AsInt32OK, Value{Type: TypeDouble, Data: AppendDouble(nil, 42.7)}, + nil, + []any{int32(42), true}, + }, + { + "AsInt32OK/From Int32", Value.AsInt32OK, Value{Type: TypeInt32, Data: AppendInt32(nil, 12345)}, + nil, + []any{int32(12345), true}, + }, + { + "AsInt32OK/From Int64", Value.AsInt32OK, Value{Type: TypeInt64, Data: AppendInt64(nil, 98765)}, + nil, + []any{int32(98765), true}, + }, + { + "AsInt64/Not Number", Value.AsInt64, Value{Type: TypeString}, + ElementTypeError{"bsoncore.Value.AsInt64", TypeString}, + nil, + }, + { + "AsInt64/Double/Insufficient Bytes", Value.AsInt64, Value{Type: TypeDouble, Data: []byte{0x01, 0x02, 0x03}}, + NewInsufficientBytesError([]byte{0x01, 0x02, 0x03}, []byte{0x01, 0x02, 0x03}), + nil, + }, + { + "AsInt64/Int32/Insufficient Bytes", Value.AsInt64, Value{Type: TypeInt32, Data: []byte{0x01, 0x02}}, + NewInsufficientBytesError([]byte{0x01, 0x02}, []byte{0x01, 0x02}), + nil, + }, + { + "AsInt64/Int64/Insufficient Bytes", Value.AsInt64, Value{Type: TypeInt64, Data: []byte{0x01, 0x02, 0x03}}, + NewInsufficientBytesError([]byte{0x01, 0x02, 0x03}, []byte{0x01, 0x02, 0x03}), + nil, + }, + { + "AsInt64/Decimal128", Value.AsInt64, Value{Type: TypeDecimal128, Data: AppendDecimal128(nil, 12345, 67890)}, + ElementTypeError{"bsoncore.Value.AsInt64", TypeDecimal128}, + nil, + }, + { + "AsInt64/From Double", Value.AsInt64, Value{Type: TypeDouble, Data: AppendDouble(nil, 123456.9)}, + nil, + []any{int64(123456)}, + }, + { + "AsInt64/From Int32", Value.AsInt64, Value{Type: TypeInt32, Data: AppendInt32(nil, 12345)}, + nil, + []any{int64(12345)}, + }, + { + "AsInt64/From Int64", Value.AsInt64, Value{Type: TypeInt64, Data: AppendInt64(nil, 9876543210)}, + nil, + []any{int64(9876543210)}, + }, + { + "AsInt64OK/Not Number", Value.AsInt64OK, Value{Type: TypeString}, + nil, + []any{int64(0), false}, + }, + { + "AsInt64OK/Double/Insufficient Bytes", Value.AsInt64OK, Value{Type: TypeDouble, Data: []byte{0x01, 0x02, 0x03}}, + nil, + []any{int64(0), false}, + }, + { + "AsInt64OK/Int32/Insufficient Bytes", Value.AsInt64OK, Value{Type: TypeInt32, Data: []byte{0x01, 0x02}}, + nil, + []any{int64(0), false}, + }, + { + "AsInt64OK/Int64/Insufficient Bytes", Value.AsInt64OK, Value{Type: TypeInt64, Data: []byte{0x01, 0x02, 0x03}}, + nil, + []any{int64(0), false}, + }, + { + "AsInt64OK/Decimal128", Value.AsInt64OK, Value{Type: TypeDecimal128, Data: AppendDecimal128(nil, 12345, 67890)}, + nil, + []any{int64(0), false}, + }, + { + "AsInt64OK/From Double", Value.AsInt64OK, Value{Type: TypeDouble, Data: AppendDouble(nil, 123456.9)}, + nil, + []any{int64(123456), true}, + }, + { + "AsInt64OK/From Int32", Value.AsInt64OK, Value{Type: TypeInt32, Data: AppendInt32(nil, 12345)}, + nil, + []any{int64(12345), true}, + }, + { + "AsInt64OK/From Int64", Value.AsInt64OK, Value{Type: TypeInt64, Data: AppendInt64(nil, 9876543210)}, + nil, + []any{int64(9876543210), true}, + }, + { + "AsFloat64/Not Number", Value.AsFloat64, Value{Type: TypeString}, + ElementTypeError{"bsoncore.Value.AsFloat64", TypeString}, + nil, + }, + { + "AsFloat64/Double/Insufficient Bytes", Value.AsFloat64, Value{Type: TypeDouble, Data: []byte{0x01, 0x02, 0x03}}, + NewInsufficientBytesError([]byte{0x01, 0x02, 0x03}, []byte{0x01, 0x02, 0x03}), + nil, + }, + { + "AsFloat64/Int32/Insufficient Bytes", Value.AsFloat64, Value{Type: TypeInt32, Data: []byte{0x01, 0x02}}, + NewInsufficientBytesError([]byte{0x01, 0x02}, []byte{0x01, 0x02}), + nil, + }, + { + "AsFloat64/Int64/Insufficient Bytes", Value.AsFloat64, Value{Type: TypeInt64, Data: []byte{0x01, 0x02, 0x03}}, + NewInsufficientBytesError([]byte{0x01, 0x02, 0x03}, []byte{0x01, 0x02, 0x03}), + nil, + }, + { + "AsFloat64/Decimal128", Value.AsFloat64, Value{Type: TypeDecimal128, Data: AppendDecimal128(nil, 12345, 67890)}, + ElementTypeError{"bsoncore.Value.AsFloat64", TypeDecimal128}, + nil, + }, + { + "AsFloat64/From Double", Value.AsFloat64, Value{Type: TypeDouble, Data: AppendDouble(nil, 3.14159)}, + nil, + []any{float64(3.14159)}, + }, + { + "AsFloat64/From Int32", Value.AsFloat64, Value{Type: TypeInt32, Data: AppendInt32(nil, 42)}, + nil, + []any{float64(42)}, + }, + { + "AsFloat64/From Int64", Value.AsFloat64, Value{Type: TypeInt64, Data: AppendInt64(nil, 1234567890)}, + nil, + []any{float64(1234567890)}, + }, + { + "AsFloat64OK/Not Number", Value.AsFloat64OK, Value{Type: TypeString}, + nil, + []any{float64(0), false}, + }, + { + "AsFloat64OK/Double/Insufficient Bytes", Value.AsFloat64OK, Value{Type: TypeDouble, Data: []byte{0x01, 0x02, 0x03}}, + nil, + []any{float64(0), false}, + }, + { + "AsFloat64OK/Int32/Insufficient Bytes", Value.AsFloat64OK, Value{Type: TypeInt32, Data: []byte{0x01, 0x02}}, + nil, + []any{float64(0), false}, + }, + { + "AsFloat64OK/Int64/Insufficient Bytes", Value.AsFloat64OK, Value{Type: TypeInt64, Data: []byte{0x01, 0x02, 0x03}}, + nil, + []any{float64(0), false}, + }, + { + "AsFloat64OK/Decimal128", Value.AsFloat64OK, Value{Type: TypeDecimal128, Data: AppendDecimal128(nil, 12345, 67890)}, + nil, + []any{float64(0), false}, + }, + { + "AsFloat64OK/From Double", Value.AsFloat64OK, Value{Type: TypeDouble, Data: AppendDouble(nil, 3.14159)}, + nil, + []any{float64(3.14159), true}, + }, + { + "AsFloat64OK/From Int32", Value.AsFloat64OK, Value{Type: TypeInt32, Data: AppendInt32(nil, 42)}, + nil, + []any{float64(42), true}, + }, + { + "AsFloat64OK/From Int64", Value.AsFloat64OK, Value{Type: TypeInt64, Data: AppendInt64(nil, 1234567890)}, + nil, + []any{float64(1234567890), true}, + }, { "Timestamp.String/Success", Value.String, Value{Type: TypeTimestamp, Data: AppendTimestamp(nil, 12345, 67890)}, nil, From afe7a03b678e11ed7e1c5e2e886eb53a2baaa1ad Mon Sep 17 00:00:00 2001 From: Rafael Cenzano Date: Wed, 3 Dec 2025 14:18:45 -0800 Subject: [PATCH 09/10] correct spelling --- x/bsonx/bsoncore/value.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x/bsonx/bsoncore/value.go b/x/bsonx/bsoncore/value.go index bb16f361b8..847d270843 100644 --- a/x/bsonx/bsoncore/value.go +++ b/x/bsonx/bsoncore/value.go @@ -250,7 +250,7 @@ func (v Value) AsFloat64OK() (float64, bool) { return f64, true } -// Equal compaes v to v2 and returns true if they are equal. +// Equal compares v to v2 and returns true if they are equal. func (v Value) Equal(v2 Value) bool { if v.Type != v2.Type { return false From 28db5cc71a919cd41c978f464eaeedaf0cad07d2 Mon Sep 17 00:00:00 2001 From: Rafael Cenzano Date: Thu, 4 Dec 2025 10:32:14 -0800 Subject: [PATCH 10/10] update pre commit hook --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 1e2422188f..0c38ef97f9 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,7 +1,7 @@ repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.5.0 + rev: v6.0.0 hooks: - id: check-case-conflict - id: check-executables-have-shebangs