Skip to content

Commit 1e7f8ee

Browse files
author
rstam
committed
CSHARP-660: Add support for GeoSpatialSpherical and Hashed indexes.
1 parent 207a824 commit 1e7f8ee

File tree

3 files changed

+301
-10
lines changed

3 files changed

+301
-10
lines changed

MongoDB.Driver/Builders/IndexKeysBuilder.cs

Lines changed: 105 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,26 @@ public static IndexKeysBuilder GeoSpatialHaystack(string name, string additional
8181
{
8282
return new IndexKeysBuilder().GeoSpatialHaystack(name, additionalName);
8383
}
84+
85+
/// <summary>
86+
/// Sets the key name to create a spherical geospatial index on.
87+
/// </summary>
88+
/// <param name="name">The key name.</param>
89+
/// <returns>The builder (so method calls can be chained).</returns>
90+
public static IndexKeysBuilder GeoSpatialSpherical(string name)
91+
{
92+
return new IndexKeysBuilder().GeoSpatialSpherical(name);
93+
}
94+
95+
/// <summary>
96+
/// Sets the key name to create a hashed index on.
97+
/// </summary>
98+
/// <param name="name">The key name.</param>
99+
/// <returns>The builder (so method calls can be chained).</returns>
100+
public static IndexKeysBuilder Hashed(string name)
101+
{
102+
return new IndexKeysBuilder().Hashed(name);
103+
}
84104
}
85105

86106
/// <summary>
@@ -164,6 +184,28 @@ public IndexKeysBuilder GeoSpatialHaystack(string name, string additionalName)
164184
return this;
165185
}
166186

187+
/// <summary>
188+
/// Sets the key name to create a spherical geospatial index on.
189+
/// </summary>
190+
/// <param name="name">The key name.</param>
191+
/// <returns>The builder (so method calls can be chained).</returns>
192+
public IndexKeysBuilder GeoSpatialSpherical(string name)
193+
{
194+
_document.Add(name, "2dsphere");
195+
return this;
196+
}
197+
198+
/// <summary>
199+
/// Sets the key name to create a hashed index on.
200+
/// </summary>
201+
/// <param name="name">The key name.</param>
202+
/// <returns>The builder (so method calls can be chained).</returns>
203+
public IndexKeysBuilder Hashed(string name)
204+
{
205+
_document.Add(name, "hashed");
206+
return this;
207+
}
208+
167209
/// <summary>
168210
/// Returns the result of the builder as a BsonDocument.
169211
/// </summary>
@@ -257,6 +299,32 @@ public static IndexKeysBuilder<TDocument> GeoSpatialHaystack<TMember, TAdditiona
257299
{
258300
return new IndexKeysBuilder<TDocument>().GeoSpatialHaystack(memberExpression, additionalMemberExpression);
259301
}
302+
303+
/// <summary>
304+
/// Sets the key name to create a spherical geospatial index on.
305+
/// </summary>
306+
/// <typeparam name="TMember">The type of the member.</typeparam>
307+
/// <param name="memberExpression">The member expression.</param>
308+
/// <returns>
309+
/// The builder (so method calls can be chained).
310+
/// </returns>
311+
public static IndexKeysBuilder<TDocument> GeoSpatialSpherical<TMember>(Expression<Func<TDocument, TMember>> memberExpression)
312+
{
313+
return new IndexKeysBuilder<TDocument>().GeoSpatialSpherical(memberExpression);
314+
}
315+
316+
/// <summary>
317+
/// Sets the key name to create a hashed index on.
318+
/// </summary>
319+
/// <typeparam name="TMember">The type of the member.</typeparam>
320+
/// <param name="memberExpression">The member expression.</param>
321+
/// <returns>
322+
/// The builder (so method calls can be chained).
323+
/// </returns>
324+
public static IndexKeysBuilder<TDocument> Hashed<TMember>(Expression<Func<TDocument, TMember>> memberExpression)
325+
{
326+
return new IndexKeysBuilder<TDocument>().Hashed(memberExpression);
327+
}
260328
}
261329

