Skip to content

Test that we are inline with the BSON Object ID spec#3

Merged
nick96 merged 11 commits intomainfrom
test-by-spec
Sep 19, 2025
Merged

Test that we are inline with the BSON Object ID spec#3
nick96 merged 11 commits intomainfrom
test-by-spec

Conversation

@nick96
Copy link
Copy Markdown
Collaborator

@nick96 nick96 commented Sep 17, 2025

I've added the spec and have done a pass on checking we're inline with it. I've (and claude)
also added tests to make sure we stay inline with it. There were a few places
where we weren't compliant with the spec, see individual commits for details.

nick96 and others added 8 commits September 19, 2025 01:05
Casting to a u32 would panic if the value was too large. Now just mode
it with the max value. This is fine per the BSON Object ID spec.
We just care that each process gets a unique value, not that there is an
ordering.
The counter is actually only 24 bits but we have to store it in a 32 bit
integer. So can't rely on the natural wrapping of integer types.
The manual implementations boil down to exactly the same thing so just
do that.
This doesn't require converting to bytes first and we more clearly
codify the ordering.
Implements spec-mandated test cases including timestamp field validation,
counter overflow behavior, hex string parsing, ordering verification, and
big endian format checking to ensure full BSON ObjectId specification
compliance.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This way you can't forget to compile the extension before running
tests.
Some of the tests modify the global counter, so they need to be run in
isolation to prevent CI being flakey. Nextest runs the each test as its
own binary so they can't interfere with one another.
With a normal add, when the counter reaches u32::MAX, adding to it will
panic. Make sure we handle this case.
let counter = COUNTER.fetch_add(1, Ordering::SeqCst);
// We don't care that there is an ordering between threads, just
// that each ID gets a unique value.
let counter = COUNTER.fetch_add(1, Ordering::Relaxed) & COUNTER_MAX;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand this use of &; does it work? It seems we'll wrap and then mask out any bits that aren't in COUNTER_MAX; is that what we want?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah it does work. It just zeros out bits on anything larger than COUNTER_MAX causing it to wrap at 24 bits

// Ensure the timestamp is 4 bytes. For timestamps far in the future
// this will truncate them but it's how the spec is defined. Note: if
// `t` is bigger than u32::MAX, the value will be truncated. This is
// expected.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about negative time-stamps?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It works-ish. It doesn't panic and you'll more than likely get a unique ID but you won't be able to get the same timestamp back. It's called out in the spec that this is explicitly not handled because MongoDB didn't exist before 1970.

// 0xFFFFFFFF: Feb 7th, 2106 06:28:15 UTC
let id = ObjectId::from_time(0xFFFFFFFFu32 as i64, false);
assert_eq!(id.timestamp(), 0xFFFFFFFFu32 as i64);
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we also test i64 values that don't fit in u32? ie: too big, negative

@@ -0,0 +1,136 @@
# BSON ObjectID
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh good idea adding this!

Timestamps before 1970 will be negative so make sure we handle that by
at least not panicking. Your ID will still be unique but you won't be
able to get the correct timestamp back.

Timestamps after 2038 won't bit into a u32 so will wrap around to 0.
Also fine in terms of uniquenessbut has the same issue where you don't
get the same timestamp backup.
@shannoncole
Copy link
Copy Markdown
Contributor

new tests look good! brew r+ 👍

@nick96 nick96 merged commit 74e8c5b into main Sep 19, 2025
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants