Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
4 changes: 2 additions & 2 deletions azure-pipelines-public.yml
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ stages:
- job: macOS
enablePublishTestResults: true
pool:
vmImage: macOS-13
vmImage: macOS-15
variables:
# Rely on task Arcade injects, not auto-injected build step.
- skipComponentGovernanceDetection: true
Expand Down Expand Up @@ -151,7 +151,7 @@ stages:
- name: _HelixBuildConfig
value: $(_BuildConfig)
- name: HelixTargetQueues
value: Windows.10.Amd64.Open;OSX.13.ARM64.Open;[email protected]/dotnet-buildtools/prereqs:ubuntu-20.04-helix-sqlserver-amd64
value: Windows.10.Amd64.Open;OSX.15.ARM64.Open;[email protected]/dotnet-buildtools/prereqs:ubuntu-20.04-helix-sqlserver-amd64
- name: _HelixAccessToken
value: '' # Needed for public queues
steps:
Expand Down
4 changes: 2 additions & 2 deletions azure-pipelines.yml
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ extends:
- job: macOS
pool:
name: Azure Pipelines
image: macOS-13
image: macOS-15
os: macOS
variables:
# Rely on task Arcade injects, not auto-injected build step.
Expand Down Expand Up @@ -195,7 +195,7 @@ extends:
- name: _HelixBuildConfig
value: $(_BuildConfig)
- name: HelixTargetQueues
value: Windows.10.Amd64;OSX.13.ARM64;[email protected]/dotnet-buildtools/prereqs:ubuntu-20.04-helix-sqlserver-amd64
value: Windows.10.Amd64;OSX.15.ARM64;[email protected]/dotnet-buildtools/prereqs:ubuntu-20.04-helix-sqlserver-amd64
- name: _HelixAccessToken
# Needed for internal queues
value: $(HelixApiAccessToken)
Expand Down
58 changes: 48 additions & 10 deletions src/EFCore/Query/Internal/ExpressionTreeFuncletizer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,9 @@ public class ExpressionTreeFuncletizer : ExpressionVisitor
private static readonly bool UseOldBehavior35100 =
AppContext.TryGetSwitch("Microsoft.EntityFrameworkCore.Issue35100", out var enabled35100) && enabled35100;

private static readonly bool UseOldBehavior37176 =
AppContext.TryGetSwitch("Microsoft.EntityFrameworkCore.Issue37176", out var enabled37176) && enabled37176;

private static readonly MethodInfo ReadOnlyCollectionIndexerGetter = typeof(ReadOnlyCollection<Expression>).GetProperties()
.Single(p => p.GetIndexParameters() is { Length: 1 } indexParameters && indexParameters[0].ParameterType == typeof(int)).GetMethod!;

