This is an automated email from the ASF dual-hosted git repository.
gurustron pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/ignite-3.git
The following commit(s) were added to refs/heads/main by this push:
new 479eeecc3c3 IGNITE-27068 .NET: LINQ: Support C# 14 span methods (#7933)
479eeecc3c3 is described below
commit 479eeecc3c3f88a789e162fbcb382579f8b57530
Author: gurustron <[email protected]>
AuthorDate: Mon Apr 6 15:52:43 2026 +0300
IGNITE-27068 .NET: LINQ: Support C# 14 span methods (#7933)
Co-authored-by: Pavel Tupitsyn <[email protected]>
Co-authored-by: sstronchinskiy <[email protected]>
---
.../Linq/LinqSqlGenerationTests.cs | 8 +++
.../Internal/Linq/IgniteQueryParser.cs | 75 ++++++++++++++++++++--
2 files changed, 79 insertions(+), 4 deletions(-)
diff --git
a/modules/platforms/dotnet/Apache.Ignite.Tests/Linq/LinqSqlGenerationTests.cs
b/modules/platforms/dotnet/Apache.Ignite.Tests/Linq/LinqSqlGenerationTests.cs
index bd07e82a0ff..9c3254429d2 100644
---
a/modules/platforms/dotnet/Apache.Ignite.Tests/Linq/LinqSqlGenerationTests.cs
+++
b/modules/platforms/dotnet/Apache.Ignite.Tests/Linq/LinqSqlGenerationTests.cs
@@ -158,6 +158,14 @@ public partial class LinqSqlGenerationTests
.Take(3)
.ToList());
+ [Test]
+ public void TestContains() =>
+ AssertSql("select (_T0.KEY IN (?, ?)) from PUBLIC.TBL1 as _T0", q =>
+ {
+ var keys = new long[] { 4, 2 };
+ return q.Select(x => keys.Contains(x.Key)).ToList();
+ });
+
[Test]
public void TestFirst() =>
AssertSql("select _T0.KEY, _T0.VAL from PUBLIC.TBL1 as _T0 limit 1", q
=> q.First());
diff --git
a/modules/platforms/dotnet/Apache.Ignite/Internal/Linq/IgniteQueryParser.cs
b/modules/platforms/dotnet/Apache.Ignite/Internal/Linq/IgniteQueryParser.cs
index 3ddcfa636cc..f3b183c8715 100644
--- a/modules/platforms/dotnet/Apache.Ignite/Internal/Linq/IgniteQueryParser.cs
+++ b/modules/platforms/dotnet/Apache.Ignite/Internal/Linq/IgniteQueryParser.cs
@@ -17,6 +17,12 @@
namespace Apache.Ignite.Internal.Linq;
+using System;
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+using System.Linq;
+using System.Linq.Expressions;
+using System.Reflection;
using System.Threading;
using Dml;
using Remotion.Linq.Parsing.ExpressionVisitors.Transformation;
@@ -44,6 +50,7 @@ internal static class IgniteQueryParser
private static QueryParser CreateParser()
{
var transformerRegistry =
ExpressionTransformerRegistry.CreateDefault();
+ transformerRegistry.Register(new
MemoryExtensionsContainsExpressionTransformer());
var proc = CreateCompoundProcessor(transformerRegistry);
@@ -78,16 +85,76 @@ internal static class IgniteQueryParser
return new CompoundExpressionTreeProcessor(
new IExpressionTreeProcessor[]
{
- new PartialEvaluatingExpressionTreeProcessor(new
NullEvaluatableExpressionFilter()),
+ new PartialEvaluatingExpressionTreeProcessor(new
IgniteEvaluatableExpressionFilter()),
new TransformingExpressionTreeProcessor(transformationProvider)
});
}
/// <summary>
- /// Empty implementation of IEvaluatableExpressionFilter.
+ /// Implementation of IEvaluatableExpressionFilter.
/// </summary>
- private sealed class NullEvaluatableExpressionFilter :
EvaluatableExpressionFilterBase
+ private sealed class IgniteEvaluatableExpressionFilter :
EvaluatableExpressionFilterBase
{
- // No-op.
+ // Ignores implicit ReadOnlySpan conversion to support C# 14 first
class span Contains.
+ public override bool IsEvaluatableMethodCall(MethodCallExpression node)
+ {
+ ArgumentNullException.ThrowIfNull(node);
+ if
(MemoryExtensionsContainsExpressionTransformer.IsSpanImplicitConversion(node))
+ {
+ return false;
+ }
+
+ return base.IsEvaluatableMethodCall(node);
+ }
+ }
+
+ /// <summary>
+ /// Implementation of IExpressionTransformer handling C# 14 first class
span Contains.
+ /// </summary>
+ private sealed class MemoryExtensionsContainsExpressionTransformer :
IExpressionTransformer<MethodCallExpression>
+ {
+ private static readonly MethodInfo SourceMethodInfo =
typeof(MemoryExtensions)
+ .GetMethod(nameof(MemoryExtensions.Contains), [
+
typeof(ReadOnlySpan<>).MakeGenericType(Type.MakeGenericMethodParameter(0)),
+ Type.MakeGenericMethodParameter(0)
+ ])!;
+
+ private static readonly MethodInfo TargetMethodInfo =
typeof(Enumerable)
+ .GetMethod(nameof(Enumerable.Contains), [
+
typeof(IEnumerable<>).MakeGenericType(Type.MakeGenericMethodParameter(0)),
+ Type.MakeGenericMethodParameter(0)
+ ])!;
+
+ [SuppressMessage("Performance", "CA1819:Properties should not return
arrays", Justification = "Interface impl")]
+ public ExpressionType[] SupportedExpressionTypes =>
[ExpressionType.Call];
+
+ public static bool IsSpanImplicitConversion([NotNullWhen(true)]
MethodCallExpression? node) =>
+ node?.Method is { IsSpecialName: true, Name: "op_Implicit" }
+ && node.Method.DeclaringType is { IsGenericType: true }
+ && node.Method.DeclaringType.GetGenericTypeDefinition() ==
typeof(ReadOnlySpan<>);
+
+ public Expression Transform(MethodCallExpression expression)
+ {
+ if (expression.Method.IsConstructedGenericMethod &&
expression.Method.GetGenericMethodDefinition() == SourceMethodInfo)
+ {
+ var genericType = expression.Method.GetGenericArguments()[0];
+
+ var enumerableParameter = expression.Arguments[0] as
MethodCallExpression;
+ if (!IsSpanImplicitConversion(enumerableParameter))
+ {
+ throw new NotSupportedException("Was not able to process
parameter for MemoryExtensions.Contains, " +
+ "expected implicit
conversion, got: " + expression.Arguments[0]);
+ }
+
+ var argument = enumerableParameter.Arguments[0]!;
+
+ var targetProp = expression.Arguments[1];
+
+ var target = TargetMethodInfo.MakeGenericMethod(genericType);
+ return Expression.Call(target, argument, targetProp);
+ }
+
+ return expression;
+ }
}
}