Skip to content

Commit

Permalink
Merge pull request #46 from dbuenor/dbueno/add-postgresql-comb-guid-g…
Browse files Browse the repository at this point in the history
…enerator

Added new method for AdvancedGuidGenerator to generate CombGuid for PostgreSQL
  • Loading branch information
pruiz committed Sep 17, 2020
2 parents e4d2533 + 76d63ca commit 396a828
Show file tree
Hide file tree
Showing 2 changed files with 82 additions and 3 deletions.
7 changes: 7 additions & 0 deletions HermaFx.Foundation.Tests/AdvancedGuidGeneratorTests.cs
Expand Up @@ -15,5 +15,12 @@ public void BasicCombGuidGeneration()
var id = AdvancedGuidGenerator.GenerateComb();
Guid.Parse(id.ToString());
}

[Test]
public void BasicPostgreSQLCombGuidGeneration()
{
var id = AdvancedGuidGenerator.GenerateCombEx();
Guid.Parse(id.ToString());
}
}
}
78 changes: 75 additions & 3 deletions HermaFx.Foundation/Utils/AdvancedGuidGenerator.cs
Expand Up @@ -6,11 +6,14 @@ public static class AdvancedGuidGenerator
{
private static readonly DateTime UnixStartTime = new DateTime(1900, 1, 1, 0, 0, 0, DateTimeKind.Utc);

// XXX: Values added for GenerateCombEx
// From private DateTime.TicksPerMillisecond
private const long TicksPerMillisecond = 10000;
private static DateTime MinDateTimeValue { get; } = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);
private static int NumDateBytes = 6;

// note: Guids generated by CombGuidGenerator are only considered ascending by SQL Server which compares Guids in an unusual way
// to generate Guids considered ascending by MongoDB use the AscendingGuidGenerator



public static Guid GenerateComb()
{
return GenerateComb(Guid.NewGuid(), DateTime.UtcNow);
Expand Down Expand Up @@ -47,5 +50,74 @@ public static Guid GenerateComb(Guid id, DateTime timestamp)
#endif
return new Guid(result);
}


#region GenerateCombForPostgreSQL
/*
* FROM: https://github.com/richardtallent/RT.Comb/blob/main/src/RT.Comb/CombProvider/PostgreSqlCombProvider.cs
*
* Copyright 2015-2020 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.
*/

// IDateTimeStrategy is required to provide the bytes we need in network byte order, and that's what we want
// in PostgreSQL as well. However, internally, GUID bytes for Data1 and Data2 are stored in little endian
// order, so we need to reverse one or both of those so the bytes we want are re-reversed to the correct
// order by Npgsql's GUID data type handler.
private static void SwapByteOrderForStringOrder(byte[] input)
{
Array.Reverse(input, 0, 4); // Swap around the first 4 bytes
if (input.Length == 4) return;
Array.Reverse(input, 4, 2); // Swap around the next 2 bytes
}

// We purposefully are not using the FromUnixTimeMilliseconds and ToUnixTimeMilliseconds to remain compatible with .NET 4.5.1
//(long)(timestamp.ToUniversalTime() - MinDateTimeValue).TotalMilliseconds;
//public DateTime FromUnixTimeMilliseconds(long ms) => MinDateTimeValue.AddMilliseconds(ms);
private static long ToUnixTimeMilliseconds(DateTime timestamp) => (timestamp.Ticks - MinDateTimeValue.Ticks) / TicksPerMillisecond;

private static byte[] DateTimeToBytes(DateTime timestamp)
{
var ms = ToUnixTimeMilliseconds(timestamp);
var msBytes = BitConverter.GetBytes(ms);
if (BitConverter.IsLittleEndian) Array.Reverse(msBytes);
var result = new byte[NumDateBytes];
var index = msBytes.GetUpperBound(0) + 1 - NumDateBytes;
Array.Copy(msBytes, index, result, 0, NumDateBytes);
return result;
}

/// <summary>
/// Generate CombGuid specially recommended for PostgreSQL databases
/// </summary>
/// <param name="id"></param>
/// <param name="timestamp"></param>
/// <returns></returns>
public static Guid GenerateCombEx(Guid id, DateTime timestamp)
{
var gbytes = id.ToByteArray();
var dbytes = DateTimeToBytes(timestamp);
Array.Copy(dbytes, 0, gbytes, 0, NumDateBytes);
SwapByteOrderForStringOrder(gbytes);
return new Guid(gbytes);
}

/// <summary>
/// Generate CombGuid specially recommended for PostgreSQL databases
/// </summary>
/// <returns></returns>
public static Guid GenerateCombEx()
{
return GenerateCombEx(Guid.NewGuid(), DateTime.UtcNow);
}
#endregion
}
}

0 comments on commit 396a828

Please sign in to comment.