diff --git a/src/EFCore.Cosmos/Query/Internal/CosmosQuerySqlGenerator.cs b/src/EFCore.Cosmos/Query/Internal/CosmosQuerySqlGenerator.cs index 9e9c8151d2f..f166a251337 100644 --- a/src/EFCore.Cosmos/Query/Internal/CosmosQuerySqlGenerator.cs +++ b/src/EFCore.Cosmos/Query/Internal/CosmosQuerySqlGenerator.cs @@ -563,6 +563,20 @@ protected override Expression VisitObjectBinary(ObjectBinaryExpression objectBin /// </summary> protected override Expression VisitSqlUnary(SqlUnaryExpression sqlUnaryExpression) { + if (sqlUnaryExpression.OperatorType == ExpressionType.Convert) + { + if (sqlUnaryExpression.TypeMapping?.ClrType == typeof(string)) + { + _sqlBuilder.Append("ToString("); + Visit(sqlUnaryExpression.Operand); + _sqlBuilder.Append(")"); + } + else + { + Visit(sqlUnaryExpression.Operand); + } + return sqlUnaryExpression; + } var op = sqlUnaryExpression.OperatorType switch { ExpressionType.UnaryPlus => "+", diff --git a/src/EFCore.Cosmos/Query/Internal/CosmosQueryableMethodTranslatingExpressionVisitor.cs b/src/EFCore.Cosmos/Query/Internal/CosmosQueryableMethodTranslatingExpressionVisitor.cs index e3d81672bf0..81db2a68130 100644 --- a/src/EFCore.Cosmos/Query/Internal/CosmosQueryableMethodTranslatingExpressionVisitor.cs +++ b/src/EFCore.Cosmos/Query/Internal/CosmosQueryableMethodTranslatingExpressionVisitor.cs @@ -473,6 +473,12 @@ protected override ShapedQueryExpression TranslateCast(ShapedQueryExpression sou /// </summary> protected override ShapedQueryExpression? TranslateContains(ShapedQueryExpression source, Expression item) { + //Strip convert to object. Other converts should be fine as they will have a type mapping found but object won't + + if (item is UnaryExpression { NodeType:ExpressionType.Convert} unaryExpression && unaryExpression.Type == typeof(object)) + { + item = unaryExpression.Operand; + } // Simplify x.Array.Contains[1] => ARRAY_CONTAINS(x.Array, 1) insert of IN+subquery if (source.TryExtractArray(out var array, ignoreOrderings: true) && array is SqlExpression scalarArray // TODO: Contains over arrays of structural types, #34027 diff --git a/src/EFCore.Cosmos/Query/Internal/CosmosSqlTranslatingExpressionVisitor.cs b/src/EFCore.Cosmos/Query/Internal/CosmosSqlTranslatingExpressionVisitor.cs index e46d7344f52..4a204796138 100644 --- a/src/EFCore.Cosmos/Query/Internal/CosmosSqlTranslatingExpressionVisitor.cs +++ b/src/EFCore.Cosmos/Query/Internal/CosmosSqlTranslatingExpressionVisitor.cs @@ -91,6 +91,12 @@ protected virtual void AddTranslationErrorDetails(string details) if (result is SqlExpression translation) { + if (translation is SqlUnaryExpression { OperatorType: ExpressionType.Convert } sqlUnaryExpression + && sqlUnaryExpression.Type == typeof(object)) + { + translation = sqlUnaryExpression.Operand; + } + if (applyDefaultTypeMapping) { translation = sqlExpressionFactory.ApplyDefaultTypeMapping(translation); @@ -185,10 +191,6 @@ when TryRewriteEntityEquality( equalsMethod: false, out var result): return result; - - case { Method: var method } when method == ConcatMethodInfo: - return QueryCompilationContext.NotTranslatedExpression; - default: var uncheckedNodeTypeVariant = binaryExpression.NodeType switch { @@ -625,7 +627,7 @@ when method.GetGenericMethodDefinition().Equals(EnumerableMethods.Contains): arguments = new SqlExpression[methodCallExpression.Arguments.Count]; for (var i = 0; i < arguments.Length; i++) { - var argument = methodCallExpression.Arguments[i]; + var argument = RemoveObjectConvert(methodCallExpression.Arguments[i]); if (TranslationFailed(argument, Visit(argument), out var sqlArgument)) { return TranslateAsSubquery(methodCallExpression); @@ -813,32 +815,46 @@ protected override Expression VisitUnary(UnaryExpression unaryExpression) return QueryCompilationContext.NotTranslatedExpression; } - return unaryExpression.NodeType switch + switch (unaryExpression.NodeType) { - ExpressionType.Not - => sqlExpressionFactory.Not(sqlOperand!), - - ExpressionType.Negate or ExpressionType.NegateChecked - => sqlExpressionFactory.Negate(sqlOperand!), - - // Convert nodes can be an explicit user gesture in the query, or they may get introduced by the compiler (e.g. when a Child is - // passed as an argument for a parameter of type Parent). The latter type should generally get stripped out as a pure C#/LINQ - // artifact that shouldn't affect translation, but the latter may be an indication from the user that they want to apply a - // type change. - ExpressionType.Convert or ExpressionType.ConvertChecked or ExpressionType.TypeAs - when operand.Type.IsInterface && unaryExpression.Type.GetInterfaces().Any(e => e == operand.Type) - // We strip out implicit conversions, e.g. float[] -> ReadOnlyMemory<float> (for vector search) - || (unaryExpression.Method is { IsSpecialName: true, Name: "op_Implicit" } - && IsReadOnlyMemory(unaryExpression.Type.UnwrapNullableType())) - || unaryExpression.Type.UnwrapNullableType() == operand.Type - || unaryExpression.Type.UnwrapNullableType() == typeof(Enum) + case ExpressionType.Not: + return sqlExpressionFactory.Not(sqlOperand!); + + case ExpressionType.Negate: + case ExpressionType.NegateChecked: + return sqlExpressionFactory.Negate(sqlOperand!); + + case ExpressionType.Convert: + case ExpressionType.ConvertChecked: + case ExpressionType.TypeAs: // Object convert needs to be converted to explicit cast when mismatching types - // But we let it pass here since we don't have explicit cast mechanism here and in some cases object convert is due to value types - || unaryExpression.Type == typeof(object) - => sqlOperand!, + if (operand.Type.IsInterface + && unaryExpression.Type.GetInterfaces().Any(e => e == operand.Type) + // We strip out implicit conversions, e.g. float[] -> ReadOnlyMemory<float> (for vector search) + || (unaryExpression.Method is { IsSpecialName: true, Name: "op_Implicit" } + && IsReadOnlyMemory(unaryExpression.Type.UnwrapNullableType())) + || unaryExpression.Type.UnwrapNullableType() == operand.Type.UnwrapNullableType() + || unaryExpression.Type.UnwrapNullableType() == typeof(Enum)) + { + return sqlOperand!; + } - _ => QueryCompilationContext.NotTranslatedExpression - }; + // Introduce explicit cast only if the target type is mapped else we need to client eval + if (unaryExpression.Type == typeof(object) + || typeMappingSource.FindMapping(unaryExpression.Type, queryCompilationContext.Model) != null) + { + sqlOperand = sqlExpressionFactory.ApplyDefaultTypeMapping(sqlOperand); + + return sqlExpressionFactory.Convert(sqlOperand!, unaryExpression.Type); + } + + break; + + case ExpressionType.Quote: + return operand; + } + + return QueryCompilationContext.NotTranslatedExpression; static bool IsReadOnlyMemory(Type type) => type is { IsGenericType: true, IsGenericTypeDefinition: false } diff --git a/src/EFCore.Cosmos/Query/Internal/Expressions/SqlUnaryExpression.cs b/src/EFCore.Cosmos/Query/Internal/Expressions/SqlUnaryExpression.cs index 56fc45ea240..7f868de9113 100644 --- a/src/EFCore.Cosmos/Query/Internal/Expressions/SqlUnaryExpression.cs +++ b/src/EFCore.Cosmos/Query/Internal/Expressions/SqlUnaryExpression.cs @@ -16,8 +16,12 @@ public class SqlUnaryExpression : SqlExpression { private static readonly ISet<ExpressionType> AllowedOperators = new HashSet<ExpressionType> { + ExpressionType.Equal, + ExpressionType.NotEqual, + ExpressionType.Convert, ExpressionType.Not, ExpressionType.Negate, + ExpressionType.OnesComplement, ExpressionType.UnaryPlus }; diff --git a/src/EFCore.Cosmos/Query/Internal/SqlExpressionFactory.cs b/src/EFCore.Cosmos/Query/Internal/SqlExpressionFactory.cs index b4cfb0938a5..48808848457 100644 --- a/src/EFCore.Cosmos/Query/Internal/SqlExpressionFactory.cs +++ b/src/EFCore.Cosmos/Query/Internal/SqlExpressionFactory.cs @@ -156,7 +156,7 @@ private SqlExpression ApplyTypeMappingOnSqlBinary( case ExpressionType.Coalesce: { inferredTypeMapping = typeMapping ?? ExpressionExtensions.InferTypeMapping(left, right); - resultType = inferredTypeMapping?.ClrType ?? left.Type; + resultType = inferredTypeMapping?.ClrType ?? (left.Type != typeof(object) ? left.Type : right.Type); resultTypeMapping = inferredTypeMapping; break; } diff --git a/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindAggregateOperatorsQueryCosmosTest.cs b/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindAggregateOperatorsQueryCosmosTest.cs index 64d6bc3b77f..4a0dfc5ae0d 100644 --- a/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindAggregateOperatorsQueryCosmosTest.cs +++ b/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindAggregateOperatorsQueryCosmosTest.cs @@ -438,22 +438,33 @@ FROM root c """); }); - public override async Task Sum_with_division_on_decimal(bool async) - { - // Aggregate selecting non-mapped type. Issue #20677. - await Assert.ThrowsAsync<KeyNotFoundException>(async () => await base.Sum_with_division_on_decimal(async)); + public override Task Sum_with_division_on_decimal(bool async) + => Fixture.NoSyncTest( + async, async a => + { + await base.Sum_with_division_on_decimal(a); - AssertSql(); - } + AssertSql( + """ +SELECT VALUE SUM((c["Quantity"] / 2.09)) +FROM root c +WHERE (c["$type"] = "OrderDetail") +"""); + }); - public override async Task Sum_with_division_on_decimal_no_significant_digits(bool async) - { - // Aggregate selecting non-mapped type. Issue #20677. - await Assert.ThrowsAsync<KeyNotFoundException>( - async () => await base.Sum_with_division_on_decimal_no_significant_digits(async)); + public override Task Sum_with_division_on_decimal_no_significant_digits(bool async) + => Fixture.NoSyncTest( + async, async a => + { + await base.Sum_with_division_on_decimal_no_significant_digits(a); - AssertSql(); - } + AssertSql( + """ +SELECT VALUE SUM((c["Quantity"] / 2.0)) +FROM root c +WHERE (c["$type"] = "OrderDetail") +"""); + }); public override Task Sum_with_coalesce(bool async) => Fixture.NoSyncTest( @@ -723,22 +734,33 @@ FROM root c """); }); - public override async Task Average_with_division_on_decimal(bool async) - { - // Aggregate selecting non-mapped type. Issue #20677. - await Assert.ThrowsAsync<KeyNotFoundException>(async () => await base.Average_with_division_on_decimal(async)); + public override Task Average_with_division_on_decimal(bool async) + => Fixture.NoSyncTest( + async, async a => + { + await base.Average_with_division_on_decimal(a); - AssertSql(); - } + AssertSql( + """ +SELECT VALUE AVG((c["Quantity"] / 2.09)) +FROM root c +WHERE (c["$type"] = "OrderDetail") +"""); + }); - public override async Task Average_with_division_on_decimal_no_significant_digits(bool async) - { - // Aggregate selecting non-mapped type. Issue #20677. - await Assert.ThrowsAsync<KeyNotFoundException>( - async () => await base.Average_with_division_on_decimal_no_significant_digits(async)); + public override Task Average_with_division_on_decimal_no_significant_digits(bool async) + => Fixture.NoSyncTest( + async, async a => + { + await base.Average_with_division_on_decimal_no_significant_digits(a); - AssertSql(); - } + AssertSql( + """ +SELECT VALUE AVG((c["Quantity"] / 2.0)) +FROM root c +WHERE (c["$type"] = "OrderDetail") +"""); + }); public override Task Average_with_coalesce(bool async) => Fixture.NoSyncTest( @@ -2036,36 +2058,47 @@ public override async Task OfType_Select_OfType_Select(bool async) AssertSql(); } - public override async Task Average_with_non_matching_types_in_projection_doesnt_produce_second_explicit_cast(bool async) - { - // Aggregate selecting non-mapped type. Issue #20677. - await Assert.ThrowsAsync<KeyNotFoundException>( - async () => await base.Average_with_non_matching_types_in_projection_doesnt_produce_second_explicit_cast(async)); + public override Task Average_with_non_matching_types_in_projection_doesnt_produce_second_explicit_cast(bool async) + => Fixture.NoSyncTest( + async, async a => + { + await base.Average_with_non_matching_types_in_projection_doesnt_produce_second_explicit_cast(a); - AssertSql(); - } + AssertSql( + """ +SELECT VALUE AVG(c["OrderID"]) +FROM root c +WHERE ((c["$type"] = "Order") AND STARTSWITH(c["CustomerID"], "A")) +"""); + }); - public override async Task Max_with_non_matching_types_in_projection_introduces_explicit_cast(bool async) - { - // Always throws for sync. - if (async) - { - // Aggregate selecting non-mapped type. Issue #20677. - await Assert.ThrowsAsync<KeyNotFoundException>( - async () => await base.Max_with_non_matching_types_in_projection_introduces_explicit_cast(async)); + public override Task Max_with_non_matching_types_in_projection_introduces_explicit_cast(bool async) + => Fixture.NoSyncTest( + async, async a => + { + await base.Max_with_non_matching_types_in_projection_introduces_explicit_cast(a); - AssertSql(); - } - } + AssertSql( + """ +SELECT VALUE MAX(c["OrderID"]) +FROM root c +WHERE ((c["$type"] = "Order") AND STARTSWITH(c["CustomerID"], "A")) +"""); + }); - public override async Task Min_with_non_matching_types_in_projection_introduces_explicit_cast(bool async) - { - // Aggregate selecting non-mapped type. Issue #20677. - await Assert.ThrowsAsync<KeyNotFoundException>( - async () => await base.Min_with_non_matching_types_in_projection_introduces_explicit_cast(async)); + public override Task Min_with_non_matching_types_in_projection_introduces_explicit_cast(bool async) + => Fixture.NoSyncTest( + async, async a => + { + await base.Min_with_non_matching_types_in_projection_introduces_explicit_cast(a); - AssertSql(); - } + AssertSql( + """ +SELECT VALUE MIN(c["OrderID"]) +FROM root c +WHERE ((c["$type"] = "Order") AND STARTSWITH(c["CustomerID"], "A")) +"""); + }); public override async Task OrderBy_Take_Last_gives_correct_result(bool async) { @@ -2488,13 +2521,19 @@ public override async Task Collection_LastOrDefault_member_access_in_projection_ AssertSql(); } - public override async Task Sum_over_explicit_cast_over_column(bool async) - { - // Aggregate selecting non-mapped type. Issue #20677. - await Assert.ThrowsAsync<KeyNotFoundException>(async () => await base.Sum_over_explicit_cast_over_column(async)); + public override Task Sum_over_explicit_cast_over_column(bool async) + => Fixture.NoSyncTest( + async, async a => + { + await base.Sum_over_explicit_cast_over_column(a); - AssertSql(); - } + AssertSql( + """ +SELECT VALUE SUM(c["OrderID"]) +FROM root c +WHERE (c["$type"] = "Order") +"""); + }); public override async Task Contains_over_scalar_with_null_should_rewrite_to_identity_equality_subquery(bool async) { @@ -2778,13 +2817,19 @@ OFFSET 0 LIMIT 1 """); }); - [ConditionalTheory(Skip = "Issue #20677")] - public override async Task Type_casting_inside_sum(bool async) - { - await base.Type_casting_inside_sum(async); + public override Task Type_casting_inside_sum(bool async) + => Fixture.NoSyncTest( + async, async a => + { + await base.Type_casting_inside_sum(a); - AssertSql(); - } + AssertSql( + """ +SELECT VALUE SUM(c["Discount"]) +FROM root c +WHERE (c["$type"] = "OrderDetail") +"""); + }); private void AssertSql(params string[] expected) => Fixture.TestSqlLoggerFactory.AssertBaseline(expected); diff --git a/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindFunctionsQueryCosmosTest.cs b/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindFunctionsQueryCosmosTest.cs index b375c17b8d8..194143fba65 100644 --- a/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindFunctionsQueryCosmosTest.cs +++ b/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindFunctionsQueryCosmosTest.cs @@ -60,8 +60,6 @@ public override async Task Order_by_length_twice_followed_by_projection_of_naked // Cosmos client evaluation. Issue #17246. await AssertTranslationFailed( () => base.Order_by_length_twice_followed_by_projection_of_naked_collection_navigation(async)); - - AssertSql(); } public override Task Sum_over_round_works_correctly_in_projection(bool async) @@ -76,13 +74,19 @@ public override Task Sum_over_truncate_works_correctly_in_projection(bool async) public override Task Sum_over_truncate_works_correctly_in_projection_2(bool async) => AssertTranslationFailed(() => base.Sum_over_truncate_works_correctly_in_projection_2(async)); - public override async Task Where_functions_nested(bool async) - { - // Cosmos client evaluation. Issue #17246. - await AssertTranslationFailed(() => base.Where_functions_nested(async)); + public override Task Where_functions_nested(bool async) + => Fixture.NoSyncTest( + async, async a => + { + await base.Where_functions_nested(a); - AssertSql(); - } + AssertSql( + """ +SELECT VALUE c +FROM root c +WHERE (POWER(LENGTH(c["id"]), 2.0) = 25.0) +"""); + }); public override Task Static_equals_nullable_datetime_compared_to_non_nullable(bool async) => Fixture.NoSyncTest( diff --git a/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindMiscellaneousQueryCosmosTest.cs b/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindMiscellaneousQueryCosmosTest.cs index e3d3a5ed169..4a993c6a7bd 100644 --- a/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindMiscellaneousQueryCosmosTest.cs +++ b/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindMiscellaneousQueryCosmosTest.cs @@ -2214,7 +2214,9 @@ public override Task Select_expression_date_add_milliseconds_large_number_divide AssertSql( """ -SELECT c["OrderDate"], DateTimePart("ms", c["OrderDate"]) AS c +@millisecondsPerDay='86400000' + +SELECT VALUE DateTimeAdd("ms", (DateTimePart("ms", c["OrderDate"]) % @millisecondsPerDay), DateTimeAdd("dd", (DateTimePart("ms", c["OrderDate"]) / @millisecondsPerDay), c["OrderDate"])) FROM root c WHERE ((c["$type"] = "Order") AND (c["OrderDate"] != null)) """); @@ -2228,7 +2230,7 @@ public override Task Add_minutes_on_constant_value(bool async) AssertSql( """ -SELECT VALUE (c["OrderID"] % 25) +SELECT VALUE DateTimeAdd("mi", (c["OrderID"] % 25), "1900-01-01T00:00:00") FROM root c WHERE ((c["$type"] = "Order") AND (c["OrderID"] < 10500)) ORDER BY c["OrderID"] @@ -3911,7 +3913,7 @@ public override Task Concat_int_string(bool async) AssertSql( """ -SELECT c["CustomerID"], c["OrderID"] +SELECT VALUE (c["CustomerID"] || ToString(c["OrderID"])) FROM root c WHERE (c["$type"] = "Order") """); @@ -3966,7 +3968,7 @@ public override Task Concat_constant_string_int(bool async) AssertSql( """ -SELECT VALUE c["OrderID"] +SELECT VALUE ("-" || ToString(c["OrderID"])) FROM root c WHERE (c["$type"] = "Order") """); @@ -4016,7 +4018,7 @@ public override Task Concat_string_int(bool async) AssertSql( """ -SELECT c["OrderID"], c["CustomerID"] +SELECT VALUE (ToString(c["OrderID"]) || c["CustomerID"]) FROM root c WHERE (c["$type"] = "Order") """); @@ -4239,7 +4241,9 @@ public override Task Concat_parameter_string_int(bool async) AssertSql( """ -SELECT VALUE c["OrderID"] +@parameter='-' + +SELECT VALUE (@parameter || ToString(c["OrderID"])) FROM root c WHERE (c["$type"] = "Order") """); @@ -4937,33 +4941,69 @@ public virtual async Task ToPageAsync_in_subquery_throws() #endregion ToPageAsync - public override async Task Ternary_Not_Null_Contains(bool async) - { - await AssertTranslationFailed(() => base.Ternary_Not_Null_Contains(async)); + public override Task Ternary_Not_Null_Contains(bool async) + => Fixture.NoSyncTest( + async, async a => + { + await base.Ternary_Not_Null_Contains(a); - AssertSql(); - } + AssertSql( + """ +SELECT VALUE ((c["OrderID"] != null) ? (ToString(c["OrderID"]) || "") : null) +FROM root c +WHERE ((c["$type"] = "Order") AND CONTAINS(((c["OrderID"] != null) ? (ToString(c["OrderID"]) || "") : null), "1")) +ORDER BY c["OrderID"] +OFFSET 0 LIMIT 1 +"""); + }); - public override async Task Ternary_Not_Null_endsWith_Non_Numeric_First_Part(bool async) - { - await AssertTranslationFailed(() => base.Ternary_Not_Null_endsWith_Non_Numeric_First_Part(async)); + public override Task Ternary_Not_Null_endsWith_Non_Numeric_First_Part(bool async) + => Fixture.NoSyncTest( + async, async a => + { + await base.Ternary_Not_Null_endsWith_Non_Numeric_First_Part(a); - AssertSql(); - } + AssertSql( + """ +SELECT VALUE ((c["OrderID"] != null) ? (("" || ToString(c["OrderID"])) || "") : null) +FROM root c +WHERE ((c["$type"] = "Order") AND ENDSWITH(((c["OrderID"] != null) ? (("" || ToString(c["OrderID"])) || "") : null), "1")) +ORDER BY c["OrderID"] +OFFSET 0 LIMIT 1 +"""); + }); - public override async Task Ternary_Null_Equals_Non_Numeric_First_Part(bool async) - { - await AssertTranslationFailed(() => base.Ternary_Null_Equals_Non_Numeric_First_Part(async)); + public override Task Ternary_Null_Equals_Non_Numeric_First_Part(bool async) + => Fixture.NoSyncTest( + async, async a => + { + await base.Ternary_Null_Equals_Non_Numeric_First_Part(a); - AssertSql(); - } + AssertSql( + """ +SELECT VALUE ((c["OrderID"] = null) ? null : (("" || ToString(c["OrderID"])) || "")) +FROM root c +WHERE ((c["$type"] = "Order") AND (((c["OrderID"] = null) ? null : (("" || ToString(c["OrderID"])) || "")) = "1")) +ORDER BY c["OrderID"] +OFFSET 0 LIMIT 1 +"""); + }); - public override async Task Ternary_Null_StartsWith(bool async) - { - await AssertTranslationFailed(() => base.Ternary_Null_StartsWith(async)); + public override Task Ternary_Null_StartsWith(bool async) + => Fixture.NoSyncTest( + async, async a => + { + await base.Ternary_Null_StartsWith(a); - AssertSql(); - } + AssertSql( + """ +SELECT VALUE ((c["OrderID"] = null) ? null : (ToString(c["OrderID"]) || "")) +FROM root c +WHERE ((c["$type"] = "Order") AND STARTSWITH(((c["OrderID"] = null) ? null : (ToString(c["OrderID"]) || "")), "1")) +ORDER BY c["OrderID"] +OFFSET 0 LIMIT 1 +"""); + }); public override async Task Column_access_inside_subquery_predicate(bool async) { diff --git a/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindSelectQueryCosmosTest.cs b/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindSelectQueryCosmosTest.cs index b05fbdf7475..39b0b7d55b7 100644 --- a/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindSelectQueryCosmosTest.cs +++ b/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindSelectQueryCosmosTest.cs @@ -570,7 +570,7 @@ public override Task Select_non_matching_value_types_from_binary_expression_nest AssertSql( """ -SELECT VALUE c["OrderID"] +SELECT VALUE (c["OrderID"] + c["OrderID"]) FROM root c WHERE ((c["$type"] = "Order") AND (c["CustomerID"] = "ALFKI")) ORDER BY c["OrderID"] @@ -600,7 +600,7 @@ public override Task Select_non_matching_value_types_from_unary_expression_intro AssertSql( """ -SELECT VALUE c["OrderID"] +SELECT VALUE -(c["OrderID"]) FROM root c WHERE ((c["$type"] = "Order") AND (c["CustomerID"] = "ALFKI")) ORDER BY c["OrderID"] @@ -645,7 +645,12 @@ public override Task Select_non_matching_value_types_from_anonymous_type_introdu AssertSql( """ -SELECT VALUE c["OrderID"] +SELECT VALUE +{ + "LongOrder" : c["OrderID"], + "ShortOrder" : c["OrderID"], + "Order" : c["OrderID"] +} FROM root c WHERE ((c["$type"] = "Order") AND (c["CustomerID"] = "ALFKI")) ORDER BY c["OrderID"] @@ -1184,11 +1189,7 @@ public override Task Explicit_cast_in_arithmetic_operation_is_preserved(bool asy AssertSql( """ -SELECT VALUE -{ - "OrderID" : c["OrderID"], - "c" : (c["OrderID"] + 1000) -} +SELECT VALUE (c["OrderID"] / (c["OrderID"] + 1000)) FROM root c WHERE ((c["$type"] = "Order") AND (c["OrderID"] = 10250)) """); diff --git a/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindWhereQueryCosmosTest.cs b/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindWhereQueryCosmosTest.cs index cef8b19a3aa..3b5696861cb 100644 --- a/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindWhereQueryCosmosTest.cs +++ b/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindWhereQueryCosmosTest.cs @@ -172,21 +172,53 @@ FROM root c """); }); - public override async Task Where_method_call_nullable_type_closure_via_query_cache(bool async) - { - // Cosmos client evaluation. Issue #17246. - await AssertTranslationFailed(() => base.Where_method_call_nullable_type_closure_via_query_cache(async)); + public override Task Where_method_call_nullable_type_closure_via_query_cache(bool async) + => Fixture.NoSyncTest( + async, async a => + { + await base.Where_method_call_nullable_type_closure_via_query_cache(a); - AssertSql(); - } + AssertSql( + """ +@p='2' - public override async Task Where_method_call_nullable_type_reverse_closure_via_query_cache(bool async) - { - // Cosmos client evaluation. Issue #17246. - await AssertTranslationFailed(() => base.Where_method_call_nullable_type_reverse_closure_via_query_cache(async)); +SELECT VALUE c +FROM root c +WHERE (c["ReportsTo"] = @p) +""", + // + """ +@p='5' - AssertSql(); - } +SELECT VALUE c +FROM root c +WHERE (c["ReportsTo"] = @p) +"""); + }); + + public override Task Where_method_call_nullable_type_reverse_closure_via_query_cache(bool async) + => Fixture.NoSyncTest( + async, async a => + { + await base.Where_method_call_nullable_type_reverse_closure_via_query_cache(a); + + AssertSql( + """ +@p='1' + +SELECT VALUE c +FROM root c +WHERE (c["EmployeeID"] > @p) +""", + // + """ +@p='5' + +SELECT VALUE c +FROM root c +WHERE (c["EmployeeID"] > @p) +"""); + }); public override Task Where_method_call_closure_via_query_cache(bool async) => Fixture.NoSyncTest( @@ -404,21 +436,69 @@ FROM root c """); }); - public override async Task Where_simple_closure_via_query_cache_nullable_type(bool async) - { - // Cosmos client evaluation. Issue #17246. - await AssertTranslationFailed(() => base.Where_simple_closure_via_query_cache_nullable_type(async)); + public override Task Where_simple_closure_via_query_cache_nullable_type(bool async) + => Fixture.NoSyncTest( + async, async a => + { + await base.Where_simple_closure_via_query_cache_nullable_type(a); - AssertSql(); - } + AssertSql( + """ +@p='2' - public override async Task Where_simple_closure_via_query_cache_nullable_type_reverse(bool async) - { - // Cosmos client evaluation. Issue #17246. - await AssertTranslationFailed(() => base.Where_simple_closure_via_query_cache_nullable_type_reverse(async)); +SELECT VALUE c +FROM root c +WHERE (c["ReportsTo"] = @p) +""", + // + """ +@p='5' - AssertSql(); - } +SELECT VALUE c +FROM root c +WHERE (c["ReportsTo"] = @p) +""", + // + """ +@p=null + +SELECT VALUE c +FROM root c +WHERE (c["ReportsTo"] = @p) +"""); + }); + + public override Task Where_simple_closure_via_query_cache_nullable_type_reverse(bool async) + => Fixture.NoSyncTest( + async, async a => + { + await base.Where_simple_closure_via_query_cache_nullable_type_reverse(a); + + AssertSql( + """ +@p=null + +SELECT VALUE c +FROM root c +WHERE (c["ReportsTo"] = @p) +""", + // + """ +@p='5' + +SELECT VALUE c +FROM root c +WHERE (c["ReportsTo"] = @p) +""", + // + """ +@p='2' + +SELECT VALUE c +FROM root c +WHERE (c["ReportsTo"] = @p) +"""); + }); [ConditionalTheory(Skip = "Always uses sync code.")] public override Task Where_subquery_closure_via_query_cache(bool async) @@ -1493,13 +1573,19 @@ FROM root c """); }); - public override async Task Decimal_cast_to_double_works(bool async) - { - // Cosmos client evaluation. Issue #17246. - await AssertTranslationFailed(() => base.Decimal_cast_to_double_works(async)); + public override Task Decimal_cast_to_double_works(bool async) + => Fixture.NoSyncTest( + async, async a => + { + await base.Decimal_cast_to_double_works(a); - AssertSql(); - } + AssertSql( + """ +SELECT VALUE c +FROM root c +WHERE ((c["$type"] = "Product") AND (c["UnitPrice"] > 100.0)) +"""); + }); public override Task Where_is_conditional(bool async) => Fixture.NoSyncTest( @@ -1523,21 +1609,38 @@ public override async Task Filter_non_nullable_value_after_FirstOrDefault_on_emp AssertSql(); } - public override async Task Using_same_parameter_twice_in_query_generates_one_sql_parameter(bool async) - { - // Cosmos client evaluation. Issue #17246. - await AssertTranslationFailed(() => base.Using_same_parameter_twice_in_query_generates_one_sql_parameter(async)); + public override Task Using_same_parameter_twice_in_query_generates_one_sql_parameter(bool async) + => Fixture.NoSyncTest( + async, async a => + { + await base.Using_same_parameter_twice_in_query_generates_one_sql_parameter(a); - AssertSql(); - } + AssertSql( + """ +@i='10' - public override async Task Two_parameters_with_same_name_get_uniquified(bool async) - { - // Concat with conversion, issue #34963. - await AssertTranslationFailed(() => base.Using_same_parameter_twice_in_query_generates_one_sql_parameter(async)); +SELECT VALUE c["id"] +FROM root c +WHERE (((ToString(@i) || c["id"]) || ToString(@i)) = "10ALFKI10") +"""); + }); - AssertSql(); - } + public override Task Two_parameters_with_same_name_get_uniquified(bool async) + => Fixture.NoSyncTest( + async, async a => + { + await base.Two_parameters_with_same_name_get_uniquified(a); + + AssertSql( + """ +@p='11' +@p0='12' + +SELECT VALUE c +FROM root c +WHERE (((c["id"] || ToString(@p)) || (c["id"] || ToString(@p0))) = "ALFKI11ALFKI12") +"""); + }); public override async Task Where_Queryable_ToList_Count(bool async) { diff --git a/test/EFCore.Cosmos.FunctionalTests/Query/PrimitiveCollectionsQueryCosmosTest.cs b/test/EFCore.Cosmos.FunctionalTests/Query/PrimitiveCollectionsQueryCosmosTest.cs index cfbed155d22..e4540a126a8 100644 --- a/test/EFCore.Cosmos.FunctionalTests/Query/PrimitiveCollectionsQueryCosmosTest.cs +++ b/test/EFCore.Cosmos.FunctionalTests/Query/PrimitiveCollectionsQueryCosmosTest.cs @@ -1758,9 +1758,11 @@ public override void Parameter_collection_in_subquery_and_Convert_as_compiled_qu { // Array indexer over a parameter array ([1,2,3][0]) isn't supported by Cosmos. // TODO: general OFFSET/LIMIT support - AssertTranslationFailed(() => base.Parameter_collection_in_subquery_and_Convert_as_compiled_query()); + //Note same problem as relational test + var exception = + Assert.Throws<InvalidOperationException>(() => base.Parameter_collection_in_subquery_and_Convert_as_compiled_query()); - AssertSql(); + Assert.Contains("in the SQL tree does not have a type mapping assigned", exception.Message); } public override async Task Parameter_collection_in_subquery_Count_as_compiled_query(bool async) diff --git a/test/EFCore.Cosmos.FunctionalTests/Query/Translations/EnumTranslationsCosmosTest.cs b/test/EFCore.Cosmos.FunctionalTests/Query/Translations/EnumTranslationsCosmosTest.cs index efa1de7e95f..9b92f41c225 100644 --- a/test/EFCore.Cosmos.FunctionalTests/Query/Translations/EnumTranslationsCosmosTest.cs +++ b/test/EFCore.Cosmos.FunctionalTests/Query/Translations/EnumTranslationsCosmosTest.cs @@ -146,14 +146,25 @@ public override async Task Bitwise_and_integral_constant(bool async) // Always throws for sync. if (async) { - // Cosmos client evaluation. Issue #17246. - await AssertTranslationFailed(() => base.Bitwise_and_integral_constant(async)); + await base.Bitwise_and_integral_constant(async); AssertSql( """ SELECT VALUE c FROM root c WHERE ((c["FlagsEnum"] & 8) = 8) +""", + // + """ +SELECT VALUE c +FROM root c +WHERE ((c["FlagsEnum"] & 8) = 8) +""", + // + """ +SELECT VALUE c +FROM root c +WHERE ((c["FlagsEnum"] & 8) = 8) """); } } diff --git a/test/EFCore.Cosmos.FunctionalTests/Query/Translations/MathTranslationsCosmosTest.cs b/test/EFCore.Cosmos.FunctionalTests/Query/Translations/MathTranslationsCosmosTest.cs index eed4814b0ad..fc393274f6d 100644 --- a/test/EFCore.Cosmos.FunctionalTests/Query/Translations/MathTranslationsCosmosTest.cs +++ b/test/EFCore.Cosmos.FunctionalTests/Query/Translations/MathTranslationsCosmosTest.cs @@ -74,13 +74,13 @@ public override Task Ceiling(bool async) => Fixture.NoSyncTest( async, async a => { - await base.Ceiling_float(a); + await base.Ceiling(a); AssertSql( """ SELECT VALUE c FROM root c -WHERE (CEILING(c["Float"]) = 9.0) +WHERE (CEILING(c["Double"]) = 9.0) """); }); @@ -168,13 +168,19 @@ FROM root c """); }); - public override async Task Power(bool async) - { - // Convert node. Issue #25120. - await AssertTranslationFailed(() => base.Power(async)); + public override Task Power(bool async) + => Fixture.NoSyncTest( + async, async a => + { + await base.Power(a); - AssertSql(); - } + AssertSql( + """ +SELECT VALUE c +FROM root c +WHERE (POWER(c["Int"], 2.0) = 64.0) +"""); + }); public override Task Power_float(bool async) => Fixture.NoSyncTest( diff --git a/test/EFCore.Cosmos.FunctionalTests/Query/Translations/Operators/BitwiseOperatorTranslationsCosmosTest.cs b/test/EFCore.Cosmos.FunctionalTests/Query/Translations/Operators/BitwiseOperatorTranslationsCosmosTest.cs index f3758d07810..1e5c991078e 100644 --- a/test/EFCore.Cosmos.FunctionalTests/Query/Translations/Operators/BitwiseOperatorTranslationsCosmosTest.cs +++ b/test/EFCore.Cosmos.FunctionalTests/Query/Translations/Operators/BitwiseOperatorTranslationsCosmosTest.cs @@ -15,7 +15,23 @@ public BitwiseOperatorTranslationsCosmosTest(BasicTypesQueryCosmosFixture fixtur } public override Task Or(bool async) - => AssertTranslationFailed(() => base.Or(async)); + => Fixture.NoSyncTest( + async, async a => + { + await base.Or(a); + + AssertSql( + """ +SELECT VALUE c +FROM root c +WHERE ((c["Int"] | c["Long"]) = 7) +""", + // + """ +SELECT VALUE (c["Int"] | c["Long"]) +FROM root c +"""); + }); public override async Task Or_over_boolean(bool async) { @@ -34,17 +50,19 @@ FROM root c } } - public override async Task Or_multiple(bool async) - { - // Always throws for sync. - if (async) - { - // Bitwise operators on booleans. Issue #13168. - await Assert.ThrowsAsync<InvalidOperationException>(() => base.Or_multiple(async)); + public override Task Or_multiple(bool async) + => Fixture.NoSyncTest( + async, async a => + { + await base.Or_multiple(a); - AssertSql(); - } - } + AssertSql( + """ +SELECT VALUE c +FROM root c +WHERE (((c["Int"] | c["Short"]) | c["Long"]) = 7) +"""); + }); public override Task And(bool async) => Fixture.NoSyncTest( diff --git a/test/EFCore.Cosmos.FunctionalTests/Query/Translations/StringTranslationsCosmosTest.cs b/test/EFCore.Cosmos.FunctionalTests/Query/Translations/StringTranslationsCosmosTest.cs index 3f9b73ecd4c..f73e9388d05 100644 --- a/test/EFCore.Cosmos.FunctionalTests/Query/Translations/StringTranslationsCosmosTest.cs +++ b/test/EFCore.Cosmos.FunctionalTests/Query/Translations/StringTranslationsCosmosTest.cs @@ -982,38 +982,68 @@ FROM root c """); }); - public override async Task Concat_string_int_comparison1(bool async) - { - // Cosmos client evaluation. Issue #17246. - await AssertTranslationFailed(() => base.Concat_string_int_comparison1(async)); + public override Task Concat_string_int_comparison1(bool async) + => Fixture.NoSyncTest( + async, async a => + { + await base.Concat_string_int_comparison1(a); - AssertSql(); - } + AssertSql( + """ +@i=? - public override async Task Concat_string_int_comparison2(bool async) - { - // Cosmos client evaluation. Issue #17246. - await AssertTranslationFailed(() => base.Concat_string_int_comparison2(async)); +SELECT VALUE c +FROM root c +WHERE ((c["String"] || ToString(@i)) = "Seattle10") +"""); + }); - AssertSql(); - } + public override Task Concat_string_int_comparison2(bool async) + => Fixture.NoSyncTest( + async, async a => + { + await base.Concat_string_int_comparison2(a); - public override async Task Concat_string_int_comparison3(bool async) - { - // Cosmos client evaluation. Issue #17246. - await AssertTranslationFailed(() => base.Concat_string_int_comparison3(async)); + AssertSql( + """ +@i=? - AssertSql(); - } +SELECT VALUE c +FROM root c +WHERE ((ToString(@i) || c["String"]) = "10Seattle") +"""); + }); - public override async Task Concat_string_int_comparison4(bool async) - { - // Cosmos client evaluation. Issue #17246. - await AssertTranslationFailed(() => base.Concat_string_int_comparison4(async)); + public override Task Concat_string_int_comparison3(bool async) + => Fixture.NoSyncTest( + async, async a => + { + await base.Concat_string_int_comparison3(a); - AssertSql( - ); - } + AssertSql( + """ +@p=? +@j=? + +SELECT VALUE c +FROM root c +WHERE ((((ToString(@p) || c["String"]) || ToString(@j)) || ToString(42)) = "30Seattle2142") +"""); + }); + + public override Task Concat_string_int_comparison4(bool async) + => Fixture.NoSyncTest( + async, async a => + { + await base.Concat_string_int_comparison4(a); + + AssertSql( + """ +SELECT VALUE c +FROM root c +WHERE ((ToString(c["Int"]) || c["String"]) = "8Seattle") +"""); + }); public override Task Concat_method_comparison(bool async) => Fixture.NoSyncTest( diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindWhereQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindWhereQuerySqlServerTest.cs index 483e31e1cc7..2cbcf321a4c 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindWhereQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindWhereQuerySqlServerTest.cs @@ -1,6 +1,8 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using Microsoft.EntityFrameworkCore.TestModels.Northwind; + namespace Microsoft.EntityFrameworkCore.Query; #nullable disable @@ -21,6 +23,17 @@ public NorthwindWhereQuerySqlServerTest( public virtual void Check_all_tests_overridden() => TestHelpers.AssertAllMethodsOverridden(GetType()); + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Equality_operator_int_to_long(bool async) + { + long arg = 10248; + + return AssertQuery( + async, + ss => ss.Set<Order>().Where(o => o.OrderID == arg)); + } + public override async Task Where_simple(bool async) { await base.Where_simple(async);