Git Product home page Git Product logo

rt.comb's Introduction

RT.COMB

Purpose

This small .NET Core library does two things:

  1. generates "COMB" Guid values in C#; and,
  2. extracts the DateTime value from an existing COMB Guid.

Background

When GUIDs (uniqueidentifier values in MSSQL parlance, UUID in PostgreSQL) are part of a database index, and particularly when they are part of the clustered index, the randomness of new values can reduce performance when inserting new values.

In SQL Server 2005, Microsoft provided the NEWSEQUENTIALID() function to alleviate this issue, but despite the name, generated GUID values from that function are still not guaranteed to be sequential over time (rebooting impacts the sequence, for example), nor do multiple instances of MSSQL produce values that would be sequential in relationship to one another.

But back in 2002, in an article for InformIT, Jimmy Nilsson described the "COMB" technique, which replaces the portion of a GUID that is sorted first with a date/time value. This guarantees (within the precision of the system clock) that values will be sequential, even when the code runs on different machines. As a side benefit, the COMB's timestamp can be easily extracted, which can be useful from time to time if your table has no other field tracking the insertion date/time.

This library implements several modern variations, as well as the original technique.

Simple Usage

A NuGet package is available, targeted for .NET Standard 2.1 (.NET 6.0+):

https://www.nuget.org/packages/RT.Comb/

Three static implementations are provided, each with a different strategy for generating the timestamp and inserting it into the GUID.

  • RT.Comb.Provider.Legacy: The original technique. Only recommended if you need to support existing COMB values created using this technique.
  • RT.Comb.Provider.Sql: This is the recommended technique for COMBs stored in Microsoft SQL Server.
  • RT.Comb.Provider.Postgre: This is the recommended technique for COMBs stored in PostgreSQL.

Each of these has only two public methods:

  • Create() returns a COMB GUID. You can optionally pass your own baseline GUID and/or timestamp to embed.
  • GetTimestamp() returns the timestamp embedded in a previously-created COMB GUID.

An example console application using the Sql version is provided in the demo folder showing a minimal-code use of both of these methods.

Advanced Usage

If the default implementations don't work for you, you can roll your own, either changing up how the timestamp is determined, or changing how it is inserted into the GUID.

There are two core interfaces: ICombProvider, responsible for embedding and extracting timestamps (described above under Simple Usage), and DateTimeStrategy, responsible for encoding timestamp values to bytes and vice versa.

There are two included implementations of ICombProvider:

  • SqlCombProvider: This creates and decodes COMBs in GUIDs that are compatible with the way Microsoft SQL Server sorts uniqueidentifier values -- i.e., starting at the 11th byte.
  • PostgreSqlCombProvider: This creates and decodes COMBs in GUIDs that are compatible with the way PostgreSQL sorts uuid values -- i.e., starting with the first byte shown in string representations of a Guid.

Both take an IDateTimeStrategy argument in their constructor. Two strategies are included:

  • UnixDateTimeStrategy encodes the DateTime using the millisecond version of the Unix epoch timestamp (recommended); and,
  • SqlDateTimeStrategy encodes the DateTime using SQL Server's datetime data structure (only for compatibility with legacy COMB values).

You can implement either of these interfaces on your own, or both, to suit your needs.

Database-side generation/decoding

This repository includes an sql folder containing scripts for two SQL Server 2016+ functions that implement UnixDateTimeStrategy:

  • NewComb: Generates a new COMB value; and
  • DateFromComb: Extracts the DATETIME2 timestamp value from an existing COMB value.

The logic in these functions is fully compatible with the .NET code in this library.

(I would be happy to accept a testable PR to add PostgreSql functions.)

If you need similar functions for the SqlDateTimeStrategy, those are included as well as NewCombLegacy and DateFromCombLegacy.

Both of the COMB-generation functions rely on a view, vwNewGuid, since NEWID() is non-deterministic and can't be called directly from within a function.

If you need to support SQL Server versions prior to 2016, look at the commit history for this README file, it contains older versions of the code that support some older versions.

Gory Details about UUIDs and GUIDs

Note: (For a more comprehensive treatment, see Wikipedia.)

Standard UUIDs/GUIDs are 128-bit (16-byte) values, wherein most of those bits hold a random value. When viewed as a string, the bytes are represented in hex in five groups, separated by hyphens:

xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
version       ^
variant            ^

Structurally, this breaks down into one 32-bit unsigned integer (Data1), two 16-bit unsigned integers (Data2, Data3), and 8 bytes (Data4). The most significant nybble of Data3 (the 7th byte in string order, 8th in the internal bytes of System.Guid) is the GUID "version" number--generally 4 (0100b). Also, the most significant 2-3 bits of the first byte of Data4 is the "variant" value. For common GUIDs, the first two of these bits will be 10, and the third is random. The remaining bits for a version 4 GUID are random.

