Skip to content

Commit 3c814ca

Browse files
author
rstam
committed
Added more unit tests for string IndexOf in LINQ queries and fixed a couple edge cases.
1 parent 0ed1649 commit 3c814ca

File tree

2 files changed

+110
-12
lines changed

2 files changed

+110
-12
lines changed

Driver/Linq/Translators/SelectQuery.cs

Lines changed: 35 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -750,6 +750,12 @@ private IMongoQuery BuildQuery(Expression expression)
750750

751751
private IMongoQuery BuildStringIndexOfQuery(Expression variableExpression, ExpressionType operatorType, ConstantExpression constantExpression)
752752
{
753+
// TODO: support other comparison operators
754+
if (operatorType != ExpressionType.Equal)
755+
{
756+
return null;
757+
}
758+
753759
if (constantExpression.Type != typeof(int))
754760
{
755761
return null;
@@ -803,51 +809,68 @@ private IMongoQuery BuildStringIndexOfQuery(Expression variableExpression, Expre
803809
string pattern = null;
804810
if (value.GetType() == typeof(char))
805811
{
806-
var c = Regex.Escape(((char)value).ToString());
812+
var escapedChar = Regex.Escape(((char)value).ToString());
807813
if (startIndex == -1)
808814
{
809815
// the regex for: IndexOf(c) == index
810816
// is: /^[^c]{index}c/
811-
pattern = string.Format("^[^{0}]{{{1}}}{0}", c, index);
817+
pattern = string.Format("^[^{0}]{{{1}}}{0}", escapedChar, index);
812818
}
813819
else
814820
{
815821
if (count == -1)
816822
{
817823
// the regex for: IndexOf(c, startIndex) == index
818824
// is: /^.{startIndex}[^c]{index - startIndex}c/
819-
pattern = string.Format("^.{{{1}}}[^{0}]{{{2}}}{0}", c, startIndex, index - startIndex);
825+
pattern = string.Format("^.{{{1}}}[^{0}]{{{2}}}{0}", escapedChar, startIndex, index - startIndex);
820826
}
821827
else
822828
{
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);
829+
if (index >= startIndex + count)
830+
{
831+
// index is outside of the substring so no match is possible
832+
return Query.Exists("_id", false); // matches no documents
833+
}
834+
else
835+
{
836+
// the regex for: IndexOf(c, startIndex, count) == index
837+
// is: /^.{startIndex}(?=.{count})[^c]{index - startIndex}c/
838+
pattern = string.Format("^.{{{1}}}(?=.{{{2}}})[^{0}]{{{3}}}{0}", escapedChar, startIndex, count, index - startIndex);
839+
}
826840
}
827841
}
828842
}
829843
else if (value.GetType() == typeof(string))
830844
{
831-
var s = Regex.Escape((string)value);
845+
var escapedString = Regex.Escape((string)value);
832846
if (startIndex == -1)
833847
{
834848
// the regex for: IndexOf(s) == index
835849
// is: /^(?!.{0,index - 1}s).{index}s/
836-
pattern = string.Format("^(?!.{{0,{2}}}{0}).{{{1}}}{0}", s, index, index - 1);
850+
pattern = string.Format("^(?!.{{0,{2}}}{0}).{{{1}}}{0}", escapedString, index, index - 1);
837851
}
838852
else
839853
{
840854
if (count == -1)
841855
{
842856
// the regex for: IndexOf(s, startIndex) == index
843857
// 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);
858+
pattern = string.Format("^.{{{1}}}(?!.{{0,{2}}}{0}).{{{3}}}{0}", escapedString, startIndex, index - startIndex - 1, index - startIndex);
845859
}
846860
else
847861
{
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);
862+
var unescapedLength = ((string)value).Length;
863+
if (unescapedLength > startIndex + count - index)
864+
{
865+
// substring isn't long enough to match
866+
return Query.Exists("_id", false); // matches no documents
867+
}
868+
else
869+
{
870+
// the regex for: IndexOf(s, startIndex, count) == index
871+
// is: /^.{startIndex}(?=.{count})(?!.{0,index - startIndex - 1}s).{index - startIndex)s/
872+
pattern = string.Format("^.{{{1}}}(?=.{{{2}}})(?!.{{0,{3}}}{0}).{{{4}}}{0}", escapedString, startIndex, count, index - startIndex - 1, index - startIndex);
873+
}
851874
}
852875
}
853876
}

DriverUnitTests/Linq/SelectQueryTests.cs

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

4354+
[Test]
4355+
public void TestWhereSIndexOfB()
4356+
{
4357+
var collection = _database.GetCollection("temp");
4358+
collection.Drop();
4359+
collection.Insert(new C { S = "bxxx" });
4360+
collection.Insert(new C { S = "xbxx" });
4361+
collection.Insert(new C { S = "xxbx" });
4362+
collection.Insert(new C { S = "xxxb" });
4363+
collection.Insert(new C { S = "bxbx" });
4364+
collection.Insert(new C { S = "xbbx" });
4365+
collection.Insert(new C { S = "xxbb" });
4366+
4367+
var query1 =
4368+
from c in collection.AsQueryable<C>()
4369+
where c.S.IndexOf('b') == 2
4370+
select c;
4371+
Assert.AreEqual(2, Consume(query1));
4372+
4373+
var query2 =
4374+
from c in collection.AsQueryable<C>()
4375+
where c.S.IndexOf('b', 1) == 2
4376+
select c;
4377+
Assert.AreEqual(3, Consume(query2));
4378+
4379+
var query3 =
4380+
from c in collection.AsQueryable<C>()
4381+
where c.S.IndexOf('b', 1, 1) == 2
4382+
select c;
4383+
Assert.AreEqual(0, Consume(query3));
4384+
4385+
var query4 =
4386+
from c in collection.AsQueryable<C>()
4387+
where c.S.IndexOf('b', 1, 2) == 2
4388+
select c;
4389+
Assert.AreEqual(3, Consume(query4));
4390+
}
4391+
43544392
[Test]
43554393
public void TestWhereSIndexOfBEquals1()
43564394
{
@@ -4420,6 +4458,43 @@ where c.S.IndexOf('b', 1, 2) == 1
44204458
Assert.AreEqual(1, Consume(query));
44214459
}
44224460

4461+
[Test]
4462+
public void TestWhereSIndexOfXyz()
4463+
{
4464+
var collection = _database.GetCollection("temp");
4465+
collection.Drop();
4466+
collection.Insert(new C { S = "xyzaaa" });
4467+
collection.Insert(new C { S = "axyzaa" });
4468+
collection.Insert(new C { S = "aaxyza" });
4469+
collection.Insert(new C { S = "aaaxyz" });
4470+
collection.Insert(new C { S = "aaaaxy" });
4471+
collection.Insert(new C { S = "xyzxyz" });
4472+
4473+
var query1 =
4474+
from c in collection.AsQueryable<C>()
4475+
where c.S.IndexOf("xyz") == 3
4476+
select c;
4477+
Assert.AreEqual(1, Consume(query1));
4478+
4479+
var query2 =
4480+
from c in collection.AsQueryable<C>()
4481+
where c.S.IndexOf("xyz", 1) == 3
4482+
select c;
4483+
Assert.AreEqual(2, Consume(query2));
4484+
4485+
var query3 =
4486+
from c in collection.AsQueryable<C>()
4487+
where c.S.IndexOf("xyz", 1, 4) == 3
4488+
select c;
4489+
Assert.AreEqual(0, Consume(query3)); // substring isn't long enough to match
4490+
4491+
var query4 =
4492+
from c in collection.AsQueryable<C>()
4493+
where c.S.IndexOf("xyz", 1, 5) == 3
4494+
select c;
4495+
Assert.AreEqual(2, Consume(query4));
4496+
}
4497+
44234498
[Test]
44244499
public void TestWhereSIndexOfXyzEquals3()
44254500
{

0 commit comments

Comments
 (0)