262330
/// <summary>
@@ -317,8 +385,7 @@ public IndexKeysBuilder<TDocument> Descending(params Expression<Func<TDocument,
317385
/// </returns>
318386
public IndexKeysBuilder<TDocument> GeoSpatial<TMember>(Expression<Func<TDocument, TMember>> memberExpression)
319387
{
320-
var serializationInfo = _serializationInfoHelper.GetSerializationInfo(memberExpression);
321-
_indexKeysBuilder = _indexKeysBuilder.GeoSpatial(serializationInfo.ElementName);
388+
_indexKeysBuilder = _indexKeysBuilder.GeoSpatial(GetElementName(memberExpression));
322389
return this;
323390
}
324391

@@ -332,8 +399,7 @@ public IndexKeysBuilder<TDocument> GeoSpatial<TMember>(Expression<Func<TDocument
332399
/// </returns>
333400
public IndexKeysBuilder<TDocument> GeoSpatialHaystack<TMember>(Expression<Func<TDocument, TMember>> memberExpression)
334401
{
335-
var serializationInfo = _serializationInfoHelper.GetSerializationInfo(memberExpression);
336-
_indexKeysBuilder = _indexKeysBuilder.GeoSpatialHaystack(serializationInfo.ElementName);
402+
_indexKeysBuilder = _indexKeysBuilder.GeoSpatialHaystack(GetElementName(memberExpression));
337403
return this;
338404
}
339405

@@ -349,9 +415,35 @@ public IndexKeysBuilder<TDocument> GeoSpatialHaystack<TMember>(Expression<Func<T
349415
/// </returns>
350416
public IndexKeysBuilder<TDocument> GeoSpatialHaystack<TMember, TAdditionalMember>(Expression<Func<TDocument, TMember>> memberExpression, Expression<Func<TDocument, TAdditionalMember>> additionalMemberExpression)
351417
{
352-
var serializationInfo = _serializationInfoHelper.GetSerializationInfo(memberExpression);
353-
var additionalSerializationInfo = _serializationInfoHelper.GetSerializationInfo(additionalMemberExpression);
354-
_indexKeysBuilder = _indexKeysBuilder.GeoSpatialHaystack(serializationInfo.ElementName, additionalSerializationInfo.ElementName);
418+
_indexKeysBuilder = _indexKeysBuilder.GeoSpatialHaystack(GetElementName(memberExpression), GetElementName(additionalMemberExpression));
419+
return this;
420+
}
421+
422+
/// <summary>
423+
/// Sets the key name to create a spherical geospatial index on.
424+
/// </summary>
425+
/// <typeparam name="TMember">The type of the member.</typeparam>
426+
/// <param name="memberExpression">The member expression.</param>
427+
/// <returns>
428+
/// The builder (so method calls can be chained).
429+
/// </returns>
430+
public IndexKeysBuilder<TDocument> GeoSpatialSpherical<TMember>(Expression<Func<TDocument, TMember>> memberExpression)
431+
{
432+
_indexKeysBuilder = _indexKeysBuilder.GeoSpatialSpherical(GetElementName(memberExpression));
433+
return this;
434+
}
435+
436+
/// <summary>
437+
/// Sets the key name to create a hashed index on.
438+
/// </summary>
439+
/// <typeparam name="TMember">The type of the member.</typeparam>
440+
/// <param name="memberExpression">The member expression.</param>
441+
/// <returns>
442+
/// The builder (so method calls can be chained).
443+
/// </returns>
444+
public IndexKeysBuilder<TDocument> Hashed<TMember>(Expression<Func<TDocument, TMember>> memberExpression)
445+
{
446+
_indexKeysBuilder = _indexKeysBuilder.Hashed(GetElementName(memberExpression));
355447
return this;
356448
}
357449

@@ -379,11 +471,14 @@ protected override void Serialize(BsonWriter bsonWriter, Type nominalType, IBson
379471
}
380472

381473
// private methods
474+
private string GetElementName<TMember>(Expression<Func<TDocument, TMember>> memberExpression)
475+
{
476+
return _serializationInfoHelper.GetSerializationInfo(memberExpression).ElementName;
477+
}
478+
382479
private IEnumerable<string> GetElementNames(IEnumerable<Expression<Func<TDocument, object>>> memberExpressions)
383480
{
384-
return memberExpressions
385-
.Select(x => _serializationInfoHelper.GetSerializationInfo(x))
386-
.Select(x => x.ElementName);
481+
return memberExpressions.Select(x => GetElementName(x));
387482
}
388483
}
389484
}

MongoDB.DriverUnitTests/Builders/IndexKeysBuilderTests.cs

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,5 +207,101 @@ public void TestAscendingGeoSpatial_Typed()
207207
string expected = "{ \"a\" : 1, \"b\" : \"2d\" }";
208208
Assert.AreEqual(expected, keys.ToJson());
209209
}
210+
211+
[Test]
212+
public void TestGeoSpatialSpherical()
213+
{
214+
var keys = IndexKeys.GeoSpatialSpherical("a");
215+
string expected = "{ \"a\" : \"2dsphere\" }";
216+
Assert.AreEqual(expected, keys.ToJson());
217+
}
218+
219+
[Test]
220+
public void TestGeoSpatialSpherical_Typed()
221+
{
222+
var keys = IndexKeys<Test>.GeoSpatialSpherical(x => x.A);
223+
string expected = "{ \"a\" : \"2dsphere\" }";
224+
Assert.AreEqual(expected, keys.ToJson());
225+
}
226+
227+
[Test]
228+
public void TestGeoSpatialSphericalAscending()
229+
{
230+
var keys = IndexKeys.GeoSpatialSpherical("a").Ascending("b");
231+
string expected = "{ \"a\" : \"2dsphere\", \"b\" : 1 }";
232+
Assert.AreEqual(expected, keys.ToJson());
233+
}
234+
235+
[Test]
236+
public void TestGeoSpatialSphericalAscending_Typed()
237+
{
238+
var keys = IndexKeys<Test>.GeoSpatialSpherical(x => x.A).Ascending(x => x.B);
239+
string expected = "{ \"a\" : \"2dsphere\", \"b\" : 1 }";
240+
Assert.AreEqual(expected, keys.ToJson());
241+
}
242+
243+
[Test]
244+
public void TestAscendingGeoSpatialSpherical()
245+
{
246+
var keys = IndexKeys.Ascending("a").GeoSpatialSpherical("b");
247+
string expected = "{ \"a\" : 1, \"b\" : \"2dsphere\" }";
248+
Assert.AreEqual(expected, keys.ToJson());
249+
}
250+
251+
[Test]
252+
public void TestAscendingGeoSpatialSpherical_Typed()
253+
{
254+
var keys = IndexKeys<Test>.Ascending(x => x.A).GeoSpatialSpherical(x => x.B);
255+
string expected = "{ \"a\" : 1, \"b\" : \"2dsphere\" }";
256+
Assert.AreEqual(expected, keys.ToJson());
257+
}
258+
259+
[Test]
260+
public void TestHashed()
261+
{
262+
var keys = IndexKeys.Hashed("a");
263+
string expected = "{ \"a\" : \"hashed\" }";
264+
Assert.AreEqual(expected, keys.ToJson());
265+
}
266+
267+
[Test]
268+
public void TestHashed_Typed()
269+
{
270+
var keys = IndexKeys<Test>.Hashed(x => x.A);
271+
string expected = "{ \"a\" : \"hashed\" }";
272+
Assert.AreEqual(expected, keys.ToJson());
273+
}
274+
275+
[Test]
276+
public void TestHashedAscending()
277+
{
278+
var keys = IndexKeys.Hashed("a").Ascending("b");
279+
string expected = "{ \"a\" : \"hashed\", \"b\" : 1 }";
280+
Assert.AreEqual(expected, keys.ToJson());
281+
}
282+
283+
[Test]
284+
public void TestHashedAscending_Typed()
285+
{
286+
var keys = IndexKeys<Test>.Hashed(x => x.A).Ascending(x => x.B);
287+
string expected = "{ \"a\" : \"hashed\", \"b\" : 1 }";
288+
Assert.AreEqual(expected, keys.ToJson());
289+
}
290+
291+
[Test]
292+
public void TestAscendingHashed()
293+
{
294+
var keys = IndexKeys.Ascending("a").Hashed("b");
295+
string expected = "{ \"a\" : 1, \"b\" : \"hashed\" }";
296+
Assert.AreEqual(expected, keys.ToJson());
297+
}
298+
299+
[Test]
300+
public void TestAscendingHashed_Typed()
301+
{
302+
var keys = IndexKeys<Test>.Ascending(x => x.A).Hashed(x => x.B);
303+
string expected = "{ \"a\" : 1, \"b\" : \"hashed\" }";
304+
Assert.AreEqual(expected, keys.ToJson());
305+
}
210306
}
211307
}

