commit 76f7772b6d1fe09655cba3c88d670c0f946ed112
parent 4000570536ddc8d678b7fef416c6494905ec929c
Author: srfsh <dev@srf.sh>
Date: Sun, 21 Aug 2022 20:00:42 +0300
Zenflows.DB.ID: switch to ULID
Diffstat:
M | src/zenflows/db/id.ex | | | 299 | ++++++++++++++++++++++++++++++++++++++++++++----------------------------------- |
1 file changed, 167 insertions(+), 132 deletions(-)
diff --git a/src/zenflows/db/id.ex b/src/zenflows/db/id.ex
@@ -17,166 +17,201 @@
defmodule Zenflows.DB.ID do
@moduledoc """
-An Ecto type to `Base.url_encode64/2` UUIDs. It provides validation for
-IDs in GraphQL schemas and just makes the IDs shorter.
+An Ecto type that implements ULIDs with Crockford's Base32. It also
+provides validation for IDs in GraphQL schemas.
"""
use Ecto.Type
-@typedoc "URL-safe Base64-encoded string."
-@type t() :: <<_::176>> # 22*8
+@typedoc "A Crockford's Base32-encoded string."
+@type t() :: <<_::208>> # 26*8
-@typedoc "A raw binary representation of a UUID."
-@type raw() :: Ecto.UUID.raw()
+@typedoc "A raw binary representation of a ULID."
+@type raw() :: <<_::128>>
@impl true
-def type() do
- :uuid
-end
+def type(), do: :uuid
-# credo:disable-for-lines:3 Credo.Check.Refactor.CyclomaticComplexity
-# credo:disable-for-lines:7 Credo.Check.Consistency.TabsOrSpaces
@impl true
-def cast(<<c01::8, c02::8, c03::8, c04::8,
- c05::8, c06::8, c07::8, c08::8,
- c09::8, c10::8, c11::8, c12::8,
- c13::8, c14::8, c15::8, c16::8,
- c17::8, c18::8, c19::8, c20::8,
- c21::8, c22::8>> = val) do
- valid? = v(c01) and v(c02) and v(c03) and v(c04)
- and v(c05) and v(c06) and v(c07) and v(c08)
- and v(c09) and v(c10) and v(c11) and v(c12)
- and v(c13) and v(c14) and v(c15) and v(c16)
- and v(c17) and v(c18) and v(c19) and v(c20)
- and v(c21) and v(c22)
- if valid? do
- {:ok, val}
- else
- :error
+def cast(<<_::208>> = data) do
+ with {:ok, _} <- decode(data) do
+ {:ok, data}
end
end
-
-def cast(<<_::128>> = raw) do
- {:ok, encode(raw)}
-end
-
-def cast(_) do
- :error
-end
+def cast(<<_::128>> = raw), do: encode(raw)
+def cast(_), do: :error
@impl true
-def dump(id) when byte_size(id) == 22 do
- decode(id)
-end
-
-def dump(_) do
- :error
-end
+def dump(id) when byte_size(id) == 26, do: decode(id)
+def dump(_), do: :error
@impl true
-def load(<<_::128>> = raw) do
- {:ok, encode(raw)}
-end
-
-def load(_) do
- :error
-end
+def load(<<_::128>> = raw), do: encode(raw)
+def load(_), do: :error
@impl true
-def autogenerate() do
- gen()
-end
+def autogenerate(), do: gen()
-@doc "Generates a random UUIDv4 encoded as URL-safe Base64 string."
+@doc "Generates a random ULID."
@spec gen() :: t()
def gen() do
- encode(bingen())
+ {:ok, id} = encode(bingen())
+ id
end
-@doc "Generates a random UUIDv4 in binary format."
+@doc "Generates a random ULID in binary format."
@spec bingen() :: raw()
def bingen() do
- Ecto.UUID.bingenerate()
+ import DateTime
+
+ <<ts::binary-6, _::binary>> =
+ utc_now() |> to_unix(:millisecond) |> :binary.encode_unsigned()
+ rand = :crypto.strong_rand_bytes(10)
+ ts <> rand
end
-@spec decode(String.t()) :: {:ok, raw()} | :error
-defp decode(str) do
- case Base.url_decode64(str, padding: false) do
- {:ok, raw} -> {:ok, raw}
- :error -> :error
- end
+@doc "Fetch the timestamp out of an encoded or raw ULID."
+@spec ts(t() | raw()) :: {:ok, DateTime.t()} | {:error, atom()}
+def ts(<<x0::8, x1::8, x2::8, x3::8, x4::8, x5::8, x6::8, x7::8, x8::8, x9::8, _::binary-16>>) do
+ import DateTime
+
+ <<ts::binary-6, _::bitstring-2>> = <<
+ d(x0)::5, d(x1)::5, d(x2)::5, d(x3)::5, d(x4)::5,
+ d(x5)::5, d(x6)::5, d(x7)::5, d(x8)::5, d(x9)::5,
+ >>
+ :binary.decode_unsigned(ts) |> from_unix(:millisecond)
end
+def ts(<<ts::binary-6, _::binary-10>>) do
+ import DateTime
-@spec encode(raw()) :: t()
-defp encode(raw) do
- Base.url_encode64(raw, padding: false)
+ :binary.decode_unsigned(ts) |> from_unix(:millisecond)
end
+def ts(_) do
+ {:error, :invalid}
+end
+
+defp encode(<<x00::5, x01::5, x02::5, x03::5, x04::5, x05::5, x06::5, x07::5,
+ x08::5, x09::5, x10::5, x11::5, x12::5, x13::5, x14::5, x15::5, x16::5,
+ x17::5, x18::5, x19::5, x20::5, x21::5, x22::5, x23::5, x24::5, x25::3>>) do
+ import Bitwise
-@compile {:inline, v: 1}
-@spec v(any()) :: boolean()
-defp v(?_), do: true
-defp v(?-), do: true
-defp v(?0), do: true
-defp v(?1), do: true
-defp v(?2), do: true
-defp v(?3), do: true
-defp v(?4), do: true
-defp v(?5), do: true
-defp v(?6), do: true
-defp v(?7), do: true
-defp v(?8), do: true
-defp v(?9), do: true
-defp v(?A), do: true
-defp v(?B), do: true
-defp v(?C), do: true
-defp v(?D), do: true
-defp v(?E), do: true
-defp v(?F), do: true
-defp v(?G), do: true
-defp v(?H), do: true
-defp v(?I), do: true
-defp v(?J), do: true
-defp v(?K), do: true
-defp v(?L), do: true
-defp v(?M), do: true
-defp v(?N), do: true
-defp v(?O), do: true
-defp v(?P), do: true
-defp v(?Q), do: true
-defp v(?R), do: true
-defp v(?S), do: true
-defp v(?T), do: true
-defp v(?U), do: true
-defp v(?V), do: true
-defp v(?W), do: true
-defp v(?X), do: true
-defp v(?Y), do: true
-defp v(?Z), do: true
-defp v(?a), do: true
-defp v(?b), do: true
-defp v(?c), do: true
-defp v(?d), do: true
-defp v(?e), do: true
-defp v(?f), do: true
-defp v(?g), do: true
-defp v(?h), do: true
-defp v(?i), do: true
-defp v(?j), do: true
-defp v(?k), do: true
-defp v(?l), do: true
-defp v(?m), do: true
-defp v(?n), do: true
-defp v(?o), do: true
-defp v(?p), do: true
-defp v(?q), do: true
-defp v(?r), do: true
-defp v(?s), do: true
-defp v(?t), do: true
-defp v(?u), do: true
-defp v(?v), do: true
-defp v(?w), do: true
-defp v(?x), do: true
-defp v(?y), do: true
-defp v(?z), do: true
-defp v(_), do: false
+ {:ok, <<
+ e(x00), e(x01), e(x02), e(x03), e(x04), e(x05), e(x06), e(x07), e(x08),
+ e(x09), e(x10), e(x11), e(x12), e(x13), e(x14), e(x15), e(x16), e(x17),
+ e(x18), e(x19), e(x20), e(x21), e(x22), e(x23), e(x24), e(bsl(x25, 2)),
+ >>}
+end
+defp encode(_), do: :error
+
+defp decode(<<x00::8, x01::8, x02::8, x03::8, x04::8, x05::8, x06::8, x07::8,
+ x08::8, x09::8, x10::8, x11::8, x12::8, x13::8, x14::8, x15::8, x16::8,
+ x17::8, x18::8, x19::8, x20::8, x21::8, x22::8, x23::8, x24::8, x25::8>>) do
+ import Bitwise
+
+ {:ok, <<
+ d(x00)::5, d(x01)::5, d(x02)::5, d(x03)::5, d(x04)::5, d(x05)::5, d(x06)::5, d(x07)::5, d(x08)::5,
+ d(x09)::5, d(x10)::5, d(x11)::5, d(x12)::5, d(x13)::5, d(x14)::5, d(x15)::5, d(x16)::5, d(x17)::5,
+ d(x18)::5, d(x19)::5, d(x20)::5, d(x21)::5, d(x22)::5, d(x23)::5, d(x24)::5, bsr(d(x25), 2)::3,
+ >>}
+rescue
+ ArgumentError -> :error
+end
+defp decode(_), do: :error
+
+@compile {:inline, e: 1}
+defp e(0), do: ?0
+defp e(1), do: ?1
+defp e(2), do: ?2
+defp e(3), do: ?3
+defp e(4), do: ?4
+defp e(5), do: ?5
+defp e(6), do: ?6
+defp e(7), do: ?7
+defp e(8), do: ?8
+defp e(9), do: ?9
+defp e(10), do: ?A
+defp e(11), do: ?B
+defp e(12), do: ?C
+defp e(13), do: ?D
+defp e(14), do: ?E
+defp e(15), do: ?F
+defp e(16), do: ?G
+defp e(17), do: ?H
+defp e(18), do: ?J
+defp e(19), do: ?K
+defp e(20), do: ?M
+defp e(21), do: ?N
+defp e(22), do: ?P
+defp e(23), do: ?Q
+defp e(24), do: ?R
+defp e(25), do: ?S
+defp e(26), do: ?T
+defp e(27), do: ?V
+defp e(28), do: ?W
+defp e(29), do: ?X
+defp e(30), do: ?Y
+defp e(31), do: ?Z
+
+@compile {:inline, d: 1}
+defp d(?0), do: 0
+defp d(?O), do: 0
+defp d(?o), do: 0
+defp d(?1), do: 1
+defp d(?I), do: 1
+defp d(?i), do: 1
+defp d(?L), do: 1
+defp d(?l), do: 1
+defp d(?2), do: 2
+defp d(?3), do: 3
+defp d(?4), do: 4
+defp d(?5), do: 5
+defp d(?6), do: 6
+defp d(?7), do: 7
+defp d(?8), do: 8
+defp d(?9), do: 9
+defp d(?A), do: 10
+defp d(?a), do: 10
+defp d(?B), do: 11
+defp d(?b), do: 11
+defp d(?C), do: 12
+defp d(?c), do: 12
+defp d(?D), do: 13
+defp d(?d), do: 13
+defp d(?E), do: 14
+defp d(?e), do: 14
+defp d(?F), do: 15
+defp d(?f), do: 15
+defp d(?G), do: 16
+defp d(?g), do: 16
+defp d(?H), do: 17
+defp d(?h), do: 17
+defp d(?J), do: 18
+defp d(?j), do: 18
+defp d(?K), do: 19
+defp d(?k), do: 19
+defp d(?M), do: 20
+defp d(?m), do: 20
+defp d(?N), do: 21
+defp d(?n), do: 21
+defp d(?P), do: 22
+defp d(?p), do: 22
+defp d(?Q), do: 23
+defp d(?q), do: 23
+defp d(?R), do: 24
+defp d(?r), do: 24
+defp d(?S), do: 25
+defp d(?s), do: 25
+defp d(?T), do: 26
+defp d(?t), do: 26
+defp d(?V), do: 27
+defp d(?v), do: 27
+defp d(?W), do: 28
+defp d(?w), do: 28
+defp d(?X), do: 29
+defp d(?x), do: 29
+defp d(?Y), do: 30
+defp d(?y), do: 30
+defp d(?Z), do: 31
+defp d(?z), do: 31
+defp d(_), do: raise ArgumentError
end