@@ -34,23 +34,21 @@ 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
4142 # The bits available for sub-millisecond fractions when using increased clock
42- # precision.
43- @ sub_ms_bits 12
44-
43+ # precision based on nanoseconds.
44+ @ ns_sub_ms_bits 12
4545 # The number of values that can be represented in the bit space (2^12).
46- @ possible_values Bitwise . bsl ( 1 , @ sub_ms_bits )
47-
46+ @ ns_possible_values Bitwise . bsl ( 1 , @ ns_sub_ms_bits )
4847 # The number of nanoseconds in a millisecond.
4948 @ ns_per_ms 1_000_000
50-
5149 # The minimum step when using increased clock precision with fractional
52- # milliseconds.
53- @ minimal_step_ns div ( @ ns_per_ms , @ possible_values )
50+ # milliseconds based on nanoseconds .
51+ @ ns_minimal_step div ( @ ns_per_ms , @ ns_possible_values )
5452
5553 @ doc false
5654 def type , do: :uuid
@@ -248,52 +246,53 @@ defmodule Ecto.UUID do
248246 end
249247
250248 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 )
249+ time_unit = Keyword . get ( opts , :precision , :millisecond )
250+ monotonic = Keyword . get ( opts , :monotonic , false )
251+
252+ case { time_unit , monotonic } do
253+ { :millisecond , _monotonic = true } ->
254+ unix_ts_ms = next_ascending ( :millisecond )
260255 << 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 >>
256+ << unix_ts_ms :: 48 , 7 :: 4 , rand_a :: 12 , 2 :: 2 , rand_b :: 62 >>
262257
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)
267- :millisecond ->
268- milliseconds = next_ascending ( :millisecond )
258+ { :millisecond , _monotonic = false } ->
259+ unix_ts_ms = System . system_time ( :millisecond )
269260 << 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 >>
261+ << unix_ts_ms :: 48 , 7 :: 4 , rand_a :: 12 , 2 :: 2 , rand_b :: 62 >>
262+
263+ { :nanosecond , _monotonic = true } ->
264+ unix_ts_ns = next_ascending ( :nanosecond )
265+ milliseconds = div ( unix_ts_ns , @ ns_per_ms )
271266
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 )
267+ clock_precision =
268+ ( rem ( unix_ts_ns , @ ns_per_ms ) * @ ns_possible_values ) |> div ( @ ns_per_ms )
269+
270+ << rand_b :: 62 , _ :: 2 >> = :crypto . strong_rand_bytes ( 8 )
278271 << milliseconds :: 48 , 7 :: 4 , clock_precision :: 12 , 2 :: 2 , rand_b :: 62 >>
279272
280- method ->
281- raise ArgumentError , "invalid monotonic method: #{ inspect ( method ) } "
273+ { :nanosecond , _monotonic = false } ->
274+ unix_ts_ns = System . system_time ( :nanosecond )
275+ milliseconds = div ( unix_ts_ns , @ ns_per_ms )
276+ nanoseconds = rem ( unix_ts_ns , @ ns_per_ms )
277+
278+ milliseconds =
279+ if nanoseconds > @ ns_possible_values , do: milliseconds + 1 , else: milliseconds
280+
281+ << rand_a :: 12 , _ :: 6 , rand_b :: 62 >> = :crypto . strong_rand_bytes ( 10 )
282+ << milliseconds :: 48 , 7 :: 4 , rand_a :: 12 , 2 :: 2 , rand_b :: 62 >>
283+
284+ { _time_unit , _monotonic } ->
285+ raise ArgumentError , "unsupported options: #{ inspect ( opts ) } "
282286 end
283287 end
284288
285289 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
290+ timestamp_ref = :persistent_term . get ( { __MODULE__ , time_unit } , nil )
292291
293292 step =
294293 case time_unit do
295294 :millisecond -> 1
296- :nanosecond -> @ minimal_step_ns
295+ :nanosecond -> @ ns_minimal_step
297296 end
298297
299298 previous_ts = :atomics . get ( timestamp_ref , 1 )
0 commit comments