Expand Down Expand Up @@ -985,14 +988,32 @@ protected override Expression VisitMethodCall(MethodCallExpression methodCall)
switch (method.Name)
{
case nameof(MemoryExtensions.Contains)
when methodCall.Arguments is [var arg0, var arg1] && TryUnwrapSpanImplicitCast(arg0, out var unwrappedArg0):
when UseOldBehavior37176
&& methodCall.Arguments is [var arg0, var arg1]
&& TryUnwrapSpanImplicitCast(arg0, out var unwrappedArg0):
{
return Visit(
Call(
EnumerableMethods.Contains.MakeGenericMethod(methodCall.Method.GetGenericArguments()[0]),
unwrappedArg0, arg1));
}

// In .NET 10, MemoryExtensions.Contains has an overload that accepts a third, optional comparer, in addition to the older
// overload that accepts two parameters only.
case nameof(MemoryExtensions.Contains)
when !UseOldBehavior37176
&& methodCall.Arguments is [var spanArg, var valueArg, ..]
&& (methodCall.Arguments.Count is 2
|| methodCall.Arguments.Count is 3
&& methodCall.Arguments[2] is ConstantExpression { Value: null })
&& TryUnwrapSpanImplicitCast(spanArg, out var unwrappedSpanArg):
{
return Visit(
Call(
EnumerableMethods.Contains.MakeGenericMethod(method.GetGenericArguments()[0]),
unwrappedSpanArg, valueArg));
}

case nameof(MemoryExtensions.SequenceEqual)
when methodCall.Arguments is [var arg0, var arg1]
&& TryUnwrapSpanImplicitCast(arg0, out var unwrappedArg0)
Expand All @@ -1005,20 +1026,37 @@ protected override Expression VisitMethodCall(MethodCallExpression methodCall)

static bool TryUnwrapSpanImplicitCast(Expression expression, [NotNullWhen(true)] out Expression? result)
{
if (expression is MethodCallExpression
switch (expression)
{
// With newer versions of the SDK, the implicit cast is represented as a MethodCallExpression;
// with older versions, it's a Convert node.
case MethodCallExpression
{
Method: { Name: "op_Implicit", DeclaringType: { IsGenericType: true } implicitCastDeclaringType },
Arguments: [var unwrapped]
} when implicitCastDeclaringType.GetGenericTypeDefinition() is var genericTypeDefinition
&& (genericTypeDefinition == typeof(Span<>) || genericTypeDefinition == typeof(ReadOnlySpan<>)):
{
result = unwrapped;
return true;
}
&& implicitCastDeclaringType.GetGenericTypeDefinition() is var genericTypeDefinition
&& (genericTypeDefinition == typeof(Span<>) || genericTypeDefinition == typeof(ReadOnlySpan<>)))
{
result = unwrapped;
return true;
}

result = null;
return false;
case UnaryExpression
{
NodeType: ExpressionType.Convert,
Operand: var unwrapped,
Type: { IsGenericType: true } convertType
} when !UseOldBehavior37176 && convertType.GetGenericTypeDefinition() is var genericTypeDefinition
&& (genericTypeDefinition == typeof(Span<>) || genericTypeDefinition == typeof(ReadOnlySpan<>)):
{
result = unwrapped;
return true;
}

default:
result = null;
return false;
}
}
}

Expand Down
16 changes: 12 additions & 4 deletions src/Microsoft.Data.Sqlite.Core/SqliteBlob.cs
Original file line number Diff line number Diff line change
Expand Up @@ -206,8 +206,12 @@ public virtual int Read(Span<byte> buffer)
count = (int)(Length - position);
}

var rc = sqlite3_blob_read(_blob, buffer.Slice(0, count), (int)position);
SqliteException.ThrowExceptionForRC(rc, _connection.Handle);
// Newer sqlite3_blob_read returns error for 0-byte reads.
if (count > 0)
{
var rc = sqlite3_blob_read(_blob, buffer.Slice(0, count), (int)position);
SqliteException.ThrowExceptionForRC(rc, _connection.Handle);
}
_position += count;
return count;
}
Expand Down Expand Up @@ -280,8 +284,12 @@ public virtual void Write(ReadOnlySpan<byte> buffer)
throw new NotSupportedException(Resources.ResizeNotSupported);
}

