Skip to content

Commit 2dede50

Browse files
author
Sridhar Nanjundeswaran
committed
CSHARP-714 : AscendingGuidGenerator and tests
1 parent 92b8bde commit 2dede50

File tree

6 files changed

+322
-0
lines changed

6 files changed

+322
-0
lines changed

MongoDB.Bson/MongoDB.Bson.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,7 @@
177177
<Compile Include="Serialization\CreatorMapDelegateCompiler.cs" />
178178
<Compile Include="Serialization\ExpressionVisitor.cs" />
179179
<Compile Include="Serialization\ICreatorSelector.cs" />
180+
<Compile Include="Serialization\IdGenerators\AscendingGuidGenerator.cs" />
180181
<Compile Include="Serialization\IdGenerators\BsonBinaryDataGuidGenerator.cs" />
181182
<Compile Include="Serialization\IdGenerators\BsonObjectIdGenerator.cs" />
182183
<Compile Include="Serialization\IdGenerators\CombGuidGenerator.cs" />
Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
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.Diagnostics;
18+
using System.Runtime.CompilerServices;
19+
using System.Security;
20+
using System.Security.Cryptography;
21+
using System.Text;
22+
using System.Threading;
23+
24+
namespace MongoDB.Bson.Serialization.IdGenerators
25+
{
26+
/// <summary>
27+
/// A GUID generator that generates GUIDs in ascending order. To enable
28+
/// an index to make use of the ascending nature make sure to use
29+
/// <see cref="GuidRepresentation.Standard">GuidRepresentation.Standard</see>
30+
/// as the storage representation.
31+
/// Internally the GUID is of the form
32+
/// 8 bytes: Ticks from DateTime.UtcNow.Ticks
33+
/// 3 bytes: hash of machine name
34+
/// 2 bytes: low order bytes of process Id
35+
/// 3 bytes: increment
36+
/// </summary>
37+
public class AscendingGuidGenerator : IIdGenerator
38+
{
39+
// private static fields
40+
private static readonly AscendingGuidGenerator __instance = new AscendingGuidGenerator();
41+
private static readonly byte[] __machineProcessId;
42+
private static int __increment;
43+
44+
// static constructor
45+
static AscendingGuidGenerator()
46+
{
47+
var machineHash = GetMachineHash();
48+
short processId;
49+
try
50+
{
51+
// use low order two bytes only
52+
processId = (short)GetCurrentProcessId();
53+
}
54+
catch (SecurityException)
55+
{
56+
processId = 0;
57+
}
58+
59+
__machineProcessId = new byte[5]
60+
{
61+
machineHash[0],
62+
machineHash[1],
63+
machineHash[2],
64+
(byte)(processId >> 8),
65+
(byte)(processId)
66+
};
67+
}
68+
69+
// public static properties
70+
71+
/// <summary>
72+
/// Gets an instance of AscendingGuidGenerator.
73+
/// </summary>
74+
public static AscendingGuidGenerator Instance
75+
{
76+
get { return __instance; }
77+
}
78+
79+
// public methods
80+
81+
/// <summary>
82+
/// Generates an ascending Guid for a document. Consecutive invocations
83+
/// should generate Guids that are ascending from a MongoDB perspective
84+
/// </summary>
85+
/// <param name="container">The container of the document (will be a
86+
/// MongoCollection when called from the driver). </param>
87+
/// <param name="document">The document it was generated for.</param>
88+
/// <returns>A Guid.</returns>
89+
public object GenerateId(object container, object document)
90+
{
91+
var increment = Interlocked.Increment(ref __increment) & 0x00ffffff;
92+
return GenerateId(DateTime.UtcNow.Ticks, __machineProcessId, increment);
93+
}
94+
95+
/// <summary>
96+
/// Generates a Guid for a document. Note - this is purely used for
97+
/// unit testing
98+
/// </summary>
99+
/// <param name="tickCount">The time portion of the Guid</param>
100+
/// <param name="machineProcessId">A 5 byte array with the first 3 bytes
101+
/// representing a machine id and the next 2 representing a process
102+
/// id</param>
103+
/// <param name="increment">The increment portion of the Guid. Used
104+
/// to distinguish between 2 Guids that have the timestamp. Note
105+
/// only the least significant 3 bytes are used.</param>
106+
/// <returns>A Guid.</returns>
107+
public object GenerateId(
108+
long tickCount,
109+
byte[] machineProcessId,
110+
int increment)
111+
{
112+
var a = (int)(tickCount >> 32);
113+
var b = (short)(tickCount >> 16);
114+
var c = (short)(tickCount);
115+
var d = new byte[8];
116+
Array.Copy(machineProcessId, d, 5);
117+
d[5] = (byte)(increment >> 16);
118+
d[6] = (byte)(increment >> 8);
119+
d[7] = (byte)(increment);
120+
return new Guid (a, b, c, d);
121+
}
122+
123+
/// <summary>
124+
/// Tests whether an id is empty.
125+
/// </summary>
126+
/// <param name="id">The id to test.</param>
127+
/// <returns>True if the Id is empty. False otherwise</returns>
128+
public bool IsEmpty(object id)
129+
{
130+
return id == null || (Guid)id == Guid.Empty;
131+
}
132+
133+
// private static methods
134+
/// <summary>
135+
/// Gets the current process id. This method exists because of how
136+
/// CAS operates on the call stack, checking for permissions before
137+
/// executing the method. Hence, if we inlined this call, the calling
138+
/// method would not execute before throwing an exception requiring the
139+
/// try/catch at an even higher level that we don't necessarily control.
140+
/// </summary>
141+
[MethodImpl(MethodImplOptions.NoInlining)]
142+
private static int GetCurrentProcessId()
143+
{
144+
return Process.GetCurrentProcess().Id;
145+
}
146+
147+
private static byte[] GetMachineHash()
148+
{
149+
// use instead of Dns.HostName so it will work offline
150+
var hostName = Environment.MachineName;
151+
var sha1 = SHA1.Create();
152+
return sha1.ComputeHash(Encoding.UTF8.GetBytes(hostName));
153+
}
154+
}
155+
}

