From 1c2765f43a435c60f75f4d052f7cfa3670d24770 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ivan=20=E2=80=9CCLOVIS=E2=80=9D=20Canet?= Date: Tue, 24 Feb 2026 20:47:01 +0100 Subject: [PATCH] docs(bson): Improve the documentation of ObjectId --- bson/src/commonMain/kotlin/types/ObjectId.kt | 63 +++++++++++++++++-- .../kotlin/types/ObjectIdGenerator.kt | 21 ++++++- 2 files changed, 77 insertions(+), 7 deletions(-) diff --git a/bson/src/commonMain/kotlin/types/ObjectId.kt b/bson/src/commonMain/kotlin/types/ObjectId.kt index a21925ad..f10fd7b3 100644 --- a/bson/src/commonMain/kotlin/types/ObjectId.kt +++ b/bson/src/commonMain/kotlin/types/ObjectId.kt @@ -30,11 +30,48 @@ import kotlin.experimental.and import kotlin.time.Instant /** - * A 12-bytes identifier for MongoDB objects. + * Small, likely unique, fast to generate, ordered identifier. * - * This class allows accessing all fields of an ObjectId as well as constructing instances from existing data. - * However, it doesn't provide a way to generate new randomized ObjectId instances (as that depends on the database configuration). - * To do so, see [opensavvy.ktmongo.bson.BsonContext.newId]. + * - **Small**: Each `ObjectId` is 12 bytes long, shorter than a UUID's 16 bytes. + * - **Likely unique**: Each driver randomly selects a generation range, so conflicts are unlikely. + * - **Fast to generate**: Depending on the [generation strategy][ObjectIdGenerator], generating a new `ObjectId` may + * be as simple as querying the time and incrementing a counter. + * - **Ordered**: All `ObjectId` instances can be sorted by creating date, with a precision of one second. + * Two `ObjectId` instances created by the same driver are sorted with even more precision. + * + * ### Composition + * + * An `ObjectId` is composed of: + * - A 4-byte [timestamp], representing the `ObjectId`'s creation date, with a precision of one second, + * - A 5-byte random [processId], unique to each machine and process, to decrease conflicts, + * - A 3-byte incrementing [counter], initialized at a random value. + * + * `ObjectId` instances are generally represented as 24-characters hexadecimal strings: + * ```kotlin + * ObjectId("699dfad90ca573f85c0eec1c").hex // 699dfad90ca573f85c0eec1c + * ``` + * + * ### Generating new ObjectIds + * + * There are different strategies to generate new `ObjectId` instances; they are encoded by the [ObjectIdGenerator] interface. + * + * The [ObjectIdGenerator] used by each driver is available directly on each collection: + * ```kotlin + * users.insert( + * User( + * _id = users.newId(), // Generate an ObjectId using the configuration specific to that collection + * name = "Bob", + * ) + * ) + * ``` + * + * ### External resources + * + * - [Official documentation](https://www.mongodb.com/docs/manual/reference/bson-types/#std-label-objectid) + * + * ### Thread safety + * + * Instances of this class are immutable and thread-safe. */ @Serializable(with = ObjectId.Serializer::class) class ObjectId : Comparable { @@ -44,6 +81,12 @@ class ObjectId : Comparable { * * This timestamp can represent time from the UNIX epoch (Jan 1 1970) and is stored as 32 unsigned bits * (approximately Feb 2 2106, see [ObjectId.MAX]'s timestamp for an exact value). + * + * ### Example + * + * ```kotlin + * ObjectId("699dfad90ca573f85c0eec1c").timestamp // 2026-02-24T19:24:09Z + * ``` */ val timestamp: Instant @@ -53,6 +96,12 @@ class ObjectId : Comparable { * This random value is unique to the machine and process. * If the process restarts of the primary node of the process changes, this value is re-regenerated. * + * ### Example + * + * ```kotlin + * ObjectId("699dfad90ca573f85c0eec1c").processId // 54315448412 + * ``` + * * @see ObjectId.PROCESS_ID_BOUND */ val processId: Long @@ -62,6 +111,12 @@ class ObjectId : Comparable { * * The counter resets when a process restarts. * + * ### Example + * + * ```kotlin + * ObjectId("699dfad90ca573f85c0eec1c").counter // 977948 + * ``` + * * @see ObjectId.COUNTER_BOUND */ val counter: Int diff --git a/bson/src/commonMain/kotlin/types/ObjectIdGenerator.kt b/bson/src/commonMain/kotlin/types/ObjectIdGenerator.kt index c5b65871..188f9675 100644 --- a/bson/src/commonMain/kotlin/types/ObjectIdGenerator.kt +++ b/bson/src/commonMain/kotlin/types/ObjectIdGenerator.kt @@ -21,15 +21,16 @@ import kotlin.concurrent.atomics.ExperimentalAtomicApi import kotlin.concurrent.atomics.fetchAndIncrement import kotlin.random.Random import kotlin.time.Clock +import kotlin.time.Instant /** * An object responsible for generating new [ObjectId] instances. * - * This object is similar to [kotlin.time.Clock]: a `Clock` is a way to generate `Instant` instances, and services + * This object is similar to [Clock]: a [Clock] is a way to generate [Instant] instances, and services * that need to generate instants should receive a clock via dependency injection. * Similarly, services that generate IDs should get a generator via dependency injection. * - * To get the real generator instance, see the database's [opensavvy.ktmongo.bson.BsonContext]. + * Each collection has its own [ObjectIdGenerator] instance, set by default by the driver. */ interface ObjectIdGenerator { @@ -53,10 +54,14 @@ interface ObjectIdGenerator { @ExperimentalAtomicApi class Default( private val clock: Clock = Clock.System, - private val random: Random = Random, + random: Random = Random, private var processId: Long = random.nextLong(0, ObjectId.PROCESS_ID_BOUND), ) : ObjectIdGenerator { + // Possible non-monotonic situations: + // - Multiple generators have the same 'processId' + // - The counter overflows, so the next ID has a smaller counter than the previous one (since the counter doesn't reset to 0 each second) + private val counter = AtomicInt(random.nextInt(0, ObjectId.COUNTER_BOUND)) override fun newId(): ObjectId { @@ -81,8 +86,18 @@ interface ObjectIdGenerator { private val ids: Iterator, ) : ObjectIdGenerator { + /** + * Each call to [newId] will return the next ID in order of [ids]. + * + * If [newId] is called once more than the number of IDs in [ids], a [NoSuchElementException] will be thrown. + */ constructor(ids: Iterable) : this(ids.iterator()) + /** + * Each call to [newId] will return the next ID in order of [ids]. + * + * If [newId] is called once more than the number of IDs in [ids], a [NoSuchElementException] will be thrown. + */ constructor(vararg ids: ObjectId) : this(ids.asIterable()) override fun newId(): ObjectId { -- 2.51.2