Skip to content

Commit 2576b87

Browse files
author
rstam
committed
Added support for string IndexOfAny in LINQ query where clauses.
1 parent 3c814ca commit 2576b87

File tree

2 files changed

+135
-25
lines changed

2 files changed

+135
-25
lines changed

Driver/Linq/Translators/SelectQuery.cs

Lines changed: 27 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -763,7 +763,9 @@ private IMongoQuery BuildStringIndexOfQuery(Expression variableExpression, Expre
763763
var index = ToInt32(constantExpression);
764764

765765
var methodCallExpression = variableExpression as MethodCallExpression;
766-
if (methodCallExpression != null && methodCallExpression.Method.Name == "IndexOf" && methodCallExpression.Method.DeclaringType == typeof(string))
766+
if (methodCallExpression != null &&
767+
(methodCallExpression.Method.Name == "IndexOf" || methodCallExpression.Method.Name == "IndexOfAny") &&
768+
methodCallExpression.Method.DeclaringType == typeof(string))
767769
{
768770
var serializationInfo = GetSerializationInfo(methodCallExpression.Object);
769771
if (serializationInfo == null)
@@ -807,22 +809,37 @@ private IMongoQuery BuildStringIndexOfQuery(Expression variableExpression, Expre
807809
}
808810

809811
string pattern = null;
810-
if (value.GetType() == typeof(char))
812+
if (value.GetType() == typeof(char) || value.GetType() == typeof(char[]))
811813
{
812-
var escapedChar = Regex.Escape(((char)value).ToString());
814+
char[] chars;
815+
if (value.GetType() == typeof(char))
816+
{
817+
chars = new char[] { (char)value };
818+
}
819+
else
820+
{
821+
chars = (char[])value;
822+
}
823+
var positiveClass = string.Join("", chars.Select(c => (c == '-') ? "\\-" : (c == ']') ? "\\]" : Regex.Escape(c.ToString())).ToArray());
824+
var negativeClass = "[^" + positiveClass + "]";
825+
if (chars.Length > 1)
826+
{
827+
positiveClass = "[" + positiveClass + "]";
828+
}
829+
813830
if (startIndex == -1)
814831
{
815832
// the regex for: IndexOf(c) == index
816833
// is: /^[^c]{index}c/
817-
pattern = string.Format("^[^{0}]{{{1}}}{0}", escapedChar, index);
834+
pattern = string.Format("^{0}{{{1}}}{2}", negativeClass, index, positiveClass);
818835
}
819836
else
820837
{
821838
if (count == -1)
822839
{
823840
// the regex for: IndexOf(c, startIndex) == index
824841
// is: /^.{startIndex}[^c]{index - startIndex}c/
825-
pattern = string.Format("^.{{{1}}}[^{0}]{{{2}}}{0}", escapedChar, startIndex, index - startIndex);
842+
pattern = string.Format("^.{{{0}}}{1}{{{2}}}{3}", startIndex, negativeClass, index - startIndex, positiveClass);
826843
}
827844
else
828845
{
@@ -835,7 +852,7 @@ private IMongoQuery BuildStringIndexOfQuery(Expression variableExpression, Expre
835852
{
836853
// the regex for: IndexOf(c, startIndex, count) == index
837854
// is: /^.{startIndex}(?=.{count})[^c]{index - startIndex}c/
838-
pattern = string.Format("^.{{{1}}}(?=.{{{2}}})[^{0}]{{{3}}}{0}", escapedChar, startIndex, count, index - startIndex);
855+
pattern = string.Format("^.{{{0}}}(?=.{{{1}}}){2}{{{3}}}{4}", startIndex, count, negativeClass, index - startIndex, positiveClass);
839856
}
840857
}
841858
}
@@ -1233,26 +1250,12 @@ private string GetTrimCharsPattern(Expression trimCharsExpression)
12331250
}
12341251

12351252
// build a pattern that matches the characters to be trimmed
1236-
var sb = new StringBuilder();
1237-
sb.Append("[");
1238-
var sawDash = false; // if dash is one of the characters it must be last in the pattern
1239-
foreach (var c in trimChars)
1240-
{
1241-
if (c == '-')
1242-
{
1243-
sawDash = true;
1244-
}
1245-
else
1246-
{
1247-
sb.Append(Regex.Escape(c.ToString()));
1248-
}
1249-
}
1250-
if (sawDash)
1253+
var characterClass = string.Join("", trimChars.Select(c => (c == '-') ? "\\-" : (c == ']') ? "\\]" : Regex.Escape(c.ToString())).ToArray());
1254+
if (trimChars.Length > 1)
12511255
{
1252-
sb.Append("-");
1256+
characterClass = "[" + characterClass + "]";
12531257
}
1254-
sb.Append("]*");
1255-
return sb.ToString();
1258+
return characterClass + "*";
12561259
}
12571260

12581261
private BsonValue SerializeValue(BsonSerializationInfo serializationInfo, object value)

DriverUnitTests/Linq/SelectQueryTests.cs

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

4354+
[Test]
4355+
public void TestWhereSIndexOfAnyBC()
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.IndexOfAny(new char[] { 'b', 'c' }) == 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.IndexOfAny(new char[] { 'b', 'c' }, 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.IndexOfAny(new char[] { 'b', 'c' }, 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.IndexOfAny(new char[] { 'b', 'c' }, 1, 2) == 2
4388+
select c;
4389+
Assert.AreEqual(3, Consume(query4));
4390+
}
4391+
4392+
[Test]
4393+
public void TestWhereSIndexOfAnyBDashCEquals1()
4394+
{
4395+
var query = from c in _collection.AsQueryable<C>()
4396+
where c.S.IndexOfAny(new char[] { 'b', '-', 'c' }) == 1
4397+
select c;
4398+
4399+
var translatedQuery = MongoQueryTranslator.Translate(query);
4400+
Assert.IsInstanceOf<SelectQuery>(translatedQuery);
4401+
Assert.AreSame(_collection, translatedQuery.Collection);
4402+
Assert.AreSame(typeof(C), translatedQuery.DocumentType);
4403+
4404+
var selectQuery = (SelectQuery)translatedQuery;
4405+
Assert.AreEqual("(C c) => (c.S.IndexOfAny(Char[]:{ 'b', '-', 'c' }) == 1)", ExpressionFormatter.ToString(selectQuery.Where));
4406+
Assert.IsNull(selectQuery.OrderBy);
4407+
Assert.IsNull(selectQuery.Projection);
4408+
Assert.IsNull(selectQuery.Skip);
4409+
Assert.IsNull(selectQuery.Take);
4410+
4411+
Assert.AreEqual("{ \"s\" : /^[^b\\-c]{1}[b\\-c]/s }", selectQuery.BuildQuery().ToJson());
4412+
Assert.AreEqual(1, Consume(query));
4413+
}
4414+
4415+
[Test]
4416+
public void TestWhereSIndexOfAnyBCStartIndex1Equals1()
4417+
{
4418+
var query = from c in _collection.AsQueryable<C>()
4419+
where c.S.IndexOfAny(new char[] { 'b', '-', 'c' }, 1) == 1
4420+
select c;
4421+
4422+
var translatedQuery = MongoQueryTranslator.Translate(query);
4423+
Assert.IsInstanceOf<SelectQuery>(translatedQuery);
4424+
Assert.AreSame(_collection, translatedQuery.Collection);
4425+
Assert.AreSame(typeof(C), translatedQuery.DocumentType);
4426+
4427+
var selectQuery = (SelectQuery)translatedQuery;
4428+
Assert.AreEqual("(C c) => (c.S.IndexOfAny(Char[]:{ 'b', '-', 'c' }, 1) == 1)", ExpressionFormatter.ToString(selectQuery.Where));
4429+
Assert.IsNull(selectQuery.OrderBy);
4430+
Assert.IsNull(selectQuery.Projection);
4431+
Assert.IsNull(selectQuery.Skip);
4432+
Assert.IsNull(selectQuery.Take);
4433+
4434+
Assert.AreEqual("{ \"s\" : /^.{1}[^b\\-c]{0}[b\\-c]/s }", selectQuery.BuildQuery().ToJson());
4435+
Assert.AreEqual(1, Consume(query));
4436+
}
4437+
4438+
[Test]
4439+
public void TestWhereSIndexOfAnyBCStartIndex1Count2Equals1()
4440+
{
4441+
var query = from c in _collection.AsQueryable<C>()
4442+
where c.S.IndexOfAny(new char[] { 'b', '-', 'c' }, 1, 2) == 1
4443+
select c;
4444+
4445+
var translatedQuery = MongoQueryTranslator.Translate(query);
4446+
Assert.IsInstanceOf<SelectQuery>(translatedQuery);
4447+
Assert.AreSame(_collection, translatedQuery.Collection);
4448+
Assert.AreSame(typeof(C), translatedQuery.DocumentType);
4449+
4450+
var selectQuery = (SelectQuery)translatedQuery;
4451+
Assert.AreEqual("(C c) => (c.S.IndexOfAny(Char[]:{ 'b', '-', 'c' }, 1, 2) == 1)", ExpressionFormatter.ToString(selectQuery.Where));
4452+
Assert.IsNull(selectQuery.OrderBy);
4453+
Assert.IsNull(selectQuery.Projection);
4454+
Assert.IsNull(selectQuery.Skip);
4455+
Assert.IsNull(selectQuery.Take);
4456+
4457+
Assert.AreEqual("{ \"s\" : /^.{1}(?=.{2})[^b\\-c]{0}[b\\-c]/s }", selectQuery.BuildQuery().ToJson());
4458+
Assert.AreEqual(1, Consume(query));
4459+
}
4460+
43544461
[Test]
43554462
public void TestWhereSIndexOfB()
43564463
{
@@ -5068,7 +5175,7 @@ where c.S.TrimStart(' ', '.', '-', '\t').TrimEnd().ToLower().Contains("xyz")
50685175
Assert.IsNull(selectQuery.Skip);
50695176
Assert.IsNull(selectQuery.Take);
50705177

5071-
Assert.AreEqual("{ \"s\" : /^[\\ \\.\\t-]*.*xyz.*\\s*$/is }", selectQuery.BuildQuery().ToJson());
5178+
Assert.AreEqual("{ \"s\" : /^[\\ \\.\\-\\t]*.*xyz.*\\s*$/is }", selectQuery.BuildQuery().ToJson());
50725179
Assert.AreEqual(1, Consume(query));
50735180
}
50745181

0 commit comments

Comments
 (0)