MongoDB.BsonUnitTests/MongoDB.BsonUnitTests.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@
117117
<Compile Include="Serialization\Conventions\PropertyFinderConventionsTests.cs" />
118118
<Compile Include="Serialization\Conventions\ReadWriteMemberFinderConventionsTests.cs" />
119119
<Compile Include="Serialization\Conventions\StringObjectIdGeneratorConventionsTests.cs" />
120+
<Compile Include="Serialization\IdGenerators\AscendingGuidGeneratorTests.cs" />
120121
<Compile Include="Serialization\LegacyBsonClassMapTests.cs" />
121122
<Compile Include="Serialization\Serializers\ExtraElementsTests.cs" />
122123
<Compile Include="Serialization\Options\RepresentationSerializationOptionsTests.cs" />
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
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.IdGenerators;
19+
using NUnit.Framework;
20+
21+
namespace MongoDB.BsonUnitTests.Serialization
22+
{
23+
[TestFixture]
24+
public class AscendingGuidGeneratorTests
25+
{
26+
private AscendingGuidGenerator _generator = new AscendingGuidGenerator();
27+
28+
[Test]
29+
public void TestIsEmpty()
30+
{
31+
Assert.IsTrue(_generator.IsEmpty(null));
32+
Assert.IsTrue(_generator.IsEmpty(Guid.Empty));
33+
var guid = _generator.GenerateId(null, null);
34+
Assert.IsFalse(_generator.IsEmpty(guid));
35+
}
36+
37+
[Test]
38+
public void TestGuid()
39+
{
40+
var expectedTicks = DateTime.Now.Ticks;
41+
var expectedIncrement = 1000;
42+
var expectedMachineProcessId = new byte[] { 1, 32, 64, 128, 255 };
43+
var guid = (Guid)_generator.GenerateId(expectedTicks, expectedMachineProcessId, expectedIncrement);
44+
var bytes = guid.ToByteArray();
45+
var actualTicks = GetTicks(bytes);
46+
var actualMachineProcessId = GetMachineProcessId(bytes);
47+
var actualIncrement = GetIncrement(bytes);
48+
Assert.AreEqual(expectedTicks, actualTicks);
49+
Assert.IsTrue(expectedMachineProcessId.SequenceEqual(actualMachineProcessId));
50+
Assert.AreEqual(expectedIncrement, actualIncrement);
51+
}
52+
53+
[Test]
54+
public void TestGuidWithSpecifiedTicks()
55+
{
56+
var expectedTicks = 0x8000L;
57+
var expectedIncrement = 32;
58+
var expectedMachineProcessId = new byte[] { 1, 32, 64, 128, 255 };
59+
var guid = (Guid)_generator.GenerateId(expectedTicks, expectedMachineProcessId, expectedIncrement);
60+
var bytes = guid.ToByteArray();
61+
var actualTicks = GetTicks(bytes);
62+
var actualMachineProcessId = GetMachineProcessId(bytes);
63+
var actualIncrement = GetIncrement(bytes);
64+
Assert.AreEqual(expectedTicks, actualTicks);
65+
Assert.IsTrue(expectedMachineProcessId.SequenceEqual(actualMachineProcessId));
66+
Assert.AreEqual(expectedIncrement, actualIncrement);
67+
}
68+
69+
private long GetTicks(byte[] bytes)
70+
{
71+
var a = (ulong)BitConverter.ToUInt32(bytes, 0);
72+
var b = (ulong)BitConverter.ToUInt16(bytes, 4);
73+
var c = (ulong)BitConverter.ToUInt16(bytes, 6);
74+
return (long)((a << 32) | (b << 16) | c);
75+
}
76+
77+
private byte[] GetMachineProcessId(byte[] bytes)
78+
{
79+
var result = new byte[5];
80+
Array.Copy(bytes, 8, result, 0, 5);
81+
return result;
82+
}
83+
84+
private int GetIncrement(byte[] bytes)
85+
{
86+
var increment = (bytes[13] << 16) + (bytes[14] << 8) + bytes[15];
87+
return increment;
88+
}
89+
}
90+
}
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
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 MongoDB.Bson;
18+
using MongoDB.Bson.Serialization;
19+
using MongoDB.Bson.Serialization.IdGenerators;
20+
using MongoDB.Driver;
21+
using MongoDB.Driver.Builders;
22+
using NUnit.Framework;
23+
24+
namespace MongoDB.DriverUnitTests.Jira.CSharp714
25+
{
26+
[TestFixture]
27+
public class CSharp714Tests
28+
{
29+
public class C
30+
{
31+
public int Id { get; set; }
32+
public Guid Guid { get; set; }
33+
}
34+
35+
private MongoServer _server;
36+
private MongoDatabase _database;
37+
private MongoCollection<C> _collection;
38+
private IIdGenerator _generator = new AscendingGuidGenerator();
39+
private static int __maxNoOfDocuments = 100;
40+
41+
[TestFixtureSetUp]
42+
public void TestFixtureSetup()
43+
{
44+
_server = Configuration.TestServer;
45+
_database = Configuration.TestDatabase;
46+
var collectionSettings = new MongoCollectionSettings() { GuidRepresentation = GuidRepresentation.Standard };
47+
_collection = _database.GetCollection<C>("csharp714", collectionSettings);
48+
_collection.Drop();
49+
}
50+
51+
[Test]
52+
public void TestGuidsAreAscending()
53+
{
54+
_collection.RemoveAll();
55+
CreateTestData();
56+
// sort descending just so we are not retrieving in insertion order
57+
var cursor = _collection.FindAll().SetSortOrder(SortBy.Descending("Guid"));
58+
var id = __maxNoOfDocuments - 1;
59+
foreach (var c in cursor)
60+
{
61+
Assert.AreEqual(id--, c.Id);
62+
}
63+
}
64+
65+
private void CreateTestData()
66+
{
67+
for (var i=0; i<__maxNoOfDocuments; i++)
68+
{
69+
_collection.Insert(new C { Id = i, Guid = (Guid)_generator.GenerateId(null, null) });
70+
}
71+
_collection.CreateIndex("Guid");
72+
}
73+
}
74+
}

MongoDB.DriverUnitTests/MongoDB.DriverUnitTests.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,7 @@
119119
<Compile Include="GeoJsonObjectModel\GeoJsonPolygonTests.cs" />
120120
<Compile Include="Jira\CSharp542Tests.cs" />
121121
<Compile Include="Jira\CSharp653Tests.cs" />
122+
<Compile Include="Jira\CSharp714Tests.cs" />
122123
<Compile Include="MongoClientSettingsTests.cs" />
123124
<Compile Include="ReadPreferenceTests.cs" />
124125
<Compile Include="MongoCollectionSettingsTests.cs" />

0 commit comments

Comments
 (0)