Skip to content

Commit 0ed1649

Browse files
author
rstam
committed
Added support for string IndexOf in LINQ query where clauses.
1 parent 5bb1538 commit 0ed1649

File tree

2 files changed

+257
-0
lines changed

2 files changed

+257
-0
lines changed

Driver/Linq/Translators/SelectQuery.cs

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -353,6 +353,12 @@ private IMongoQuery BuildComparisonQuery(BinaryExpression binaryExpression)
353353
return query;
354354
}
355355

356+
query = BuildStringIndexOfQuery(variableExpression, operatorType, constantExpression);
357+
if (query != null)
358+
{
359+
return query;
360+
}
361+
356362
query = BuildStringLengthQuery(variableExpression, operatorType, constantExpression);
357363
if (query != null)
358364
{
@@ -742,6 +748,119 @@ private IMongoQuery BuildQuery(Expression expression)
742748
return query;
743749
}
744750

751+
private IMongoQuery BuildStringIndexOfQuery(Expression variableExpression, ExpressionType operatorType, ConstantExpression constantExpression)
752+
{
753+
if (constantExpression.Type != typeof(int))
754+
{
755+
return null;
756+
}
757+
var index = ToInt32(constantExpression);
758+
759+
var methodCallExpression = variableExpression as MethodCallExpression;
760+
if (methodCallExpression != null && methodCallExpression.Method.Name == "IndexOf" && methodCallExpression.Method.DeclaringType == typeof(string))
761+
{
762+
var serializationInfo = GetSerializationInfo(methodCallExpression.Object);
763+
if (serializationInfo == null)
764+
{
765+
return null;
766+
}
767+
768+
object value;
769+
var startIndex = -1;
770+
var count = -1;
771+
772+
var args = methodCallExpression.Arguments.ToArray();
773+
switch (args.Length)
774+
{
775+
case 3:
776+
var countExpression = args[2] as ConstantExpression;
777+
if (countExpression == null)
778+
{
779+
return null;
780+
}
781+
count = ToInt32(countExpression);
782+
goto case 2;
783+
case 2:
784+
var startIndexExpression = args[1] as ConstantExpression;
785+
if (startIndexExpression == null)
786+
{
787+
return null;
788+
}
789+
startIndex = ToInt32(startIndexExpression);
790+
goto case 1;
791+
case 1:
792+
var valueExpression = args[0] as ConstantExpression;
793+
if (valueExpression == null)
794+
{
795+
return null;
796+
}
797+
value = valueExpression.Value;
798+
break;
799+
default:
800+
return null;
801+
}
802+
803+
string pattern = null;
804+
if (value.GetType() == typeof(char))
805+
{
806+
var c = Regex.Escape(((char)value).ToString());
807+
if (startIndex == -1)
808+
{
809+
// the regex for: IndexOf(c) == index
810+
// is: /^[^c]{index}c/
811+
pattern = string.Format("^[^{0}]{{{1}}}{0}", c, index);
812+
}
813+
else
814+
{
815+
if (count == -1)
816+
{
817+
// the regex for: IndexOf(c, startIndex) == index
818+
// is: /^.{startIndex}[^c]{index - startIndex}c/
819+
pattern = string.Format("^.{{{1}}}[^{0}]{{{2}}}{0}", c, startIndex, index - startIndex);
820+
}
821+
else
822+
{
823+
// the regex for: IndexOf(c, startIndex, count) == index
824+
// is: /^.{startIndex}(?=.{count})[^c]{index - startIndex}c/
825+
pattern = string.Format("^.{{{1}}}(?=.{{{2}}})[^{0}]{{{3}}}{0}", c, startIndex, count, index - startIndex);
826+
}
827+
}
828+
}
829+
else if (value.GetType() == typeof(string))
830+
{
831+
var s = Regex.Escape((string)value);
832+
if (startIndex == -1)
833+
{
834+
// the regex for: IndexOf(s) == index
835+
// is: /^(?!.{0,index - 1}s).{index}s/
836+
pattern = string.Format("^(?!.{{0,{2}}}{0}).{{{1}}}{0}", s, index, index - 1);
837+
}
838+
else
839+
{
840+
if (count == -1)
841+
{
842+
// the regex for: IndexOf(s, startIndex) == index
843+
// is: /^.{startIndex}(?!.{0, index - startIndex - 1}s).{index - startIndex}s/
844+
pattern = string.Format("^.{{{1}}}(?!.{{0,{2}}}{0}).{{{3}}}{0}", s, startIndex, index - startIndex - 1, index - startIndex);
845+
}
846+
else
847+
{
848+
// the regex for: IndexOf(s, startIndex, count) == index
849+
// is: /^.{startIndex}(?=.{count})(?!.{0,index - startIndex - 1}s).{index - startIndex)s/
850+
pattern = string.Format("^.{{{1}}}(?=.{{{2}}})(?!.{{0,{3}}}{0}).{{{4}}}{0}", s, startIndex, count, index - startIndex - 1, index - startIndex);
851+
}
852+
}
853+
}
854+
855+
if (pattern != null)
856+
{
857+
return Query.Matches(serializationInfo.ElementName, new BsonRegularExpression(pattern, "s"));
858+
}
859+
}
860+
861+
return null;
862+
}
863+
745864
private IMongoQuery BuildStringLengthQuery(Expression variableExpression, ExpressionType operatorType, ConstantExpression constantExpression)
746865
{
747866
if (constantExpression.Type != typeof(int))

DriverUnitTests/Linq/SelectQueryTests.cs

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4351,6 +4351,144 @@ public void TestWhereSEndsWithAbcNot()
43514351
Assert.AreEqual(4, Consume(query));
43524352
}
43534353

