Add a way to create an UUIDv7 for a specific timestamp
Things to keep in mind :
- It's not just making UuidV7Generator.New(DateTime) public
- Most of the time, calls to UuidAdvanced.NewV7FromADateTime (name not final) shouldn't interfere with calls to Uuid.NewSequential and Uuid.NewDatabaseFriendly (see exception below)
- Developers that use this feature by mistake by calling UuidAdvanced.NewV7FromADateTime(DateTime.Now) or something similar should except the same behavior as calling Uuid.NewSequential
- Multiple calls to UuidAdvanced.NewV7FromADateTime with the same timestamp during a reasonably short period of time should produce ordered Uuids
See .NET 9's Guid.CreateVersion7(DateTimeOffset) and #14 for inspiration
One thing that would be very nice is, besides support for the time component, to also be able to determine the random component. This is pretty important for database queries.
Suppose I have a UUIDv7 Id field. It's indexed/partitioned/sharded based on the Id field. I want to query the database for data for the last day, without creating additional indexes. Then being able to construct a query like this:
SELECT * FROM Id >= lowerBoundry
would be nice, where the random component for the lower boundary is initialised to zero. To support these types of queries initializing the random component to the maximum or minimum value needs to be supported in the API.
I added a new static class UUIDNext.Tools.UuidToolkit with advanced methods to create custom UUID v7 :
-
CreateUuidV7FromSpecificDate(DateTimeOffset date)that address this issue -
CreateUuidV7(long timestamp, Span<byte> followingBytes)that address @timstokman's comment
I will publish a beta version with this new class in the coming weeks and write some documentation about theses methods
One thing that would be very nice is, besides support for the time component, to also be able to determine the random component. This is pretty important for database queries. ... would be nice, where the random component for the lower boundary is initialised to zero. To support these types of queries initializing the random component to the maximum or minimum value needs to be supported in the API.
Hi @timstokman,
I've released a new beta version that allow you to create a UUID v7 based on a timestamp and a span of bytes by using the UUIDNext.Tools.UuidToolkit.CreateUuidV7(long, Span<byte>) method.
Can you try it and tell me if it meet your needs ?
Thanks in advance
The API will work for me.
I can give you a bit of context, might help you out. My application right now has two custom helper methods for uuidv7 where this would help:
public static Guid GenerateMinRangeId(DateTimeOffset dateTime)
{
Span<byte> bytes = stackalloc byte[16];
Span<byte> timestampInMillisecondsBytes = stackalloc byte[8];
BinaryPrimitives.TryWriteInt64BigEndian(timestampInMillisecondsBytes, dateTime.ToUnixTimeMilliseconds());
timestampInMillisecondsBytes.Slice(2, 6).CopyTo(bytes);
return new Guid(bytes, true);
}
public static Guid GenerateMaxRangeId(DateTimeOffset dateTime)
{
Span<byte> bytes = stackalloc byte[16];
Span<byte> timestampInMillisecondsBytes = stackalloc byte[8];
BinaryPrimitives.TryWriteInt64BigEndian(timestampInMillisecondsBytes, dateTime.ToUnixTimeMilliseconds());
timestampInMillisecondsBytes.Slice(2, 6).CopyTo(bytes);
bytes.Slice(6, 10).Fill(byte.MaxValue);
return new Guid(bytes, true);
}
I use these methods for a couple of things.
Querying things based on a time-range:
Guid minRangeId = GenerateMinRangeId(minTimestamp);
Guid maxRangeId = GenerateMaxRangeId(maxTimestamp);
myQueryable.Where(entity => entity.Id >= minRangeId && entity.Id <= maxRangeId)
Creating citus postgres time-based partitions for UUIDv7 for one day:
Guid minGuid = GenerateMinRangeId(referenceDate);
Guid maxGuid = GenerateMinRangeId(referenceDate.AddDays(1)); // Postgres partition ranges are inclusive-exclusive, so this works
ExecuteSql($@"CREATE TABLE IF NOT EXISTS public.""{table}_{referenceDate:yyyyMMdd}"" PARTITION OF public.""{table}"" FOR VALUES FROM ('{minGuid}') TO ('{maxGuid}')");
It'd be nice if these methods don't need internal UUIDv7 methods, which your API achieves. An extra overload that takes a DateTimeOffset instead of a long might be nice though.
Thank you for your feedback, I'm glad that the new method fit your use case.
I plan to keep the method signature with a timestamp and a span
Is there a way to do this for the Sql Server V8 variant?
@Aaron-P no, there is currently no way to do this for the SQL Server V8 variant
Is there a way to do this for the Sql Server V8 variant?
@Aaron-P an issue has been created for this : #32