The UUID standard (RFC 4122) specifies that the Data1, Data2, and Data3 integers are stored and shown in network byte order ("big-endian" or most significant byte first). However, while Microsoft's GUID implementation shows and sorts the values as if they were in big-endian form, internally it stores the bytes for these values in the native form for the processor (which, on x86, means little-endian).

This means that the bytes you get from Guid.ToString() are actually stored in the GUID's internal byte array in the following order:

33221100-5544-7766-8899-AABBCCDDEEFF

Npgsql, the standard .NET library for working with PostgreSQL databases, reads incoming bytes for these three fields using GetInt32() and GetInt16(), which assume the bytes are in little-endian form. This ensures that .NET will return the same string as non-.NET tools that show PostgreSQL UUID values, but the bytes inside .NET and inside PostgreSQL are stored in a different order. Reference:

https://github.com/npgsql/npgsql/blob/4ef74fa78cffbb4b1fdac00601d0ee5bff5e242b/src/Npgsql/TypeHandlers/UuidHandler.cs

IDateTimeStrategy

IDateTimeStrategy implementations are responsible for returning a byte array, most significant byte first, representing the timestamp. This byte order is independent of whatever swapping we might have to do to embed the value in a GUID at a certain index. They can return either 4 or 6 bytes, both built-in implementations use 6 bytes.

UnixDateTimeStrategy

This implementation was inspired by work done on the Marten project, which uses a modified version of RT.Comb. This also returns 6 bytes, but as a 48-bit unsigned integer representing the number of milliseconds since the UNIX epoch date (1970-01-01). Since this method is far more space-efficient than the MSSQL datetime structure, it will cover you well into the 85th Century. This is the recommended implementation.

SqlDateTimeStrategy

This strategy returns bytes for a timestamp with part of an MSSQL datetime value. The datetime type in MSSQL is an 8-byte structure. The first four bytes are a signed integer representing the number of days since January 1, 1900. Negative values are permitted to go back to 1752-01-01 (for Reasons), and positive values are permitted through 9999-12-12. The remaining four bytes represent the time as the number of unsigned 300ths of a second since midnight. Since a day is always 24 hours, this integer never uses more than 25 bits.

For the COMB, we use all four bytes of the time and two bytes of the date. This means our date has no sign bit and has a maximum value of 65,535, limiting our date range to January 1, 1900 through June 5, 2079 -- a very reasonable spread.

If you use this in conjunction with SqlCombProvider, your COMB values will be compatible with the original COMB article, and you can easily use plain T-SQL to encode and decode the timestamps without requiring .NET (the overhead for doing this in T-SQL is minimal):

ICombProvider

Regardless which strategy is used for encoding the timestamp, we need to overwrite the portion of the GUID that is sorted first, so our GUIDs are sortable in date/time order and minimize index page splits. This differs by database platform.

MSSQL and System.Data.SqlTypes.SqlGuid sort first by the last 6 Data4 bytes, left to right, then the first two bytes of Data4 (again, left to right), then Data3, Data2, and Data1 right to left. This means for COMB purposes, we want to overwrite the last 6 bytes of Data4 (byte index 10), left to right.

However, PostgreSQL and System.Guid sort GUIDs in the order shown as a string, which means for PostgreSQL COMB values, we want to overwrite the bytes that are shown first from the left. Since System.Guid shows bytes for Data1, Data2, and Data3 in a different order than it stores them internally, we have to account for this when overwriting those bytes. For example, our most significant byte inside a Guid will be at index 3, not index 0.

Here is a fiddler illustrating some of this:

https://dotnetfiddle.net/rW9vt7

SqlCombProvider

As mentioned above, MSSQL sorts the last 6 bytes first, left to right, so we plug our timestamp into the GUID structure starting at the 11th byte, most significant byte first:

00112233-4455-6677-8899-AABBCCDDEEFF
xxxxxxxx xxxx 4xxx Vxxx MMMMMMMMMMMM  UnixDateTimeStrategy, milliseconds
xxxxxxxx xxxx 4xxx Vxxx DDDDTTTTTTTT  SqlDateTimeStrategy, days and 1/300s
4 = version
V = variant
x = random

PostgreSqlCombProvider

For PostgreSQL, the first bytes are sorted first, so those are the ones we want to overwrite.

MMMMMMMM MMMM 4xxx Vxxx xxxxxxxxxxxx  UnixDateTimeStrategy, milliseconds
DDDDTTTT TTTT 4xxx Vxxx xxxxxxxxxxxx  SqlDateTimeStrategy, days and 1/300s
4 = version
V = variant
x = random

