Skip to content

Commit

Permalink
Change to an Elixir-based implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
martinthenth committed Sep 10, 2024
1 parent b6b4b7f commit bdba226
Show file tree
Hide file tree
Showing 4 changed files with 48 additions and 27 deletions.
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
# 1.0.0

- Removes the dependency on Rust, it is now an Elixir-based implementation
- A test suite was added to ensure the Elixir implementation remains compliant to the RFC
- Adds a test suite to ensure the implementation remains compliant to the RFC
- RFC: [](https://datatracker.ietf.org/doc/rfc9562/)
- Renames `generate_from_ms/1` to `generate/1`
- Adds `timestamp/1` to get the timestamp (milliseconds) from a version 7 UUID.
- Graduates the library to version `1.0.0` 🎉

# 0.2.1
Expand Down
10 changes: 2 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,20 +1,14 @@
# UUIDv7

A UUID v7 implementation and `Ecto.Type` for Elixir - based on Rust.

This library defers the UUID v7 implementation to the Rust create [UUID](https://crates.io/crates/uuid)
using an Erlang NIF. It includes an `Ecto.Type` to (auto-)generate version 7 UUIDs in `Ecto.Schema` and beyond.

Thanks to Rust, it is ~72% faster in generating version 7 UUIDs than the Elixir implementation
of version 4 UUIDs by Ecto. See the benchmarks for more details.
A UUID v7 implementation and `Ecto.Type` for Elixir. The RFC for the version 7 UUID: [RFC 9562](https://datatracker.ietf.org/doc/rfc9562/). This library includes an `Ecto.Type` to (auto-)generate version 7 UUIDs in `Ecto.Schema` and beyond.

## Installation

The package can be installed by adding `uuidv7` to your list of dependencies in `mix.exs`:

```elixir
def deps do
[{:uuidv7, "~> 0.2"}]
[{:uuidv7, "~> 1.0"}]
end
```

Expand Down
22 changes: 13 additions & 9 deletions lib/uuidv7.ex
Original file line number Diff line number Diff line change
Expand Up @@ -59,29 +59,33 @@ defmodule UUIDv7 do
Generates a version 7 UUID.
"""
@spec generate() :: t()
def generate(), do: Ecto.UUID.cast!(bingenerate())
def generate, do: Ecto.UUID.cast!(bingenerate())

@doc """
Generates a version 7 UUID based on the timestamp (ms).
"""
@spec generate(non_neg_integer()) :: t()
def generate(ms), do: Ecto.UUID.cast!(bingenerate(ms))
def generate(milliseconds), do: milliseconds |> bingenerate() |> Ecto.UUID.cast!()

@doc """
Generates a version 7 UUID in the binary format.
"""
@spec bingenerate() :: raw()
def bingenerate() do
<<u0::48, _::4, u1::12, _::2, u2::62>> = :crypto.strong_rand_bytes(16)
<<u0::48, 4::4, u1::12, 2::2, u2::62>>
end
def bingenerate, do: :millisecond |> System.system_time() |> bingenerate()

@doc """
Generates a version 7 UUID in the binary format based on the timestamp (ms).
"""
@spec bingenerate(non_neg_integer()) :: raw()
def bingenerate(_ms) do
<<u0::48, _::4, u1::12, _::2, u2::62>> = :crypto.strong_rand_bytes(16)
<<u0::48, 4::4, u1::12, 2::2, u2::62>>
def bingenerate(milliseconds) do
<<u1::12, u2::62, _::6>> = :crypto.strong_rand_bytes(10)
<<milliseconds::48, 7::4, u1::12, 2::2, u2::62>>
end

@doc """
Extracts the timestamp (ms) from the version 7 UUID.
"""
@spec timestamp(t() | raw()) :: non_neg_integer()
def timestamp(<<milliseconds::48, 7::4, _::76>>), do: milliseconds
def timestamp(<<_::288>> = uuid), do: uuid |> Ecto.UUID.dump!() |> timestamp()
end
40 changes: 31 additions & 9 deletions test/uuidv7_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ defmodule UUIDv7Test do

describe "dump!/1" do
test "dumps the string to a binary" do
assert UUIDv7.dump("0188e590-0dca-7ced-a90d-c3838376becb") ==
assert UUIDv7.dump!("0188e590-0dca-7ced-a90d-c3838376becb") ==
<<1, 136, 229, 144, 13, 202, 124, 237, 169, 13, 195, 131, 131, 118, 190, 203>>
end
end
Expand All @@ -63,20 +63,20 @@ defmodule UUIDv7Test do

describe "load!/1" do
test "loads the binary into a string" do
assert UUIDv7.load(
assert UUIDv7.load!(
<<1, 136, 229, 144, 13, 202, 124, 237, 169, 13, 195, 131, 131, 118, 190, 203>>
) == "0188e590-0dca-7ced-a90d-c3838376becb"
end
end

describe "autogenerate/0" do
test "generates a string UUID" do
test "generates a string uuid" do
assert is_binary(UUIDv7.autogenerate())
end
end

describe "generate/0" do
test "generates an UUID" do
test "generates a string uuid" do
str_uuid = UUIDv7.generate()
raw_uuid = UUIDv7.dump!(str_uuid)

Expand All @@ -100,7 +100,7 @@ defmodule UUIDv7Test do
end

describe "generate/1" do
test "generates an UUID" do
test "generates a binary uuid" do
str_uuid = UUIDv7.generate(1_687_467_090_902)
raw_uuid = UUIDv7.dump!(str_uuid)

Expand All @@ -121,14 +121,36 @@ defmodule UUIDv7Test do
end

describe "bingenerate/0" do
test "to do" do
assert false
test "generates a byte array uuid" do
raw_uuid = UUIDv7.bingenerate()
str_uuid = UUIDv7.load!(raw_uuid)

assert is_binary(raw_uuid)
assert UUIDv7.cast!(raw_uuid) == str_uuid
end
end

describe "bingenerate/1" do
test "to do" do
assert false
test "generates a byte array uuid" do
raw_uuid = UUIDv7.bingenerate(1_687_467_090_902)
str_uuid = UUIDv7.load!(raw_uuid)

assert String.starts_with?(str_uuid, "0188e4e0-63d6-7")
assert UUIDv7.cast!(raw_uuid) == str_uuid
end
end

describe "timestamp/1" do
test "returns the timestamp for a bytes uuid" do
raw_uuid = UUIDv7.bingenerate(1_687_467_090_902)

assert UUIDv7.timestamp(raw_uuid) == 1_687_467_090_902
end

test "returns the timestamp for a string uuid" do
str_uuid = UUIDv7.generate(1_687_467_090_902)

assert UUIDv7.timestamp(str_uuid) == 1_687_467_090_902
end
end
end

0 comments on commit bdba226

Please sign in to comment.