var rc = sqlite3_blob_write(_blob, buffer.Slice(0, count), (int)position);
SqliteException.ThrowExceptionForRC(rc, _connection.Handle);
// Newer sqlite3_blob_write returns error for 0-byte writes.
if (count > 0)
{
var rc = sqlite3_blob_write(_blob, buffer.Slice(0, count), (int)position);
SqliteException.ThrowExceptionForRC(rc, _connection.Handle);
}
_position += count;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -997,6 +997,50 @@ FROM root c
""");
});

public override Task Contains_on_Enumerable(bool async)
=> CosmosTestHelpers.Instance.NoSyncTest(
async, async a =>
{
await base.Contains_on_Enumerable(a);

AssertSql(
"""
SELECT VALUE c
FROM root c
WHERE c["Int"] IN (10, 999)
""");
});

public override Task Contains_on_MemoryExtensions(bool async)
=> CosmosTestHelpers.Instance.NoSyncTest(
async, async a =>
{
await base.Contains_on_MemoryExtensions(a);

AssertSql(
"""
SELECT VALUE c
FROM root c
WHERE c["Int"] IN (10, 999)
""");
});

#if NET10_0_OR_GREATER
public override Task Contains_with_MemoryExtensions_with_null_comparer(bool async)
=> CosmosTestHelpers.Instance.NoSyncTest(
async, async a =>
{
await base.Contains_with_MemoryExtensions_with_null_comparer(a);

AssertSql(
"""
SELECT VALUE c
FROM root c
WHERE c["Int"] IN (10, 999)
""");
});
#endif

public override Task Column_collection_Length(bool async)
=> CosmosTestHelpers.Instance.NoSyncTest(
async, async a =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -607,6 +607,34 @@ public virtual Task Column_collection_of_bools_Contains(bool async)
async,
ss => ss.Set<PrimitiveCollectionsEntity>().Where(c => c.Bools.Contains(true)));

// C# 14 first-class spans caused MemoryExtensions.Contains to get resolved instead of Enumerable.Contains.
// The following tests that the various overloads are all supported.
[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task Contains_on_Enumerable(bool async)
=> AssertQuery(
async,
ss => ss.Set<PrimitiveCollectionsEntity>().Where(c => Enumerable.Contains(new[] { 10, 999 }, c.Int)));

// C# 14 first-class spans caused MemoryExtensions.Contains to get resolved instead of Enumerable.Contains.
// The following tests that the various overloads are all supported.
[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task Contains_on_MemoryExtensions(bool async)
=> AssertQuery(
async,
ss => ss.Set<PrimitiveCollectionsEntity>().Where(c => MemoryExtensions.Contains(new[] { 10, 999 }, c.Int)));

// Note that we don't test EF 8/9 with .NET 10; this test is here for completeness/documentation purposes.
#if NET10_0_OR_GREATER
[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task Contains_with_MemoryExtensions_with_null_comparer(bool async)
=> AssertQuery(
async,
ss => ss.Set<PrimitiveCollectionsEntity>().Where(c => MemoryExtensions.Contains(new[] { 10, 999 }, c.Int, comparer: null)));
#endif

[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task Column_collection_Count_method(bool async)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -761,6 +761,45 @@ await context.Database.SqlQuery<string>($"SELECT [Bools] AS [Value] FROM [Primit
.SingleAsync());
}

public override async Task Contains_on_Enumerable(bool async)
{
await base.Contains_on_Enumerable(async);

AssertSql(
"""
SELECT [p].[Id], [p].[Bool], [p].[Bools], [p].[DateTime], [p].[DateTimes], [p].[Enum], [p].[Enums], [p].[Int], [p].[Ints], [p].[NullableInt], [p].[NullableInts], [p].[NullableString], [p].[NullableStrings], [p].[String], [p].[Strings]
FROM [PrimitiveCollectionsEntity] AS [p]
WHERE [p].[Int] IN (10, 999)
""");
}


public override async Task Contains_on_MemoryExtensions(bool async)
{
await base.Contains_on_MemoryExtensions(async);

AssertSql(
"""
SELECT [p].[Id], [p].[Bool], [p].[Bools], [p].[DateTime], [p].[DateTimes], [p].[Enum], [p].[Enums], [p].[Int], [p].[Ints], [p].[NullableInt], [p].[NullableInts], [p].[NullableString], [p].[NullableStrings], [p].[String], [p].[Strings]
FROM [PrimitiveCollectionsEntity] AS [p]
WHERE [p].[Int] IN (10, 999)
""");
}

#if NET10_0_OR_GREATER
public override async Task Contains_with_MemoryExtensions_with_null_comparer(bool async)
{
await base.Contains_with_MemoryExtensions_with_null_comparer(async);

AssertSql(
"""
SELECT [p].[Id], [p].[Bool], [p].[Bools], [p].[DateTime], [p].[DateTimes], [p].[Enum], [p].[Enums], [p].[Int], [p].[Ints], [p].[NullableInt], [p].[NullableInts], [p].[NullableString], [p].[NullableStrings], [p].[String], [p].[Strings]
FROM [PrimitiveCollectionsEntity] AS [p]
WHERE [p].[Int] IN (10, 999)
""");
}
#endif

public override Task Column_collection_Count_method(bool async)
=> AssertCompatibilityLevelTooLow(() => base.Column_collection_Count_method(async));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -955,6 +955,45 @@ await context.Database.SqlQuery<string>($"SELECT [Bools] AS [Value] FROM [Primit
.SingleAsync());
}

public override async Task Contains_on_Enumerable(bool async)
{
await base.Contains_on_Enumerable(async);

AssertSql(
"""
SELECT [p].[Id], [p].[Bool], [p].[Bools], [p].[DateTime], [p].[DateTimes], [p].[Enum], [p].[Enums], [p].[Int], [p].[Ints], [p].[NullableInt], [p].[NullableInts], [p].[NullableString], [p].[NullableStrings], [p].[String], [p].[Strings]
FROM [PrimitiveCollectionsEntity] AS [p]
WHERE [p].[Int] IN (10, 999)
""");
}


public override async Task Contains_on_MemoryExtensions(bool async)
{
await base.Contains_on_MemoryExtensions(async);

AssertSql(
"""
SELECT [p].[Id], [p].[Bool], [p].[Bools], [p].[DateTime], [p].[DateTimes], [p].[Enum], [p].[Enums], [p].[Int], [p].[Ints], [p].[NullableInt], [p].[NullableInts], [p].[NullableString], [p].[NullableStrings], [p].[String], [p].[Strings]
FROM [PrimitiveCollectionsEntity] AS [p]
WHERE [p].[Int] IN (10, 999)
""");
}

#if NET10_0_OR_GREATER
public override async Task Contains_with_MemoryExtensions_with_null_comparer(bool async)
{
await base.Contains_with_MemoryExtensions_with_null_comparer(async);

AssertSql(
"""
SELECT [p].[Id], [p].[Bool], [p].[Bools], [p].[DateTime], [p].[DateTimes], [p].[Enum], [p].[Enums], [p].[Int], [p].[Ints], [p].[NullableInt], [p].[NullableInts], [p].[NullableString], [p].[NullableStrings], [p].[String], [p].[Strings]
FROM [PrimitiveCollectionsEntity] AS [p]
WHERE [p].[Int] IN (10, 999)
""");
}
#endif

public override async Task Column_collection_Count_method(bool async)
{
await base.Column_collection_Count_method(async);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -929,6 +929,45 @@ await context.Database.SqlQuery<string>($"SELECT [Bools] AS [Value] FROM [Primit
.SingleAsync());
}

public override async Task Contains_on_Enumerable(bool async)
{
await base.Contains_on_Enumerable(async);

AssertSql(
"""
SELECT [p].[Id], [p].[Bool], [p].[Bools], [p].[DateTime], [p].[DateTimes], [p].[Enum], [p].[Enums], [p].[Int], [p].[Ints], [p].[NullableInt], [p].[NullableInts], [p].[NullableString], [p].[NullableStrings], [p].[String], [p].[Strings]
FROM [PrimitiveCollectionsEntity] AS [p]
WHERE [p].[Int] IN (10, 999)
""");
}


public override async Task Contains_on_MemoryExtensions(bool async)
{
await base.Contains_on_MemoryExtensions(async);

AssertSql(
"""
SELECT [p].[Id], [p].[Bool], [p].[Bools], [p].[DateTime], [p].[DateTimes], [p].[Enum], [p].[Enums], [p].[Int], [p].[Ints], [p].[NullableInt], [p].[NullableInts], [p].[NullableString], [p].[NullableStrings], [p].[String], [p].[Strings]
FROM [PrimitiveCollectionsEntity] AS [p]
WHERE [p].[Int] IN (10, 999)
""");
}

#if NET10_0_OR_GREATER
public override async Task Contains_with_MemoryExtensions_with_null_comparer(bool async)
{
await base.Contains_with_MemoryExtensions_with_null_comparer(async);

AssertSql(
"""
SELECT [p].[Id], [p].[Bool], [p].[Bools], [p].[DateTime], [p].[DateTimes], [p].[Enum], [p].[Enums], [p].[Int], [p].[Ints], [p].[NullableInt], [p].[NullableInts], [p].[NullableString], [p].[NullableStrings], [p].[String], [p].[Strings]
FROM [PrimitiveCollectionsEntity] AS [p]
WHERE [p].[Int] IN (10, 999)
""");
}
#endif

public override async Task Column_collection_Count_method(bool async)
{
await base.Column_collection_Count_method(async);
Expand Down
Loading