Recall that System.Guid stores its bytes for Data1 and Data2 in a different order than they are shown, so we have to reverse the bytes we're putting into those areas so they are stored and sorted in PostgreSQL in network-byte order.

This is a breaking change for RT.Comb v.1.4. In prior versions, I wasn't actually able to test on PostgreSQL and got this all wrong, along with misplacing where the version nybble was and doing unnecessary bit-gymnastics to avoid overwriting it. My error was kindly pointed out by Barry Hagan and has been fixed.

Comparing COMB to UUIDv7

Here is more information on UUIDv7: https://www.ietf.org/archive/id/draft-peabody-dispatch-new-uuid-format-04.html#v7

PostgreSqlCombProvider with the default UnixDateTimeStrategy (above) is compatible with UUIDv7.

However, while SqlCombProvider with the default UnixDateTimeStrategy also uses a 48-bit UNIX timestamp, the location of the timestamp bytes is different, based on how SQL Server sorts UNIQUEIDENTIFIER values.

Note about entropy

The default implementations overwrite 48 bits of random data with the timestamp, and another 6 bits are also pre-determined (the version and variant). This still leaves 74 random bits per unit of time (1/300th of a second for SqlDateTimeStrategy, 1/1000th of a second for UnixDateTimeStrategy). This provides well beyond a reasonable amount of protection against collision.

TimestampProvider / GuidProvider

By default, SqlCombProvider and PostgreSqlCombProvider use DateTime.UtcNow when a timestamp argument is not provided for Create(). If you want the convenience of using Create with fewer arguments but need to choose the timestamp another way, you can set the TimestampProvider delegate to a parameter-less function that returns a DateTime value. Another delegate, GuidProvider, provides the same functionality for overriding how the base GUID is created.

UtcNoRepeatTimestampProvider

The DateTime strategies described above are limited to 1-3ms resolution, which means if you create many COMB values per second, there is a chance you'll create two with the same timestamp value.

This won't result in a database collision--the remaining random bits in the GUID protect you there. But COMBs with exactly the same timestamp value aren't guaranteed to sort in order of insertion because once the timestamp bytes are sorted, the sort order will rely on the random bytes after that. This is expected behavior. As with any timestamp-based field, COMBs are not guaranteed to be sequential once you're inserting records faster than the stored clock resolution. Also, on Windows platforms, the system timer only has a resolution of 15.625ms, which amplifies this problem. So, in general, don't rely on COMBs to have values that sort in exactly the same order as they were inserted.

If your sort order must be guaranteed, I've come up with a workaround -- a TimestampProvider delegate called UtcNoRepeatTimestampProvider.GetTimestamp. This method checks to ensure that the current time is at least IncrementMs milliseconds more recent than the previous value it generated. If not, it returns the previous value plus IncrementMs instead. Either way, it then keeps track of what it returned for the next round. This checking is thread-safe. By default, IncrementMs is set to 4ms, which is sufficient to ensure that SqlDateTimeStrategy timestamp values won't collide (which has a ~3ms resolution). If you're using UnixDateTimeStrategy, you can optionally set this to a lower value (such as 1ms) instead. The table below shows some examples values you might get from DateTime.UtcNow in a tight loop vs. what UtcNoRepeatTimestampProvider would return:

02:08:50.613    02:08:50.613
02:08:50.613    02:08:50.617
02:08:50.613    02:08:50.621
02:08:50.617    02:08:50.625
02:08:50.617    02:08:50.629
02:08:50.617    02:08:50.632

Note that you're trading a "time slip" of a few milliseconds during high insert rates for a guarantee that the UtcNoRepeatTimestampProvider won't repeat its timestamp, so COMBs will always sort exactly in insert order. This is fine if your transaction rate just has occasional bumps, but if you're constantly writing thousands of records per second, the time slip could accumulate into something real, especially with the 4ms default increment.

To use this workaround, you'll need to create your own ICombProvider instance rather than using the built-in static instances in RT.Comb.Provider, and pass this delegate in the provider constructor. You can find an example of this in the test suite.

How To Contribute

Some missing pieces:

  • More unit tests.
  • Please keep all contributions compatible with .NET Core.
  • Please use the same style (tabs, same-line opening braces, compact line spacing, etc.)
  • Please keep project/solution files compatible with Visual Studio Code.

Security and Performance

(1) It's a good idea to always use UTC date/time values for COMBs, so they remain highly sequential regardless of server locale or daylight savings, etc. This is even more important if the values will be generated on multiple machines that may have different time zone settings.

