@@ -34,6 +34,20 @@ defmodule Ecto.UUID do
3434 """
3535 @ type options :: [ version: 4 | 7 ]
3636
37+ # The bits available for sub-millisecond fractions when using increased clock
38+ # precision.
39+ @ sub_ms_bits 12
40+
41+ # The number of values that can be represented in the bit space (2^12).
42+ @ possible_values Bitwise . bsl ( 1 , @ sub_ms_bits )
43+
44+ # The number of nanoseconds in a millisecond.
45+ @ ns_per_ms 1_000_000
46+
47+ # The minimum step when using increased clock precision with fractional
48+ # milliseconds.
49+ @ minimal_step_ns div ( @ ns_per_ms , @ possible_values )
50+
3751 @ doc false
3852 def type , do: :uuid
3953
@@ -217,9 +231,9 @@ defmodule Ecto.UUID do
217231 """
218232 @ spec bingenerate ( options ) :: raw
219233 def bingenerate ( opts \\ [ ] ) do
220- case Keyword . get ( opts , :version , @ default_version ) do
221- 4 -> bingenerate_v4 ( )
222- 7 -> bingenerate_v7 ( )
234+ case Keyword . pop ( opts , :version , @ default_version ) do
235+ { 4 , _opts } -> bingenerate_v4 ( )
236+ { 7 , opts } -> bingenerate_v7 ( opts )
223237 version -> raise ArgumentError , "unknown UUID version: #{ inspect ( version ) } "
224238 end
225239 end
@@ -229,11 +243,79 @@ defmodule Ecto.UUID do
229243 << u0 :: 48 , 4 :: 4 , u1 :: 12 , 2 :: 2 , u2 :: 62 >>
230244 end
231245
232- defp bingenerate_v7 do
233- milliseconds = System . system_time ( :millisecond )
234- << u0 :: 12 , u1 :: 62 , _ :: 6 >> = :crypto . strong_rand_bytes ( 10 )
246+ defp bingenerate_v7 ( opts ) do
247+ # From RFC 9562:
248+ # Monotonicity (each subsequent value being greater than the last) is the
249+ # backbone of time-based sortable UUIDs. Normally, time-based UUIDs will be
250+ # monotonic due to an embedded timestamp; however, implementations can
251+ # guarantee additional monotonicity via the concepts covered in section 6.2.
252+ case Keyword . get ( opts , :monotonic_method ) do
253+ # Millisecond granularity only. No monotonic guarantee.
254+ nil ->
255+ milliseconds = System . system_time ( :millisecond )
256+ << rand_a :: 12 , _ :: 6 , rand_b :: 62 >> = :crypto . strong_rand_bytes ( 10 )
257+ << milliseconds :: 48 , 7 :: 4 , rand_a :: 12 , 2 :: 2 , rand_b :: 62 >>
258+
259+ # For single-node UUID implementations that do not need to create
260+ # batches of UUIDs, the embedded timestamp within UUIDv7 can provide
261+ # sufficient monotonicity guarantees by simply ensuring that timestamp
262+ # increments before creating a new UUID. (RFC9562§6.2)
263+ :millisecond ->
264+ milliseconds = next_ascending ( :millisecond )
265+ << rand_a :: 12 , _ :: 6 , rand_b :: 62 >> = :crypto . strong_rand_bytes ( 10 )
266+ << milliseconds :: 48 , 7 :: 4 , rand_a :: 12 , 2 :: 2 , rand_b :: 62 >>
267+
268+ # Replace Leftmost Random Bits with Increased Clock Precision (RFC9562§6.2, Method 3):
269+ :increased_clock_precision ->
270+ nanoseconds = next_ascending ( :nanosecond )
271+ milliseconds = div ( nanoseconds , @ ns_per_ms )
272+ clock_precision = ( rem ( nanoseconds , @ ns_per_ms ) * @ possible_values ) |> div ( @ ns_per_ms )
273+ << _rand_a :: 2 , rand_b :: 62 >> = :crypto . strong_rand_bytes ( 8 )
274+ << milliseconds :: 48 , 7 :: 4 , clock_precision :: 12 , 2 :: 2 , rand_b :: 62 >>
275+
276+ method ->
277+ raise ArgumentError , "invalid monotonic method: #{ inspect ( method ) } "
278+ end
279+ end
280+
281+ defp next_ascending ( time_unit ) when time_unit in [ :millisecond , :nanosecond ] do
282+ timestamp_ref =
283+ with nil <- :persistent_term . get ( { __MODULE__ , time_unit } , nil ) do
284+ timestamp_ref = :atomics . new ( 1 , signed: false )
285+ :ok = :persistent_term . put ( { __MODULE__ , time_unit } , timestamp_ref )
286+ timestamp_ref
287+ end
288+
289+ step =
290+ case time_unit do
291+ :millisecond -> 1
292+ :nanosecond -> @ minimal_step_ns
293+ end
294+
295+ previous_ts = :atomics . get ( timestamp_ref , 1 )
296+ min_step_ts = previous_ts + step
297+ current_ts = System . system_time ( time_unit )
298+
299+ # If the current timestamp is not at least the minimal step greater than the
300+ # previous step, then we make it so.
301+ new_ts =
302+ if current_ts > min_step_ts do
303+ current_ts
304+ else
305+ min_step_ts
306+ end
307+
308+ compare_exchange ( timestamp_ref , previous_ts , new_ts , step )
309+ end
235310
236- << milliseconds :: 48 , 7 :: 4 , u0 :: 12 , 2 :: 2 , u1 :: 62 >>
311+ defp compare_exchange ( timestamp_ref , previous_ts , new_ts , step ) do
312+ case :atomics . compare_exchange ( timestamp_ref , 1 , previous_ts , new_ts ) do
313+ # If the new value was written, then we return it.
314+ :ok -> new_ts
315+ # If the atomic value has changed in the meantime, we add the minimal step
316+ # nanoseconds value to that and try again.
317+ updated_ts -> compare_exchange ( timestamp_ref , updated_ts , updated_ts + step , step )
318+ end
237319 end
238320
239321 # Callback invoked by autogenerate fields.
0 commit comments