Skip to content

Commit 05b35a0

Browse files
author
rstam
committed
CSHARP-700: Improvements to CombGuidGenerator and its unit tests.
1 parent 2dede50 commit 05b35a0

File tree

3 files changed

+95
-16
lines changed

3 files changed

+95
-16
lines changed

MongoDB.Bson/Serialization/IdGenerators/CombGuidGenerator.cs

Lines changed: 33 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -51,34 +51,51 @@ public static CombGuidGenerator Instance
5151
/// <returns>An Id.</returns>
5252
public object GenerateId(object container, object document)
5353
{
54+
var guid = Guid.NewGuid();
55+
var timestamp = DateTime.UtcNow;
56+
return NewCombGuid(guid, timestamp);
57+
}
58+
59+
/// <summary>
60+
/// Tests whether an Id is empty.
61+
/// </summary>
62+
/// <param name="id">The Id.</param>
63+
/// <returns>True if the Id is empty.</returns>
64+
public bool IsEmpty(object id)
65+
{
66+
return id == null || (Guid)id == Guid.Empty;
67+
}
68+
69+
/// <summary>
70+
/// Create a new CombGuid from a given Guid and timestamp.
71+
/// </summary>
72+
/// <param name="guid">The base Guid.</param>
73+
/// <param name="timestamp">The timestamp.</param>
74+
/// <returns>A new CombGuid created by combining the base Guid with the timestamp.</returns>
75+
public Guid NewCombGuid(Guid guid, DateTime timestamp)
76+
{
77+
// note: Guids generated by CombGuidGenerator are only considered ascending by SQL Server which compares Guids in an unusual way
78+
// to generate Guids considered ascending by MongoDB use the AscendingGuidGenerator
79+
5480
var baseDate = new DateTime(1900, 1, 1, 0, 0, 0, DateTimeKind.Utc);
55-
var now = DateTime.UtcNow;
56-
var days = (ushort)(now - baseDate).TotalDays;
57-
var milliseconds = (int)now.TimeOfDay.TotalMilliseconds;
81+
var days = (ushort)(timestamp - baseDate).Days;
82+
var timeTicks = (int)(timestamp.TimeOfDay.Ticks * 300 / TimeSpan.TicksPerSecond); // convert from .NET resolution to SQL Server resolution
5883

59-
// replace last 6 bytes of a new Guid with 2 bytes from days and 4 bytes from milliseconds
84+
// replace last 6 bytes of a new Guid with 2 bytes from days and 4 bytes from time of day
6085
// see: The Cost of GUIDs as Primary Keys by Jimmy Nilson
6186
// at: http://www.informit.com/articles/article.aspx?p=25862&seqNum=7
6287

63-
var bytes = Guid.NewGuid().ToByteArray();
88+
var bytes = guid.ToByteArray();
89+
6490
Array.Copy(BitConverter.GetBytes(days), 0, bytes, 10, 2);
65-
Array.Copy(BitConverter.GetBytes(milliseconds), 0, bytes, 12, 4);
91+
Array.Copy(BitConverter.GetBytes(timeTicks), 0, bytes, 12, 4);
6692
if (BitConverter.IsLittleEndian)
6793
{
6894
Array.Reverse(bytes, 10, 2);
6995
Array.Reverse(bytes, 12, 4);
7096
}
71-
return new Guid(bytes);
72-
}
7397

74-
/// <summary>
75-
/// Tests whether an Id is empty.
76-
/// </summary>
77-
/// <param name="id">The Id.</param>
78-
/// <returns>True if the Id is empty.</returns>
79-
public bool IsEmpty(object id)
80-
{
81-
return id == null || (Guid)id == Guid.Empty;
98+
return new Guid(bytes);
8299
}
83100
}
84101
}

MongoDB.BsonUnitTests/MongoDB.BsonUnitTests.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@
118118
<Compile Include="Serialization\Conventions\ReadWriteMemberFinderConventionsTests.cs" />
119119
<Compile Include="Serialization\Conventions\StringObjectIdGeneratorConventionsTests.cs" />
120120
<Compile Include="Serialization\IdGenerators\AscendingGuidGeneratorTests.cs" />
121+
<Compile Include="Serialization\IdGenerators\CombGuidGeneratorTests.cs" />
121122
<Compile Include="Serialization\LegacyBsonClassMapTests.cs" />
122123
<Compile Include="Serialization\Serializers\ExtraElementsTests.cs" />
123124
<Compile Include="Serialization\Options\RepresentationSerializationOptionsTests.cs" />
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
/* Copyright 2010-2013 10gen Inc.
2+
*
3+
* Licensed under the Apache License, Version 2.0 (the "License");
4+
* you may not use this file except in compliance with the License.
5+
* You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software
10+
* distributed under the License is distributed on an "AS IS" BASIS,
11+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
* See the License for the specific language governing permissions and
13+
* limitations under the License.
14+
*/
15+
16+
using System;
17+
using System.Linq;
18+
using MongoDB.Bson.Serialization;
19+
using MongoDB.Bson.Serialization.IdGenerators;
20+
using NUnit.Framework;
21+
22+
namespace MongoDB.BsonUnitTests.Serialization
23+
{
24+
[TestFixture]
25+
public class CombGuidGeneratorTests
26+
{
27+
private CombGuidGenerator _generator = new CombGuidGenerator();
28+
29+
[Test]
30+
public void TestNewCombGuid()
31+
{
32+
var guid = Guid.NewGuid();
33+
var timestamp = new DateTime(2013, 4, 2, 0, 0, 0, 500, DateTimeKind.Utc); // half a second past midnight
34+
var combGuid = _generator.NewCombGuid(guid, timestamp);
35+
36+
var expectedDays = (short)(timestamp.Date - new DateTime(1900, 1, 1, 0, 0, 0, DateTimeKind.Utc)).Days;
37+
var expectedTimeTicks = 150; // half a second in SQL Server resolution
38+
39+
var bytes = combGuid.ToByteArray();
40+
if (BitConverter.IsLittleEndian)
41+
{
42+
Array.Reverse(bytes, 10, 2);
43+
Array.Reverse(bytes, 12, 4);
44+
}
45+
var days = BitConverter.ToInt16(bytes, 10);
46+
var timeTicks = BitConverter.ToInt32(bytes, 12);
47+
48+
Assert.IsTrue(guid.ToByteArray().Take(10).SequenceEqual(bytes.Take(10))); // first 10 bytes are from the base Guid
49+
Assert.AreEqual(expectedDays, days);
50+
Assert.AreEqual(expectedTimeTicks, timeTicks);
51+
}
52+
53+
[Test]
54+
public void TestIsEmpty()
55+
{
56+
Assert.IsTrue(_generator.IsEmpty(null));
57+
Assert.IsTrue(_generator.IsEmpty(Guid.Empty));
58+
Assert.IsFalse(_generator.IsEmpty(Guid.NewGuid()));
59+
}
60+
}
61+
}

0 commit comments

Comments
 (0)