Skip to content

Commit

Permalink
Introduce liftable constants to shaper to prepare for precompilation. (
Browse files Browse the repository at this point in the history
…#33351)

Fix materializer code to replace non-primitive constants with liftable constants.
Precompilation is opt-in in QueryCompilationContext, switched on for relational, off for everything else.

Architecture, design and initial implementation done by @roji, polish done by @maumar

Part of #25009
  • Loading branch information
maumar committed Apr 12, 2024
1 parent 97fc62a commit f0e88d4
Show file tree
Hide file tree
Showing 126 changed files with 3,783 additions and 548 deletions.
1 change: 1 addition & 0 deletions EFCore.sln.DotSettings
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,7 @@ The .NET Foundation licenses this file to you under the MIT license.
<s:Boolean x:Key="/Default/UserDictionary/Words/=Includable/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=initializers/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=keyless/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=liftable/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Lite_0027s/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=materializer/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=materializers/@EntryIndexedValue">True</s:Boolean>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
// The .NET Foundation licenses this file to you under the MIT license.

using Microsoft.EntityFrameworkCore.InMemory.Internal;
using Microsoft.EntityFrameworkCore.Internal;
using ExpressionExtensions = Microsoft.EntityFrameworkCore.Infrastructure.ExpressionExtensions;

namespace Microsoft.EntityFrameworkCore.InMemory.Query.Internal;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ public static readonly IDictionary<Type, ServiceCharacteristics> RelationalServi
{ typeof(IQuerySqlGeneratorFactory), new ServiceCharacteristics(ServiceLifetime.Singleton) },
{ typeof(IModificationCommandFactory), new ServiceCharacteristics(ServiceLifetime.Singleton) },
{ typeof(ISqlAliasManagerFactory), new ServiceCharacteristics(ServiceLifetime.Singleton) },
{ typeof(IRelationalLiftableConstantFactory), new ServiceCharacteristics(ServiceLifetime.Singleton) },
{ typeof(ICommandBatchPreparer), new ServiceCharacteristics(ServiceLifetime.Scoped) },
{ typeof(IModificationCommandBatchFactory), new ServiceCharacteristics(ServiceLifetime.Scoped) },
{ typeof(IRelationalSqlTranslatingExpressionVisitorFactory), new ServiceCharacteristics(ServiceLifetime.Scoped) },
Expand Down Expand Up @@ -189,6 +190,9 @@ public override EntityFrameworkServicesBuilder TryAddCoreServices()
TryAdd<IQueryCompilationContextFactory, RelationalQueryCompilationContextFactory>();
TryAdd<IAdHocMapper, RelationalAdHocMapper>();
TryAdd<ISqlAliasManagerFactory, SqlAliasManagerFactory>();
TryAdd<ILiftableConstantFactory>(p => p.GetRequiredService<IRelationalLiftableConstantFactory>());
TryAdd<IRelationalLiftableConstantFactory, RelationalLiftableConstantFactory>();
TryAdd<ILiftableConstantProcessor, RelationalLiftableConstantProcessor>();

ServiceCollectionMap.GetInfrastructure()
.AddDependencySingleton<RelationalSqlGenerationHelperDependencies>()
Expand All @@ -204,6 +208,7 @@ public override EntityFrameworkServicesBuilder TryAddCoreServices()
.AddDependencySingleton<RelationalEvaluatableExpressionFilterDependencies>()
.AddDependencySingleton<RelationalModelDependencies>()
.AddDependencySingleton<RelationalModelRuntimeInitializerDependencies>()
.AddDependencySingleton<RelationalLiftableConstantExpressionDependencies>()
.AddDependencyScoped<MigrationsSqlGeneratorDependencies>()
.AddDependencyScoped<RelationalConventionSetBuilderDependencies>()
.AddDependencyScoped<ModificationCommandBatchFactoryDependencies>()
Expand Down
28 changes: 28 additions & 0 deletions src/EFCore.Relational/Query/IRelationalLiftableConstantFactory.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Diagnostics.CodeAnalysis;

namespace Microsoft.EntityFrameworkCore.Query;

/// <summary>
/// This is an experimental API used by the Entity Framework Core feature and it is not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
[Experimental(EFDiagnostics.PrecompiledQueryExperimental)]
public interface IRelationalLiftableConstantFactory : ILiftableConstantFactory
{
/// <summary>
/// This is an experimental API used by the Entity Framework Core feature and it is not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
LiftableConstantExpression CreateLiftableConstant(
object? originalValue,
Expression<Func<RelationalMaterializerLiftableConstantContext, object>> resolverExpression,
string variableName,
Type type);
}
5 changes: 1 addition & 4 deletions src/EFCore.Relational/Query/Internal/BufferedDataReader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -836,10 +836,7 @@ public ulong GetUInt64(int ordinal)
public object GetValue(int ordinal)
=> GetFieldValue<object>(ordinal);

#pragma warning disable IDE0060 // Remove unused parameter
public static int GetValues(object[] values)
#pragma warning restore IDE0060 // Remove unused parameter
=> throw new NotSupportedException();
public static int GetValues(object[] values) => throw new NotSupportedException();

public T GetFieldValue<T>(int ordinal)
=> (_columnTypeCases[ordinal]) switch
Expand Down
36 changes: 36 additions & 0 deletions src/EFCore.Relational/Query/Internal/FromSqlQueryingEnumerable.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,42 @@

namespace Microsoft.EntityFrameworkCore.Query.Internal;

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public static class FromSqlQueryingEnumerable
{
/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public static FromSqlQueryingEnumerable<T> Create<T>(
RelationalQueryContext relationalQueryContext,
RelationalCommandCache relationalCommandCache,
IReadOnlyList<ReaderColumn?>? readerColumns,
IReadOnlyList<string> columnNames,
Func<QueryContext, DbDataReader, int[], T> shaper,
Type contextType,
bool standAloneStateManager,
bool detailedErrorsEnabled,
bool threadSafetyChecksEnabled)
=> new(
relationalQueryContext,
relationalCommandCache,
readerColumns,
columnNames,
shaper,
contextType,
standAloneStateManager,
detailedErrorsEnabled,
threadSafetyChecksEnabled);
}

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,46 @@

namespace Microsoft.EntityFrameworkCore.Query.Internal;

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public static class GroupBySingleQueryingEnumerable
{
/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public static GroupBySingleQueryingEnumerable<TKey, TElement> Create<TKey, TElement>(
RelationalQueryContext relationalQueryContext,
RelationalCommandCache relationalCommandCache,
IReadOnlyList<ReaderColumn?>? readerColumns,
Func<QueryContext, DbDataReader, TKey> keySelector,
Func<QueryContext, DbDataReader, object[]> keyIdentifier,
IReadOnlyList<Func<object, object, bool>> keyIdentifierValueComparers,
Func<QueryContext, DbDataReader, ResultContext, SingleQueryResultCoordinator, TElement> elementSelector,
Type contextType,
bool standAloneStateManager,
bool detailedErrorsEnabled,
bool threadSafetyChecksEnabled)
=> new(
relationalQueryContext,
relationalCommandCache,
readerColumns,
keySelector,
keyIdentifier,
keyIdentifierValueComparers,
elementSelector,
contextType,
standAloneStateManager,
detailedErrorsEnabled,
threadSafetyChecksEnabled);
}

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
Expand All @@ -20,7 +60,7 @@ public class GroupBySingleQueryingEnumerable<TKey, TElement>
private readonly IReadOnlyList<ReaderColumn?>? _readerColumns;
private readonly Func<QueryContext, DbDataReader, TKey> _keySelector;
private readonly Func<QueryContext, DbDataReader, object[]> _keyIdentifier;
private readonly IReadOnlyList<ValueComparer> _keyIdentifierValueComparers;
private readonly IReadOnlyList<Func<object, object, bool>> _keyIdentifierValueComparers;
private readonly Func<QueryContext, DbDataReader, ResultContext, SingleQueryResultCoordinator, TElement> _elementSelector;
private readonly Type _contextType;
private readonly IDiagnosticsLogger<DbLoggerCategory.Query> _queryLogger;
Expand All @@ -40,7 +80,7 @@ public GroupBySingleQueryingEnumerable(
IReadOnlyList<ReaderColumn?>? readerColumns,
Func<QueryContext, DbDataReader, TKey> keySelector,
Func<QueryContext, DbDataReader, object[]> keyIdentifier,
IReadOnlyList<ValueComparer> keyIdentifierValueComparers,
IReadOnlyList<Func<object, object, bool>> keyIdentifierValueComparers,
Func<QueryContext, DbDataReader, ResultContext, SingleQueryResultCoordinator, TElement> elementSelector,
Type contextType,
bool standAloneStateManager,
Expand Down Expand Up @@ -139,12 +179,12 @@ IEnumerator IEnumerable.GetEnumerator()
=> GetEnumerator();
}

private static bool CompareIdentifiers(IReadOnlyList<ValueComparer> valueComparers, object[] left, object[] right)
private static bool CompareIdentifiers(IReadOnlyList<Func<object, object, bool>> valueComparers, object[] left, object[] right)
{
// Ignoring size check on all for perf as they should be same unless bug in code.
for (var i = 0; i < left.Length; i++)
{
if (!valueComparers[i].Equals(left[i], right[i]))
if (!valueComparers[i](left[i], right[i]))
{
return false;
}
Expand All @@ -160,7 +200,7 @@ private sealed class Enumerator : IEnumerator<IGrouping<TKey, TElement>>
private readonly IReadOnlyList<ReaderColumn?>? _readerColumns;
private readonly Func<QueryContext, DbDataReader, TKey> _keySelector;
private readonly Func<QueryContext, DbDataReader, object[]> _keyIdentifier;
private readonly IReadOnlyList<ValueComparer> _keyIdentifierValueComparers;
private readonly IReadOnlyList<Func<object, object, bool>> _keyIdentifierValueComparers;
private readonly Func<QueryContext, DbDataReader, ResultContext, SingleQueryResultCoordinator, TElement> _elementSelector;
private readonly Type _contextType;
private readonly IDiagnosticsLogger<DbLoggerCategory.Query> _queryLogger;
Expand Down Expand Up @@ -344,7 +384,7 @@ private sealed class AsyncEnumerator : IAsyncEnumerator<IGrouping<TKey, TElement
private readonly IReadOnlyList<ReaderColumn?>? _readerColumns;
private readonly Func<QueryContext, DbDataReader, TKey> _keySelector;
private readonly Func<QueryContext, DbDataReader, object[]> _keyIdentifier;
private readonly IReadOnlyList<ValueComparer> _keyIdentifierValueComparers;
private readonly IReadOnlyList<Func<object, object, bool>> _keyIdentifierValueComparers;
private readonly Func<QueryContext, DbDataReader, ResultContext, SingleQueryResultCoordinator, TElement> _elementSelector;
private readonly Type _contextType;
private readonly IDiagnosticsLogger<DbLoggerCategory.Query> _queryLogger;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,50 @@

namespace Microsoft.EntityFrameworkCore.Query.Internal;

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public static class GroupBySplitQueryingEnumerable
{
/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public static GroupBySplitQueryingEnumerable<TKey, TElement> Create<TKey, TElement>(
RelationalQueryContext relationalQueryContext,
RelationalCommandCache relationalCommandCache,
IReadOnlyList<ReaderColumn?>? readerColumns,
Func<QueryContext, DbDataReader, TKey> keySelector,
Func<QueryContext, DbDataReader, object[]> keyIdentifier,
IReadOnlyList<Func<object, object, bool>> keyIdentifierValueComparers,
Func<QueryContext, DbDataReader, ResultContext, SplitQueryResultCoordinator, TElement> elementSelector,
Action<QueryContext, IExecutionStrategy, SplitQueryResultCoordinator>? relatedDataLoaders,
Func<QueryContext, IExecutionStrategy, SplitQueryResultCoordinator, Task>? relatedDataLoadersAsync,
Type contextType,
bool standAloneStateManager,
bool detailedErrorsEnabled,
bool threadSafetyChecksEnabled)
=> new(
relationalQueryContext,
relationalCommandCache,
readerColumns,
keySelector,
keyIdentifier,
keyIdentifierValueComparers,
elementSelector,
relatedDataLoaders,
relatedDataLoadersAsync,
contextType,
standAloneStateManager,
detailedErrorsEnabled,
threadSafetyChecksEnabled);
}

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
Expand All @@ -20,7 +64,7 @@ public class GroupBySplitQueryingEnumerable<TKey, TElement>
private readonly IReadOnlyList<ReaderColumn?>? _readerColumns;
private readonly Func<QueryContext, DbDataReader, TKey> _keySelector;
private readonly Func<QueryContext, DbDataReader, object[]> _keyIdentifier;
private readonly IReadOnlyList<ValueComparer> _keyIdentifierValueComparers;
private readonly IReadOnlyList<Func<object, object, bool>> _keyIdentifierValueComparers;
private readonly Func<QueryContext, DbDataReader, ResultContext, SplitQueryResultCoordinator, TElement> _elementSelector;
private readonly Action<QueryContext, IExecutionStrategy, SplitQueryResultCoordinator>? _relatedDataLoaders;
private readonly Func<QueryContext, IExecutionStrategy, SplitQueryResultCoordinator, Task>? _relatedDataLoadersAsync;
Expand All @@ -42,7 +86,7 @@ public GroupBySplitQueryingEnumerable(
IReadOnlyList<ReaderColumn?>? readerColumns,
Func<QueryContext, DbDataReader, TKey> keySelector,
Func<QueryContext, DbDataReader, object[]> keyIdentifier,
IReadOnlyList<ValueComparer> keyIdentifierValueComparers,
IReadOnlyList<Func<object, object, bool>> keyIdentifierValueComparers,
Func<QueryContext, DbDataReader, ResultContext, SplitQueryResultCoordinator, TElement> elementSelector,
Action<QueryContext, IExecutionStrategy, SplitQueryResultCoordinator>? relatedDataLoaders,
Func<QueryContext, IExecutionStrategy, SplitQueryResultCoordinator, Task>? relatedDataLoadersAsync,
Expand Down Expand Up @@ -145,12 +189,12 @@ IEnumerator IEnumerable.GetEnumerator()
=> GetEnumerator();
}

private static bool CompareIdentifiers(IReadOnlyList<ValueComparer> valueComparers, object[] left, object[] right)
private static bool CompareIdentifiers(IReadOnlyList<Func<object, object, bool>> valueComparers, object[] left, object[] right)
{
// Ignoring size check on all for perf as they should be same unless bug in code.
for (var i = 0; i < left.Length; i++)
{
if (!valueComparers[i].Equals(left[i], right[i]))
if (!valueComparers[i](left[i], right[i]))
{
return false;
}
Expand All @@ -166,7 +210,7 @@ private sealed class Enumerator : IEnumerator<IGrouping<TKey, TElement>>
private readonly IReadOnlyList<ReaderColumn?>? _readerColumns;
private readonly Func<QueryContext, DbDataReader, TKey> _keySelector;
private readonly Func<QueryContext, DbDataReader, object[]> _keyIdentifier;
private readonly IReadOnlyList<ValueComparer> _keyIdentifierValueComparers;
private readonly IReadOnlyList<Func<object, object, bool>> _keyIdentifierValueComparers;
private readonly Func<QueryContext, DbDataReader, ResultContext, SplitQueryResultCoordinator, TElement> _elementSelector;
private readonly Action<QueryContext, IExecutionStrategy, SplitQueryResultCoordinator>? _relatedDataLoaders;
private readonly Type _contextType;
Expand Down Expand Up @@ -340,7 +384,7 @@ private sealed class AsyncEnumerator : IAsyncEnumerator<IGrouping<TKey, TElement
private readonly IReadOnlyList<ReaderColumn?>? _readerColumns;
private readonly Func<QueryContext, DbDataReader, TKey> _keySelector;
private readonly Func<QueryContext, DbDataReader, object[]> _keyIdentifier;
private readonly IReadOnlyList<ValueComparer> _keyIdentifierValueComparers;
private readonly IReadOnlyList<Func<object, object, bool>> _keyIdentifierValueComparers;
private readonly Func<QueryContext, DbDataReader, ResultContext, SplitQueryResultCoordinator, TElement> _elementSelector;
private readonly Func<QueryContext, IExecutionStrategy, SplitQueryResultCoordinator, Task>? _relatedDataLoaders;
private readonly Type _contextType;
Expand Down
Loading

0 comments on commit f0e88d4

Please sign in to comment.