parent
293ac81fe1
commit
f76a390a4e
|
|
@ -193,7 +193,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
{
|
{
|
||||||
Debug.Assert(candidate.StartsWith(prefix, StringComparison.OrdinalIgnoreCase));
|
Debug.Assert(candidate.StartsWith(prefix, StringComparison.OrdinalIgnoreCase));
|
||||||
|
|
||||||
// Ok, so now we have a candiate that starts with the prefix. If the candidate is longer than
|
// Okay, now we have a candidate that starts with the prefix. If the candidate is longer than
|
||||||
// the prefix, we need to look at the next character and see if it's a delimiter.
|
// the prefix, we need to look at the next character and see if it's a delimiter.
|
||||||
if (candidate.Length == prefix.Length)
|
if (candidate.Length == prefix.Length)
|
||||||
{
|
{
|
||||||
|
|
@ -208,15 +208,17 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
return pivot;
|
return pivot;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ok, so the candidate has some extra text. We need to keep searching, but we know
|
// Okay, so the candidate has some extra text. We need to keep searching.
|
||||||
// the candidate string is considered "greater" than the prefix, so treat it as-if
|
|
||||||
// the comparer returned a negative number.
|
|
||||||
//
|
//
|
||||||
// Ex:
|
// Can often assume the candidate string is greater than the prefix e.g. that works for
|
||||||
// prefix: product
|
// prefix: product
|
||||||
// candidate: productId
|
// candidate: productId
|
||||||
|
// most of the time because "product", "product.id", etc. will sort earlier than "productId". But,
|
||||||
|
// the assumption isn't correct if "product[0]" is also in _sortedValues because that value will
|
||||||
|
// sort later than "productId".
|
||||||
//
|
//
|
||||||
compare = -1;
|
// Fall back to brute force and cover all the cases.
|
||||||
|
return LinearSearch(prefix, start, end);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (compare > 0)
|
if (compare > 0)
|
||||||
|
|
@ -231,5 +233,49 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
|
|
||||||
return ~start;
|
return ~start;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private int LinearSearch(string prefix, int start, int end)
|
||||||
|
{
|
||||||
|
for (; start <= end; start++)
|
||||||
|
{
|
||||||
|
var candidate = _sortedValues[start];
|
||||||
|
var compare = string.Compare(
|
||||||
|
prefix,
|
||||||
|
0,
|
||||||
|
candidate,
|
||||||
|
0,
|
||||||
|
prefix.Length,
|
||||||
|
StringComparison.OrdinalIgnoreCase);
|
||||||
|
if (compare == 0)
|
||||||
|
{
|
||||||
|
Debug.Assert(candidate.StartsWith(prefix, StringComparison.OrdinalIgnoreCase));
|
||||||
|
|
||||||
|
// Okay, now we have a candidate that starts with the prefix. If the candidate is longer than
|
||||||
|
// the prefix, we need to look at the next character and see if it's a delimiter.
|
||||||
|
if (candidate.Length == prefix.Length)
|
||||||
|
{
|
||||||
|
// Exact match
|
||||||
|
return start;
|
||||||
|
}
|
||||||
|
|
||||||
|
var c = candidate[prefix.Length];
|
||||||
|
if (c == '.' || c == '[')
|
||||||
|
{
|
||||||
|
// Match, followed by delimiter
|
||||||
|
return start;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keep checking until we've passed all StartsWith() matches.
|
||||||
|
}
|
||||||
|
|
||||||
|
if (compare < 0)
|
||||||
|
{
|
||||||
|
// Prefix is less than the candidate. No potential matches left.
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ~start;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -137,6 +137,54 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
Assert.True(result);
|
Assert.True(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData(1)]
|
||||||
|
[InlineData(2)]
|
||||||
|
[InlineData(3)]
|
||||||
|
[InlineData(4)]
|
||||||
|
[InlineData(5)]
|
||||||
|
public void ContainsPrefix_HasEntries_PartialAndPrefixMatch_WithDot(int partialMatches)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var keys = new string[partialMatches + 1];
|
||||||
|
for (var i = 0; i < partialMatches; i++)
|
||||||
|
{
|
||||||
|
keys[i] = $"aa[{i}]";
|
||||||
|
}
|
||||||
|
keys[partialMatches] = "a.b"; // Sorted before all "aa" keys.
|
||||||
|
var container = new PrefixContainer(keys);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = container.ContainsPrefix("a");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.True(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData(1)]
|
||||||
|
[InlineData(2)]
|
||||||
|
[InlineData(3)]
|
||||||
|
[InlineData(4)]
|
||||||
|
[InlineData(5)]
|
||||||
|
public void ContainsPrefix_HasEntries_PartialAndPrefixMatch_WithSquareBrace(int partialMatches)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var keys = new string[partialMatches + 1];
|
||||||
|
for (var i = 0; i < partialMatches; i++)
|
||||||
|
{
|
||||||
|
keys[i] = $"aa[{i}]";
|
||||||
|
}
|
||||||
|
keys[partialMatches] = "a[0]"; // Sorted after all "aa" keys.
|
||||||
|
var container = new PrefixContainer(keys);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = container.ContainsPrefix("a");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.True(result);
|
||||||
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[InlineData("")]
|
[InlineData("")]
|
||||||
[InlineData("foo")]
|
[InlineData("foo")]
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue