Skip to content

Commit

Permalink
Report cross-document join exception on Cosmos (#34066)
Browse files Browse the repository at this point in the history
And some test cleanup

Closes #33969
  • Loading branch information
roji authored Jun 24, 2024
1 parent 7e313ab commit e6dedc3
Show file tree
Hide file tree
Showing 6 changed files with 182 additions and 44 deletions.
6 changes: 6 additions & 0 deletions src/EFCore.Cosmos/Properties/CosmosStrings.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions src/EFCore.Cosmos/Properties/CosmosStrings.resx
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,9 @@
<data name="CosmosNotInUse" xml:space="preserve">
<value>Cosmos-specific methods can only be used when the context is using the Cosmos provider.</value>
</data>
<data name="CrossDocumentJoinNotSupported" xml:space="preserve">
<value>Joins across documents aren't supported in Cosmos; consider modeling your data differently so that related data is in the same document. Alternatively, perform two separate queries to query the two documents.</value>
</data>
<data name="DefaultTTLMismatch" xml:space="preserve">
<value>The default time to live was configured to '{ttl1}' on '{entityType1}', but on '{entityType2}' it was configured to '{ttl2}'. All entity types mapped to the same container '{container}' must be configured with the same default time to live.</value>
</data>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -762,7 +762,10 @@ protected override ShapedQueryExpression TranslateDistinct(ShapedQueryExpression
LambdaExpression outerKeySelector,
LambdaExpression innerKeySelector,
LambdaExpression resultSelector)
=> null;
{
AddTranslationErrorDetails(CosmosStrings.CrossDocumentJoinNotSupported);
return null;
}

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
Expand Down Expand Up @@ -807,7 +810,10 @@ protected override ShapedQueryExpression TranslateDistinct(ShapedQueryExpression
LambdaExpression outerKeySelector,
LambdaExpression innerKeySelector,
LambdaExpression resultSelector)
=> null;
{
AddTranslationErrorDetails(CosmosStrings.CrossDocumentJoinNotSupported);
return null;
}

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
Expand Down
150 changes: 108 additions & 42 deletions test/EFCore.Cosmos.FunctionalTests/Query/OwnedQueryCosmosTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,11 @@ public OwnedQueryCosmosTest(OwnedQueryCosmosFixture fixture, ITestOutputHelper t
Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper);
}

// TODO: Fake LeftJoin, #33969
// Fink.Barton is a non-owned navigation, cross-document join
public override Task Query_loads_reference_nav_automatically_in_projection(bool async)
=> AssertTranslationFailed(() => base.Query_loads_reference_nav_automatically_in_projection(async));
=> AssertTranslationFailedWithDetails(
() => base.Query_loads_reference_nav_automatically_in_projection(async),
CosmosStrings.CrossDocumentJoinNotSupported);

// Non-correlated queries not supported by Cosmos
public override Task Query_with_owned_entity_equality_operator(bool async)
Expand Down Expand Up @@ -162,10 +164,11 @@ FROM root c
""");
});

// TODO: Fake LeftJoin, #33969
// Address.Planet is a non-owned navigation, cross-document join
public override Task Filter_owned_entity_chained_with_regular_entity_followed_by_projecting_owned_collection(bool async)
=> AssertTranslationFailed(
() => base.Filter_owned_entity_chained_with_regular_entity_followed_by_projecting_owned_collection(async));
=> AssertTranslationFailedWithDetails(
() => base.Filter_owned_entity_chained_with_regular_entity_followed_by_projecting_owned_collection(async),
CosmosStrings.CrossDocumentJoinNotSupported);

public override async Task Set_throws_for_owned_type(bool async)
{
Expand All @@ -174,55 +177,65 @@ public override async Task Set_throws_for_owned_type(bool async)
AssertSql();
}

// TODO: Fake LeftJoin, #33969
// Address.Planet is a non-owned navigation, cross-document join
public override Task Navigation_rewrite_on_owned_reference_followed_by_regular_entity(bool async)
=> AssertTranslationFailed(
() => base.Navigation_rewrite_on_owned_reference_followed_by_regular_entity(async));
=> AssertTranslationFailedWithDetails(
() => base.Navigation_rewrite_on_owned_reference_followed_by_regular_entity(async),
CosmosStrings.CrossDocumentJoinNotSupported);

// TODO: Fake LeftJoin, #33969
// Address.Planet is a non-owned navigation, cross-document join
public override Task Navigation_rewrite_on_owned_reference_followed_by_regular_entity_filter(bool async)
=> AssertTranslationFailed(
() => base.Navigation_rewrite_on_owned_reference_followed_by_regular_entity_filter(async));
=> AssertTranslationFailedWithDetails(
() => base.Navigation_rewrite_on_owned_reference_followed_by_regular_entity(async),
CosmosStrings.CrossDocumentJoinNotSupported);

// TODO: Fake LeftJoin, #33969
// Address.Planet is a non-owned navigation, cross-document join
public override Task Navigation_rewrite_on_owned_reference_followed_by_regular_entity_and_another_reference(bool async)
=> AssertTranslationFailed(
() => base.Navigation_rewrite_on_owned_reference_followed_by_regular_entity_and_another_reference(async));
=> AssertTranslationFailedWithDetails(
() => base.Navigation_rewrite_on_owned_reference_followed_by_regular_entity_and_another_reference(async),
CosmosStrings.CrossDocumentJoinNotSupported);

// TODO: Fake LeftJoin, #33969
// Address.Planet is a non-owned navigation, cross-document join
public override Task Navigation_rewrite_on_owned_reference_followed_by_regular_entity_and_another_reference_and_scalar(bool async)
=> AssertTranslationFailed(
() => base.Navigation_rewrite_on_owned_reference_followed_by_regular_entity_and_another_reference_and_scalar(async));
=> AssertTranslationFailedWithDetails(
() => base.Navigation_rewrite_on_owned_reference_followed_by_regular_entity_and_another_reference_and_scalar(async),
CosmosStrings.CrossDocumentJoinNotSupported);

// TODO: Fake LeftJoin, #33969
// Address.Planet is a non-owned navigation, cross-document join
public override Task Navigation_rewrite_on_owned_reference_followed_by_regular_entity_and_collection(bool async)
=> AssertTranslationFailed(
() => base.Navigation_rewrite_on_owned_reference_followed_by_regular_entity_and_collection(async));
=> AssertTranslationFailedWithDetails(
() => base.Navigation_rewrite_on_owned_reference_followed_by_regular_entity_and_collection(async),
CosmosStrings.CrossDocumentJoinNotSupported);

// TODO: Fake LeftJoin, #33969
// Address.Planet is a non-owned navigation, cross-document join
public override Task Navigation_rewrite_on_owned_reference_followed_by_regular_entity_and_collection_count(bool async)
=> AssertTranslationFailed(
() => base.Navigation_rewrite_on_owned_reference_followed_by_regular_entity_and_collection(async));
=> AssertTranslationFailedWithDetails(
() => base.Navigation_rewrite_on_owned_reference_followed_by_regular_entity_and_collection_count(async),
CosmosStrings.CrossDocumentJoinNotSupported);

// TODO: Fake LeftJoin, #33969
// Address.Planet is a non-owned navigation, cross-document join
public override Task Navigation_rewrite_on_owned_reference_followed_by_regular_entity_and_property(bool async)
=> AssertTranslationFailed(
() => base.Navigation_rewrite_on_owned_reference_followed_by_regular_entity_and_property(async));
=> AssertTranslationFailedWithDetails(
() => base.Navigation_rewrite_on_owned_reference_followed_by_regular_entity_and_property(async),
CosmosStrings.CrossDocumentJoinNotSupported);

// TODO: Fake LeftJoin, #33969
// Address.Planet is a non-owned navigation, cross-document join
public override Task Navigation_rewrite_on_owned_reference_followed_by_regular_entity_and_another_reference_in_predicate_and_projection(bool async)
=> AssertTranslationFailed(
() => base.Navigation_rewrite_on_owned_reference_followed_by_regular_entity_and_another_reference_in_predicate_and_projection(async));
=> AssertTranslationFailedWithDetails(
() => base.Navigation_rewrite_on_owned_reference_followed_by_regular_entity_and_another_reference_in_predicate_and_projection(async),
CosmosStrings.CrossDocumentJoinNotSupported);

// TODO: Fake LeftJoin, #33969
// Address.Planet is a non-owned navigation, cross-document join
public override Task Project_multiple_owned_navigations(bool async)
=> AssertTranslationFailed(
() => base.Project_multiple_owned_navigations(async));
=> AssertTranslationFailedWithDetails(
() => base.Project_multiple_owned_navigations(async),
CosmosStrings.CrossDocumentJoinNotSupported);

// TODO: Fake LeftJoin, #33969
// Address.Planet is a non-owned navigation, cross-document join
public override Task Project_multiple_owned_navigations_with_expansion_on_owned_collections(bool async)
=> AssertTranslationFailed(
() => base.Project_multiple_owned_navigations_with_expansion_on_owned_collections(async));
=> AssertTranslationFailedWithDetails(
() => base.Project_multiple_owned_navigations_with_expansion_on_owned_collections(async),
CosmosStrings.CrossDocumentJoinNotSupported);

[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
Expand All @@ -241,13 +254,38 @@ JOIN a IN c["Orders"]
""");
});

// TODO: Fake LeftJoin, #33969
[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public override Task SelectMany_with_result_selector(bool async)
=> CosmosTestHelpers.Instance.NoSyncTest(
async, async a =>
{
await base.SelectMany_with_result_selector(a);

AssertSql(
"""
SELECT VALUE
{
"PersonId" : c["Id"],
"OrderId" : a["Id"]
}
FROM root c
JOIN a IN c["Orders"]
WHERE c["Discriminator"] IN ("OwnedPerson", "Branch", "LeafB", "LeafA")
""");
});

// Address.Planet is a non-owned navigation, cross-document join
public override Task SelectMany_on_owned_reference_followed_by_regular_entity_and_collection(bool async)
=> AssertTranslationFailed(() => base.SelectMany_on_owned_reference_followed_by_regular_entity_and_collection(async));
=> AssertTranslationFailedWithDetails(
() => base.SelectMany_on_owned_reference_followed_by_regular_entity_and_collection(async),
CosmosStrings.CrossDocumentJoinNotSupported);

// TODO: Fake LeftJoin, #33969
// Address.Planet is a non-owned navigation, cross-document join
public override Task SelectMany_on_owned_reference_with_entity_in_between_ending_in_owned_collection(bool async)
=> AssertTranslationFailed(() => base.SelectMany_on_owned_reference_with_entity_in_between_ending_in_owned_collection(async));
=> AssertTranslationFailedWithDetails(
() => base.SelectMany_on_owned_reference_with_entity_in_between_ending_in_owned_collection(async),
CosmosStrings.CrossDocumentJoinNotSupported);

// Non-correlated queries not supported by Cosmos
public override Task Query_with_owned_entity_equality_method(bool async)
Expand Down Expand Up @@ -275,6 +313,32 @@ FROM root c
public override Task Query_when_subquery(bool async)
=> AssertTranslationFailed(() => base.Query_when_subquery(async));

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

// TODO: The following should project out c["PersonAddress"], not c: #34067
AssertSql(
"""
SELECT c
FROM root c
WHERE c["Discriminator"] IN ("OwnedPerson", "Branch", "LeafB", "LeafA")
ORDER BY c["Id"]
""");
});

// TODO: #34068
public override async Task Project_owned_reference_navigation_which_does_not_own_additional(bool async)
{
// Always throws for sync.
if (async)
{
await Assert.ThrowsAsync<NullReferenceException>(() => base.Project_owned_reference_navigation_which_does_not_own_additional(async));
}
}

