Skip to content

Commit

Permalink
Initial work on collection as array of rows strategy, only works with…
Browse files Browse the repository at this point in the history
… Npgsql at the moment
  • Loading branch information
Emill committed Feb 28, 2021
1 parent dea57ad commit c283075
Show file tree
Hide file tree
Showing 12 changed files with 802 additions and 15 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.Linq.Expressions;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.EntityFrameworkCore.Query.SqlExpressions;
using Microsoft.EntityFrameworkCore.Utilities;

#nullable enable

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 class CollectionArrayApplyingExpressionVisitor : ExpressionVisitor
{
private readonly bool _noConfiguredBehavior;
private readonly IDiagnosticsLogger<DbLoggerCategory.Query> _logger;
private int _collectionId;

/// <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 CollectionArrayApplyingExpressionVisitor([NotNull] RelationalQueryCompilationContext queryCompilationContext)
{
Check.NotNull(queryCompilationContext, nameof(queryCompilationContext));

_noConfiguredBehavior = queryCompilationContext.QuerySplittingBehavior == null;
_logger = queryCompilationContext.Logger;
}

/// <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>
protected override Expression VisitExtension(Expression extensionExpression)
{
Check.NotNull(extensionExpression, nameof(extensionExpression));

if (extensionExpression is CollectionShaperExpression collectionShaperExpression)
{
var collectionId = _collectionId++;

var projectionBindingExpression = (ProjectionBindingExpression)collectionShaperExpression.Projection;
var selectExpression = (SelectExpression)projectionBindingExpression.QueryExpression;

var innerShaper = Visit(collectionShaperExpression.InnerShaper);

var collectionJoin = selectExpression.ApplyCollectionArray(
projectionBindingExpression.Index!.Value,
collectionId,
innerShaper,
collectionShaperExpression.Navigation,
collectionShaperExpression.ElementType);

return collectionJoin!;
}

return extensionExpression is ShapedQueryExpression shapedQueryExpression
? shapedQueryExpression.UpdateShaperExpression(Visit(shapedQueryExpression.ShaperExpression))
: base.VisitExtension(extensionExpression);
}
}
}
20 changes: 20 additions & 0 deletions src/EFCore.Relational/Query/QuerySqlGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1138,5 +1138,25 @@ protected override Expression VisitUnion(UnionExpression unionExpression)

return unionExpression;
}

/// <inheritdoc />
protected override Expression VisitRow([NotNull] RowExpression rowExpression)
{
Check.NotNull(rowExpression, nameof(rowExpression));

_relationalCommandBuilder.Append("ROW(");
GenerateList(rowExpression.Arguments, e => Visit(e));
_relationalCommandBuilder.Append(")");

return rowExpression;
}

