Permalink
Browse files

Implemented support for combining ToLower, ToUpper, Trim, TrimStart o…

…r TrimEnd with Contains, StartsWith or EndsWith in LINQ queries.
  • Loading branch information...
1 parent 12f9bfc commit 2b9e193f9dece7eef5746826fe01b62996a916e1 Robert Stam committed Apr 9, 2012
View
@@ -540,7 +540,7 @@ public override void WriteRegularExpression(string pattern, string options)
case JsonOutputMode.Shell:
WriteNameHelper(Name);
_textWriter.Write("/");
- var escaped = (pattern == "") ? "(?:)" : pattern.Replace(@"\", @"\\").Replace("/", @"\/");
+ var escaped = (pattern == "") ? "(?:)" : pattern.Replace("/", @"\/");
_textWriter.Write(escaped);
_textWriter.Write("/");
_textWriter.Write(options);
@@ -47,7 +47,7 @@ public BsonRegularExpression(string pattern)
{
var index = pattern.LastIndexOf('/');
var escaped = pattern.Substring(1, index - 1);
- var unescaped = (escaped == "(?:)") ? "" : Regex.Replace(escaped, @"\\(.)", "$1");
+ var unescaped = (escaped == "(?:)") ? "" : escaped.Replace("\\/", "/");
_pattern = unescaped;
_options = pattern.Substring(index + 1);
}
@@ -333,7 +333,7 @@ public Regex ToRegex()
/// <returns>A string representation of the value.</returns>
public override string ToString()
{
- var escaped = _pattern.Replace(@"\", @"\\").Replace("/", @"\/");
+ var escaped = (_pattern == "") ? "(?:)" :_pattern.Replace("/", @"\/");
return string.Format("/{0}/{1}", escaped, _options);
}
}
@@ -424,7 +424,7 @@ public void TestRegularExpressionShell()
new TestData<BsonRegularExpression>(BsonRegularExpression.Create(""), "/(?:)/"),
new TestData<BsonRegularExpression>(BsonRegularExpression.Create("a"), "/a/"),
new TestData<BsonRegularExpression>(BsonRegularExpression.Create("a/b"), "/a\\/b/"),
- new TestData<BsonRegularExpression>(BsonRegularExpression.Create("a\\b"), "/a\\\\b/"),
+ new TestData<BsonRegularExpression>(BsonRegularExpression.Create("a\\b"), "/a\\b/"),
new TestData<BsonRegularExpression>(BsonRegularExpression.Create("a", "i"), "/a/i"),
new TestData<BsonRegularExpression>(BsonRegularExpression.Create("a", "m"), "/a/m"),
new TestData<BsonRegularExpression>(BsonRegularExpression.Create("a", "x"), "/a/x"),
@@ -425,8 +425,8 @@ private void VisitValue(object value)
if (a != null && a.Rank == 1)
{
var elementType = a.GetType().GetElementType();
- _sb.AppendFormat("{0}[]:{{ ", elementType.Name);
- var separator = "";
+ _sb.AppendFormat("{0}[]:{{", elementType.Name);
+ var separator = " ";
foreach (var item in a)
{
_sb.Append(separator);
@@ -443,6 +443,13 @@ private void VisitValue(object value)
return;
}
+ if (value.GetType() == typeof(char))
+ {
+ var c = (char)value;
+ _sb.AppendFormat("'{0}'", c.ToString());
+ return;
+ }
+
if (value.GetType() == typeof(DateTime))
{
var dt = (DateTime)value;
@@ -806,35 +806,103 @@ private IMongoQuery BuildStringLengthQuery(Expression variableExpression, Expres
private IMongoQuery BuildStringQuery(MethodCallExpression methodCallExpression)
{
- if (methodCallExpression.Method.DeclaringType == typeof(string))
+ if (methodCallExpression.Method.DeclaringType != typeof(string))
{
- switch (methodCallExpression.Method.Name)
+ return null;
+ }
+
+ var arguments = methodCallExpression.Arguments.ToArray();
+ if (arguments.Length != 1)
+ {
+ return null;
+ }
+
+ var stringExpression = methodCallExpression.Object;
+ var constantExpression = arguments[0] as ConstantExpression;
+ if (constantExpression == null)
+ {
+ return null;
+ }
+
+ var pattern = (string)constantExpression.Value; // TODO: escape value
@optimiz3

optimiz3 Apr 9, 2012

Contributor

Should this be leveraging $lt + $gt for .StartsWith queries so ordered indexes can be used?

@optimiz3

optimiz3 Apr 9, 2012

Contributor

Is there a JIRA bug tracking the need to escape values? .Net provides Regex.Escape, however I'm not sure if this covers the full case of characters MongoDB may interpret.

@rstam

rstam Apr 9, 2012

Contributor

Good point, but I think the query optimizer already uses the index for "rooted" regular expressions (regular expressions that start with "^") so it should already be using an index if it's available.

@optimiz3

optimiz3 Apr 9, 2012

Contributor

Might want to add a comment to this effect then as others might otherwise try to add this in the future.

@rstam

rstam Apr 9, 2012

Contributor

I researched Javascript Regular expression escape conventions and compared it to .NET's Regex.Escape method and using the Escape method is fine (it escapes a few more characters than absolutely necessary but no harm is done by that).

@optimiz3

optimiz3 via email Apr 10, 2012

Contributor
+ switch (methodCallExpression.Method.Name)
+ {
+ case "Contains": pattern = ".*" + pattern + ".*"; break;
+ case "EndsWith": pattern = ".*" + pattern; break;
+ case "StartsWith": pattern = pattern + ".*"; break;
+ default: return null;
+ }
+
+ var caseInsensitive = false;
+ MethodCallExpression stringMethodCallExpression;
+ while ((stringMethodCallExpression = stringExpression as MethodCallExpression) != null)
+ {
+ var trimStart = false;
+ var trimEnd = false;
+ Expression trimCharsExpression = null;
+ switch (stringMethodCallExpression.Method.Name)
{
- case "Contains":
- case "EndsWith":
- case "StartsWith":
- var arguments = methodCallExpression.Arguments.ToArray();
- if (arguments.Length == 1)
- {
- var serializationInfo = GetSerializationInfo(methodCallExpression.Object);
- var valueExpression = arguments[0] as ConstantExpression;
- if (serializationInfo != null && valueExpression != null)
- {
- var s = (string)valueExpression.Value;
- BsonRegularExpression regex;
- switch (methodCallExpression.Method.Name)
- {
- case "Contains": regex = new BsonRegularExpression(s); break;
- case "EndsWith": regex = new BsonRegularExpression(s + "$"); break;
- case "StartsWith": regex = new BsonRegularExpression("^" + s); break;
- default: throw new InvalidOperationException("Unreachable code");
- }
- return Query.Matches(serializationInfo.ElementName, regex);
- }
- }
+ case "ToLower":
+ caseInsensitive = true;
+ break;
+ case "ToUpper":
+ caseInsensitive = true;
break;
+ case "Trim":
+ trimStart = true;
+ trimEnd = true;
+ trimCharsExpression = stringMethodCallExpression.Arguments.FirstOrDefault();
+ break;
+ case "TrimEnd":
+ trimEnd = true;
+ trimCharsExpression = stringMethodCallExpression.Arguments.First();
+ break;
+ case "TrimStart":
+ trimStart = true;
+ trimCharsExpression = stringMethodCallExpression.Arguments.First();
+ break;
+ default:
+ return null;
}
+
+ if (trimStart || trimEnd)
+ {
+ var trimCharsPattern = GetTrimCharsPattern(trimCharsExpression);
+ if (trimCharsPattern == null)
+ {
+ return null;
+ }
+
+ if (trimStart)
+ {
+ pattern = trimCharsPattern + pattern;
+ }
+ if (trimEnd)
+ {
+ pattern = pattern + trimCharsPattern;
+ }
+ }
+
+ stringExpression = stringMethodCallExpression.Object;
+ }
+
+ pattern = "^" + pattern + "$";
+ if (pattern.StartsWith("^.*"))
+ {
+ pattern = pattern.Substring(3);
+ }
+ if (pattern.EndsWith(".*$"))
+ {
+ pattern = pattern.Substring(0, pattern.Length - 3);
}
+
+ var serializationInfo = GetSerializationInfo(stringExpression);
+ if (serializationInfo != null)
+ {
+ var options = caseInsensitive ? "is" : "s";
+ return Query.Matches(serializationInfo.ElementName, new BsonRegularExpression(pattern, options));
+ }
+
return null;
}
@@ -1004,6 +1072,49 @@ private BsonSerializationInfo GetSerializationInfoMember(IBsonSerializer seriali
}
}
+ private string GetTrimCharsPattern(Expression trimCharsExpression)
+ {
+ if (trimCharsExpression == null)
+ {
+ return "\\s*";
+ }
+
+ var constantExpresion = trimCharsExpression as ConstantExpression;
+ if (constantExpresion == null || constantExpresion.Type != typeof(char[]))
+ {
+ return null;
+ }
+
+ var trimChars = (char[])constantExpresion.Value;
+ if (trimChars.Length == 0)
+ {
+ return "\\s*";
+ }
+
+ // build a pattern that matches the characters to be trimmed
+ var sb = new StringBuilder();
+ sb.Append("[");
+ var sawDash = false; // if dash is one of the characters it must be last in the pattern
+ foreach (var c in trimChars)
+ {
+ if (c == '-')
+ {
+ sawDash = true;
+ }
+ else
+ {
+ // TODO: handle special characters better
+ sb.Append(c.ToString());
+ }
+ }
+ if (sawDash)
+ {
+ sb.Append("-");
+ }
+ sb.Append("]*");
+ return sb.ToString();
+ }
+
private BsonValue SerializeValue(BsonSerializationInfo serializationInfo, object value)
{
var bsonDocument = new BsonDocument();
Oops, something went wrong.

0 comments on commit 2b9e193

Please sign in to comment.