Skip to content
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

Pregenerate SQL for precompiled queries #33510

Merged
merged 1 commit into from May 3, 2024
Merged

Conversation

roji
Copy link
Member

@roji roji commented Apr 10, 2024

When generating the shaper for a precompiled query, we now check the number of nullable parameters it has, and if that number is low (currently 3), we pregenerate SQLs (or rather, RelationCommands) for it. This PR is stacked on top of #33297 (review only the last commits).

Implementation overview:

  • Instead of accepting a RelationalCommandCache (which wraps the 2nd part of the query pipeline), querying enumerables now accept a RelationalCommandResolver, which is a lambda reponsible for producing the relational command.
    • In normal mode, the resolver references the RelationalCommandCache, so things work as before.
    • For precompiled queries with 3 nullable parameters or less, the resolver lambda directly contains code which switches over the nullness of the nullable parameters, and returns captured, fully-built relational commands containing the final SQL string. Example:
var relationalCommandTemplate = ((IRelationalCommandTemplate)(new RelationalCommand(commandBuilderDependencies, "SELECT [b].[Id], [b].[Name]\nFROM [Blogs] AS [b]\nWHERE 0 = 1", new IRelationalParameter[] { })));
var relationalCommandTemplate0 = ((IRelationalCommandTemplate)(new RelationalCommand(commandBuilderDependencies, "SELECT [b].[Id], [b].[Name]\nFROM [Blogs] AS [b]\nWHERE [b].[Id] = @__id_0", new IRelationalParameter[] { new TypeMappedRelationalParameter("__id_0", "@__id_0", relationalTypeMappingSource.FindMapping(typeof(int), "int", false, false, null, false, false, null, null), true, ParameterDirection.Input) })));

SingleQueryingEnumerable.Create(((RelationalQueryContext)(queryContext)), (IReadOnlyDictionary<string, object> parameters) =>
{
    IRelationalCommandTemplate result = default(IRelationalCommandTemplate);
    if (parameters["__id_0"] == null)
    {
        result = relationalCommandTemplate;
    }
    else
    {
        result = relationalCommandTemplate0;
    }

    return result;
}, ...
  • This PR also implements awareness of NRT captured variables, so that e.g. a non-nullable string parameter causes only one SQL to get generated (with that SQL being optimized for the non-nullable case), just like for value types:
    • When translating the user's LINQ query from C# to LINQ, the captured variable is represented via an internal fake FieldInfo which contains the NRT information.
    • The funcletizer checks for this information and reports it out, and we store the list of all non-nullable NRT parameters on QueryCompilationContext.
    • When translating ParameterExpression to SqlParameterExpression in RelationalSqlTranslatingExpressionVisitor, we consult this list, and create a non-nullable SqlParameterExpression accordingly.

Closes #29753

@roji roji changed the title Pregenerate sql Pregenerate SQL for precompiled queries Apr 10, 2024
@roji roji force-pushed the PregenerateSql branch 4 times, most recently from dd2bc53 to d53215a Compare April 11, 2024 15:32
@@ -41,15 +43,16 @@ public class RelationalQueryCompilationContextFactory : IQueryCompilationContext
/// 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 virtual QueryCompilationContext Create(bool async, bool precompiling)
=> new RelationalQueryCompilationContext(Dependencies, RelationalDependencies, async, precompiling);
public virtual QueryCompilationContext Create(bool async)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this PR introduces the new CreatePrecompiled() variant below (which accepts the new nonNullableReferenceTypeParameters), this reverts the previous change (this is also good because it leaves precompilation out of non-experimental public APIs).

/// <param name="dependencies">Parameter object containing dependencies for this class.</param>
/// <param name="relationalDependencies">Parameter object containing relational dependencies for this class.</param>
/// <param name="async">A bool value indicating whether it is for async query.</param>
public RelationalQueryCompilationContext(
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similarly, this reverts the earlier change and we have separation between the existing public APIs (which are precompilation-unaware) and new, experimental APIs for precompilation.


static object GenerateNonNullParameterValue(Type type)
{
// In general, the (2nd part of) the query pipeline doesn't care about actual values - it mostly looks a null vs. non-null.
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The alternative approach here would be to just put an object instance, and to allow the 2nd part of the query pipeline to throw if it attempts to cast it (e.g. to a collection); we'd simply catch exceptions and assume we can't pregenerate in that case.

@roji roji marked this pull request as ready for review April 11, 2024 15:32
@roji roji requested a review from a team April 30, 2024 10:50
@roji
Copy link
Member Author

roji commented Apr 30, 2024

@dotnet/efteam rebased this on latest main, should be ready for reviewing.

@roji roji merged commit cd8e049 into dotnet:main May 3, 2024
5 of 7 checks passed
@roji roji deleted the PregenerateSql branch May 3, 2024 12:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Precompiled queries: pre-generate SQL (2nd part of the query pipeline)
3 participants