(2) It should go without saying, but using a COMB "leaks" the date/time to anyone knows how to decode it, so don't use one if this information (or even the relative order of their creation) should remain private.

(3) Don't use this with MySQL if you plan to store GUIDs as varchar(36) -- performance will be terrible.

(4) Test. Don't assume that a GUID key will be significantly slower for you than a 32-bit int field. Don't assume it will be roughly the same. The relative size of your tables (and especially of your indexed columns) compared to your primary key column will impact the overall database size and relative performance. I use COMB values frequently in moderate-sized databases without any performance issues, but YMMV.

(5) The best use for a COMB is where (a) you want the range and randomness of the GUID structure without index splits under load, and (b) any actual use of the embedded timestamp is incidental (for example, for debugging purposes).

(6) As described under UtcNoRepeatTimestampProvider, timestamps are only as precise as the timer resolution, so unless you use the aforementioned functionality to work around this, COMBs generated within the same timestamp period are not guaranteed to sort in the order of generation.

Revision History

  • 1.1.0 2016-01 First release
  • 1.2.0 2016-01 Clean up, add unit tests, published on NuGet
  • 1.3.0 2016-01-18 Major revision of the interface
  • 1.4.0 2016-08-10 Upgrade to .NETCore 1.0, switch tests to Xunit
  • 1.4.1 2016-09-16 Bug fix
  • 1.4.2 2016-09-16 Fix build issue
  • 2.0.0 2016-11-19 Corrected byte-order placement, upgraded to .NETCore 1.1, downgraded to .NET 4.5.1. Switched from static classes to instance-based, allowing me to create a singleton with injected options for ASP.NET Core.
  • 2.1.0 2016-11-20 Simplified API and corrected/tested byte order for PostgreSql, more README rewrites, git commit issue
  • 2.2.0 2017-03-28 Fixed namespace for ICombProvider, adjusted the interface to allow overriding how the default timestamp and Guid are obtained. Created TimestampProvider implementation that forces unique, increasing timestamps (for its instance) as a solution for #5.
  • 2.2.1 2017-04-02 Converted to .csproj. Now targeting .NET Standard 1.2. Not packaged for NuGet.
  • 2.3.0 2017-07-09 Simplify interface w/static class, remove TimestampProvider and GuidProvider from the interface and make them immutable in the concrete implementations.
  • 2.3.1 2018-06-10 Migrated demo and test apps to netstandard 2.1, downgraded library to netstandard 1.0 for maximum compatibility.
  • 2.4.0 2020-04-23 Bumped to netstandard 2.0. (#16)
  • 2.5.0 2020-12-13 Test package bumped to .NET 5.0. Added .NET Core DI package (#18, thanks @joaopgrassi!).
  • 3.0.0 2021-10-27 Zero-alloc and nullable support (thanks @skarllot!); Switch to production build.
  • 4.0.0 2022-12-11 Drop .NET 5 and .NET Framework support. Please continue to use v3.0 if you are still supporting these. Minor updates to bump versions and take advantage of newer C# features.
  • 4.0.1 2023-02-11 Move back to .NET 6 version of DI for now for better compatibility. (#26)
  • (Unreleased) 2023-10-28: Drop .NET 6, bump the dependencies.
  • (Unreleased) 2024-03-20: Move SQL code to separate files (thanks for the idea, @Reikooters! #29)

More Information

The original COMB article: http://www.informit.com/articles/article.aspx?p=25862

Another implementation (not compatible): http://www.siepman.nl/blog/post/2013/10/28/ID-Sequential-Guid-COMB-Vs-Int-Identity-using-Entity-Framework.aspx

License (MIT "Expat")

Copyright 2015-2024 Richard S. Tallent, II

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

rt.comb's People

Contributors

coreycaldwell avatar joaopgrassi avatar richardtallent avatar skarllot avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

rt.comb's Issues

Generate the smallest/bigest COMB for a date in C#

Hello,

I'm are using Entity Framework to select data from DB. The PK of the table is a COMB GUID.
I would like to get the list of records for a date using the COMB PK for efficiency.
Following your doc, I'm able to generate the COMB "boundaries" of the day in SQL Server side but not in C#.
e.g. >= 2023-10-11 00:00:00.000 to <= 2023-10-11 23:59:59.999 or better < 2023-10-12 00:00:00
=>

'00000000-0000-0000-0000-018B1C087C00' to <= FFFFFFFF-FFFF-FFFF-FFFF-018B212ED800 or < 00000000-0000-0000-0000-018B212ED800

I could do it by splitting/concatating string but is there another way?

Tx.

Michael

PostgreSql bad byte order

Now that I'm actually able to test this library in PostgreSql, I see where I'm going wrong on encoding the timestamp so the order is preserved. Marking this so anyone wanting to use this in PostgreSql knows about the issue, I hope to have it resolved soon.

T-SQL in README for extracting DateTime from COMB needs some tweaks

The T-SQL in the README for extracting the DateTime value from the COMB is off (at least for the SQL Server 2014).

Currently, this is in the README:
CAST(SUBSTRING(CAST(0 AS binary(2)) + CAST(value AS binary(16), 10, 6) AS datetime)

The Substring function's index is 1-based instead of 0-based. Additionally, I think the 0-fill at the beginning needs to be prepended separately from the Substring of the value. Here is the revised version:
CAST(CAST(0 AS binary(2)) + SUBSTRING(CAST(value AS binary(16)), 11, 6) AS datetime)

Additionally, here is a quick script to verify everything in SQL Server.

-- Quick verification of extracting date/time from COMB GUID value.
DECLARE @Today AS datetime
DECLARE @ToCombGuid AS uniqueidentifier
DECLARE @FromCombGuid AS datetime
DECLARE @IsMatch AS bit

SET @Today = GETUTCDATE()
SET @ToCombGuid = CAST(CAST(NEWID() AS binary(10)) + CAST(@Today AS binary(6)) AS uniqueidentifier)
SET @FromCombGuid = CAST(CAST(0 AS binary(2)) + SUBSTRING(CAST(@ToCombGuid AS binary(16)), 11, 6) AS datetime)
SET @IsMatch = IIF(@Today = @FromCombGuid, 1, 0)

select @Today AS Today, @ToCombGuid AS ToCombGuid, @FromCombGuid AS FromCombGuid, @IsMatch

This library is a great idea! I've got code like it scattered around as well.

Comparing two Guid (created with RT.Comb) if lower

In SQL server I can check two GUID if one is lower. (So if one is created before.)

I needed the same functionality in C#
Do you have a suggestion how can I do that?

Thanks for advance,
Best Regards, PP

Suggestion for T-SQL query to use SYSUTCDATETIME() instead of GETUTCDATE()

Using SYSUTCDATETIME() instead of GETUTCDATE()

First of all, thank you for creating the library.

The readme has the following examples for generating a COMB in T-SQL

Creating a COMB uniqueidentifier in T-SQL with the current date and time:

DECLARE @now DATETIME = GETUTCDATE();
DECLARE @daysSinceEpoch BIGINT = DATEDIFF(DAY, '1970-1-1', @now);
DECLARE @msLeftOver INT = DATEDIFF(MILLISECOND, DATEADD(DAY, @daysSinceEpoch, '1970-1-1'), @now);
SELECT CAST(
        CAST(NEWID() AS BINARY(10))
        + CAST(@daysSinceEpoch * 24 * 60 * 60 * 1000 + @msLeftOver AS BINARY(6))
    AS UNIQUEIDENTIFIER);

Or on MSSQL 2016/Azure:

SELECT CAST(
        CAST(NEWID() AS BINARY(10))
        + CAST(DATEDIFF_BIG(MILLISECOND, '1970-1-1', GETUTCDATE())
    AS BINARY(6)) AS UNIQUEIDENTIFIER);

These use GETUTCDATE() which returns a datetime with 1/300s precision.

Instead use SYSUTCDATETIME() which returns a datetime2(7) which for this purpose will give you 1/1000s precision as per what you would get with the UnixDateTimeStrategy in C#.

datetime2 and SYSUTCDATETIME() were introduced in SQL Server 2008 so is usable in both above examples.

Suggested change:

Creating a COMB uniqueidentifier in T-SQL with the current date and time:

DECLARE @now DATETIME2(7) = SYSUTCDATETIME();
DECLARE @daysSinceEpoch BIGINT = DATEDIFF(DAY, '1970-1-1', @now);
DECLARE @msLeftOver INT = DATEDIFF(MILLISECOND, DATEADD(DAY, @daysSinceEpoch, '1970-1-1'), @now);
SELECT CAST(
        CAST(NEWID() AS BINARY(10))
        + CAST(@daysSinceEpoch * 24 * 60 * 60 * 1000 + @msLeftOver AS BINARY(6))
    AS UNIQUEIDENTIFIER);

Or on MSSQL 2016/Azure:

SELECT CAST(
        CAST(NEWID() AS BINARY(10))
        + CAST(DATEDIFF_BIG(MILLISECOND, '1970-1-1', SYSUTCDATETIME())
    AS BINARY(6)) AS UNIQUEIDENTIFIER);

UDF to generate a COMB

The below is separate but I'd also like to contribute a user defined function that I use to make queries simpler to read, at the performance cost of the overhead of calling a function. Don't have to add it to the readme.

/****** Object:  UserDefinedFunction [dbo].[NewComb] ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO

-- =============================================
-- Description:    Return a COMB GUID (sortable uniqueidentifier). See https://github.com/richardtallent/RT.Comb
-- =============================================
CREATE FUNCTION [dbo].[NewComb]()
RETURNS uniqueidentifier
AS
BEGIN
    -- Generate a COMB as per https://github.com/richardtallent/RT.Comb documentation
    RETURN CAST(
        CAST((select new_id from vwGetNewID) AS BINARY(10))
        + CAST(DATEDIFF_BIG(MILLISECOND, '1970-1-1', SYSUTCDATETIME())
    AS BINARY(6)) AS UNIQUEIDENTIFIER);
END
GO

/****** Object:  UserDefinedFunction [dbo].[NewCombFromDateTime] ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO

-- =============================================
-- Description:    Return a COMB GUID (sortable uniqueidentifier). See https://github.com/richardtallent/RT.Comb
-- =============================================
CREATE FUNCTION [dbo].[NewCombFromDateTime]
(
    @datetime datetime2(3)
)
RETURNS uniqueidentifier
AS
BEGIN
    -- Generate a COMB as per https://github.com/richardtallent/RT.Comb documentation
    RETURN CAST(
        CAST((select new_id from vwGetNewID) AS BINARY(10))
        + CAST(DATEDIFF_BIG(MILLISECOND, '1970-1-1', @datetime)
    AS BINARY(6)) AS UNIQUEIDENTIFIER);
END
GO

/****** Object:  View [dbo].[vwGetNewID] ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO

-- =============================================
-- Description:    View which returns newid()
-- =============================================
CREATE VIEW [dbo].[vwGetNewID] AS SELECT newid() AS new_id
GO

The above script creates two user defined functions (UDFs), NewComb which uses the current time, and NewCombFromDateTime which takes the date/time as a parameter. These return a COMB.

Usable like so:

-- Generate a COMB with the current utc date/time
select dbo.NewComb()

-- or:

-- Generate a COMB with a given utc date/time
declare @now datetime2(3) = sysucdatetime()

select dbo.NewCombFromDateTime(@now)

Note that the script above also creates a view named vwGetNewID which returns newid(). This is a hack to allow the use of newid() in a UDF which isn't allowed normally. Alternatively, you could eliminate the view but would need to add a uniqueidentifier parameter in which you pass newid() as a parameter to the function so that it has a guid to work with.

deploy into SQL Server

It would be beneficial if it can be easily deployed in SQL Server.
Do you see any disadvantages/cons not to do that?

M.E.DependencyInjection v7.0 potentially breaks .NET 6.0 apps/sites

Proposal

Please change the dependency on M.E.DependencyInjection v7.0 to v6.0 for RT.Comb.AspNetCore v4.0.

Reasoning

All the .NET 6.0 stuff (e.g. ASP.NET Core 6.0) relies on M.E.DependencyInjection v6.0 and using NuGet packages shipped alongside with .NET 7.0 seems like a potentially dangerous move, because nobody can guarantee that the interface surface didn't change between v6.0 and v7.0.

check whether a Guid is an RT.Comb one

Hi Richard,
I have been using for the RT.Comb GUIDs very successfully both in OLTP, and OLAP projects
Update: even with tables having more hundreds million rows! Both row, and clustered columnstore indexed tables in SQL Server!
So I think it should be a MUST to use this RT.Comb GUID if you want to use it for PK. Which has big advantages!)

All the time these GUIDs were generated by us, I mean, at our side: so we were sure that these are ordered.

But now I will be dependent on an outer application.
The question is whether I can check/test whether this GUID is RT.Comb one?
(If it will not be the case, I will substitute this with an RT.Comb one, so at the database side everything will be ordered.)

Thanks in advance,
Pál

Unit Test runner not working under OS X

After upgrading to .NET Core 1.0.1 and migrating the project.json files to .csproj, I'm no longer able to successfully use dotnet test to run unit tests.

It says Starting test execution, please wait..., then appears to hang for a few minutes, then responds with:

Testhost process exited with error: A fatal error was encountered.
The library 'libhostpolicy.dylib' required to execute the application
was not found in '[foo]/test/RT.Comb.Tests/bin/Debug/netstandard1.5'.

where [foo] is the root solution directory.

I would appreciate if anyone could confirm the same issue on OS X or other platforms, or please send a PR if you have a fix!

If it matters, I still want the library to target .NET Standard 1.2, since I want to remain compatible with .NET 4.5.1 and apparently we can't multi-target that separately on OS X anymore.

MsSQL bad order when creating Guids in for loop

This code:

ICombProvider SqlCombs = new SqlCombProvider(new SqlDateTimeStrategy());
for (int i = 0; i < 10; i++)
{
    string userEmail = $"user{i}@c.com";
    if (!context.Users.Any(a => a.Email == userEmail))
    {
        User user = new User
        {
            UserId = SqlCombs.Create(), // I tried also Create(DateTime.Now) but it didn't help
            Email = userEmail,
            TimeCreated = DateTime.Now 
        };
        context.Users.Add(user);
    } 
}

gives me following rows in Db:

UserId_________________________________________Email______________DateCreated
[email protected]____2017-03-28 15:19:01.4968188
[email protected]____2017-03-28 15:19:01.4991283
[email protected]____2017-03-28 15:19:01.5031330
[email protected]____2017-03-28 15:19:01.5011320
[email protected]____2017-03-28 15:19:01.5051359
...

user4 comes before user3.
I think that you need to have _lastSequence like it is used on the blog (line 87):
http://www.siepman.nl/blog/post/2013/10/28/ID-Sequential-Guid-COMB-Vs-Int-Identity-using-Entity-Framework.aspx

Create AspNetCore package

Hey thanks for this library! I have been digging into this Comb Guid stuff lately and really liked the way this is implemented :)

Would be nice though to have a package in order to make it easier to add a comb provider to DI. Similar as the packages in ASP.NET Core (pay-to-play), we could have something like RT.Comb.AspNetCore.

Since you are the owner, not sure if it makes sense to have someone else do the extension. I also saw it needs to be bumped to .NET Standard 2.0. If you need help with that I can jump in :).

Full comb from timestamp

Hi, newbie question here.

I'm wondering if it's possible to convert a timestamp to the exact original comb guid.

SqlCombProvider provider = new(new UnixDateTimeStrategy(), new UtcNoRepeatTimestampProvider().GetTimestamp);

Guid comb = provider.Create();
DateTime timestamp = provider.GetTimestamp(comb);
Guid comb1 = provider.Create(timestamp);
Guid comb2 = provider.Create(comb, timestamp);

Console.WriteLine(comb.Equals(comb1)); // false
Console.WriteLine(comb.Equals(comb2)); // true, but possible only from timestamp?

If I understand correctly, documentation explain that first bytes of guid is random. There is a way to obtain the original full comb id only from timestamp?

Thank you so much.

Add zero configuration support

ICombProvider myCombProvider = new SqlCombProvider(new SqlDateTimeStrategy());

This is too much work and totally leaks all kinds of implementation details.

You really should have a class that behaves something like

public class Comb

public static ICombProvider Sql { get; } = new SqlCombProvider(new SqlDateTimeStrategy());
public static ICombProvider Unix  { get; } = new whatever;

etc.

There's probably almost never a reason a person needs to actually touch your construction. Just give people the defaults in a statically available manner.

Querying records via the COMB timestamp

Hi there. I've been using your library for years, so a very big thank you.

I was reading a comment you made here, regarding querying via the timestamp contained in the COMB:

#10 (comment)

Even though I used COMBs on pretty much all tables these days, I also have a date/time of insertion field, because the COMB's timestamp value is unwieldy to query directly.

Which made me question a couple of things:

  1. I understand that there is a chance of collision with regards to the timestamp portion of a COMB e.g. if many are generated and inserted at around the same time; is this the only factor that make querying via the timestamp "unwieldy" or are there other factors?
  2. Can you recommend a optimum/performant way of querying DB records via a COMB timestamp, or range?
    e.g. all records between timestamp X and Y, from a COMB?
  3. If you recommend using a separate date/time insertion field, as well as a COMB, is there any real value to being able to extract the timestamp from the generated COMB? Other than for testing purposes.
    In my mind, half the point of using a COMB was so that I could avoid having to store a separate date time value in the table (rightly or wrongly).

Thank you!

Migrating from SQL Server to PostgreSQL with COMB guids

Hi,
Thanks for the awesome library. I don't necessarily have any issue with this library, but I was hoping your expertise on the matter would be able to help me.

I've been generating the SQL Server legacy COMB GUIDs for primary key values in a product for quite some time now (millions of records). We are currently evaluating the possibility of migrating the product from SQL Server to PostgreSQL and with this comes migrating all of the existing data.

My concern is that the existing comb GUIDs that were generated for SQL Server are of a different sort algorithm than what would have been for PostgreSQL. My guess is that generating new values under the PostgreSQL algorithm is not going to create the desired incremental GUID values based on the already existing IDs that would be migrated. New values would likely be somewhere in the middle of the index and will lead to fragmented tables.

Does that make sense?

Could you recommend any way of dealing with a situation like this? Perhaps there is some way I can seed the new PostgreSQL comb generator to always generate incremental GUIDs after the "last" (in terms of PostgreSQL sorting) one that was generated from the previous SQL Server legacy algorithm?

Thanks!

Installing NuGet package pulls in lots of unwanted "dependencies"

My project is targeting .NET Framework 4.6.1 and when I go to install RT.Comb 2.3.0 I get the following prompt to install a load of dependencies for code that's already part of the .NET standard library:

image

I think this issue might be fixable by either bumping the version of .NET Standard you target to 2.0 (I'm using Dapper which targets 2.0 and don't have this issue) or adding another entry to <TargetFrameworks> as discussed here.

Binary order for PostgreSql and Sqlite what Provider or Strategy to use.

For SqlServer Sequential Guid creation I have:

public static class SeqGuid
{
    private static ICombProvider SqlNoRepeatCombs = new SqlCombProvider(new UnixDateTimeStrategy(), new UtcNoRepeatTimestampProvider().GetTimestamp);

    public static Guid Create()
    {
        return SqlNoRepeatCombs.Create();
    }
}

How to configure the same for binary order ?

.NET Framework v4.6 is too restrictive

This library is great especially since it targets .NET Standard and .NET Framework but your latest version specifically targets .NET Framework v4.6 which most people have not adopted yet. Would it be possible to lower the runtime version down to v4.5.1? It should be okay since you are not using anything specific from the newer runtime.

PostgreSqlCombProvider not sequential in Postgresql

When using RT.Comb.Provider.PostgreSql , the generated Guids are not in sorted order in Postgresql 13.3.

            using var context = new TDbContext();
            for (int i = 0; i < 20; i++)
            {
                var entity = new CombGuidEntity
                {
                    Id = Provider.PostgreSql.Create(),
                    Sequence = i + 1
                };
                context.Add(entity);
            }
            context.SaveChanges();
id(uuid) sequence(int4)
017c5af4-2617-4ae0-98f0-98086eef50ca 1
017c5af4-2b83-49b1-b643-24e6f3070551 18
017c5af4-2b81-49fb-88f8-25640c96d998 17
017c5af4-2b7f-42f0-919d-99f6098c42b3 16
017c5af4-2b7d-4606-b2d7-d9b2f45efce7 15
017c5af4-2b7b-4ad0-8357-93fc27d8b942 14
017c5af4-2b79-4fde-87a6-88a8e50eab56 13
017c5af4-2b77-4cd0-8832-d707c1b564ae 12
017c5af4-2b75-455f-8652-308d00a4fd7f 11
017c5af4-2b73-4542-aa93-c9bbeacc22dc 10
017c5af4-2b71-44a5-8583-486a37343a78 9
017c5af4-2b6f-4f27-869a-331cff9848d0 8
017c5af4-2b6d-400a-9b38-48fe422ac145 7
017c5af4-2b6b-4b1a-8d64-67bb34f24a04 6
017c5af4-2b69-4cb6-af43-738d0539f9ff 5
017c5af4-2b67-4a99-9111-2ba09802c266 4
017c5af4-2b65-44d4-9c4c-1a62d65f3ccf 3
017c5af4-2b63-42f9-995e-d7850262de6c 2
017c5af4-2b85-4b71-8803-33a133404423 19
017c5af4-2b87-4780-b18d-00772d031e97 20

RT.Comb.Provider.Postgre.Create().ToString("N") JavaScript version.

I use RT.Comb on the back-end, however I also need it on the browser, so I wrote the following JavaScript function:

function newSequentialGuid() {
  var ms = new Date().getTime().toString(16).padStart(12, "0");
  var a = new Uint8Array(10);
  crypto.getRandomValues(a);
  a[0] = a[0] & 15 | 64;
  a[2] = a[2] & 63 | 128;
  return ms + Array.prototype.map.call(a, x => x.toString(16).padStart(2, "0")).join("");
}

Please, could you confirm it is equivalent to RT.Comb.Provider.Postgre.Create().ToString("N") ?

Generate Guid for a specific DateTime (i.e. not for Now)

Hi, I used RT.Comb succesfully many years. (In SQL Server these Guids can even be used as Primary Keys)

For efficient querying purposes can I generate a Guid for a Specific DateTime.
(Note I will use it for only in SQL Where '>', '<' clause.

Thanks...

MySql Support

Hello,

Based on the documentation under security and performance, can i use it with MySQL but store the UUID as BINARY(16)?
if not, when you planning to support MySql?

Many Thanks
RA

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.