Skip to content

Process Span<>/ReadOnlySpan<>/.Contains() cases in LINQ Expression #328

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Jan 9, 2025
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .github/workflows/test-sql.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ jobs:
timezoneLinux: "UTC"
- name: Setup .NET
uses: actions/setup-dotnet@v4
with: { dotnet-version: 9 }
with:
dotnet-version: 9.x
- name: Setup MSSQL Tools
uses: rails-sqlserver/setup-mssql@v1
with:
Expand Down
13 changes: 13 additions & 0 deletions Orm/Xtensive.Orm.Tests/Linq/InTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -657,5 +657,18 @@ where track.Name.In(IncludeAlgorithm.TableValuedParameter, names)
select track
).ToList();
}

// Related to https://github.com/DataObjects-NET/dataobjects-net/issues/402
[Test]
public void ReadOnlySpanContains_Test()
{
var result = ReadOnlySpanContains_GetCustomers(["Michelle", "Jack"]);
Assert.AreEqual(2, result.Count);
Assert.IsTrue(result.Contains("Michelle"));
Assert.IsTrue(result.Contains("Jack"));
}

private List<string> ReadOnlySpanContains_GetCustomers(string[] customerNames) =>
(from c in Session.Query.All<Customer>() where MemoryExtensions.Contains(customerNames, c.FirstName) select c.FirstName).ToList();
}
}
6 changes: 4 additions & 2 deletions Orm/Xtensive.Orm/Orm/Linq/ExpressionExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,9 @@ public static bool IsLocalCollection(this Expression expression, TranslatorConte
var expressionType => !IsEntitySet(expressionType)
&& !expression.IsSubqueryExpression()
&& expressionType != WellKnownTypes.String
&& expressionType.IsOfGenericInterface(WellKnownInterfaces.EnumerableOfT)
&& (expressionType.IsOfGenericInterface(WellKnownInterfaces.EnumerableOfT)
|| expressionType.IsOfGenericType(WellKnownTypes.ReadOnlySpanOfT)
|| expressionType.IsOfGenericType(WellKnownTypes.SpanOfT))
&& (IsEvaluableCollection(context, expression, expressionType) ||
IsForeignQuery(expression))
};
Expand Down Expand Up @@ -203,4 +205,4 @@ public static ParameterInfo[] GetConstructorParameters(this NewExpression expres
return expression.Constructor.GetParameters();
}
}
}
}
3 changes: 2 additions & 1 deletion Orm/Xtensive.Orm/Orm/Linq/QueryHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,8 @@ public static void TryAddConvarianceCast(ref Expression source, Type baseType)

public static Type GetSequenceElementType(Type type)
{
var sequenceType = type.GetGenericInterface(WellKnownInterfaces.EnumerableOfT);
var sequenceType = type.GetGenericInterface(WellKnownInterfaces.EnumerableOfT)
?? (type.IsOfGenericType(WellKnownTypes.ReadOnlySpanOfT) || type.IsOfGenericType(WellKnownTypes.SpanOfT) ? type : null);
return sequenceType?.GetGenericArguments()[0];
}

Expand Down
21 changes: 13 additions & 8 deletions Orm/Xtensive.Orm/Orm/Linq/Translator.Expressions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -541,15 +541,20 @@ protected override Expression VisitMethodCall(MethodCallExpression mc)
}
}


// Process local collections
if (mc.Object.IsLocalCollection(context)) {
// IList.Contains
// List.Contains
// Array.Contains
var parameters = method.GetParameters();
if (methodName == nameof(ICollection<int>.Contains) && parameters.Length == 1)
return VisitContains(mc.Object, mc.Arguments[0], false);
if (methodName == nameof(ICollection<int>.Contains)) {
if (mc.Object.IsLocalCollection(context)) {
// IList.Contains
// List.Contains
// Array.Contains
var parameters = method.GetParameters();
if (parameters.Length == 1)
return VisitContains(mc.Object, mc.Arguments[0], false);
}
else if (methodDeclaringType == typeof(MemoryExtensions)) {
// ReadOnlySpan<>.Contains
return VisitContains(mc.Arguments[0], mc.Arguments[1], false);
}
}

var result = base.VisitMethodCall(mc);
Expand Down
11 changes: 9 additions & 2 deletions Orm/Xtensive.Orm/Orm/Linq/Translator.Queryable.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1767,9 +1767,16 @@ private ProjectionExpression VisitSequence(Expression sequenceExpression, Expres
string.Format(Strings.ExExpressionXIsNotASequence, expressionPart.ToString(true)));
}

private ProjectionExpression VisitLocalCollectionSequence<TItem>(Expression sequence) =>
CreateLocalCollectionProjectionExpression(typeof(TItem), ParameterAccessorFactory.CreateAccessorExpression<IEnumerable<TItem>>(
private ProjectionExpression VisitLocalCollectionSequence<TItem>(Expression sequence)
{
var type = sequence.Type;
if (type.IsOfGenericType(WellKnownTypes.ReadOnlySpanOfT) || type.IsOfGenericType(WellKnownTypes.SpanOfT)) {
sequence = Expression.Call(sequence, type.GetMethod("ToArray"));
}

return CreateLocalCollectionProjectionExpression(typeof(TItem), ParameterAccessorFactory.CreateAccessorExpression<IEnumerable<TItem>>(
compiledQueryScope is not null ? compiledQueryScope.QueryParameterReplacer.Replace(sequence) : sequence).CachingCompile(), this, sequence);
}

private Expression VisitContainsAny(Expression setA, Expression setB, bool isRoot, Type elementType)
{
Expand Down
5 changes: 4 additions & 1 deletion Orm/Xtensive.Orm/Reflection/WellKnownTypes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -81,5 +81,8 @@ internal static class WellKnownTypes
public static readonly Type ObjectArray = typeof(object[]);

public static readonly Type DefaultMemberAttribute = typeof(DefaultMemberAttribute);

public static readonly Type ReadOnlySpanOfT = typeof(ReadOnlySpan<>);
public static readonly Type SpanOfT = typeof(Span<>);
}
}
}
Loading