@@ -34,23 +34,14 @@ defmodule Ecto.UUID do
3434 """
3535 @ type option ::
3636 { :version , 4 | 7 }
37- | { :monotonic_method , :millisecond | :increased_clock_precision }
37+ | { :precision , :millisecond | :nanosecond }
38+ | { :monotonic , boolean ( ) }
3839
3940 @ type options :: [ option ]
4041
41- # The bits available for sub-millisecond fractions when using increased clock
42- # precision.
43- @ sub_ms_bits 12
44-
45- # The number of values that can be represented in the bit space (2^12).
46- @ possible_values Bitwise . bsl ( 1 , @ sub_ms_bits )
47-
48- # The number of nanoseconds in a millisecond.
49- @ ns_per_ms 1_000_000
50-
51- # The minimum step when using increased clock precision with fractional
52- # milliseconds.
53- @ minimal_step_ns div ( @ ns_per_ms , @ possible_values )
42+ @ version_4 4
43+ @ version_7 7
44+ @ variant 2
5445
5546 @ doc false
5647 def type , do: :uuid
@@ -244,56 +235,57 @@ defmodule Ecto.UUID do
244235
245236 defp bingenerate_v4 do
246237 << u0 :: 48 , _ :: 4 , u1 :: 12 , _ :: 2 , u2 :: 62 >> = :crypto . strong_rand_bytes ( 16 )
247- << u0 :: 48 , 4 :: 4 , u1 :: 12 , 2 :: 2 , u2 :: 62 >>
238+ << u0 :: 48 , @ version_4 :: 4 , u1 :: 12 , @ variant :: 2 , u2 :: 62 >>
248239 end
249240
241+ # The bits available for sub-millisecond fractions when using increased clock
242+ # precision based on nanoseconds.
243+ @ ns_sub_ms_bits 12
244+ # The number of values that can be represented in the bit space (2^12).
245+ @ ns_possible_values Bitwise . bsl ( 1 , @ ns_sub_ms_bits )
246+ # The number of nanoseconds in a millisecond.
247+ @ ns_per_ms 1_000_000
248+ # The minimum step when using increased clock precision with fractional
249+ # milliseconds based on nanoseconds.
250+ @ ns_minimal_step div ( @ ns_per_ms , @ ns_possible_values )
251+
250252 defp bingenerate_v7 ( opts ) do
251- # From RFC 9562:
252- # Monotonicity (each subsequent value being greater than the last) is the
253- # backbone of time-based sortable UUIDs. Normally, time-based UUIDs will be
254- # monotonic due to an embedded timestamp; however, implementations can
255- # guarantee additional monotonicity via the concepts covered in section 6.2.
256- case Keyword . get ( opts , :monotonic_method ) do
257- # Millisecond granularity only. No monotonic guarantee.
258- nil ->
259- milliseconds = System . system_time ( :millisecond )
260- << rand_a :: 12 , _ :: 6 , rand_b :: 62 >> = :crypto . strong_rand_bytes ( 10 )
261- << milliseconds :: 48 , 7 :: 4 , rand_a :: 12 , 2 :: 2 , rand_b :: 62 >>
253+ time_unit = Keyword . get ( opts , :precision , :millisecond )
254+ monotonic = Keyword . get ( opts , :monotonic , false )
255+
256+ timestamp =
257+ case monotonic do
258+ true -> next_ascending ( time_unit )
259+ false -> System . system_time ( time_unit )
260+ monotonic -> raise ArgumentError , "invalid monotonic value: #{ inspect ( monotonic ) } "
261+ end
262262
263- # For single-node UUID implementations that do not need to create
264- # batches of UUIDs, the embedded timestamp within UUIDv7 can provide
265- # sufficient monotonicity guarantees by simply ensuring that timestamp
266- # increments before creating a new UUID. (RFC9562§6.2)
263+ case time_unit do
267264 :millisecond ->
268- milliseconds = next_ascending ( :millisecond )
269265 << rand_a :: 12 , _ :: 6 , rand_b :: 62 >> = :crypto . strong_rand_bytes ( 10 )
270- << milliseconds :: 48 , 7 :: 4 , rand_a :: 12 , 2 :: 2 , rand_b :: 62 >>
271-
272- # Replace Leftmost Random Bits with Increased Clock Precision (RFC9562§6.2, Method 3):
273- :increased_clock_precision ->
274- nanoseconds = next_ascending ( :nanosecond )
275- milliseconds = div ( nanoseconds , @ ns_per_ms )
276- clock_precision = ( rem ( nanoseconds , @ ns_per_ms ) * @ possible_values ) |> div ( @ ns_per_ms )
277- << _rand_a :: 2 , rand_b :: 62 >> = :crypto . strong_rand_bytes ( 8 )
278- << milliseconds :: 48 , 7 :: 4 , clock_precision :: 12 , 2 :: 2 , rand_b :: 62 >>
279-
280- method ->
281- raise ArgumentError , "invalid monotonic method: #{ inspect ( method ) } "
266+ << timestamp :: 48 , @ version_7 :: 4 , rand_a :: 12 , @ variant :: 2 , rand_b :: 62 >>
267+
268+ :nanosecond ->
269+ milliseconds = div ( timestamp , @ ns_per_ms )
270+
271+ clock_precision =
272+ ( rem ( timestamp , @ ns_per_ms ) * @ ns_possible_values ) |> div ( @ ns_per_ms )
273+
274+ << rand_b :: 62 , _ :: 2 >> = :crypto . strong_rand_bytes ( 8 )
275+ << milliseconds :: 48 , @ version_7 :: 4 , clock_precision :: 12 , @ variant :: 2 , rand_b :: 62 >>
276+
277+ time_unit ->
278+ raise ArgumentError , "unsupported precision: #{ inspect ( time_unit ) } "
282279 end
283280 end
284281
285282 defp next_ascending ( time_unit ) when time_unit in [ :millisecond , :nanosecond ] do
286- timestamp_ref =
287- with nil <- :persistent_term . get ( { __MODULE__ , time_unit } , nil ) do
288- timestamp_ref = :atomics . new ( 1 , signed: false )
289- :ok = :persistent_term . put ( { __MODULE__ , time_unit } , timestamp_ref )
290- timestamp_ref
291- end
283+ timestamp_ref = :persistent_term . get ( { __MODULE__ , time_unit } , nil )
292284
293285 step =
294286 case time_unit do
295287 :millisecond -> 1
296- :nanosecond -> @ minimal_step_ns
288+ :nanosecond -> @ ns_minimal_step
297289 end
298290
299291 previous_ts = :atomics . get ( timestamp_ref , 1 )
0 commit comments