/// <inheritdoc />
protected override Expression VisitArraySubquery([NotNull] ArraySubqueryExpression arraySubqueryExpression)
{
_relationalCommandBuilder.Append("ARRAY");

return VisitScalarSubquery(arraySubqueryExpression);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Query.SqlExpressions;
using Microsoft.EntityFrameworkCore.Utilities;

#nullable enable

namespace Microsoft.EntityFrameworkCore.Query
{
/// <summary>
/// <para>
/// An expression that represents creation of an array collection for relational provider in
/// <see cref="ShapedQueryExpression.ShaperExpression" />.
/// </para>
/// <para>
/// This type is typically used by database providers (and other extensions). It is generally
/// not used in application code.
/// </para>
/// </summary>
public class RelationalCollectionArrayShaperExpression : Expression, IPrintableExpression
{
/// <summary>
/// Creates a new instance of the <see cref="RelationalCollectionShaperExpression" /> class.
/// </summary>
/// <param name="collectionId"> A unique id for the collection being shaped. </param>
/// <param name="outerProjection"> An outer projection binding that maps the array location. </param>
/// <param name="innerSelectExpression"> An expression where elements will be fetched from. </param>
/// <param name="innerShaper"> An expression used to create individual elements of the collection. </param>
/// <param name="navigation"> A navigation associated with this collection, if any. </param>
/// <param name="elementType"> The clr type of individual elements in the collection. </param>
public RelationalCollectionArrayShaperExpression(
int collectionId,
[NotNull] ProjectionBindingExpression outerProjection,
[NotNull] SelectExpression innerSelectExpression,
[NotNull] Expression innerShaper,
[CanBeNull] INavigationBase? navigation,
[NotNull] Type elementType)
{
Check.NotNull(outerProjection, nameof(outerProjection));
Check.NotNull(innerSelectExpression, nameof(innerSelectExpression));
Check.NotNull(innerShaper, nameof(innerShaper));
Check.NotNull(elementType, nameof(elementType));

CollectionId = collectionId;
OuterProjection = outerProjection;
InnerSelectExpression = innerSelectExpression;
InnerShaper = innerShaper;
Navigation = navigation;
ElementType = elementType;
}

/// <summary>
/// A unique id for this collection shaper.
/// </summary>
public virtual int CollectionId { get; }

/// <summary>
/// The outer projection binding that maps the array location.
/// </summary>
public virtual ProjectionBindingExpression OuterProjection { get; }

/// <summary>
/// The expression where elements will be fetched from.
/// </summary>
public virtual SelectExpression InnerSelectExpression { get; }

/// <summary>
/// The expression to create inner elements.
/// </summary>
public virtual Expression InnerShaper { get; }

/// <summary>
/// The navigation if associated with the collection.
/// </summary>
public virtual INavigationBase? Navigation { get; }

/// <summary>
/// The clr type of elements of the collection.
/// </summary>
public virtual Type ElementType { get; }

/// <inheritdoc />
public override Type Type
=> Navigation?.ClrType ?? typeof(List<>).MakeGenericType(ElementType);

/// <inheritdoc />
public sealed override ExpressionType NodeType
=> ExpressionType.Extension;

/// <inheritdoc />
protected override Expression VisitChildren(ExpressionVisitor visitor)
{
Check.NotNull(visitor, nameof(visitor));

var innerShaper = visitor.Visit(InnerShaper);

return Update(innerShaper);
}

/// <summary>
/// Creates a new expression that is like this one, but using the supplied children. If all of the children are the same, it will
/// return this expression.
/// </summary>
/// <param name="innerShaper"> The <see cref="InnerShaper" /> property of the result. </param>
/// <returns> This expression if no children changed, or an expression with the updated children. </returns>
public virtual RelationalCollectionArrayShaperExpression Update(
[NotNull] Expression innerShaper)
{
Check.NotNull(innerShaper, nameof(innerShaper));

return innerShaper != InnerShaper
? new RelationalCollectionArrayShaperExpression(
CollectionId, OuterProjection, InnerSelectExpression, innerShaper, Navigation, ElementType)
: this;
}

/// <inheritdoc />
void IPrintableExpression.Print(ExpressionPrinter expressionPrinter)
{
Check.NotNull(expressionPrinter, nameof(expressionPrinter));

expressionPrinter.AppendLine("RelationalCollectionArrayShaper:");
using (expressionPrinter.Indent())
{
expressionPrinter.AppendLine($"CollectionId: {CollectionId}");
expressionPrinter.Append("InnerShaper:");
expressionPrinter.Visit(InnerShaper);
expressionPrinter.AppendLine();
expressionPrinter.AppendLine($"Navigation: {Navigation?.Name}");
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,10 @@ public override Expression Process(Expression query)
{
query = base.Process(query);
query = new SelectExpressionProjectionApplyingExpressionVisitor().Visit(query);
query = new CollectionJoinApplyingExpressionVisitor((RelationalQueryCompilationContext)QueryCompilationContext).Visit(query);
query = ((RelationalQueryCompilationContext)QueryCompilationContext).QuerySplittingBehavior
== QuerySplittingBehavior.SplitQuery
? new CollectionJoinApplyingExpressionVisitor((RelationalQueryCompilationContext)QueryCompilationContext).Visit(query)
: new CollectionArrayApplyingExpressionVisitor((RelationalQueryCompilationContext)QueryCompilationContext).Visit(query);
query = new TableAliasUniquifyingExpressionVisitor().Visit(query);
query = new SelectExpressionPruningExpressionVisitor().Visit(query);
query = new SqlExpressionSimplifyingExpressionVisitor(RelationalDependencies.SqlExpressionFactory, _useRelationalNulls).Visit(query);
Expand Down
Loading

0 comments on commit c283075

Please sign in to comment.