Skip to content

Conversation

@dkuku
Copy link
Contributor

@dkuku dkuku commented Nov 19, 2025

Add uuid v7 generator.
Also the formatter updated the files - I think it's because it was not touched for long time.
example uuid generated by some random page from google:
019a9da1-53b4-7f77-9609-140ad8880bd2
and one generated with ecto:

iex(2)> Ecto.UUID.generate(:v7)
"019a9da1-568e-7981-9bc3-4ba057de8778"

@greg-rychlewski
Copy link
Member

greg-rychlewski commented Nov 20, 2025

I wonder if we want to have a 2 arity function to let users control the sub millisecond monotonicity guarantee. For example from what I know Postgres and Maria DB use 12 extra timestamp bits to include the nano seconds. Or maybe we want to consider just using nanoseconds in the 1 arity function to be more inline with these databases

lib/ecto/uuid.ex Outdated
"""
@spec generate() :: t
def generate(), do: encode(bingenerate())
def generate(), do: encode(bingenerate(@default_version))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
def generate(), do: encode(bingenerate(@default_version))
def generate(version \\ @default_version), do: encode(bingenerate(version))

And similar below.

section for more info). Primary keys are automatically set up for embedded
schemas as well, defaulting to `{:id, :binary_id, autogenerate: true}`.
This will generate the default UUID v4. You can use UUID v7 instead by setting
the primary key to `{:id, :binary_id, autogenerate: {Ecto.UUID, :generate, [:v7]}}`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's also document this in Ecto.UUID's module doc.

@josevalim
Copy link
Member

@greg-rychlewski in such cases, we can provide a :v7_nano in the future?

@dkuku dkuku requested a review from josevalim November 20, 2025 16:33
@warmwaffles
Copy link
Member

I know that @ryanwinchester implemented / improved uuid v7 for :uniq, so his input may be valuable here.

@ryanwinchester
Copy link
Contributor

ryanwinchester commented Nov 20, 2025

I know that @ryanwinchester implemented / improved uuid v7 for :uniq, so his input may be valuable here.

In uniq it's a naive implementation of just ms timestamps, before the RFC had section 6.2.

I wonder if we want to have a 2 arity function to let users control the sub millisecond monotonicity guarantee. For example from what I know Postgres and Maria DB use 12 extra timestamp bits to include the nano seconds. Or maybe we want to consider just using nanoseconds in the 1 arity function to be more inline with these databases

Yes. In postgres at least, it's monotonic and stepped by a certain minimum number of bits as per section 6.2, method 3. I created a separate UUIDv7 library that does this as well.

I think if we're adding UUIDv7 to Ecto, it would be useful if we did this to match the way the most used database(s) do it. Or at least the option to.

@josevalim
Copy link
Member

I think if we're adding UUIDv7 to Ecto, it would be useful if we did this to match the way the most used database(s) do it. Or at least the option to.

@ryanwinchester I agree. The question is if we do it by default or opt-in. Do you have any thoughts on the matter?

In any case, I think we can merge this now and you could contribute your nano implementation next. Would that work for you? Or would prefer for it to be baked in right now?

@ryanwinchester
Copy link
Contributor

ryanwinchester commented Nov 20, 2025

I think if we're adding UUIDv7 to Ecto, it would be useful if we did this to match the way the most used database(s) do it. Or at least the option to.

@ryanwinchester I agree. The question is if we do it by default or opt-in. Do you have any thoughts on the matter?

Personally, I would prefer it to be default as it is in Postgres, but I don't know what most people would prefer.

In any case, I think we can merge this now and you could contribute your nano implementation next. Would that work for you? Or would prefer for it to be baked in right now?

Maybe instead of version \\ @default_version, we could make it opts \\ [] with a :version field and merge it, leaving it more open to changing without breaking changes?

i.e. we could have version: 7 (or version: :v7) with only millisecond precision be the default, and the increased precision version could still be the same :version, but with other options to specify which method of extra precision you prefer. Just a thought

(there are 3 optional methods for increased precision + monotonicity in the RFC, and I can contribute method 3)

@josevalim
Copy link
Member

Maybe instead of version \ @default_version, we could make it opts \ [] with a :version field and merge it, but leaving it more open to changing without breaking changes?

Yes, regardless of what we do here, I think options would be better indeed, thank you. @dkuku, can you please adjust accordingly?

Also, can you please go ahead and change Ecto.Schema to make it so that, if autogenerate: list_of_options is given, it automatically becomes {mod, :autogenerate, [list_of_options]}? This should keep it flexible and ergonomic to everyone.

@dkuku
Copy link
Contributor Author

dkuku commented Nov 20, 2025

Also, can you please go ahead and change Ecto.Schema to make it so that, if autogenerate: list_of_options is given, it automatically becomes {mod, :autogenerate, [list_of_options]}? This should keep it flexible and ergonomic to everyone.

It started to work with type instead of mod, I also added a new callback but I'm not sure if it's necessary ?

lib/ecto/type.ex Outdated
to `field` with the `:autogenerate` flag.
"""
@callback autogenerate() :: term()
@callback autogenerate(term) :: term()
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it could be a Keyword but I see the functions in this module mostly operate on terms so I keep it consistent

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can skip this change I think... since theoretically with the MFA you can call it anything or even have higher arity?

lib/ecto/uuid.ex Outdated
@spec generate() :: t
def generate(), do: encode(bingenerate())
@spec generate(options) :: t
def generate(opts \\ [@default_options]), do: encode(bingenerate(opts))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

probably don't need the @default_options and could just use an empty list since you supply a default version with Keyword.get/3

lib/ecto/uuid.ex Outdated
Comment on lines 219 to 232
@spec bingenerate() :: raw
def bingenerate() do
def bingenerate(), do: bingenerate(@default_options)

@doc """
Generates a uuid with the given options in binary format.
"""
@spec bingenerate(options) :: raw
def bingenerate(opts) do
case Keyword.get(opts, :version, @default_version) do
4 -> bingenerate_v4()
7 -> bingenerate_v7()
_ -> raise ArgumentError, "unknown UUID version: #{inspect(opts[:version])}"
end
end
Copy link
Contributor

@ryanwinchester ryanwinchester Nov 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same idea as generate, we don't need the default options

Suggested change
@spec bingenerate() :: raw
def bingenerate() do
def bingenerate(), do: bingenerate(@default_options)
@doc """
Generates a uuid with the given options in binary format.
"""
@spec bingenerate(options) :: raw
def bingenerate(opts) do
case Keyword.get(opts, :version, @default_version) do
4 -> bingenerate_v4()
7 -> bingenerate_v7()
_ -> raise ArgumentError, "unknown UUID version: #{inspect(opts[:version])}"
end
end
@doc """
Generates a uuid with the given options in binary format.
"""
@spec bingenerate(options) :: raw
def bingenerate(opts \\ []) do
case Keyword.get(opts, :version, @default_version) do
4 -> bingenerate_v4()
7 -> bingenerate_v7()
version -> raise ArgumentError, "unknown UUID version: #{inspect(version)}"
end
end

Copy link
Member

@josevalim josevalim left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please address @ryanwinchester's feedback and we can ship it!

Thank you all! ❤️

@josevalim josevalim merged commit e6da70d into elixir-ecto:master Nov 21, 2025
7 checks passed
@josevalim
Copy link
Member

💚 💙 💜 💛 ❤️

@warmwaffles
Copy link
Member

Thank you @ryanwinchester and @dkuku, looking forward to moving to ecto for generating uuidv7 instead of relying on uniq

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants