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

Failing unit test for bug #1924 #1925

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from
Draft
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
54 changes: 54 additions & 0 deletions src/Marten.Testing/Bugs/Bug_1924_subquery_projections.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Marten.Testing.Harness;
using Shouldly;
using Xunit;

namespace Marten.Testing.Bugs
{
public class Bug_1924_subquery_projections : BugIntegrationContext
{
[Fact]
public async Task Should_be_able_project_scalar_entire_objects_from_a_collection()
{
var userId = Guid.NewGuid();
var roleId1 = Guid.NewGuid();
var roleId2 = Guid.NewGuid();

var insertSession = theStore.LightweightSession();
await using (insertSession)
{
var user = new Bug1924User
{
Id = userId,
RoleIds = new List<Guid> { roleId1 },
EmbeddedUser = new Bug1924User { Id = Guid.NewGuid(), RoleIds = new List<Guid> { roleId2 } }
};
insertSession.Store(user);
await insertSession.SaveChangesAsync();
}

var retrievedUser = await theSession
.Query<Bug1924User>()
.Select(x => new { x.Id, x.RoleIds })
.Select(x => new
{
x.Id,
FirstRoleId = x.RoleIds.FirstOrDefault()
})
.SingleAsync();

retrievedUser.Id.ShouldBe(userId);
retrievedUser.FirstRoleId.ShouldBe(roleId1);
}
}

public class Bug1924User
{
public Guid Id { get; set; }
public List<Guid> RoleIds { get; set; }
public Bug1924User EmbeddedUser { get; set; }
}
}
61 changes: 60 additions & 1 deletion src/Marten/Linq/Parsing/SelectTransformBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,27 @@
using System.Reflection;
using Baseline;
using Marten.Linq.Fields;
using Remotion.Linq;
using Remotion.Linq.Clauses;
using Remotion.Linq.Clauses.Expressions;
using Remotion.Linq.Parsing;

namespace Marten.Linq.Parsing
{
internal class SelectTransformBuilder : RelinqExpressionVisitor
{
private readonly MainFromClause _mainFromClause;
private TargetObject _target;
private SelectedField _currentField;

public SelectTransformBuilder(Expression clause, IFieldMapping fields, ISerializer serializer)
public SelectTransformBuilder(
Expression clause,
IFieldMapping fields,
MainFromClause mainFromClause,
ISerializer serializer)
{
_mainFromClause = mainFromClause;

// ReSharper disable once VirtualMemberCallInConstructor
Visit(@clause);
SelectedFieldExpression = _target.ToSelectField(fields, serializer);
Expand Down Expand Up @@ -55,6 +65,55 @@ protected override MemberBinding VisitMemberBinding(MemberBinding node)
return base.VisitMemberBinding(node);
}

protected override Expression VisitSubQuery(SubQueryExpression expression)
{
var autoCorrelate = true;

var subQueryFromExpression = expression.QueryModel.MainFromClause.FromExpression;
if (subQueryFromExpression is not MemberExpression { Expression: QuerySourceReferenceExpression querySourceReferenceExpression })
{
throw new NotSupportedException(
"subQueryFromExpression should be a MemberExpression which contains a QuerySourceReferenceExpression.");
}

if (querySourceReferenceExpression.ReferencedQuerySource != _mainFromClause)
{
autoCorrelate = false;
}

if (querySourceReferenceExpression.ReferencedQuerySource
is not MainFromClause { FromExpression: ConstantExpression { Value: IMartenLinqQueryable martenLinqQueryable } })
{
throw new NotSupportedException(
"ReferencedQuerySource should be a MainFromClause referencing a constant MartenQueryable instance");
}

Expression newSubQueryExpression = null;

var correlatedQueryModelBuilder = new QueryModelBuilder();
correlatedQueryModelBuilder.AddClause(_mainFromClause);
correlatedQueryModelBuilder.AddClause(new SelectClause(subQueryFromExpression));
// TODO: Where clause to match the subquery id with the outer query id

foreach (var bodyClause in expression.QueryModel.BodyClauses)
{
correlatedQueryModelBuilder.AddClause(bodyClause);
}

foreach (var resultOperator in expression.QueryModel.ResultOperators)
{
correlatedQueryModelBuilder.AddResultOperator(resultOperator);
}

// TODO LinqHandlerBuilder needs to be refactored to extract out the Sql building logic
// and make it operate on a ready-parsed Model directly.
var subQueryBuilder = new LinqHandlerBuilder(
martenLinqQueryable.MartenProvider,
martenLinqQueryable.Session,
newSubQueryExpression);

return base.VisitSubQuery(expression);
}

public class TargetObject
{
Expand Down
4 changes: 2 additions & 2 deletions src/Marten/Linq/Parsing/SelectorVisitor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,13 @@ protected override Expression VisitMember(MemberExpression node)

protected override Expression VisitMemberInit(MemberInitExpression node)
{
_parent.CurrentStatement.ToSelectTransform(node, _serializer);
_parent.CurrentStatement.ToSelectTransform(node, _parent.Model.MainFromClause, _serializer);
return null;
}

protected override Expression VisitNew(NewExpression node)
{
_parent.CurrentStatement.ToSelectTransform(node, _serializer);
_parent.CurrentStatement.ToSelectTransform(node, _parent.Model.MainFromClause, _serializer);
return null;
}

Expand Down
8 changes: 6 additions & 2 deletions src/Marten/Linq/SqlGeneration/SelectorStatement.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
using Marten.Linq.Selectors;
using Weasel.Postgresql;
using Marten.Util;
using Remotion.Linq.Clauses;

namespace Marten.Linq.SqlGeneration
{
Expand Down Expand Up @@ -160,9 +161,12 @@ public void ApplyAggregateOperator(string databaseOperator)
ReturnDefaultWhenEmpty = true;
}

public void ToSelectTransform(Expression selectExpression, ISerializer serializer)
public void ToSelectTransform(
Expression selectExpression,
MainFromClause mainFromClause,
ISerializer serializer)
{
var builder = new SelectTransformBuilder(selectExpression, Fields, serializer);
var builder = new SelectTransformBuilder(selectExpression, Fields, mainFromClause, serializer);
var transformField = builder.SelectedFieldExpression;

SelectClause = typeof(DataSelectClause<>).CloseAndBuildAs<ISelectClause>(SelectClause.FromObject, transformField, selectExpression.Type);
Expand Down