MongoDB.DriverUnitTests/MongoCollectionTests.cs

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -881,6 +881,52 @@ public void TestGeoNearSphericalTrue()
881881
}
882882
}
883883

884+
[Test]
885+
public void TestGeoSphericalIndex()
886+
{
887+
if (_server.BuildInfo.Version >= new Version(2, 4, 0, 0))
888+
{
889+
if (_collection.Exists()) { _collection.Drop(); }
890+
_collection.Insert(new BsonDocument { { "Location", new BsonDocument { { "type", "Point" }, { "coordinates", new BsonArray { -74.0, 40.74 } } } }, { "Name", "10gen" }, { "Type", "Office" } });
891+
_collection.Insert(new BsonDocument { { "Location", new BsonDocument { { "type", "Point" }, { "coordinates", new BsonArray { -74.0, 41.73 } } } }, { "Name", "Three" }, { "Type", "Coffee" } });
892+
_collection.Insert(new BsonDocument { { "Location", new BsonDocument { { "type", "Point" }, { "coordinates", new BsonArray { -75.0, 40.74 } } } }, { "Name", "Two" }, { "Type", "Coffee" } });
893+
_collection.CreateIndex(IndexKeys.GeoSpatialSpherical("Location"));
894+
895+
// TODO: add Query builder support for 2dsphere queries
896+
var query = new QueryDocument {
897+
{ "Location", new BsonDocument {
898+
{ "$near", new BsonDocument {
899+
{ "$geometry", new BsonDocument {
900+
{ "type", "Point" },
901+
{ "coordinates", new BsonArray { -74.0, 40.74 } }
902+
} }
903+
} }
904+
} }
905+
};
906+
var cursor = _collection.FindAs<BsonDocument>(query);
907+
var hits = cursor.ToArray();
908+
909+
var hit0 = hits[0];
910+
Assert.AreEqual(-74.0, hit0["Location"]["coordinates"][0].AsDouble);
911+
Assert.AreEqual(40.74, hit0["Location"]["coordinates"][1].AsDouble);
912+
Assert.AreEqual("10gen", hit0["Name"].AsString);
913+
Assert.AreEqual("Office", hit0["Type"].AsString);
914+
915+
// with spherical true "Two" is considerably closer than "Three"
916+
var hit1 = hits[1];
917+
Assert.AreEqual(-75.0, hit1["Location"]["coordinates"][0].AsDouble);
918+
Assert.AreEqual(40.74, hit1["Location"]["coordinates"][1].AsDouble);
919+
Assert.AreEqual("Two", hit1["Name"].AsString);
920+
Assert.AreEqual("Coffee", hit1["Type"].AsString);
921+
922+
var hit2 = hits[2];
923+
Assert.AreEqual(-74.0, hit2["Location"]["coordinates"][0].AsDouble);
924+
Assert.AreEqual(41.73, hit2["Location"]["coordinates"][1].AsDouble);
925+
Assert.AreEqual("Three", hit2["Name"].AsString);
926+
Assert.AreEqual("Coffee", hit2["Type"].AsString);
927+
}
928+
}
929+
884930
[Test]
885931
public void TestGetIndexes()
886932
{
@@ -952,6 +998,29 @@ public void TestGroupByFunction()
952998
Assert.AreEqual(3, results[2]["count"].ToInt32());
953999
}
9541000