public override Task No_ignored_include_warning_when_implicit_load(bool async)
=> CosmosTestHelpers.Instance.NoSyncTest(
async, async a =>
Expand Down Expand Up @@ -321,6 +385,7 @@ await AssertQuery(
assertOrder: true,
elementAsserter: (e, a) => AssertCollection(e, a));

// TODO: The following should project out a["Details"], not a: #34067
AssertSql(
"""
SELECT a
Expand Down Expand Up @@ -688,12 +753,13 @@ public override async Task NoTracking_Include_with_cycles_throws(bool async)
AssertSql();
}

// TODO: Fake LeftJoin, #33969
// Order.Client is a non-owned navigation, cross-document join
public override Task NoTracking_Include_with_cycles_does_not_throw_when_performing_identity_resolution(
bool async,
bool useAsTracking)
=> AssertTranslationFailed(
() => base.NoTracking_Include_with_cycles_does_not_throw_when_performing_identity_resolution(async, useAsTracking));
=> AssertTranslationFailedWithDetails(
() => base.NoTracking_Include_with_cycles_does_not_throw_when_performing_identity_resolution(async, useAsTracking),
CosmosStrings.CrossDocumentJoinNotSupported);

public override async Task Trying_to_access_non_existent_indexer_property_throws_meaningful_exception(bool async)
{
Expand Down
21 changes: 21 additions & 0 deletions test/EFCore.Specification.Tests/Query/OwnedQueryTestBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,20 @@ public virtual Task Query_when_subquery(bool async)
assertOrder: true,
elementAsserter: (e, a) => AssertEqual(e.op, a.op));

[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task Project_owned_reference_navigation_which_owns_additional(bool async)
=> AssertQuery(
async,
ss => ss.Set<OwnedPerson>().OrderBy(o => o.Id).Select(p => p.PersonAddress));

[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task Project_owned_reference_navigation_which_does_not_own_additional(bool async)
=> AssertQuery(
async,
ss => ss.Set<OwnedPerson>().OrderBy(o => o.Id).Select(p => p.PersonAddress.Country));

[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task Navigation_rewrite_on_owned_reference_projecting_scalar(bool async)
Expand Down Expand Up @@ -177,6 +191,13 @@ public virtual Task SelectMany_on_owned_collection(bool async)
async,
ss => ss.Set<OwnedPerson>().SelectMany(p => p.Orders));

[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task SelectMany_with_result_selector(bool async)
=> AssertQuery(
async,
ss => ss.Set<OwnedPerson>().SelectMany(o => o.Orders, (p, o) => new { PersonId = p.Id, OrderId = o.Id }));

[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual async Task Set_throws_for_owned_type(bool async)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,30 @@ FROM [Order] AS [o1]
""");
}

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

AssertSql(
"""
SELECT [o].[Id], [o].[PersonAddress_AddressLine], [o].[PersonAddress_PlaceType], [o].[PersonAddress_ZipCode], [o].[PersonAddress_Country_Name], [o].[PersonAddress_Country_PlanetId]
FROM [OwnedPerson] AS [o]
ORDER BY [o].[Id]
""");
}

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

AssertSql(
"""
SELECT [o].[Id], [o].[PersonAddress_Country_Name], [o].[PersonAddress_Country_PlanetId]
FROM [OwnedPerson] AS [o]
ORDER BY [o].[Id]
""");
}

public override async Task Navigation_rewrite_on_owned_reference_projecting_scalar(bool async)
{
await base.Navigation_rewrite_on_owned_reference_projecting_scalar(async);
Expand Down Expand Up @@ -291,6 +315,18 @@ FROM [OwnedPerson] AS [o]
""");
}

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

AssertSql(
"""
SELECT [o].[Id] AS [PersonId], [o0].[Id] AS [OrderId]
FROM [OwnedPerson] AS [o]
INNER JOIN [Order] AS [o0] ON [o].[Id] = [o0].[ClientId]
""");
}

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

0 comments on commit e6dedc3

Please sign in to comment.