4354+
[Test]
4355+
public void TestWhereSIndexOfBEquals1()
4356+
{
4357+
var query = from c in _collection.AsQueryable<C>()
4358+
where c.S.IndexOf('b') == 1
4359+
select c;
4360+
4361+
var translatedQuery = MongoQueryTranslator.Translate(query);
4362+
Assert.IsInstanceOf<SelectQuery>(translatedQuery);
4363+
Assert.AreSame(_collection, translatedQuery.Collection);
4364+
Assert.AreSame(typeof(C), translatedQuery.DocumentType);
4365+
4366+
var selectQuery = (SelectQuery)translatedQuery;
4367+
Assert.AreEqual("(C c) => (c.S.IndexOf('b') == 1)", ExpressionFormatter.ToString(selectQuery.Where));
4368+
Assert.IsNull(selectQuery.OrderBy);
4369+
Assert.IsNull(selectQuery.Projection);
4370+
Assert.IsNull(selectQuery.Skip);
4371+
Assert.IsNull(selectQuery.Take);
4372+
4373+
Assert.AreEqual("{ \"s\" : /^[^b]{1}b/s }", selectQuery.BuildQuery().ToJson());
4374+
Assert.AreEqual(1, Consume(query));
4375+
}
4376+
4377+
[Test]
4378+
public void TestWhereSIndexOfBStartIndex1Equals1()
4379+
{
4380+
var query = from c in _collection.AsQueryable<C>()
4381+
where c.S.IndexOf('b', 1) == 1
4382+
select c;
4383+
4384+
var translatedQuery = MongoQueryTranslator.Translate(query);
4385+
Assert.IsInstanceOf<SelectQuery>(translatedQuery);
4386+
Assert.AreSame(_collection, translatedQuery.Collection);
4387+
Assert.AreSame(typeof(C), translatedQuery.DocumentType);
4388+
4389+
var selectQuery = (SelectQuery)translatedQuery;
4390+
Assert.AreEqual("(C c) => (c.S.IndexOf('b', 1) == 1)", ExpressionFormatter.ToString(selectQuery.Where));
4391+
Assert.IsNull(selectQuery.OrderBy);
4392+
Assert.IsNull(selectQuery.Projection);
4393+
Assert.IsNull(selectQuery.Skip);
4394+
Assert.IsNull(selectQuery.Take);
4395+
4396+
Assert.AreEqual("{ \"s\" : /^.{1}[^b]{0}b/s }", selectQuery.BuildQuery().ToJson());
4397+
Assert.AreEqual(1, Consume(query));
4398+
}
4399+
4400+
[Test]
4401+
public void TestWhereSIndexOfBStartIndex1Count2Equals1()
4402+
{
4403+
var query = from c in _collection.AsQueryable<C>()
4404+
where c.S.IndexOf('b', 1, 2) == 1
4405+
select c;
4406+
4407+
var translatedQuery = MongoQueryTranslator.Translate(query);
4408+
Assert.IsInstanceOf<SelectQuery>(translatedQuery);
4409+
Assert.AreSame(_collection, translatedQuery.Collection);
4410+
Assert.AreSame(typeof(C), translatedQuery.DocumentType);
4411+
4412+
var selectQuery = (SelectQuery)translatedQuery;
4413+
Assert.AreEqual("(C c) => (c.S.IndexOf('b', 1, 2) == 1)", ExpressionFormatter.ToString(selectQuery.Where));
4414+
Assert.IsNull(selectQuery.OrderBy);
4415+
Assert.IsNull(selectQuery.Projection);
4416+
Assert.IsNull(selectQuery.Skip);
4417+
Assert.IsNull(selectQuery.Take);
4418+
4419+
Assert.AreEqual("{ \"s\" : /^.{1}(?=.{2})[^b]{0}b/s }", selectQuery.BuildQuery().ToJson());
4420+
Assert.AreEqual(1, Consume(query));
4421+
}
4422+
4423+
[Test]
4424+
public void TestWhereSIndexOfXyzEquals3()
4425+
{
4426+
var query = from c in _collection.AsQueryable<C>()
4427+
where c.S.IndexOf("xyz") == 3
4428+
select c;
4429+
4430+
var translatedQuery = MongoQueryTranslator.Translate(query);
4431+
Assert.IsInstanceOf<SelectQuery>(translatedQuery);
4432+
Assert.AreSame(_collection, translatedQuery.Collection);
4433+
Assert.AreSame(typeof(C), translatedQuery.DocumentType);
4434+
4435+
var selectQuery = (SelectQuery)translatedQuery;
4436+
Assert.AreEqual("(C c) => (c.S.IndexOf(\"xyz\") == 3)", ExpressionFormatter.ToString(selectQuery.Where));
4437+
Assert.IsNull(selectQuery.OrderBy);
4438+
Assert.IsNull(selectQuery.Projection);
4439+
Assert.IsNull(selectQuery.Skip);
4440+
Assert.IsNull(selectQuery.Take);
4441+
4442+
Assert.AreEqual("{ \"s\" : /^(?!.{0,2}xyz).{3}xyz/s }", selectQuery.BuildQuery().ToJson());
4443+
Assert.AreEqual(1, Consume(query));
4444+
}
4445+
4446+
[Test]
4447+
public void TestWhereSIndexOfXyzStartIndex1Equals3()
4448+
{
4449+
var query = from c in _collection.AsQueryable<C>()
4450+
where c.S.IndexOf("xyz", 1) == 3
4451+
select c;
4452+
4453+
var translatedQuery = MongoQueryTranslator.Translate(query);
4454+
Assert.IsInstanceOf<SelectQuery>(translatedQuery);
4455+
Assert.AreSame(_collection, translatedQuery.Collection);
4456+
Assert.AreSame(typeof(C), translatedQuery.DocumentType);
4457+
4458+
var selectQuery = (SelectQuery)translatedQuery;
4459+
Assert.AreEqual("(C c) => (c.S.IndexOf(\"xyz\", 1) == 3)", ExpressionFormatter.ToString(selectQuery.Where));
4460+
Assert.IsNull(selectQuery.OrderBy);
4461+
Assert.IsNull(selectQuery.Projection);
4462+
Assert.IsNull(selectQuery.Skip);
4463+
Assert.IsNull(selectQuery.Take);
4464+
4465+
Assert.AreEqual("{ \"s\" : /^.{1}(?!.{0,1}xyz).{2}xyz/s }", selectQuery.BuildQuery().ToJson());
4466+
Assert.AreEqual(1, Consume(query));
4467+
}
4468+
4469+
[Test]
4470+
public void TestWhereSIndexOfXyzStartIndex1Count5Equals3()
4471+
{
4472+
var query = from c in _collection.AsQueryable<C>()
4473+
where c.S.IndexOf("xyz", 1, 5) == 3
4474+
select c;
4475+
4476+
var translatedQuery = MongoQueryTranslator.Translate(query);
4477+
Assert.IsInstanceOf<SelectQuery>(translatedQuery);
4478+
Assert.AreSame(_collection, translatedQuery.Collection);
4479+
Assert.AreSame(typeof(C), translatedQuery.DocumentType);
4480+
4481+
var selectQuery = (SelectQuery)translatedQuery;
4482+
Assert.AreEqual("(C c) => (c.S.IndexOf(\"xyz\", 1, 5) == 3)", ExpressionFormatter.ToString(selectQuery.Where));
4483+
Assert.IsNull(selectQuery.OrderBy);
4484+
Assert.IsNull(selectQuery.Projection);
4485+
Assert.IsNull(selectQuery.Skip);
4486+
Assert.IsNull(selectQuery.Take);
4487+
4488+
Assert.AreEqual("{ \"s\" : /^.{1}(?=.{5})(?!.{0,1}xyz).{2}xyz/s }", selectQuery.BuildQuery().ToJson());
4489+
Assert.AreEqual(1, Consume(query));
4490+
}
4491+
43544492
[Test]
43554493
public void TestWhereSIsMatch()
43564494
{

0 commit comments

Comments
 (0)