So as I’m already using https://hex.pm/packages/uuid I just use that for creating a UUIDV1 and then just tweak the dump/load so that it does the ordering under the hood. I have a generated column for the tables that shows the original UUID as set by the application/user to aid debugging.
@OvermindDL1 Unfortunately switching everything to use PG is not an option at the moment 
defmodule App.Ecto.OrderedUuidType do
@moduledoc """
Custom type for handling ordered UUIDV1. Ecto.UUID is always V4.
Because MySQL is not great at UUIDs, we store it as a BINARY(16) - same as
Ecto.UUID, but the difference is we also make sure it is a V1 UUID and move
some parts around so that it can be cached better.
@see http://mysql.rjweb.org/doc.php/uuid for more info.
"""
@behaviour Ecto.Type
alias UUID
@doc """
The Ecto type.
"""
def type, do: :uuid
@doc """
String UUID. Make sure it is a valid UUID V1
"""
def cast(<< _::64, ?-, _::32, ?-, _::32, ?-, _::32, ?-, _::96 >> = uuid) do
case is_valid_uuid_type?(uuid) do
true ->
{:ok, String.downcase(uuid)}
_ ->
:error
end
end
@doc """
binary representaiton of UUID
"""
def cast(<< _::128 >> = binary) do
UUID.binary_to_string!(binary) |> cast
end
# Everything else is error
def cast(_), do: :error
defp is_valid_uuid_type?(uuid) do
case UUID.info(uuid) do
{:ok, [uuid: _, binary: _, type: _, version: 1, variant: _]} ->
true
{_, _} ->
false
end
end
@doc """
Load a binary UUId from DB and convert to string. Move parts back to unordered
UUID so that it matches what the user put in.
"""
def load(<< _::128 >> = binary) do
<< c1, c2, c3, c4, b1, b2, b3, b4, ?-,
a1, a2, a3, a4, ?-,
a5, a6, a7, a8, ?-,
d1, d2, d3, d4, ?-,
e1, e2, e3, e4, e5, e6, e7, e8, e9, e10, e11, e12 >> = UUID.binary_to_string!(binary)
{:ok, << a1, a2, a3, a4, a5, a6, a7, a8, ?-,
b1, b2, b3, b4, ?-,
c1, c2, c3, c4, ?-,
d1, d2, d3, d4, ?-,
e1, e2, e3, e4, e5, e6, e7, e8, e9, e10, e11, e12 >>}
end
# Everything else is error
def load(_), do: :error
@doc """
Take a string representation of a UUID and re-order it into a time ordered
UUID, then convert it to binary for putting in the DB.
Convert this: ce093c9e-027f-11e8-ba89-0ed5f89f718b
To this: 11e8027f-ce09-3c9e-ba89-0ed5f89f718b
"""
def dump(<< a1, a2, a3, a4, a5, a6, a7, a8, ?-,
b1, b2, b3, b4, ?-,
c1, c2, c3, c4, ?-,
d1, d2, d3, d4, ?-,
e1, e2, e3, e4, e5, e6, e7, e8, e9, e10, e11, e12 >>) do
ordered = << c1, c2, c3, c4, b1, b2, b3, b4, ?-,
a1, a2, a3, a4, ?-,
a5, a6, a7, a8, ?-,
d1, d2, d3, d4, ?-,
e1, e2, e3, e4, e5, e6, e7, e8, e9, e10, e11, e12 >>
{:ok, UUID.string_to_binary!(ordered)}
end
# Everything else is error
def dump(_), do: :error
# Callback invoked by autogenerate fields.
@doc false
def autogenerate do
UUID.uuid1()
end
end
defmodule App.Ecto.OrderedUuidTypeTest do
use ExUnit.Case
alias App.Ecto.OrderedUuidType
@test_uuid "ce093c9e-027f-11e8-ba89-0ed5f89f718b"
@test_uuid_upper_case "CE093C9E-027F-11E8-BA89-0ED5F89F718B"
@test_uuid_binary <<206, 9, 60, 158, 2, 127, 17, 232, 186, 137, 14, 213, 248, 159, 113, 139>>
@test_uuid_ordered_binary <<17, 232, 2, 127, 206, 9, 60, 158, 186, 137, 14, 213, 248, 159, 113, 139>>
@test_uuid_invalid_characters "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
@test_uuid_invalid_type "8eebc69b-b3f7-4ee8-a034-f9bcf69e30cc"
test "cast" do
assert OrderedUuidType.cast(@test_uuid) == {:ok, @test_uuid}
assert OrderedUuidType.cast(@test_uuid_binary) == {:ok, @test_uuid}
assert OrderedUuidType.cast(@test_uuid_upper_case) == {:ok, String.downcase(@test_uuid_upper_case)}
assert OrderedUuidType.cast(@test_uuid_invalid_characters) == :error
assert OrderedUuidType.cast(@test_uuid_invalid_type) == :error
assert OrderedUuidType.cast(nil) == :error
end
test "load" do
assert OrderedUuidType.load(@test_uuid_ordered_binary) == {:ok, @test_uuid}
assert OrderedUuidType.load("") == :error
end
test "dump" do
assert OrderedUuidType.dump(@test_uuid) == {:ok, @test_uuid_ordered_binary}
assert OrderedUuidType.dump(@test_uuid_binary) == :error
end
test "autogenerate" do
assert << _::64, ?-, _::32, ?-, _::32, ?-, _::32, ?-, _::96 >> = Ecto.UUID.generate
end
end