1001+
[Test]
1002+
public void TestHashedIndex()
1003+
{
1004+
if (_server.BuildInfo.Version >= new Version(2, 4, 0, 0))
1005+
{
1006+
if (_collection.Exists()) { _collection.Drop(); }
1007+
_collection.Insert(new BsonDocument { { "x", "abc" } });
1008+
_collection.Insert(new BsonDocument { { "x", "def" } });
1009+
_collection.Insert(new BsonDocument { { "x", "ghi" } });
1010+
_collection.CreateIndex(IndexKeys.Hashed("x"));
1011+
1012+
var query = Query.EQ("x", "abc");
1013+
var cursor = _collection.FindAs<BsonDocument>(query);
1014+
var documents = cursor.ToArray();
1015+
1016+
Assert.AreEqual(1, documents.Length);
1017+
Assert.AreEqual("abc", documents[0]["x"].AsString);
1018+
1019+
var explain = cursor.Explain();
1020+
Assert.AreEqual("BtreeCursor x_hashed", explain["cursor"].AsString);
1021+
}
1022+
}
1023+
9551024
[Test]
9561025
public void TestIndexExists()
9571026
{
@@ -1384,6 +1453,37 @@ public void TestGetStatsUsePowerOf2Sizes()
13841453
}
13851454
}
13861455

1456+
[Test]
1457+
public void TestTextIndex()
1458+
{
1459+
if (_server.BuildInfo.Version >= new Version(2, 4, 0, 0))
1460+
{
1461+
if (_collection.Exists()) { _collection.Drop(); }
1462+
_collection.Insert(new BsonDocument("x", "The quick brown fox"));
1463+
_collection.Insert(new BsonDocument("x", "jumped over the fence"));
1464+
_collection.CreateIndex(new IndexKeysDocument("x", "text"));
1465+
1466+
var enableTextSearchCommand = new CommandDocument
1467+
{
1468+
{ "setParameter", 1 },
1469+
{ "textSearchEnabled", true }
1470+
};
1471+
var adminDatabase = _server.GetDatabase("admin");
1472+
adminDatabase.RunCommand(enableTextSearchCommand);
1473+
1474+
var textSearchCommand = new CommandDocument
1475+
{
1476+
{ "text", _collection.Name },
1477+
{ "search", "fox" }
1478+
};
1479+
var commandResult = _database.RunCommand(textSearchCommand);
1480+
var response = commandResult.Response;
1481+
1482+
Assert.AreEqual(1, response["stats"]["nfound"].ToInt32());
1483+
Assert.AreEqual("The quick brown fox", response["results"][0]["obj"]["x"].AsString);
1484+
}
1485+
}
1486+
13871487
[Test]
13881488
public void TestTotalDataSize()
13891489
{

0 commit comments

Comments
 (0)