diff --git a/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs b/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs index d0fb4fd1135..1ed5c6c8d0f 100644 --- a/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs +++ b/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs @@ -168,12 +168,12 @@ public static string ConflictingRowValuesSensitive(object? firstEntityType, obje firstEntityType, secondEntityType, keyValue, firstConflictingValue, secondConflictingValue, column); /// - /// Store type '{storeType1}' was inferred for a primitive collection, but that primitive collection was previously inferred to have store type '{storeType2}'. + /// Conflicting type mappings were inferred for column '{column}'. /// - public static string ConflictingTypeMappingsForPrimitiveCollection(object? storeType1, object? storeType2) + public static string ConflictingTypeMappingsInferredForColumn(object? column) => string.Format( - GetString("ConflictingTypeMappingsForPrimitiveCollection", nameof(storeType1), nameof(storeType2)), - storeType1, storeType2); + GetString("ConflictingTypeMappingsInferredForColumn", nameof(column)), + column); /// /// A seed entity for entity type '{entityType}' has the same key value as another seed entity mapped to the same table '{table}', but have different values for the column '{column}'. Consider using 'DbContextOptionsBuilder.EnableSensitiveDataLogging' to see the conflicting values. diff --git a/src/EFCore.Relational/Properties/RelationalStrings.resx b/src/EFCore.Relational/Properties/RelationalStrings.resx index d6969185c9a..97b5fc2bc0d 100644 --- a/src/EFCore.Relational/Properties/RelationalStrings.resx +++ b/src/EFCore.Relational/Properties/RelationalStrings.resx @@ -175,8 +175,8 @@ Instances of entity types '{firstEntityType}' and '{secondEntityType}' are mapped to the same row with the key value '{keyValue}', but have different property values '{firstConflictingValue}' and '{secondConflictingValue}' for the column '{column}'. - - Store type '{storeType1}' was inferred for a primitive collection, but that primitive collection was previously inferred to have store type '{storeType2}'. + + Conflicting type mappings were inferred for column '{column}'. A seed entity for entity type '{entityType}' has the same key value as another seed entity mapped to the same table '{table}', but have different values for the column '{column}'. Consider using 'DbContextOptionsBuilder.EnableSensitiveDataLogging' to see the conflicting values. diff --git a/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs b/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs index 04e8e1a6847..fec3abd6c8e 100644 --- a/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs +++ b/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs @@ -491,6 +491,21 @@ private static ShapedQueryExpression CreateShapedQueryExpression(IEntityType ent /// protected override ShapedQueryExpression? TranslateContains(ShapedQueryExpression source, Expression item) { + // Note that we don't apply the default type mapping to the item in order to allow it to be inferred from e.g. the subquery + // projection on the other side. + if (TranslateExpression(item, applyDefaultTypeMapping: false) is not SqlExpression translatedItem + || !TryGetProjection(source, out var projection)) + { + // If the item can't be translated, we can't translate to an IN expression. + // However, attempt to translate as Any since that passes through Where predicate translation, which e.g. does entity equality. + var anyLambdaParameter = Expression.Parameter(item.Type, "p"); + var anyLambda = Expression.Lambda( + Infrastructure.ExpressionExtensions.CreateEqualsExpression(anyLambdaParameter, item), + anyLambdaParameter); + + return TranslateAny(source, anyLambda); + } + // Pattern-match Contains over ValuesExpression, translating to simplified 'item IN (1, 2, 3)' with constant elements if (source.QueryExpression is SelectExpression { @@ -511,15 +526,9 @@ private static ShapedQueryExpression CreateShapedQueryExpression(IEntityType ent // Note that in the context of Contains we don't care about orderings } // Make sure that the source projects the column from the ValuesExpression directly, i.e. no projection out with some expression - && TryGetProjection(source, out var projection) && projection is ColumnExpression projectedColumn && projectedColumn.Table == valuesExpression) { - if (TranslateExpression(item) is not SqlExpression translatedItem) - { - return null; - } - var values = new object?[valuesExpression.RowValues.Count]; for (var i = 0; i < values.Length; i++) { @@ -543,13 +552,25 @@ private static ShapedQueryExpression CreateShapedQueryExpression(IEntityType ent } } - // TODO: This generates an EXISTS subquery. Translate to IN instead: #30955 - var anyLambdaParameter = Expression.Parameter(item.Type, "p"); - var anyLambda = Expression.Lambda( - Infrastructure.ExpressionExtensions.CreateEqualsExpression(anyLambdaParameter, item), - anyLambdaParameter); + // Translate to IN with a subquery. + // Note that because of null semantics, this may get transformed to an EXISTS subquery in SqlNullabilityProcessor. + var subquery = (SelectExpression)source.QueryExpression; + if (subquery.Limit == null + && subquery.Offset == null) + { + subquery.ClearOrdering(); + } + + subquery.ReplaceProjection(new List { projection }); + subquery.ApplyProjection(); + + var translation = _sqlExpressionFactory.In(translatedItem, subquery, false); + subquery = _sqlExpressionFactory.Select(translation); - return TranslateAny(source, anyLambda); + return source.Update( + subquery, + Expression.Convert( + new ProjectionBindingExpression(subquery, new ProjectionMember(), typeof(bool?)), typeof(bool))); } /// @@ -1771,10 +1792,13 @@ protected virtual bool IsValidSelectExpressionForExecuteUpdate( /// Translates the given expression into equivalent SQL representation. /// /// An expression to translate. + /// + /// Whether to apply the default type mapping on the top-most element if it has none. Defaults to . + /// /// A which is translation of given expression or . - protected virtual SqlExpression? TranslateExpression(Expression expression) + protected virtual SqlExpression? TranslateExpression(Expression expression, bool applyDefaultTypeMapping = true) { - var translation = _sqlTranslator.Translate(expression); + var translation = _sqlTranslator.Translate(expression, applyDefaultTypeMapping); if (translation is null) { @@ -1809,7 +1833,7 @@ protected virtual bool IsValidSelectExpressionForExecuteUpdate( /// protected virtual Expression ApplyInferredTypeMappings( Expression expression, - IReadOnlyDictionary<(TableExpressionBase, string), RelationalTypeMapping> inferredTypeMappings) + IReadOnlyDictionary<(TableExpressionBase, string), RelationalTypeMapping?> inferredTypeMappings) => new RelationalInferredTypeMappingApplier(inferredTypeMappings).Visit(expression); /// @@ -2541,12 +2565,12 @@ private bool TryGetProjection(ShapedQueryExpression shapedQueryExpression, [NotN /// private sealed class ColumnTypeMappingScanner : ExpressionVisitor { - private readonly Dictionary<(TableExpressionBase, string), RelationalTypeMapping> _inferredColumns = new(); + private readonly Dictionary<(TableExpressionBase, string), RelationalTypeMapping?> _inferredColumns = new(); private SelectExpression? _currentSelectExpression; private ProjectionExpression? _currentProjectionExpression; - public IReadOnlyDictionary<(TableExpressionBase, string), RelationalTypeMapping> Scan(Expression expression) + public IReadOnlyDictionary<(TableExpressionBase, string), RelationalTypeMapping?> Scan(Expression expression) { _inferredColumns.Clear(); @@ -2583,6 +2607,19 @@ when WasMaybeOriginallyUntyped(columnExpression): return base.VisitExtension(node); } + // InExpression over a subquery: apply the item's type mapping on the subquery + case InExpression + { + Item.TypeMapping: { } typeMapping, + Subquery.Projection: [{ Expression: ColumnExpression columnExpression }] + } + when WasMaybeOriginallyUntyped(columnExpression): + { + RegisterInferredTypeMapping(columnExpression, typeMapping); + + return base.VisitExtension(node); + } + // For set operations involving a leg with a type mapping (e.g. some column) and a leg without one (queryable constant or // parameter), we infer the missing type mapping from the other side. case SetOperationBase @@ -2646,7 +2683,7 @@ when _currentSelectExpression is not null return base.VisitExtension(node); } - bool WasMaybeOriginallyUntyped(ColumnExpression columnExpression) + static bool WasMaybeOriginallyUntyped(ColumnExpression columnExpression) { var underlyingTable = columnExpression.Table is JoinExpressionBase joinExpression ? joinExpression.Table @@ -2654,9 +2691,15 @@ bool WasMaybeOriginallyUntyped(ColumnExpression columnExpression) return underlyingTable switch { + // TableExpressions are always fully-typed, with type mappings coming from the model TableExpression => false, + // FromSqlExpressions always receive the default type mapping for the projected element type - we never need to infer + // them. + FromSqlExpression + => false, + SelectExpression subquery => subquery.Projection.FirstOrDefault(p => p.Alias == columnExpression.Name) is { Expression.TypeMapping: null }, @@ -2675,7 +2718,7 @@ SqlExpression UnwrapConvert(SqlExpression expression) : expression; } - private void RegisterInferredTypeMapping(ColumnExpression columnExpression, RelationalTypeMapping inferredTypeMapping) + private void RegisterInferredTypeMapping(ColumnExpression columnExpression, RelationalTypeMapping? inferredTypeMapping) { var underlyingTable = columnExpression.Table is JoinExpressionBase joinExpression ? joinExpression.Table @@ -2684,9 +2727,12 @@ private void RegisterInferredTypeMapping(ColumnExpression columnExpression, Rela if (_inferredColumns.TryGetValue((underlyingTable, columnExpression.Name), out var knownTypeMapping) && inferredTypeMapping != knownTypeMapping) { - throw new InvalidOperationException( - RelationalStrings.ConflictingTypeMappingsForPrimitiveCollection( - inferredTypeMapping.StoreType, knownTypeMapping.StoreType)); + // A different type mapping was already inferred for this column - we have a conflict. + // Null out the value for the inferred type mapping as an indication of the conflict. If it turns out that we need the + // inferred mapping later, during the application phase, we'll throw an exception at that point (not all the inferred type + // mappings here will actually be needed, so we don't want to needlessly throw here). + _inferredColumns[(underlyingTable, columnExpression.Name)] = null; + return; } _inferredColumns[(underlyingTable, columnExpression.Name)] = inferredTypeMapping; @@ -2704,15 +2750,44 @@ protected class RelationalInferredTypeMappingApplier : ExpressionVisitor /// /// The inferred type mappings to be applied back on their query roots. /// - protected IReadOnlyDictionary<(TableExpressionBase Table, string ColumnName), RelationalTypeMapping> InferredTypeMappings { get; } + private IReadOnlyDictionary<(TableExpressionBase Table, string ColumnName), RelationalTypeMapping?> _inferredTypeMappings; /// /// Creates a new instance of the class. /// /// The inferred type mappings to be applied back on their query roots. public RelationalInferredTypeMappingApplier( - IReadOnlyDictionary<(TableExpressionBase, string), RelationalTypeMapping> inferredTypeMappings) - => InferredTypeMappings = inferredTypeMappings; + IReadOnlyDictionary<(TableExpressionBase, string), RelationalTypeMapping?> inferredTypeMappings) + => _inferredTypeMappings = inferredTypeMappings; + + /// + /// Attempts to find an inferred type mapping for the given table column. + /// + /// The table containing the column for which to find the inferred type mapping. + /// The name of the column for which to find the inferred type mapping. + /// The inferred type mapping, or if none could be found. + /// Whether an inferred type mapping could be found. + protected virtual bool TryGetInferredTypeMapping( + TableExpressionBase table, + string columnName, + [NotNullWhen(true)] out RelationalTypeMapping? inferredTypeMapping) + { + if (_inferredTypeMappings.TryGetValue((table, columnName), out inferredTypeMapping)) + { + // The inferred type mapping scanner records a null when two conflicting type mappings were inferred for the same + // column. + if (inferredTypeMapping is null) + { + throw new InvalidOperationException( + RelationalStrings.ConflictingTypeMappingsInferredForColumn(columnName)); + } + + return true; + } + + inferredTypeMapping = null; + return false; + } /// protected override Expression VisitExtension(Expression expression) @@ -2720,7 +2795,7 @@ protected override Expression VisitExtension(Expression expression) switch (expression) { case ColumnExpression { TypeMapping: null } columnExpression - when InferredTypeMappings.TryGetValue((columnExpression.Table, columnExpression.Name), out var typeMapping): + when TryGetInferredTypeMapping(columnExpression.Table, columnExpression.Name, out var typeMapping): return columnExpression.ApplyTypeMapping(typeMapping); case SelectExpression selectExpression: @@ -2762,7 +2837,7 @@ when InferredTypeMappings.TryGetValue((columnExpression.Table, columnExpression. /// Whether to strip the _ord column. protected virtual ValuesExpression ApplyTypeMappingsOnValuesExpression(ValuesExpression valuesExpression, bool stripOrdering) { - var inferredTypeMappings = InferredTypeMappings.TryGetValue((valuesExpression, ValuesValueColumnName), out var typeMapping) + var inferredTypeMappings = TryGetInferredTypeMapping(valuesExpression, ValuesValueColumnName, out var typeMapping) ? new[] { null, typeMapping } : new RelationalTypeMapping?[] { null, null }; diff --git a/src/EFCore.Relational/Query/RelationalSqlTranslatingExpressionVisitor.cs b/src/EFCore.Relational/Query/RelationalSqlTranslatingExpressionVisitor.cs index f0f490fc73f..3d50be65513 100644 --- a/src/EFCore.Relational/Query/RelationalSqlTranslatingExpressionVisitor.cs +++ b/src/EFCore.Relational/Query/RelationalSqlTranslatingExpressionVisitor.cs @@ -118,33 +118,38 @@ protected virtual void AddTranslationErrorDetails(string details) /// Translates an expression to an equivalent SQL representation. /// /// An expression to translate. + /// + /// Whether to apply the default type mapping on the top-most element if it has none. Defaults to . + /// /// A SQL translation of the given expression. - public virtual SqlExpression? Translate(Expression expression) + public virtual SqlExpression? Translate(Expression expression, bool applyDefaultTypeMapping = true) { TranslationErrorDetails = null; - return TranslateInternal(expression); + return TranslateInternal(expression, applyDefaultTypeMapping); } - private SqlExpression? TranslateInternal(Expression expression) + private SqlExpression? TranslateInternal(Expression expression, bool applyDefaultTypeMapping = true) { var result = Visit(expression); if (result is SqlExpression translation) { - if (translation is SqlUnaryExpression sqlUnaryExpression - && sqlUnaryExpression.OperatorType == ExpressionType.Convert + if (translation is SqlUnaryExpression { OperatorType: ExpressionType.Convert } sqlUnaryExpression && sqlUnaryExpression.Type == typeof(object)) { translation = sqlUnaryExpression.Operand; } - translation = _sqlExpressionFactory.ApplyDefaultTypeMapping(translation); - - if (translation.TypeMapping == null) + if (applyDefaultTypeMapping) { - // The return type is not-mappable hence return null - return null; + translation = _sqlExpressionFactory.ApplyDefaultTypeMapping(translation); + + if (translation.TypeMapping == null) + { + // The return type is not-mappable hence return null + return null; + } } return translation; diff --git a/src/EFCore.Relational/Query/SqlExpressionFactory.cs b/src/EFCore.Relational/Query/SqlExpressionFactory.cs index 53d23cbdfdf..77898d2898f 100644 --- a/src/EFCore.Relational/Query/SqlExpressionFactory.cs +++ b/src/EFCore.Relational/Query/SqlExpressionFactory.cs @@ -600,9 +600,13 @@ public virtual InExpression In(SqlExpression item, SqlExpression values, bool ne public virtual InExpression In(SqlExpression item, SelectExpression subquery, bool negated) { var sqlExpression = subquery.Projection.Single().Expression; - var typeMapping = sqlExpression.TypeMapping; + var subqueryTypeMapping = sqlExpression.TypeMapping; + + if (item.TypeMapping is null) + { + item = subqueryTypeMapping is null ? ApplyDefaultTypeMapping(item) : ApplyTypeMapping(item, subqueryTypeMapping); + } - item = ApplyTypeMapping(item, typeMapping); return new InExpression(item, subquery, negated, _boolTypeMapping); } diff --git a/src/EFCore.Relational/Query/SqlNullabilityProcessor.cs b/src/EFCore.Relational/Query/SqlNullabilityProcessor.cs index bf39f8f5947..909d9daa921 100644 --- a/src/EFCore.Relational/Query/SqlNullabilityProcessor.cs +++ b/src/EFCore.Relational/Query/SqlNullabilityProcessor.cs @@ -651,6 +651,9 @@ protected virtual SqlExpression VisitIn(InExpression inExpression, bool allowOpt { var item = Visit(inExpression.Item, out var itemNullable); + // If the InExpression is negated, it's as if we have an enclosing NOT, which prohibits optimized expansion. + allowOptimizedExpansion &= !inExpression.IsNegated; + if (inExpression.Subquery != null) { var subquery = Visit(inExpression.Subquery); @@ -660,20 +663,121 @@ protected virtual SqlExpression VisitIn(InExpression inExpression, bool allowOpt { nullable = false; - return subquery.Predicate!; + return _sqlExpressionFactory.Constant(inExpression.IsNegated, inExpression.TypeMapping); + } + + // Check whether the subquery projects out a nullable value; note that we unwrap any casts to get to the underlying + // ColumnExpression (since casts don't affect nullability). + // Note: we could broaden the optimization if we knew the nullability of the projection but we don't keep that information and + // we want to avoid double visitation + var subqueryProjection = subquery.Projection.Single().Expression; + + inExpression = inExpression.Update(item, values: null, subquery); + + var unwrappedSubqueryProjection = subqueryProjection; + while (unwrappedSubqueryProjection is SqlUnaryExpression + { + OperatorType: ExpressionType.Convert or ExpressionType.ConvertChecked, Operand: var operand + }) + { + unwrappedSubqueryProjection = operand; + } + + var projectionNullable = + unwrappedSubqueryProjection is not ColumnExpression { IsNullable: false } and not SqlConstantExpression { Value: null }; + + if (UseRelationalNulls) + { + nullable = itemNullable || projectionNullable; + + return inExpression; } - // if item is not nullable, and subquery contains a non-nullable column we know the result can never be null - // note: in this case we could broaden the optimization if we knew the nullability of the projection - // but we don't keep that information and we want to avoid double visitation - nullable = !(!itemNullable - && subquery.Projection.Count == 1 - && subquery.Projection[0].Expression is ColumnExpression columnProjection - && !columnProjection.IsNullable); + nullable = false; + + // SQL IN returns null when the item is null, and when the values (subquery projection) contains NULL and no match was made. + + switch ((itemNullable, projectionNullable)) + { + // If both sides are non-nullable, IN never returns null, so is safe to use as-is. + case (false, false): + return inExpression; + + case (true, false): + { + // If the item is actually null (not just nullable) and the projection is non-nullable, just return false immediately: + // WHERE NULL IN (SELECT NonNullable FROM foo) -> false + if (IsNull(item)) + { + return _sqlExpressionFactory.Constant(false, inExpression.TypeMapping); + } + + // Otherwise, since the projection is non-nullable, NULL will only be returned if the item wasn't found. Use as-is + // in optimized expansion (NULL is interpreted as false anyway), or compensate for the item being possibly null: + // WHERE Nullable IN (SELECT NonNullable FROM foo) -> WHERE Nullable IN (SELECT NonNullable FROM foo) AND Nullable IS NOT NULL + // WHERE Nullable NOT IN (SELECT NonNullable FROM foo) -> WHERE Nullable NOT IN (SELECT NonNullable FROM foo) OR Nullable IS NULL + return allowOptimizedExpansion + ? inExpression + : inExpression.IsNegated + ? _sqlExpressionFactory.OrElse(inExpression, _sqlExpressionFactory.IsNull(item)) + : _sqlExpressionFactory.AndAlso(inExpression, _sqlExpressionFactory.IsNotNull(item)); + } + + case (false, true): + { + // If the item is non-nullable but the projection is nullable, NULL will only be returned if the item wasn't found + // (as with the above case). + // Use as-is in optimized expansion (NULL is interpreted as false anyway), or compensate by coalescing NULL to false: + // WHERE NonNullable IN (SELECT Nullable FROM foo) -> WHERE COALESCE(NonNullable IN (SELECT Nullable FROM foo), false) + if (allowOptimizedExpansion) + { + return inExpression; + } + + // On SQL Server, EXISTS isn't less efficient than IN, and the additional COALESCE (and CASE/WHEN which it requires) + // add unneeded clutter (and possibly hurt perf). So allow providers to prefer EXISTS. + if (PreferExistsToComplexIn) + { + goto TransformToExists; + } + + return inExpression.IsNegated + ? _sqlExpressionFactory.Not( + _sqlExpressionFactory.Coalesce(inExpression.Negate(), _sqlExpressionFactory.Constant(false))) + : _sqlExpressionFactory.Coalesce(inExpression, _sqlExpressionFactory.Constant(false)); + } - return inExpression.Update(item, values: null, subquery); + case (true, true): + TransformToExists: + // Worst case: both sides are nullable; there's no way to distinguish the item was found or not. + // We rewrite to an EXISTS subquery where we can generate a precise predicate to check for what we need. Note that this + // performs (significantly) worse than an IN expression, since it involves a correlated subquery. + + // We'll need to mutate the subquery to introduce the predicate inside it, but it might be referenced by other places in + // the tree, so we create a copy to work on. + + // No need for a projection with EXISTS, clear it to get SELECT 1 + subquery = subquery.Update( + Array.Empty(), + subquery.Tables, + subquery.Predicate, + subquery.GroupBy, + subquery.Having, + subquery.Orderings, + subquery.Limit, + subquery.Offset); + + var predicate = VisitSqlBinary( + _sqlExpressionFactory.Equal(subqueryProjection, item), allowOptimizedExpansion: true, out _); + subquery.ApplyPredicate(predicate); + subquery.ClearOrdering(); + + return _sqlExpressionFactory.Exists(subquery, inExpression.IsNegated); + } } + // Non-subquery case + // for relational null semantics we don't need to extract null values from the array if (UseRelationalNulls || !(inExpression.Values is SqlConstantExpression || inExpression.Values is SqlParameterExpression)) @@ -1243,11 +1347,14 @@ protected virtual SqlExpression VisitJsonScalar( return jsonScalarExpression; } + /// + /// Determines whether an will be transformed to an when it would + /// otherwise require complex compensation for null semantics. + /// + protected virtual bool PreferExistsToComplexIn => false; + private static bool? TryGetBoolConstantValue(SqlExpression? expression) - => expression is SqlConstantExpression constantExpression - && constantExpression.Value is bool boolValue - ? boolValue - : null; + => expression is SqlConstantExpression { Value: bool boolValue } ? boolValue : null; private void RestoreNonNullableColumnsList(int counter) { @@ -1930,6 +2037,10 @@ private static bool IsLogicalNot(SqlUnaryExpression? sqlUnaryExpression) && sqlUnaryExpression.OperatorType == ExpressionType.Not && sqlUnaryExpression.Type == typeof(bool); + private bool IsNull(SqlExpression expression) + => expression is SqlConstantExpression { Value: null } + || expression is SqlParameterExpression { Name: string parameterName } && ParameterValues[parameterName] is null; + // ?a == ?b -> [(a == b) && (a != null && b != null)] || (a == null && b == null)) // // a | b | F1 = a == b | F2 = (a != null && b != null) | F3 = F1 && F2 | diff --git a/src/EFCore.SqlServer/Query/Internal/SqlServerQueryableMethodTranslatingExpressionVisitor.cs b/src/EFCore.SqlServer/Query/Internal/SqlServerQueryableMethodTranslatingExpressionVisitor.cs index 73f5377c7c7..49b119e9a12 100644 --- a/src/EFCore.SqlServer/Query/Internal/SqlServerQueryableMethodTranslatingExpressionVisitor.cs +++ b/src/EFCore.SqlServer/Query/Internal/SqlServerQueryableMethodTranslatingExpressionVisitor.cs @@ -132,10 +132,11 @@ protected override ShapedQueryExpression TranslateCollection( // apply a conversion from the values coming out of OPENJSON (always NVARCHAR(MAX)) to the required relational store type. var openJsonExpression = new TableValuedFunctionExpression(tableAlias, "OPENJSON", new[] { sqlExpression }); - // TODO: When we have metadata to determine if the element is nullable, pass that here to SelectExpression + // TODO: This is a temporary CLR type-based check; when we have proper metadata to determine if the element is nullable, use it here + var isColumnNullable = elementClrType.IsNullableType(); + var selectExpression = new SelectExpression( - openJsonExpression, columnName: "value", columnType: elementClrType, columnTypeMapping: elementTypeMapping, - isColumnNullable: null); + openJsonExpression, columnName: "value", columnType: elementClrType, columnTypeMapping: elementTypeMapping, isColumnNullable); if (elementTypeMapping is { StoreType: not "nvarchar(max)" }) { @@ -155,9 +156,7 @@ protected override ShapedQueryExpression TranslateCollection( "value", typeof(string), _typeMappingSource.FindMapping("nvarchar(max)"), - // TODO: When we have metadata to determine if the element is nullable, pass that here to - // SelectExpression - columnNullable: null), + isColumnNullable), elementClrType, elementTypeMapping) } @@ -402,7 +401,7 @@ public TemporalAnnotationApplyingExpressionVisitor(Func protected override Expression ApplyInferredTypeMappings( Expression expression, - IReadOnlyDictionary<(TableExpressionBase, string), RelationalTypeMapping> inferredTypeMappings) + IReadOnlyDictionary<(TableExpressionBase, string), RelationalTypeMapping?> inferredTypeMappings) => new SqlServerInferredTypeMappingApplier(_typeMappingSource, _sqlExpressionFactory, inferredTypeMappings).Visit(expression); /// @@ -426,7 +425,7 @@ protected class SqlServerInferredTypeMappingApplier : RelationalInferredTypeMapp public SqlServerInferredTypeMappingApplier( IRelationalTypeMappingSource typeMappingSource, ISqlExpressionFactory sqlExpressionFactory, - IReadOnlyDictionary<(TableExpressionBase, string), RelationalTypeMapping> inferredTypeMappings) + IReadOnlyDictionary<(TableExpressionBase, string), RelationalTypeMapping?> inferredTypeMappings) : base(inferredTypeMappings) => (_typeMappingSource, _sqlExpressionFactory) = (typeMappingSource, sqlExpressionFactory); @@ -441,7 +440,7 @@ protected override Expression VisitExtension(Expression expression) switch (expression) { case TableValuedFunctionExpression { Name: "OPENJSON", Schema: null, IsBuiltIn: true } openJsonExpression - when InferredTypeMappings.TryGetValue((openJsonExpression, "value"), out var typeMapping): + when TryGetInferredTypeMapping(openJsonExpression, "value", out var typeMapping): return ApplyTypeMappingsOnOpenJsonExpression(openJsonExpression, new[] { typeMapping }); // Above, we applied the type mapping the the parameter that OPENJSON accepts as an argument. @@ -455,7 +454,7 @@ when InferredTypeMappings.TryGetValue((openJsonExpression, "value"), out var typ foreach (var table in selectExpression.Tables) { if (table is TableValuedFunctionExpression { Name: "OPENJSON", Schema: null, IsBuiltIn: true } openJsonExpression - && InferredTypeMappings.TryGetValue((openJsonExpression, "value"), out var inferredTypeMapping)) + && TryGetInferredTypeMapping(openJsonExpression, "value", out var inferredTypeMapping)) { if (previousSelectInferredTypeMappings is null) { diff --git a/src/EFCore.SqlServer/Query/Internal/SqlServerSqlNullabilityProcessor.cs b/src/EFCore.SqlServer/Query/Internal/SqlServerSqlNullabilityProcessor.cs index 581d699f869..1ed6b70820a 100644 --- a/src/EFCore.SqlServer/Query/Internal/SqlServerSqlNullabilityProcessor.cs +++ b/src/EFCore.SqlServer/Query/Internal/SqlServerSqlNullabilityProcessor.cs @@ -104,4 +104,12 @@ protected virtual SqlExpression VisitSqlServerAggregateFunction( orderings ?? aggregateFunctionExpression.Orderings) : aggregateFunctionExpression; } + + /// + /// 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. + /// + protected override bool PreferExistsToComplexIn => true; } diff --git a/src/EFCore.Sqlite.Core/Query/Internal/SqliteQueryableMethodTranslatingExpressionVisitor.cs b/src/EFCore.Sqlite.Core/Query/Internal/SqliteQueryableMethodTranslatingExpressionVisitor.cs index ccd80eb9f4a..6cdd83945e6 100644 --- a/src/EFCore.Sqlite.Core/Query/Internal/SqliteQueryableMethodTranslatingExpressionVisitor.cs +++ b/src/EFCore.Sqlite.Core/Query/Internal/SqliteQueryableMethodTranslatingExpressionVisitor.cs @@ -167,10 +167,11 @@ protected override ShapedQueryExpression TranslateCollection( var jsonEachExpression = new TableValuedFunctionExpression(tableAlias, "json_each", new[] { sqlExpression }); - // TODO: When we have metadata to determine if the element is nullable, pass that here to SelectExpression + // TODO: This is a temporary CLR type-based check; when we have proper metadata to determine if the element is nullable, use it here + var isColumnNullable = elementClrType.IsNullableType(); + var selectExpression = new SelectExpression( - jsonEachExpression, columnName: "value", columnType: elementClrType, columnTypeMapping: elementTypeMapping, - isColumnNullable: null); + jsonEachExpression, columnName: "value", columnType: elementClrType, columnTypeMapping: elementTypeMapping, isColumnNullable); // TODO: SQLite does have REAL and BLOB types, which JSON does not. Need to possibly cast to that. if (elementTypeMapping is not null) @@ -187,7 +188,7 @@ protected override ShapedQueryExpression TranslateCollection( "key", typeof(int), typeMapping: _typeMappingSource.FindMapping(typeof(int)), - columnNullable: false), + isColumnNullable), ascending: true)); Expression shaperExpression = new ProjectionBindingExpression( @@ -284,7 +285,7 @@ private static Type GetProviderType(SqlExpression expression) /// protected override Expression ApplyInferredTypeMappings( Expression expression, - IReadOnlyDictionary<(TableExpressionBase, string), RelationalTypeMapping> inferredTypeMappings) + IReadOnlyDictionary<(TableExpressionBase, string), RelationalTypeMapping?> inferredTypeMappings) => new SqliteInferredTypeMappingApplier(_typeMappingSource, _sqlExpressionFactory, inferredTypeMappings).Visit(expression); /// @@ -308,7 +309,7 @@ protected class SqliteInferredTypeMappingApplier : RelationalInferredTypeMapping public SqliteInferredTypeMappingApplier( IRelationalTypeMappingSource typeMappingSource, ISqlExpressionFactory sqlExpressionFactory, - IReadOnlyDictionary<(TableExpressionBase, string), RelationalTypeMapping> inferredTypeMappings) + IReadOnlyDictionary<(TableExpressionBase, string), RelationalTypeMapping?> inferredTypeMappings) : base(inferredTypeMappings) => (_typeMappingSource, _sqlExpressionFactory) = (typeMappingSource, sqlExpressionFactory); @@ -323,7 +324,7 @@ protected override Expression VisitExtension(Expression expression) switch (expression) { case TableValuedFunctionExpression { Name: "json_each", Schema: null, IsBuiltIn: true } jsonEachExpression - when InferredTypeMappings.TryGetValue((jsonEachExpression, "value"), out var typeMapping): + when TryGetInferredTypeMapping(jsonEachExpression, "value", out var typeMapping): return ApplyTypeMappingsOnJsonEachExpression(jsonEachExpression, typeMapping); // Above, we applied the type mapping the the parameter that json_each accepts as an argument. @@ -337,7 +338,7 @@ when InferredTypeMappings.TryGetValue((jsonEachExpression, "value"), out var typ foreach (var table in selectExpression.Tables) { if (table is TableValuedFunctionExpression { Name: "json_each", Schema: null, IsBuiltIn: true } jsonEachExpression - && InferredTypeMappings.TryGetValue((jsonEachExpression, "value"), out var inferredTypeMapping)) + && TryGetInferredTypeMapping(jsonEachExpression, "value", out var inferredTypeMapping)) { if (previousSelectInferredTypeMappings is null) { diff --git a/test/EFCore.Cosmos.FunctionalTests/CustomConvertersCosmosTest.cs b/test/EFCore.Cosmos.FunctionalTests/CustomConvertersCosmosTest.cs index eae0210accf..7bc1bc61ddd 100644 --- a/test/EFCore.Cosmos.FunctionalTests/CustomConvertersCosmosTest.cs +++ b/test/EFCore.Cosmos.FunctionalTests/CustomConvertersCosmosTest.cs @@ -133,6 +133,9 @@ public override void GroupBy_converted_enum() Assert.Throws(() => base.GroupBy_converted_enum()).Message); } + public override void Infer_type_mapping_from_in_subquery_to_item() + => Assert.Throws(() => base.Infer_type_mapping_from_in_subquery_to_item()); + private void AssertSql(params string[] expected) => Fixture.TestSqlLoggerFactory.AssertBaseline(expected); diff --git a/test/EFCore.Relational.Specification.Tests/Query/NullSemanticsQueryTestBase.cs b/test/EFCore.Relational.Specification.Tests/Query/NullSemanticsQueryTestBase.cs index d3e7c2b59ad..6b698250e53 100644 --- a/test/EFCore.Relational.Specification.Tests/Query/NullSemanticsQueryTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/Query/NullSemanticsQueryTestBase.cs @@ -1154,7 +1154,75 @@ await AssertQueryScalar( [ConditionalTheory] [MemberData(nameof(IsAsyncData))] - public virtual async Task Null_semantics_contains_non_nullable_argument(bool async) + public virtual async Task Null_semantics_contains_non_nullable_item_with_non_nullable_subquery(bool async) + { + await AssertQueryScalar( + async, + ss => ss.Set() + .Where(e => ss.Set().Select(e => e.StringA).Contains(e.StringA)) + .Select(e => e.Id)); + + await AssertQueryScalar( + async, + ss => ss.Set() + .Where(e => !ss.Set().Select(e => e.StringA).Contains(e.StringA)) + .Select(e => e.Id)); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual async Task Null_semantics_contains_nullable_item_with_non_nullable_subquery(bool async) + { + await AssertQueryScalar( + async, + ss => ss.Set() + .Where(e => ss.Set().Select(e => e.StringA).Contains(e.NullableStringA)) + .Select(e => e.Id)); + + await AssertQueryScalar( + async, + ss => ss.Set() + .Where(e => !ss.Set().Select(e => e.StringA).Contains(e.NullableStringA)) + .Select(e => e.Id)); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual async Task Null_semantics_contains_non_nullable_item_with_nullable_subquery(bool async) + { + await AssertQueryScalar( + async, + ss => ss.Set() + .Where(e => ss.Set().Select(e => e.NullableStringA).Contains(e.StringA)) + .Select(e => e.Id)); + + await AssertQueryScalar( + async, + ss => ss.Set() + .Where(e => !ss.Set().Select(e => e.NullableStringA).Contains(e.StringA)) + .Select(e => e.Id)); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual async Task Null_semantics_contains_nullable_item_with_nullable_subquery(bool async) + { + await AssertQueryScalar( + async, + ss => ss.Set() + .Where(e => ss.Set().Select(e => e.NullableStringA).Contains(e.NullableStringA)) + .Select(e => e.Id)); + + await AssertQueryScalar( + async, + ss => ss.Set() + .Where(e => !ss.Set().Select(e => e.NullableStringA).Contains(e.NullableStringA)) + .Select(e => e.Id)); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual async Task Null_semantics_contains_non_nullable_item_with_values(bool async) { var ids = new List { diff --git a/test/EFCore.Specification.Tests/CustomConvertersTestBase.cs b/test/EFCore.Specification.Tests/CustomConvertersTestBase.cs index c49fce96e5e..b31f6d30ac0 100644 --- a/test/EFCore.Specification.Tests/CustomConvertersTestBase.cs +++ b/test/EFCore.Specification.Tests/CustomConvertersTestBase.cs @@ -841,6 +841,16 @@ public enum SomeEnum No } + [ConditionalFact] + public virtual void Infer_type_mapping_from_in_subquery_to_item() + { + using var context = CreateContext(); + var results = context.Set().Where(b => + context.Set().Select(bb => bb.TestBoolean).Contains(true) && b.Id == 13).ToList(); + + Assert.Equal(1, results.Count); + } + public abstract class CustomConvertersFixtureBase : BuiltInDataTypesFixtureBase { protected override string StoreName diff --git a/test/EFCore.Specification.Tests/Query/PrimitiveCollectionsQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/PrimitiveCollectionsQueryTestBase.cs index 5b941c53520..24db1694251 100644 --- a/test/EFCore.Specification.Tests/Query/PrimitiveCollectionsQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/PrimitiveCollectionsQueryTestBase.cs @@ -168,19 +168,19 @@ public virtual Task Parameter_collection_of_ints_Contains(bool async) [ConditionalTheory] [MemberData(nameof(IsAsyncData))] - public virtual Task Parameter_collection_of_nullable_ints_Contains(bool async) + public virtual Task Parameter_collection_of_nullable_ints_Contains_int(bool async) { var nullableInts = new int?[] { 10, 999 }; return AssertQuery( async, - ss => ss.Set().Where(c => nullableInts.Contains(c.NullableInt)), + ss => ss.Set().Where(c => nullableInts.Contains(c.Int)), entryCount: 1); } [ConditionalTheory] [MemberData(nameof(IsAsyncData))] - public virtual Task Parameter_collection_of_nullable_ints_Contains_null(bool async) + public virtual Task Parameter_collection_of_nullable_ints_Contains_nullable_int(bool async) { var nullableInts = new int?[] { null, 999 }; @@ -278,6 +278,14 @@ public virtual Task Column_collection_of_nullable_ints_Contains_null(bool async) ss => ss.Set().Where(c => c.NullableInts.Contains(null)), entryCount: 1); + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Column_collection_of_strings_contains_null(bool async) + => AssertQuery( + async, + ss => ss.Set().Where(c => c.Strings.Contains(null)), + entryCount: 0); + [ConditionalTheory] [MemberData(nameof(IsAsyncData))] public virtual Task Column_collection_of_bools_Contains(bool async) diff --git a/test/EFCore.SqlServer.FunctionalTests/CustomConvertersSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/CustomConvertersSqlServerTest.cs index 4f830041610..8b15e630f00 100644 --- a/test/EFCore.SqlServer.FunctionalTests/CustomConvertersSqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/CustomConvertersSqlServerTest.cs @@ -346,6 +346,21 @@ SELECT [HoldingEnum] FROM [HolderClass] """); } + public override void Infer_type_mapping_from_in_subquery_to_item() + { + base.Infer_type_mapping_from_in_subquery_to_item(); + + AssertSql( +""" +SELECT [b].[Id], [b].[Enum16], [b].[Enum32], [b].[Enum64], [b].[Enum8], [b].[EnumS8], [b].[EnumU16], [b].[EnumU32], [b].[EnumU64], [b].[PartitionId], [b].[TestBoolean], [b].[TestByte], [b].[TestCharacter], [b].[TestDateOnly], [b].[TestDateTime], [b].[TestDateTimeOffset], [b].[TestDecimal], [b].[TestDouble], [b].[TestInt16], [b].[TestInt32], [b].[TestInt64], [b].[TestSignedByte], [b].[TestSingle], [b].[TestTimeOnly], [b].[TestTimeSpan], [b].[TestUnsignedInt16], [b].[TestUnsignedInt32], [b].[TestUnsignedInt64] +FROM [BuiltInDataTypes] AS [b] +WHERE N'Yeps' IN ( + SELECT [b0].[TestBoolean] + FROM [BuiltInDataTypes] AS [b0] +) AND [b].[Id] = 13 +"""); + } + private void AssertSql(params string[] expected) => Fixture.TestSqlLoggerFactory.AssertBaseline(expected); diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/ComplexNavigationsQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/ComplexNavigationsQuerySqlServerTest.cs index 3b776e520e0..7b5cfba9801 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/ComplexNavigationsQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/ComplexNavigationsQuerySqlServerTest.cs @@ -1993,14 +1993,14 @@ public override async Task Contains_with_subquery_optional_navigation_and_consta SELECT [l].[Id], [l].[Date], [l].[Name], [l].[OneToMany_Optional_Self_Inverse1Id], [l].[OneToMany_Required_Self_Inverse1Id], [l].[OneToOne_Optional_Self1Id] FROM [LevelOne] AS [l] LEFT JOIN [LevelTwo] AS [l0] ON [l].[Id] = [l0].[Level1_Optional_Id] -WHERE EXISTS ( - SELECT 1 +WHERE 1 IN ( + SELECT [t].[Id] FROM ( SELECT DISTINCT [l1].[Id], [l1].[Level2_Optional_Id], [l1].[Level2_Required_Id], [l1].[Name], [l1].[OneToMany_Optional_Inverse3Id], [l1].[OneToMany_Optional_Self_Inverse3Id], [l1].[OneToMany_Required_Inverse3Id], [l1].[OneToMany_Required_Self_Inverse3Id], [l1].[OneToOne_Optional_PK_Inverse3Id], [l1].[OneToOne_Optional_Self3Id] FROM [LevelThree] AS [l1] WHERE [l0].[Id] IS NOT NULL AND [l0].[Id] = [l1].[OneToMany_Optional_Inverse3Id] ) AS [t] - WHERE [t].[Id] = 1) +) """); } @@ -2013,10 +2013,11 @@ public override async Task Contains_with_subquery_optional_navigation_scalar_dis SELECT [l].[Id], [l].[Date], [l].[Name], [l].[OneToMany_Optional_Self_Inverse1Id], [l].[OneToMany_Required_Self_Inverse1Id], [l].[OneToOne_Optional_Self1Id] FROM [LevelOne] AS [l] LEFT JOIN [LevelTwo] AS [l0] ON [l].[Id] = [l0].[Level1_Optional_Id] -WHERE EXISTS ( - SELECT DISTINCT 1 +WHERE 1 IN ( + SELECT DISTINCT CAST(LEN([l1].[Name]) AS int) FROM [LevelThree] AS [l1] - WHERE [l0].[Id] IS NOT NULL AND [l0].[Id] = [l1].[OneToMany_Optional_Inverse3Id] AND CAST(LEN([l1].[Name]) AS int) = 1) + WHERE [l0].[Id] IS NOT NULL AND [l0].[Id] = [l1].[OneToMany_Optional_Inverse3Id] +) """); } @@ -4046,10 +4047,11 @@ FROM [LevelOne] AS [l] OUTER APPLY ( SELECT [l0].[Id], [l0].[Level2_Optional_Id], [l0].[Level2_Required_Id], [l0].[Name], [l0].[OneToMany_Optional_Inverse3Id], [l0].[OneToMany_Optional_Self_Inverse3Id], [l0].[OneToMany_Required_Inverse3Id], [l0].[OneToMany_Required_Self_Inverse3Id], [l0].[OneToOne_Optional_PK_Inverse3Id], [l0].[OneToOne_Optional_Self3Id] FROM [LevelThree] AS [l0] - WHERE EXISTS ( - SELECT 1 + WHERE [l0].[Level2_Required_Id] IN ( + SELECT [l1].[Id] FROM [LevelTwo] AS [l1] - WHERE [l].[Id] = [l1].[OneToMany_Required_Inverse2Id] AND [l1].[Id] = [l0].[Level2_Required_Id]) + WHERE [l].[Id] = [l1].[OneToMany_Required_Inverse2Id] + ) ) AS [t] LEFT JOIN [LevelTwo] AS [l2] ON [l].[Id] = [l2].[OneToMany_Required_Inverse2Id] ORDER BY [l].[Id], [t].[Id] diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/ComplexNavigationsSharedTypeQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/ComplexNavigationsSharedTypeQuerySqlServerTest.cs index 9d01362a8e4..bfed06a4f5b 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/ComplexNavigationsSharedTypeQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/ComplexNavigationsSharedTypeQuerySqlServerTest.cs @@ -571,7 +571,11 @@ WHEN [t0].[Level2_Required_Id] IS NOT NULL AND [t0].[OneToMany_Required_Inverse3 WHERE [t].[OneToOne_Required_PK_Date] IS NOT NULL AND [t].[Level1_Required_Id] IS NOT NULL AND [t].[OneToMany_Required_Inverse2Id] IS NOT NULL AND [t0].[Level2_Required_Id] IS NOT NULL AND [t0].[OneToMany_Required_Inverse3Id] IS NOT NULL AND EXISTS ( SELECT 1 FROM [Level1] AS [l3] - WHERE [l3].[OneToOne_Required_PK_Date] IS NOT NULL AND [l3].[Level1_Required_Id] IS NOT NULL AND [l3].[OneToMany_Required_Inverse2Id] IS NOT NULL AND [l].[Id] = [l3].[OneToMany_Required_Inverse2Id] AND [l3].[Id] = [t0].[Level2_Required_Id]) + WHERE [l3].[OneToOne_Required_PK_Date] IS NOT NULL AND [l3].[Level1_Required_Id] IS NOT NULL AND [l3].[OneToMany_Required_Inverse2Id] IS NOT NULL AND [l].[Id] = [l3].[OneToMany_Required_Inverse2Id] AND (CASE + WHEN [l3].[OneToOne_Required_PK_Date] IS NOT NULL AND [l3].[Level1_Required_Id] IS NOT NULL AND [l3].[OneToMany_Required_Inverse2Id] IS NOT NULL THEN [l3].[Id] + END = [t0].[Level2_Required_Id] OR (CASE + WHEN [l3].[OneToOne_Required_PK_Date] IS NOT NULL AND [l3].[Level1_Required_Id] IS NOT NULL AND [l3].[OneToMany_Required_Inverse2Id] IS NOT NULL THEN [l3].[Id] + END IS NULL AND [t0].[Level2_Required_Id] IS NULL))) ) AS [t1] LEFT JOIN ( SELECT [l4].[Id], [l4].[OneToOne_Required_PK_Date], [l4].[Level1_Optional_Id], [l4].[Level1_Required_Id], [l4].[Level2_Name], [l4].[OneToMany_Optional_Inverse2Id], [l4].[OneToMany_Required_Inverse2Id], [l4].[OneToOne_Optional_PK_Inverse2Id] @@ -1856,8 +1860,10 @@ LEFT JOIN ( FROM [Level1] AS [l0] WHERE [l0].[OneToOne_Required_PK_Date] IS NOT NULL AND [l0].[Level1_Required_Id] IS NOT NULL AND [l0].[OneToMany_Required_Inverse2Id] IS NOT NULL ) AS [t] ON [l].[Id] = [t].[Level1_Optional_Id] -WHERE EXISTS ( - SELECT 1 +WHERE 1 IN ( + SELECT CASE + WHEN [t0].[Level2_Required_Id] IS NOT NULL AND [t0].[OneToMany_Required_Inverse3Id] IS NOT NULL THEN [t0].[Id] + END FROM ( SELECT DISTINCT [l1].[Id], [l1].[Level2_Optional_Id], [l1].[Level2_Required_Id], [l1].[Level3_Name], [l1].[OneToMany_Optional_Inverse3Id], [l1].[OneToMany_Required_Inverse3Id], [l1].[OneToOne_Optional_PK_Inverse3Id] FROM [Level1] AS [l1] @@ -1869,9 +1875,7 @@ WHEN [t].[OneToOne_Required_PK_Date] IS NOT NULL AND [t].[Level1_Required_Id] IS WHEN [t].[OneToOne_Required_PK_Date] IS NOT NULL AND [t].[Level1_Required_Id] IS NOT NULL AND [t].[OneToMany_Required_Inverse2Id] IS NOT NULL THEN [t].[Id] END IS NULL AND [l1].[OneToMany_Optional_Inverse3Id] IS NULL)) ) AS [t0] - WHERE CASE - WHEN [t0].[Level2_Required_Id] IS NOT NULL AND [t0].[OneToMany_Required_Inverse3Id] IS NOT NULL THEN [t0].[Id] - END = 1) +) """); } @@ -1958,8 +1962,8 @@ LEFT JOIN ( FROM [Level1] AS [l0] WHERE [l0].[OneToOne_Required_PK_Date] IS NOT NULL AND [l0].[Level1_Required_Id] IS NOT NULL AND [l0].[OneToMany_Required_Inverse2Id] IS NOT NULL ) AS [t] ON [l].[Id] = [t].[Level1_Optional_Id] -WHERE EXISTS ( - SELECT DISTINCT 1 +WHERE 1 IN ( + SELECT DISTINCT CAST(LEN([l1].[Level3_Name]) AS int) FROM [Level1] AS [l1] WHERE [l1].[Level2_Required_Id] IS NOT NULL AND [l1].[OneToMany_Required_Inverse3Id] IS NOT NULL AND CASE WHEN [t].[OneToOne_Required_PK_Date] IS NOT NULL AND [t].[Level1_Required_Id] IS NOT NULL AND [t].[OneToMany_Required_Inverse2Id] IS NOT NULL THEN [t].[Id] @@ -1967,7 +1971,8 @@ END IS NOT NULL AND (CASE WHEN [t].[OneToOne_Required_PK_Date] IS NOT NULL AND [t].[Level1_Required_Id] IS NOT NULL AND [t].[OneToMany_Required_Inverse2Id] IS NOT NULL THEN [t].[Id] END = [l1].[OneToMany_Optional_Inverse3Id] OR (CASE WHEN [t].[OneToOne_Required_PK_Date] IS NOT NULL AND [t].[Level1_Required_Id] IS NOT NULL AND [t].[OneToMany_Required_Inverse2Id] IS NOT NULL THEN [t].[Id] - END IS NULL AND [l1].[OneToMany_Optional_Inverse3Id] IS NULL)) AND CAST(LEN([l1].[Level3_Name]) AS int) = 1) + END IS NULL AND [l1].[OneToMany_Optional_Inverse3Id] IS NULL)) +) """); } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/FromSqlQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/FromSqlQuerySqlServerTest.cs index dd571b961aa..8e8ffa09cc2 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/FromSqlQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/FromSqlQuerySqlServerTest.cs @@ -144,12 +144,12 @@ public override async Task FromSqlRaw_composed_contains(bool async) """ SELECT [c].[CustomerID], [c].[Address], [c].[City], [c].[CompanyName], [c].[ContactName], [c].[ContactTitle], [c].[Country], [c].[Fax], [c].[Phone], [c].[PostalCode], [c].[Region] FROM [Customers] AS [c] -WHERE EXISTS ( - SELECT 1 +WHERE [c].[CustomerID] IN ( + SELECT [m].[CustomerID] FROM ( SELECT * FROM "Orders" ) AS [m] - WHERE [m].[CustomerID] = [c].[CustomerID]) +) """); } @@ -161,12 +161,12 @@ public override async Task FromSqlRaw_composed_contains2(bool async) """ SELECT [c].[CustomerID], [c].[Address], [c].[City], [c].[CompanyName], [c].[ContactName], [c].[ContactTitle], [c].[Country], [c].[Fax], [c].[Phone], [c].[PostalCode], [c].[Region] FROM [Customers] AS [c] -WHERE [c].[CustomerID] = N'ALFKI' AND EXISTS ( - SELECT 1 +WHERE [c].[CustomerID] = N'ALFKI' AND [c].[CustomerID] IN ( + SELECT [m].[CustomerID] FROM ( SELECT * FROM "Orders" ) AS [m] - WHERE [m].[CustomerID] = [c].[CustomerID]) +) """); } @@ -738,12 +738,12 @@ SELECT [m].[OrderID] SELECT [o].[OrderID] FROM [Orders] AS [o] -WHERE [o].[OrderID] <= @__max_0 AND EXISTS ( - SELECT 1 +WHERE [o].[OrderID] <= @__max_0 AND [o].[OrderID] IN ( + SELECT [m].[OrderID] FROM ( SELECT * FROM "Orders" WHERE "OrderID" >= @p0 ) AS [m] - WHERE [m].[OrderID] = [o].[OrderID]) +) """, // """ @@ -752,12 +752,12 @@ SELECT 1 SELECT [o].[OrderID] FROM [Orders] AS [o] -WHERE [o].[OrderID] <= @__max_0 AND EXISTS ( - SELECT 1 +WHERE [o].[OrderID] <= @__max_0 AND [o].[OrderID] IN ( + SELECT [m].[OrderID] FROM ( SELECT * FROM "Orders" WHERE "OrderID" >= @p0 ) AS [m] - WHERE [m].[OrderID] = [o].[OrderID]) +) """); } @@ -870,12 +870,12 @@ public override async Task FromSqlRaw_in_subquery_with_dbParameter(bool async) SELECT [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate] FROM [Orders] AS [o] -WHERE EXISTS ( - SELECT 1 +WHERE [o].[CustomerID] IN ( + SELECT [m].[CustomerID] FROM ( SELECT * FROM "Customers" WHERE "City" = @city ) AS [m] - WHERE [m].[CustomerID] = [o].[CustomerID]) +) """); } @@ -889,12 +889,12 @@ public override async Task FromSqlRaw_in_subquery_with_positional_dbParameter_wi SELECT [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate] FROM [Orders] AS [o] -WHERE EXISTS ( - SELECT 1 +WHERE [o].[CustomerID] IN ( + SELECT [m].[CustomerID] FROM ( SELECT * FROM "Customers" WHERE "City" = @p0 ) AS [m] - WHERE [m].[CustomerID] = [o].[CustomerID]) +) """); } @@ -908,12 +908,12 @@ public override async Task FromSqlRaw_in_subquery_with_positional_dbParameter_wi SELECT [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate] FROM [Orders] AS [o] -WHERE EXISTS ( - SELECT 1 +WHERE [o].[CustomerID] IN ( + SELECT [m].[CustomerID] FROM ( SELECT * FROM "Customers" WHERE "City" = @city ) AS [m] - WHERE [m].[CustomerID] = [o].[CustomerID]) +) """); } @@ -928,12 +928,12 @@ public override async Task FromSqlRaw_with_dbParameter_mixed_in_subquery(bool as SELECT [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate] FROM [Orders] AS [o] -WHERE EXISTS ( - SELECT 1 +WHERE [o].[CustomerID] IN ( + SELECT [m].[CustomerID] FROM ( SELECT * FROM "Customers" WHERE "City" = @p0 AND "ContactTitle" = @title ) AS [m] - WHERE [m].[CustomerID] = [o].[CustomerID]) +) """, // """ @@ -942,12 +942,12 @@ SELECT 1 SELECT [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate] FROM [Orders] AS [o] -WHERE EXISTS ( - SELECT 1 +WHERE [o].[CustomerID] IN ( + SELECT [m].[CustomerID] FROM ( SELECT * FROM "Customers" WHERE "City" = @city AND "ContactTitle" = @p1 ) AS [m] - WHERE [m].[CustomerID] = [o].[CustomerID]) +) """); } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/GearsOfWarQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/GearsOfWarQuerySqlServerTest.cs index 111d5855b81..6198f19c203 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/GearsOfWarQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/GearsOfWarQuerySqlServerTest.cs @@ -2560,10 +2560,10 @@ public override async Task Optional_navigation_type_compensation_works_with_cont SELECT [t].[Id], [t].[GearNickName], [t].[GearSquadId], [t].[IssueDate], [t].[Note] FROM [Tags] AS [t] LEFT JOIN [Gears] AS [g] ON [t].[GearNickName] = [g].[Nickname] AND [t].[GearSquadId] = [g].[SquadId] -WHERE ([t].[Note] <> N'K.I.A.' OR [t].[Note] IS NULL) AND EXISTS ( - SELECT 1 +WHERE ([t].[Note] <> N'K.I.A.' OR [t].[Note] IS NULL) AND [g].[SquadId] IN ( + SELECT [g0].[SquadId] FROM [Gears] AS [g0] - WHERE [g0].[SquadId] = [g].[SquadId]) +) """); } @@ -3106,10 +3106,10 @@ public override async Task Contains_with_local_nullable_guid_list_closure(bool a SELECT [t].[Id], [t].[GearNickName], [t].[GearSquadId], [t].[IssueDate], [t].[Note] FROM [Tags] AS [t] -WHERE EXISTS ( - SELECT 1 +WHERE [t].[Id] IN ( + SELECT CAST([i].[value] AS uniqueidentifier) AS [value] FROM OPENJSON(@__ids_0) AS [i] - WHERE CAST([i].[value] AS uniqueidentifier) = [t].[Id]) +) """); } @@ -6667,10 +6667,10 @@ public override async Task DateTimeOffset_Contains_Less_than_Greater_than(bool a SELECT [m].[Id], [m].[BriefingDocument], [m].[BriefingDocumentFileExtension], [m].[CodeName], [m].[Date], [m].[Duration], [m].[Rating], [m].[Time], [m].[Timeline] FROM [Missions] AS [m] -WHERE @__start_0 <= CAST(CONVERT(date, [m].[Timeline]) AS datetimeoffset) AND [m].[Timeline] < @__end_1 AND EXISTS ( - SELECT 1 +WHERE @__start_0 <= CAST(CONVERT(date, [m].[Timeline]) AS datetimeoffset) AND [m].[Timeline] < @__end_1 AND [m].[Timeline] IN ( + SELECT CAST([d].[value] AS datetimeoffset) AS [value] FROM OPENJSON(@__dates_2) AS [d] - WHERE CAST([d].[value] AS datetimeoffset) = [m].[Timeline]) +) """); } @@ -7382,10 +7382,10 @@ public override async Task OrderBy_Contains_empty_list(bool async) SELECT [g].[Nickname], [g].[SquadId], [g].[AssignedCityName], [g].[CityOfBirthName], [g].[Discriminator], [g].[FullName], [g].[HasSoulPatch], [g].[LeaderNickname], [g].[LeaderSquadId], [g].[Rank] FROM [Gears] AS [g] ORDER BY CASE - WHEN EXISTS ( - SELECT 1 + WHEN [g].[SquadId] IN ( + SELECT CAST([i].[value] AS int) AS [value] FROM OPENJSON(@__ids_0) AS [i] - WHERE CAST([i].[value] AS int) = [g].[SquadId]) THEN CAST(1 AS bit) + ) THEN CAST(1 AS bit) ELSE CAST(0 AS bit) END """); @@ -7933,10 +7933,10 @@ public override async Task Contains_on_collection_of_byte_subquery(bool async) """ SELECT [l].[Name], [l].[Discriminator], [l].[LocustHordeId], [l].[ThreatLevel], [l].[ThreatLevelByte], [l].[ThreatLevelNullableByte], [l].[DefeatedByNickname], [l].[DefeatedBySquadId], [l].[HighCommandId] FROM [LocustLeaders] AS [l] -WHERE EXISTS ( - SELECT 1 +WHERE [l].[ThreatLevelByte] IN ( + SELECT [l0].[ThreatLevelByte] FROM [LocustLeaders] AS [l0] - WHERE [l0].[ThreatLevelByte] = [l].[ThreatLevelByte]) +) """); } @@ -8010,10 +8010,10 @@ FROM [LocustLeaders] AS [l] CROSS APPLY ( SELECT [g].[Nickname], [g].[SquadId], [g].[AssignedCityName], [g].[CityOfBirthName], [g].[Discriminator], [g].[FullName], [g].[HasSoulPatch], [g].[LeaderNickname], [g].[LeaderSquadId], [g].[Rank] FROM [Gears] AS [g] - WHERE EXISTS ( - SELECT 1 + WHERE [l].[ThreatLevelByte] IN ( + SELECT [l0].[ThreatLevelByte] FROM [LocustLeaders] AS [l0] - WHERE [l0].[ThreatLevelByte] = [l].[ThreatLevelByte]) + ) ) AS [t] """); } @@ -8030,10 +8030,10 @@ FROM [LocustLeaders] AS [l] CROSS APPLY ( SELECT [g].[Nickname], [g].[SquadId], [g].[AssignedCityName], [g].[CityOfBirthName], [g].[Discriminator], [g].[FullName], [g].[HasSoulPatch], [g].[LeaderNickname], [g].[LeaderSquadId], [g].[Rank] FROM [Gears] AS [g] - WHERE NOT EXISTS ( - SELECT 1 + WHERE [l].[ThreatLevelByte] NOT IN ( + SELECT [l0].[ThreatLevelByte] FROM [LocustLeaders] AS [l0] - WHERE [l0].[ThreatLevelByte] = [l].[ThreatLevelByte]) + ) ) AS [t] """); } @@ -9379,10 +9379,10 @@ public override async Task Where_bool_column_and_Contains(bool async) SELECT [g].[Nickname], [g].[SquadId], [g].[AssignedCityName], [g].[CityOfBirthName], [g].[Discriminator], [g].[FullName], [g].[HasSoulPatch], [g].[LeaderNickname], [g].[LeaderSquadId], [g].[Rank] FROM [Gears] AS [g] -WHERE [g].[HasSoulPatch] = CAST(1 AS bit) AND EXISTS ( - SELECT 1 +WHERE [g].[HasSoulPatch] = CAST(1 AS bit) AND [g].[HasSoulPatch] IN ( + SELECT CAST([v].[value] AS bit) AS [value] FROM OPENJSON(@__values_0) AS [v] - WHERE CAST([v].[value] AS bit) = [g].[HasSoulPatch]) +) """); } @@ -9396,10 +9396,10 @@ public override async Task Where_bool_column_or_Contains(bool async) SELECT [g].[Nickname], [g].[SquadId], [g].[AssignedCityName], [g].[CityOfBirthName], [g].[Discriminator], [g].[FullName], [g].[HasSoulPatch], [g].[LeaderNickname], [g].[LeaderSquadId], [g].[Rank] FROM [Gears] AS [g] -WHERE [g].[HasSoulPatch] = CAST(1 AS bit) AND EXISTS ( - SELECT 1 +WHERE [g].[HasSoulPatch] = CAST(1 AS bit) AND [g].[HasSoulPatch] IN ( + SELECT CAST([v].[value] AS bit) AS [value] FROM OPENJSON(@__values_0) AS [v] - WHERE CAST([v].[value] AS bit) = [g].[HasSoulPatch]) +) """); } @@ -10097,8 +10097,8 @@ SELECT COALESCE(SUM(CAST(LEN([c].[Location]) AS int)), 0) FROM [Gears] AS [g2] INNER JOIN [Squads] AS [s0] ON [g2].[SquadId] = [s0].[Id] INNER JOIN [Cities] AS [c] ON [g2].[CityOfBirthName] = [c].[Name] - WHERE EXISTS ( - SELECT 1 + WHERE N'Marcus' IN ( + SELECT [t0].[Nickname] FROM ( SELECT [g3].[Nickname], [g3].[SquadId], [g3].[AssignedCityName], [g3].[CityOfBirthName], [g3].[Discriminator], [g3].[FullName], [g3].[HasSoulPatch], [g3].[LeaderNickname], [g3].[LeaderSquadId], [g3].[Rank] FROM [Gears] AS [g3] @@ -10106,11 +10106,11 @@ UNION ALL SELECT [g4].[Nickname], [g4].[SquadId], [g4].[AssignedCityName], [g4].[CityOfBirthName], [g4].[Discriminator], [g4].[FullName], [g4].[HasSoulPatch], [g4].[LeaderNickname], [g4].[LeaderSquadId], [g4].[Rank] FROM [Gears] AS [g4] ) AS [t0] - WHERE [t0].[Nickname] = N'Marcus') AND ([s].[Name] = [s0].[Name] OR ([s].[Name] IS NULL AND [s0].[Name] IS NULL))) AS [SumOfLengths] + ) AND ([s].[Name] = [s0].[Name] OR ([s].[Name] IS NULL AND [s0].[Name] IS NULL))) AS [SumOfLengths] FROM [Gears] AS [g] INNER JOIN [Squads] AS [s] ON [g].[SquadId] = [s].[Id] -WHERE EXISTS ( - SELECT 1 +WHERE N'Marcus' IN ( + SELECT [t].[Nickname] FROM ( SELECT [g0].[Nickname], [g0].[SquadId], [g0].[AssignedCityName], [g0].[CityOfBirthName], [g0].[Discriminator], [g0].[FullName], [g0].[HasSoulPatch], [g0].[LeaderNickname], [g0].[LeaderSquadId], [g0].[Rank] FROM [Gears] AS [g0] @@ -10118,7 +10118,7 @@ UNION ALL SELECT [g1].[Nickname], [g1].[SquadId], [g1].[AssignedCityName], [g1].[CityOfBirthName], [g1].[Discriminator], [g1].[FullName], [g1].[HasSoulPatch], [g1].[LeaderNickname], [g1].[LeaderSquadId], [g1].[Rank] FROM [Gears] AS [g1] ) AS [t] - WHERE [t].[Nickname] = N'Marcus') +) GROUP BY [s].[Name] """); } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/NonSharedPrimitiveCollectionsQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/NonSharedPrimitiveCollectionsQuerySqlServerTest.cs index 560225a1f9c..026de43e882 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/NonSharedPrimitiveCollectionsQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/NonSharedPrimitiveCollectionsQuerySqlServerTest.cs @@ -303,13 +303,13 @@ public virtual async Task Same_parameter_with_different_type_mappings() SELECT [t].[Id], [t].[DateTime], [t].[DateTime2], [t].[Ints] FROM [TestEntity] AS [t] -WHERE EXISTS ( - SELECT 1 +WHERE [t].[DateTime] IN ( + SELECT CAST([d].[value] AS datetime) AS [value] FROM OPENJSON(@__dateTimes_0) AS [d] - WHERE CAST([d].[value] AS datetime) = [t].[DateTime]) AND EXISTS ( - SELECT 1 +) AND [t].[DateTime2] IN ( + SELECT CAST([d0].[value] AS datetime2) AS [value] FROM OPENJSON(@__dateTimes_0_1) AS [d0] - WHERE CAST([d0].[value] AS datetime2) = [t].[DateTime2]) +) """); } @@ -334,7 +334,7 @@ public virtual async Task Same_collection_with_conflicting_type_mappings_not_sup m => dateTimes .Any(d => d == EF.Property(m, "DateTime") && d == EF.Property(m, "DateTime2"))) .ToArrayAsync()); - Assert.Equal(RelationalStrings.ConflictingTypeMappingsForPrimitiveCollection("datetime2", "datetime"), exception.Message); + Assert.Equal(RelationalStrings.ConflictingTypeMappingsInferredForColumn("value"), exception.Message); } #endregion Type mapping inference diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindAggregateOperatorsQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindAggregateOperatorsQuerySqlServerTest.cs index 3f459abd147..84cfe8b8efb 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindAggregateOperatorsQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindAggregateOperatorsQuerySqlServerTest.cs @@ -1537,10 +1537,10 @@ public override async Task Contains_with_subquery(bool async) """ SELECT [c].[CustomerID], [c].[Address], [c].[City], [c].[CompanyName], [c].[ContactName], [c].[ContactTitle], [c].[Country], [c].[Fax], [c].[Phone], [c].[PostalCode], [c].[Region] FROM [Customers] AS [c] -WHERE EXISTS ( - SELECT 1 +WHERE [c].[CustomerID] IN ( + SELECT [o].[CustomerID] FROM [Orders] AS [o] - WHERE [o].[CustomerID] = [c].[CustomerID]) +) """); } @@ -1554,10 +1554,10 @@ public override async Task Contains_with_local_array_closure(bool async) SELECT [c].[CustomerID], [c].[Address], [c].[City], [c].[CompanyName], [c].[ContactName], [c].[ContactTitle], [c].[Country], [c].[Fax], [c].[Phone], [c].[PostalCode], [c].[Region] FROM [Customers] AS [c] -WHERE EXISTS ( - SELECT 1 +WHERE [c].[CustomerID] IN ( + SELECT CAST([i].[value] AS nchar(5)) AS [value] FROM OPENJSON(@__ids_0) AS [i] - WHERE CAST([i].[value] AS nchar(5)) = [c].[CustomerID]) +) """, // """ @@ -1565,10 +1565,10 @@ WHERE CAST([i].[value] AS nchar(5)) = [c].[CustomerID]) SELECT [c].[CustomerID], [c].[Address], [c].[City], [c].[CompanyName], [c].[ContactName], [c].[ContactTitle], [c].[Country], [c].[Fax], [c].[Phone], [c].[PostalCode], [c].[Region] FROM [Customers] AS [c] -WHERE EXISTS ( - SELECT 1 +WHERE [c].[CustomerID] IN ( + SELECT CAST([i].[value] AS nchar(5)) AS [value] FROM OPENJSON(@__ids_0) AS [i] - WHERE CAST([i].[value] AS nchar(5)) = [c].[CustomerID]) +) """); } @@ -1616,10 +1616,10 @@ public override async Task Contains_with_local_uint_array_closure(bool async) SELECT [e].[EmployeeID], [e].[City], [e].[Country], [e].[FirstName], [e].[ReportsTo], [e].[Title] FROM [Employees] AS [e] -WHERE EXISTS ( - SELECT 1 +WHERE [e].[EmployeeID] IN ( + SELECT CAST([i].[value] AS int) AS [value] FROM OPENJSON(@__ids_0) AS [i] - WHERE CAST([i].[value] AS int) = [e].[EmployeeID]) +) """, // """ @@ -1627,10 +1627,10 @@ WHERE CAST([i].[value] AS int) = [e].[EmployeeID]) SELECT [e].[EmployeeID], [e].[City], [e].[Country], [e].[FirstName], [e].[ReportsTo], [e].[Title] FROM [Employees] AS [e] -WHERE EXISTS ( - SELECT 1 +WHERE [e].[EmployeeID] IN ( + SELECT CAST([i].[value] AS int) AS [value] FROM OPENJSON(@__ids_0) AS [i] - WHERE CAST([i].[value] AS int) = [e].[EmployeeID]) +) """); } @@ -1644,10 +1644,10 @@ public override async Task Contains_with_local_nullable_uint_array_closure(bool SELECT [e].[EmployeeID], [e].[City], [e].[Country], [e].[FirstName], [e].[ReportsTo], [e].[Title] FROM [Employees] AS [e] -WHERE EXISTS ( - SELECT 1 +WHERE [e].[EmployeeID] IN ( + SELECT CAST([i].[value] AS int) AS [value] FROM OPENJSON(@__ids_0) AS [i] - WHERE CAST([i].[value] AS int) = [e].[EmployeeID]) +) """, // """ @@ -1655,10 +1655,10 @@ WHERE CAST([i].[value] AS int) = [e].[EmployeeID]) SELECT [e].[EmployeeID], [e].[City], [e].[Country], [e].[FirstName], [e].[ReportsTo], [e].[Title] FROM [Employees] AS [e] -WHERE EXISTS ( - SELECT 1 +WHERE [e].[EmployeeID] IN ( + SELECT CAST([i].[value] AS int) AS [value] FROM OPENJSON(@__ids_0) AS [i] - WHERE CAST([i].[value] AS int) = [e].[EmployeeID]) +) """); } @@ -1684,10 +1684,10 @@ public override async Task Contains_with_local_list_closure(bool async) SELECT [c].[CustomerID], [c].[Address], [c].[City], [c].[CompanyName], [c].[ContactName], [c].[ContactTitle], [c].[Country], [c].[Fax], [c].[Phone], [c].[PostalCode], [c].[Region] FROM [Customers] AS [c] -WHERE EXISTS ( - SELECT 1 +WHERE [c].[CustomerID] IN ( + SELECT CAST([i].[value] AS nchar(5)) AS [value] FROM OPENJSON(@__ids_0) AS [i] - WHERE CAST([i].[value] AS nchar(5)) = [c].[CustomerID]) +) """); } @@ -1701,10 +1701,10 @@ public override async Task Contains_with_local_object_list_closure(bool async) SELECT [c].[CustomerID], [c].[Address], [c].[City], [c].[CompanyName], [c].[ContactName], [c].[ContactTitle], [c].[Country], [c].[Fax], [c].[Phone], [c].[PostalCode], [c].[Region] FROM [Customers] AS [c] -WHERE EXISTS ( - SELECT 1 +WHERE [c].[CustomerID] IN ( + SELECT CAST([i].[value] AS nchar(5)) AS [value] FROM OPENJSON(@__ids_0) AS [i] - WHERE CAST([i].[value] AS nchar(5)) = [c].[CustomerID]) +) """); } @@ -1718,10 +1718,10 @@ public override async Task Contains_with_local_list_closure_all_null(bool async) SELECT [c].[CustomerID], [c].[Address], [c].[City], [c].[CompanyName], [c].[ContactName], [c].[ContactTitle], [c].[Country], [c].[Fax], [c].[Phone], [c].[PostalCode], [c].[Region] FROM [Customers] AS [c] -WHERE EXISTS ( - SELECT 1 +WHERE [c].[CustomerID] IN ( + SELECT CAST([i].[value] AS nchar(5)) AS [value] FROM OPENJSON(@__ids_0) AS [i] - WHERE CAST([i].[value] AS nchar(5)) = [c].[CustomerID]) +) """); } @@ -1747,10 +1747,10 @@ public override async Task Contains_with_local_list_inline_closure_mix(bool asyn SELECT [c].[CustomerID], [c].[Address], [c].[City], [c].[CompanyName], [c].[ContactName], [c].[ContactTitle], [c].[Country], [c].[Fax], [c].[Phone], [c].[PostalCode], [c].[Region] FROM [Customers] AS [c] -WHERE EXISTS ( - SELECT 1 +WHERE [c].[CustomerID] IN ( + SELECT CAST([p].[value] AS nchar(5)) AS [value] FROM OPENJSON(@__p_0) AS [p] - WHERE CAST([p].[value] AS nchar(5)) = [c].[CustomerID]) +) """, // """ @@ -1758,10 +1758,10 @@ WHERE CAST([p].[value] AS nchar(5)) = [c].[CustomerID]) SELECT [c].[CustomerID], [c].[Address], [c].[City], [c].[CompanyName], [c].[ContactName], [c].[ContactTitle], [c].[Country], [c].[Fax], [c].[Phone], [c].[PostalCode], [c].[Region] FROM [Customers] AS [c] -WHERE EXISTS ( - SELECT 1 +WHERE [c].[CustomerID] IN ( + SELECT CAST([p].[value] AS nchar(5)) AS [value] FROM OPENJSON(@__p_0) AS [p] - WHERE CAST([p].[value] AS nchar(5)) = [c].[CustomerID]) +) """); } @@ -1775,10 +1775,10 @@ public override async Task Contains_with_local_non_primitive_list_inline_closure SELECT [c].[CustomerID], [c].[Address], [c].[City], [c].[CompanyName], [c].[ContactName], [c].[ContactTitle], [c].[Country], [c].[Fax], [c].[Phone], [c].[PostalCode], [c].[Region] FROM [Customers] AS [c] -WHERE EXISTS ( - SELECT 1 +WHERE [c].[CustomerID] IN ( + SELECT CAST([s].[value] AS nchar(5)) AS [value] FROM OPENJSON(@__Select_0) AS [s] - WHERE CAST([s].[value] AS nchar(5)) = [c].[CustomerID]) +) """, // """ @@ -1786,10 +1786,10 @@ WHERE CAST([s].[value] AS nchar(5)) = [c].[CustomerID]) SELECT [c].[CustomerID], [c].[Address], [c].[City], [c].[CompanyName], [c].[ContactName], [c].[ContactTitle], [c].[Country], [c].[Fax], [c].[Phone], [c].[PostalCode], [c].[Region] FROM [Customers] AS [c] -WHERE EXISTS ( - SELECT 1 +WHERE [c].[CustomerID] IN ( + SELECT CAST([s].[value] AS nchar(5)) AS [value] FROM OPENJSON(@__Select_0) AS [s] - WHERE CAST([s].[value] AS nchar(5)) = [c].[CustomerID]) +) """); } @@ -1803,10 +1803,10 @@ public override async Task Contains_with_local_non_primitive_list_closure_mix(bo SELECT [c].[CustomerID], [c].[Address], [c].[City], [c].[CompanyName], [c].[ContactName], [c].[ContactTitle], [c].[Country], [c].[Fax], [c].[Phone], [c].[PostalCode], [c].[Region] FROM [Customers] AS [c] -WHERE EXISTS ( - SELECT 1 +WHERE [c].[CustomerID] IN ( + SELECT CAST([s].[value] AS nchar(5)) AS [value] FROM OPENJSON(@__Select_0) AS [s] - WHERE CAST([s].[value] AS nchar(5)) = [c].[CustomerID]) +) """); } @@ -1837,10 +1837,10 @@ public override async Task Contains_with_local_collection_complex_predicate_and( SELECT [c].[CustomerID], [c].[Address], [c].[City], [c].[CompanyName], [c].[ContactName], [c].[ContactTitle], [c].[Country], [c].[Fax], [c].[Phone], [c].[PostalCode], [c].[Region] FROM [Customers] AS [c] -WHERE [c].[CustomerID] IN (N'ALFKI', N'ABCDE') AND EXISTS ( - SELECT 1 +WHERE [c].[CustomerID] IN (N'ALFKI', N'ABCDE') AND [c].[CustomerID] IN ( + SELECT CAST([i].[value] AS nchar(5)) AS [value] FROM OPENJSON(@__ids_0) AS [i] - WHERE CAST([i].[value] AS nchar(5)) = [c].[CustomerID]) +) """); } @@ -1878,10 +1878,10 @@ public override async Task Contains_with_local_collection_sql_injection(bool asy SELECT [c].[CustomerID], [c].[Address], [c].[City], [c].[CompanyName], [c].[ContactName], [c].[ContactTitle], [c].[Country], [c].[Fax], [c].[Phone], [c].[PostalCode], [c].[Region] FROM [Customers] AS [c] -WHERE EXISTS ( - SELECT 1 +WHERE [c].[CustomerID] IN ( + SELECT CAST([i].[value] AS nchar(5)) AS [value] FROM OPENJSON(@__ids_0) AS [i] - WHERE CAST([i].[value] AS nchar(5)) = [c].[CustomerID]) OR [c].[CustomerID] IN (N'ALFKI', N'ABCDE') +) OR [c].[CustomerID] IN (N'ALFKI', N'ABCDE') """); } @@ -1895,10 +1895,10 @@ public override async Task Contains_with_local_collection_empty_closure(bool asy SELECT [c].[CustomerID], [c].[Address], [c].[City], [c].[CompanyName], [c].[ContactName], [c].[ContactTitle], [c].[Country], [c].[Fax], [c].[Phone], [c].[PostalCode], [c].[Region] FROM [Customers] AS [c] -WHERE EXISTS ( - SELECT 1 +WHERE [c].[CustomerID] IN ( + SELECT CAST([i].[value] AS nchar(5)) AS [value] FROM OPENJSON(@__ids_0) AS [i] - WHERE CAST([i].[value] AS nchar(5)) = [c].[CustomerID]) +) """); } @@ -1922,10 +1922,10 @@ public override async Task Contains_top_level(bool async) @__p_0='ALFKI' (Size = 5) (DbType = StringFixedLength) SELECT CASE - WHEN EXISTS ( - SELECT 1 + WHEN @__p_0 IN ( + SELECT [c].[CustomerID] FROM [Customers] AS [c] - WHERE [c].[CustomerID] = @__p_0) THEN CAST(1 AS bit) + ) THEN CAST(1 AS bit) ELSE CAST(0 AS bit) END """); @@ -2353,10 +2353,10 @@ public override async Task Where_subquery_any_equals_operator(bool async) SELECT [c].[CustomerID], [c].[Address], [c].[City], [c].[CompanyName], [c].[ContactName], [c].[ContactTitle], [c].[Country], [c].[Fax], [c].[Phone], [c].[PostalCode], [c].[Region] FROM [Customers] AS [c] -WHERE EXISTS ( - SELECT 1 +WHERE [c].[CustomerID] IN ( + SELECT CAST([i].[value] AS nchar(5)) AS [value] FROM OPENJSON(@__ids_0) AS [i] - WHERE CAST([i].[value] AS nchar(5)) = [c].[CustomerID]) +) """); } @@ -2382,10 +2382,10 @@ public override async Task Where_subquery_any_equals_static(bool async) SELECT [c].[CustomerID], [c].[Address], [c].[City], [c].[CompanyName], [c].[ContactName], [c].[ContactTitle], [c].[Country], [c].[Fax], [c].[Phone], [c].[PostalCode], [c].[Region] FROM [Customers] AS [c] -WHERE EXISTS ( - SELECT 1 +WHERE [c].[CustomerID] IN ( + SELECT CAST([i].[value] AS nchar(5)) AS [value] FROM OPENJSON(@__ids_0) AS [i] - WHERE CAST([i].[value] AS nchar(5)) = [c].[CustomerID]) +) """); } @@ -2399,10 +2399,10 @@ public override async Task Where_subquery_where_any(bool async) SELECT [c].[CustomerID], [c].[Address], [c].[City], [c].[CompanyName], [c].[ContactName], [c].[ContactTitle], [c].[Country], [c].[Fax], [c].[Phone], [c].[PostalCode], [c].[Region] FROM [Customers] AS [c] -WHERE [c].[City] = N'México D.F.' AND EXISTS ( - SELECT 1 +WHERE [c].[City] = N'México D.F.' AND [c].[CustomerID] IN ( + SELECT CAST([i].[value] AS nchar(5)) AS [value] FROM OPENJSON(@__ids_0) AS [i] - WHERE CAST([i].[value] AS nchar(5)) = [c].[CustomerID]) +) """, // """ @@ -2410,10 +2410,10 @@ WHERE CAST([i].[value] AS nchar(5)) = [c].[CustomerID]) SELECT [c].[CustomerID], [c].[Address], [c].[City], [c].[CompanyName], [c].[ContactName], [c].[ContactTitle], [c].[Country], [c].[Fax], [c].[Phone], [c].[PostalCode], [c].[Region] FROM [Customers] AS [c] -WHERE [c].[City] = N'México D.F.' AND EXISTS ( - SELECT 1 +WHERE [c].[City] = N'México D.F.' AND [c].[CustomerID] IN ( + SELECT CAST([i].[value] AS nchar(5)) AS [value] FROM OPENJSON(@__ids_0) AS [i] - WHERE CAST([i].[value] AS nchar(5)) = [c].[CustomerID]) +) """); } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindCompiledQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindCompiledQuerySqlServerTest.cs index 471826b8fdd..0b0146fc71c 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindCompiledQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindCompiledQuerySqlServerTest.cs @@ -182,10 +182,10 @@ public override void Query_with_contains() SELECT [c].[CustomerID], [c].[Address], [c].[City], [c].[CompanyName], [c].[ContactName], [c].[ContactTitle], [c].[Country], [c].[Fax], [c].[Phone], [c].[PostalCode], [c].[Region] FROM [Customers] AS [c] -WHERE EXISTS ( - SELECT 1 +WHERE [c].[CustomerID] IN ( + SELECT CAST([a].[value] AS nchar(5)) AS [value] FROM OPENJSON(@__args) AS [a] - WHERE CAST([a].[value] AS nchar(5)) = [c].[CustomerID]) +) """, // """ @@ -193,10 +193,10 @@ WHERE CAST([a].[value] AS nchar(5)) = [c].[CustomerID]) SELECT [c].[CustomerID], [c].[Address], [c].[City], [c].[CompanyName], [c].[ContactName], [c].[ContactTitle], [c].[Country], [c].[Fax], [c].[Phone], [c].[PostalCode], [c].[Region] FROM [Customers] AS [c] -WHERE EXISTS ( - SELECT 1 +WHERE [c].[CustomerID] IN ( + SELECT CAST([a].[value] AS nchar(5)) AS [value] FROM OPENJSON(@__args) AS [a] - WHERE CAST([a].[value] AS nchar(5)) = [c].[CustomerID]) +) """); } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindMiscellaneousQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindMiscellaneousQuerySqlServerTest.cs index dd3be5d7c6d..f59f8df4023 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindMiscellaneousQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindMiscellaneousQuerySqlServerTest.cs @@ -2724,10 +2724,10 @@ public override async Task Where_subquery_on_bool(bool async) """ SELECT [p].[ProductID], [p].[Discontinued], [p].[ProductName], [p].[SupplierID], [p].[UnitPrice], [p].[UnitsInStock] FROM [Products] AS [p] -WHERE EXISTS ( - SELECT 1 +WHERE N'Chai' IN ( + SELECT [p0].[ProductName] FROM [Products] AS [p0] - WHERE [p0].[ProductName] = N'Chai') +) """); } @@ -2739,10 +2739,11 @@ public override async Task Where_subquery_on_collection(bool async) """ SELECT [p].[ProductID], [p].[Discontinued], [p].[ProductName], [p].[SupplierID], [p].[UnitPrice], [p].[UnitsInStock] FROM [Products] AS [p] -WHERE EXISTS ( - SELECT 1 +WHERE CAST(5 AS smallint) IN ( + SELECT [o].[Quantity] FROM [Order Details] AS [o] - WHERE [o].[ProductID] = [p].[ProductID] AND [o].[Quantity] = CAST(5 AS smallint)) + WHERE [o].[ProductID] = [p].[ProductID] +) """); } @@ -4039,10 +4040,10 @@ public override async Task Contains_with_DateTime_Date(bool async) SELECT [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate] FROM [Orders] AS [o] -WHERE EXISTS ( - SELECT 1 +WHERE CONVERT(date, [o].[OrderDate]) IN ( + SELECT CAST([d].[value] AS datetime) AS [value] FROM OPENJSON(@__dates_0) AS [d] - WHERE CAST([d].[value] AS datetime) = CONVERT(date, [o].[OrderDate])) +) """, // """ @@ -4050,10 +4051,10 @@ WHERE CAST([d].[value] AS datetime) = CONVERT(date, [o].[OrderDate])) SELECT [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate] FROM [Orders] AS [o] -WHERE EXISTS ( - SELECT 1 +WHERE CONVERT(date, [o].[OrderDate]) IN ( + SELECT CAST([d].[value] AS datetime) AS [value] FROM OPENJSON(@__dates_0) AS [d] - WHERE CAST([d].[value] AS datetime) = CONVERT(date, [o].[OrderDate])) +) """); } @@ -4065,11 +4066,12 @@ public override async Task Contains_with_subquery_involving_join_binds_to_correc """ SELECT [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate] FROM [Orders] AS [o] -WHERE [o].[OrderID] > 11000 AND EXISTS ( - SELECT 1 +WHERE [o].[OrderID] > 11000 AND [o].[OrderID] IN ( + SELECT [o0].[OrderID] FROM [Order Details] AS [o0] INNER JOIN [Products] AS [p] ON [o0].[ProductID] = [p].[ProductID] - WHERE [p].[ProductName] = N'Chai' AND [o0].[OrderID] = [o].[OrderID]) + WHERE [p].[ProductName] = N'Chai' +) """); } @@ -6274,10 +6276,10 @@ FROM [Customers] AS [c] SELECT [c].[CustomerID], [c].[Address], [c].[City], [c].[CompanyName], [c].[ContactName], [c].[ContactTitle], [c].[Country], [c].[Fax], [c].[Phone], [c].[PostalCode], [c].[Region] FROM [Orders] AS [o] LEFT JOIN [Customers] AS [c] ON [o].[CustomerID] = [c].[CustomerID] -WHERE EXISTS ( - SELECT 1 +WHERE [o].[OrderID] IN ( + SELECT CAST([o0].[value] AS int) AS [value] FROM OPENJSON(@__orderIds_0) AS [o0] - WHERE CAST([o0].[value] AS int) = [o].[OrderID]) +) """); } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindNavigationsQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindNavigationsQuerySqlServerTest.cs index 5bb41fbeb7c..33b317711d1 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindNavigationsQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindNavigationsQuerySqlServerTest.cs @@ -784,10 +784,10 @@ LEFT JOIN ( SELECT [c0].[CustomerID], [c0].[Address], [c0].[City], [c0].[CompanyName], [c0].[ContactName], [c0].[ContactTitle], [c0].[Country], [c0].[Fax], [c0].[Phone], [c0].[PostalCode], [c0].[Region], [o].[CustomerID] AS [CustomerID0], ROW_NUMBER() OVER(PARTITION BY [o].[CustomerID] ORDER BY [o].[OrderID], [c0].[CustomerID]) AS [row] FROM [Orders] AS [o] LEFT JOIN [Customers] AS [c0] ON [o].[CustomerID] = [c0].[CustomerID] - WHERE EXISTS ( - SELECT 1 + WHERE [o].[OrderID] IN ( + SELECT CAST([o0].[value] AS int) AS [value] FROM OPENJSON(@__orderIds_0) AS [o0] - WHERE CAST([o0].[value] AS int) = [o].[OrderID]) + ) ) AS [t] WHERE [t].[row] <= 1 ) AS [t0] ON [c].[CustomerID] = [t0].[CustomerID0] diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindSelectQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindSelectQuerySqlServerTest.cs index 14a62d6a3bf..73d3cb6b16b 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindSelectQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindSelectQuerySqlServerTest.cs @@ -2089,10 +2089,10 @@ FROM [Orders] AS [o] OUTER APPLY ( SELECT [t].[CustomerID], [o0].[OrderID], [o0].[OrderDate] FROM [Orders] AS [o0] - WHERE [t].[CustomerID] IS NOT NULL AND [t].[CustomerID] = [o0].[CustomerID] AND EXISTS ( - SELECT 1 + WHERE [t].[CustomerID] IS NOT NULL AND [t].[CustomerID] = [o0].[CustomerID] AND [o0].[OrderID] IN ( + SELECT CAST([f].[value] AS int) AS [value] FROM OPENJSON(@__filteredOrderIds_0) AS [f] - WHERE CAST([f].[value] AS int) = [o0].[OrderID]) + ) ) AS [t0] ORDER BY [t].[CustomerID], [t0].[OrderID] """); @@ -2114,10 +2114,10 @@ FROM [Orders] AS [o] OUTER APPLY ( SELECT [t].[OrderID] AS [Outer], [o0].[OrderID] AS [Inner], [o0].[OrderDate] FROM [Orders] AS [o0] - WHERE [o0].[OrderID] = [t].[OrderID] AND EXISTS ( - SELECT 1 + WHERE [o0].[OrderID] = [t].[OrderID] AND [o0].[OrderID] IN ( + SELECT CAST([f].[value] AS int) AS [value] FROM OPENJSON(@__filteredOrderIds_0) AS [f] - WHERE CAST([f].[value] AS int) = [o0].[OrderID]) + ) ) AS [t0] ORDER BY [t].[OrderID] """); @@ -2139,10 +2139,10 @@ FROM [Orders] AS [o] OUTER APPLY ( SELECT [t].[OrderDate] AS [Outer1], [t].[CustomerID] AS [Outer2], [o0].[OrderID] AS [Inner], [o0].[OrderDate] FROM [Orders] AS [o0] - WHERE ([o0].[CustomerID] = [t].[CustomerID] OR ([o0].[CustomerID] IS NULL AND [t].[CustomerID] IS NULL)) AND EXISTS ( - SELECT 1 + WHERE ([o0].[CustomerID] = [t].[CustomerID] OR ([o0].[CustomerID] IS NULL AND [t].[CustomerID] IS NULL)) AND [o0].[OrderID] IN ( + SELECT CAST([f].[value] AS int) AS [value] FROM OPENJSON(@__filteredOrderIds_0) AS [f] - WHERE CAST([f].[value] AS int) = [o0].[OrderID]) + ) ) AS [t0] ORDER BY [t].[OrderDate], [t].[CustomerID] """); @@ -2180,10 +2180,10 @@ FROM [Orders] AS [o] OUTER APPLY ( SELECT [t0].[OrderID] AS [Outer], [o0].[OrderID] AS [Inner], [o0].[OrderDate] FROM [Orders] AS [o0] - WHERE [o0].[OrderID] = [t0].[OrderID] AND EXISTS ( - SELECT 1 + WHERE [o0].[OrderID] = [t0].[OrderID] AND [o0].[OrderID] IN ( + SELECT CAST([f].[value] AS int) AS [value] FROM OPENJSON(@__filteredOrderIds_0) AS [f] - WHERE CAST([f].[value] AS int) = [o0].[OrderID]) + ) ) AS [t1] ORDER BY [t0].[OrderID] """); @@ -2620,10 +2620,10 @@ FROM [Orders] AS [o] OUTER APPLY ( SELECT [t0].[CustomerID] AS [Outer], [o0].[OrderID] AS [Inner], [o0].[OrderDate] FROM [Orders] AS [o0] - WHERE ([o0].[CustomerID] = [t0].[CustomerID] OR ([o0].[CustomerID] IS NULL AND [t0].[CustomerID] IS NULL)) AND EXISTS ( - SELECT 1 + WHERE ([o0].[CustomerID] = [t0].[CustomerID] OR ([o0].[CustomerID] IS NULL AND [t0].[CustomerID] IS NULL)) AND [o0].[OrderID] IN ( + SELECT CAST([f].[value] AS int) AS [value] FROM OPENJSON(@__filteredOrderIds_0) AS [f] - WHERE CAST([f].[value] AS int) = [o0].[OrderID]) + ) ) AS [t1] ORDER BY [t0].[CustomerID], [t0].[Complex] """); diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindSqlQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindSqlQuerySqlServerTest.cs index 996eb55ff57..0220c2b5d22 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindSqlQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindSqlQuerySqlServerTest.cs @@ -35,12 +35,12 @@ public override async Task SqlQuery_composed_Contains(bool async) """ SELECT [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate] FROM [Orders] AS [o] -WHERE EXISTS ( - SELECT 1 +WHERE [o].[OrderID] IN ( + SELECT [t].[Value] FROM ( SELECT "ProductID" AS "Value" FROM "Products" ) AS [t] - WHERE CAST([t].[Value] AS int) = [o].[OrderID]) +) """); } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindWhereQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindWhereQuerySqlServerTest.cs index 4e68138bfe5..709c4acbb67 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindWhereQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindWhereQuerySqlServerTest.cs @@ -1897,21 +1897,15 @@ public override async Task Where_multiple_contains_in_subquery_with_or(bool asyn """ SELECT [o].[OrderID], [o].[ProductID], [o].[Discount], [o].[Quantity], [o].[UnitPrice] FROM [Order Details] AS [o] -WHERE EXISTS ( - SELECT 1 - FROM ( - SELECT TOP(1) [p].[ProductID] - FROM [Products] AS [p] - ORDER BY [p].[ProductID] - ) AS [t] - WHERE [t].[ProductID] = [o].[ProductID]) OR EXISTS ( - SELECT 1 - FROM ( - SELECT TOP(1) [o0].[OrderID] - FROM [Orders] AS [o0] - ORDER BY [o0].[OrderID] - ) AS [t0] - WHERE [t0].[OrderID] = [o].[OrderID]) +WHERE [o].[ProductID] IN ( + SELECT TOP(1) [p].[ProductID] + FROM [Products] AS [p] + ORDER BY [p].[ProductID] +) OR [o].[OrderID] IN ( + SELECT TOP(1) [o0].[OrderID] + FROM [Orders] AS [o0] + ORDER BY [o0].[OrderID] +) """); } @@ -1923,21 +1917,15 @@ public override async Task Where_multiple_contains_in_subquery_with_and(bool asy """ SELECT [o].[OrderID], [o].[ProductID], [o].[Discount], [o].[Quantity], [o].[UnitPrice] FROM [Order Details] AS [o] -WHERE EXISTS ( - SELECT 1 - FROM ( - SELECT TOP(20) [p].[ProductID] - FROM [Products] AS [p] - ORDER BY [p].[ProductID] - ) AS [t] - WHERE [t].[ProductID] = [o].[ProductID]) AND EXISTS ( - SELECT 1 - FROM ( - SELECT TOP(10) [o0].[OrderID] - FROM [Orders] AS [o0] - ORDER BY [o0].[OrderID] - ) AS [t0] - WHERE [t0].[OrderID] = [o].[OrderID]) +WHERE [o].[ProductID] IN ( + SELECT TOP(20) [p].[ProductID] + FROM [Products] AS [p] + ORDER BY [p].[ProductID] +) AND [o].[OrderID] IN ( + SELECT TOP(10) [o0].[OrderID] + FROM [Orders] AS [o0] + ORDER BY [o0].[OrderID] +) """); } @@ -2171,10 +2159,11 @@ public override async Task Where_Queryable_ToList_Contains(bool async) SELECT [c].[CustomerID], [o0].[CustomerID], [o0].[OrderID] FROM [Customers] AS [c] LEFT JOIN [Orders] AS [o0] ON [c].[CustomerID] = [o0].[CustomerID] -WHERE EXISTS ( - SELECT 1 +WHERE N'ALFKI' IN ( + SELECT [o].[CustomerID] FROM [Orders] AS [o] - WHERE [o].[CustomerID] = [c].[CustomerID] AND [o].[CustomerID] = N'ALFKI') + WHERE [o].[CustomerID] = [c].[CustomerID] +) ORDER BY [c].[CustomerID] """); } @@ -2205,10 +2194,11 @@ public override async Task Where_Queryable_ToArray_Contains(bool async) SELECT [c].[CustomerID], [o0].[CustomerID], [o0].[OrderID] FROM [Customers] AS [c] LEFT JOIN [Orders] AS [o0] ON [c].[CustomerID] = [o0].[CustomerID] -WHERE EXISTS ( - SELECT 1 +WHERE N'ALFKI' IN ( + SELECT [o].[CustomerID] FROM [Orders] AS [o] - WHERE [o].[CustomerID] = [c].[CustomerID] AND [o].[CustomerID] = N'ALFKI') + WHERE [o].[CustomerID] = [c].[CustomerID] +) ORDER BY [c].[CustomerID] """); } @@ -2239,10 +2229,11 @@ public override async Task Where_Queryable_AsEnumerable_Contains(bool async) SELECT [c].[CustomerID], [o0].[CustomerID], [o0].[OrderID] FROM [Customers] AS [c] LEFT JOIN [Orders] AS [o0] ON [c].[CustomerID] = [o0].[CustomerID] -WHERE EXISTS ( - SELECT 1 +WHERE N'ALFKI' IN ( + SELECT [o].[CustomerID] FROM [Orders] AS [o] - WHERE [o].[CustomerID] = [c].[CustomerID] AND [o].[CustomerID] = N'ALFKI') + WHERE [o].[CustomerID] = [c].[CustomerID] +) ORDER BY [c].[CustomerID] """); } @@ -2450,10 +2441,10 @@ public override async Task Where_list_object_contains_over_value_type(bool async SELECT [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate] FROM [Orders] AS [o] -WHERE EXISTS ( - SELECT 1 +WHERE [o].[OrderID] IN ( + SELECT CAST([o0].[value] AS int) AS [value] FROM OPENJSON(@__orderIds_0) AS [o0] - WHERE CAST([o0].[value] AS int) = [o].[OrderID]) +) """); } @@ -2467,10 +2458,10 @@ public override async Task Where_array_of_object_contains_over_value_type(bool a SELECT [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate] FROM [Orders] AS [o] -WHERE EXISTS ( - SELECT 1 +WHERE [o].[OrderID] IN ( + SELECT CAST([o0].[value] AS int) AS [value] FROM OPENJSON(@__orderIds_0) AS [o0] - WHERE CAST([o0].[value] AS int) = [o].[OrderID]) +) """); } @@ -2574,10 +2565,10 @@ public override async Task Array_of_parameters_Contains_OrElse_comparison_with_c SELECT [c].[CustomerID], [c].[Address], [c].[City], [c].[CompanyName], [c].[ContactName], [c].[ContactTitle], [c].[Country], [c].[Fax], [c].[Phone], [c].[PostalCode], [c].[Region] FROM [Customers] AS [c] -WHERE EXISTS ( - SELECT 1 +WHERE [c].[CustomerID] IN ( + SELECT CAST([p].[value] AS nchar(5)) AS [value] FROM OPENJSON(@__p_0) AS [p] - WHERE CAST([p].[value] AS nchar(5)) = [c].[CustomerID]) OR [c].[CustomerID] = N'ANTON' +) OR [c].[CustomerID] = N'ANTON' """); } @@ -2604,10 +2595,10 @@ public override async Task Parameter_array_Contains_OrElse_comparison_with_const SELECT [c].[CustomerID], [c].[Address], [c].[City], [c].[CompanyName], [c].[ContactName], [c].[ContactTitle], [c].[Country], [c].[Fax], [c].[Phone], [c].[PostalCode], [c].[Region] FROM [Customers] AS [c] -WHERE EXISTS ( - SELECT 1 +WHERE [c].[CustomerID] IN ( + SELECT CAST([a].[value] AS nchar(5)) AS [value] FROM OPENJSON(@__array_0) AS [a] - WHERE CAST([a].[value] AS nchar(5)) = [c].[CustomerID]) OR [c].[CustomerID] = N'ANTON' +) OR [c].[CustomerID] = N'ANTON' """); } @@ -2623,10 +2614,10 @@ public override async Task Parameter_array_Contains_OrElse_comparison_with_param SELECT [c].[CustomerID], [c].[Address], [c].[City], [c].[CompanyName], [c].[ContactName], [c].[ContactTitle], [c].[Country], [c].[Fax], [c].[Phone], [c].[PostalCode], [c].[Region] FROM [Customers] AS [c] -WHERE [c].[CustomerID] = @__prm1_0 OR EXISTS ( - SELECT 1 +WHERE [c].[CustomerID] = @__prm1_0 OR [c].[CustomerID] IN ( + SELECT CAST([a].[value] AS nchar(5)) AS [value] FROM OPENJSON(@__array_1) AS [a] - WHERE CAST([a].[value] AS nchar(5)) = [c].[CustomerID]) OR [c].[CustomerID] = @__prm2_2 +) OR [c].[CustomerID] = @__prm2_2 """); } @@ -2932,10 +2923,10 @@ public override async Task Where_Contains_and_comparison(bool async) SELECT [c].[CustomerID], [c].[Address], [c].[City], [c].[CompanyName], [c].[ContactName], [c].[ContactTitle], [c].[Country], [c].[Fax], [c].[Phone], [c].[PostalCode], [c].[Region] FROM [Customers] AS [c] -WHERE EXISTS ( - SELECT 1 +WHERE [c].[CustomerID] IN ( + SELECT CAST([c0].[value] AS nchar(5)) AS [value] FROM OPENJSON(@__customerIds_0) AS [c0] - WHERE CAST([c0].[value] AS nchar(5)) = [c].[CustomerID]) AND [c].[City] = N'Seattle' +) AND [c].[City] = N'Seattle' """); } @@ -2949,10 +2940,10 @@ public override async Task Where_Contains_or_comparison(bool async) SELECT [c].[CustomerID], [c].[Address], [c].[City], [c].[CompanyName], [c].[ContactName], [c].[ContactTitle], [c].[Country], [c].[Fax], [c].[Phone], [c].[PostalCode], [c].[Region] FROM [Customers] AS [c] -WHERE EXISTS ( - SELECT 1 +WHERE [c].[CustomerID] IN ( + SELECT CAST([c0].[value] AS nchar(5)) AS [value] FROM OPENJSON(@__customerIds_0) AS [c0] - WHERE CAST([c0].[value] AS nchar(5)) = [c].[CustomerID]) OR [c].[City] = N'Seattle' +) OR [c].[City] = N'Seattle' """); } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/NullSemanticsQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/NullSemanticsQuerySqlServerTest.cs index 79c6dc8fbc6..6aa94446969 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/NullSemanticsQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/NullSemanticsQuerySqlServerTest.cs @@ -1247,10 +1247,10 @@ public override async Task Where_conditional_search_condition_in_result(bool asy SELECT [e].[Id] FROM [Entities1] AS [e] -WHERE EXISTS ( - SELECT 1 +WHERE [e].[StringA] IN ( + SELECT [l].[value] FROM OPENJSON(@__list_0) AS [l] - WHERE [l].[value] = [e].[StringA]) +) """, // """ @@ -1293,10 +1293,10 @@ public override void Where_contains_on_parameter_array_with_relational_null_sema SELECT [e].[NullableStringA] FROM [Entities1] AS [e] -WHERE EXISTS ( - SELECT 1 +WHERE [e].[NullableStringA] IN ( + SELECT [n].[value] FROM OPENJSON(@__names_0) AS [n] - WHERE [n].[value] = [e].[NullableStringA]) +) """); } @@ -1310,10 +1310,10 @@ public override void Where_contains_on_parameter_empty_array_with_relational_nul SELECT [e].[NullableStringA] FROM [Entities1] AS [e] -WHERE EXISTS ( - SELECT 1 +WHERE [e].[NullableStringA] IN ( + SELECT [n].[value] FROM OPENJSON(@__names_0) AS [n] - WHERE [n].[value] = [e].[NullableStringA]) +) """); } @@ -1327,10 +1327,10 @@ public override void Where_contains_on_parameter_array_with_just_null_with_relat SELECT [e].[NullableStringA] FROM [Entities1] AS [e] -WHERE EXISTS ( - SELECT 1 +WHERE [e].[NullableStringA] IN ( + SELECT [n].[value] FROM OPENJSON(@__names_0) AS [n] - WHERE [n].[value] = [e].[NullableStringA]) +) """); } @@ -1921,20 +1921,116 @@ WHERE [e].[NullableIntA] IS NOT NULL """); } - public override async Task Null_semantics_contains_non_nullable_argument(bool async) + public override async Task Null_semantics_contains_non_nullable_item_with_non_nullable_subquery(bool async) { - await base.Null_semantics_contains_non_nullable_argument(async); + await base.Null_semantics_contains_non_nullable_item_with_non_nullable_subquery(async); AssertSql( """ -@__ids_0='[1,2,null]' (Size = 4000) +SELECT [e].[Id] +FROM [Entities1] AS [e] +WHERE [e].[StringA] IN ( + SELECT [e0].[StringA] + FROM [Entities2] AS [e0] +) +""", + // +""" +SELECT [e].[Id] +FROM [Entities1] AS [e] +WHERE [e].[StringA] NOT IN ( + SELECT [e0].[StringA] + FROM [Entities2] AS [e0] +) +"""); + } + + public override async Task Null_semantics_contains_nullable_item_with_non_nullable_subquery(bool async) + { + await base.Null_semantics_contains_nullable_item_with_non_nullable_subquery(async); + + AssertSql( +""" +SELECT [e].[Id] +FROM [Entities1] AS [e] +WHERE [e].[NullableStringA] IN ( + SELECT [e0].[StringA] + FROM [Entities2] AS [e0] +) +""", + // +""" +SELECT [e].[Id] +FROM [Entities1] AS [e] +WHERE [e].[NullableStringA] NOT IN ( + SELECT [e0].[StringA] + FROM [Entities2] AS [e0] +) OR [e].[NullableStringA] IS NULL +"""); + } + + public override async Task Null_semantics_contains_non_nullable_item_with_nullable_subquery(bool async) + { + await base.Null_semantics_contains_non_nullable_item_with_nullable_subquery(async); + + AssertSql( +""" +SELECT [e].[Id] +FROM [Entities1] AS [e] +WHERE [e].[StringA] IN ( + SELECT [e0].[NullableStringA] + FROM [Entities2] AS [e0] +) +""", + // +""" +SELECT [e].[Id] +FROM [Entities1] AS [e] +WHERE NOT EXISTS ( + SELECT 1 + FROM [Entities2] AS [e0] + WHERE [e0].[NullableStringA] = [e].[StringA]) +"""); + } + + public override async Task Null_semantics_contains_nullable_item_with_nullable_subquery(bool async) + { + await base.Null_semantics_contains_nullable_item_with_nullable_subquery(async); + AssertSql( +""" SELECT [e].[Id] FROM [Entities1] AS [e] WHERE EXISTS ( SELECT 1 + FROM [Entities2] AS [e0] + WHERE [e0].[NullableStringA] = [e].[NullableStringA] OR ([e0].[NullableStringA] IS NULL AND [e].[NullableStringA] IS NULL)) +""", + // +""" +SELECT [e].[Id] +FROM [Entities1] AS [e] +WHERE NOT EXISTS ( + SELECT 1 + FROM [Entities2] AS [e0] + WHERE [e0].[NullableStringA] = [e].[NullableStringA] OR ([e0].[NullableStringA] IS NULL AND [e].[NullableStringA] IS NULL)) +"""); + } + + public override async Task Null_semantics_contains_non_nullable_item_with_values(bool async) + { + await base.Null_semantics_contains_non_nullable_item_with_values(async); + + AssertSql( +""" +@__ids_0='[1,2,null]' (Size = 4000) + +SELECT [e].[Id] +FROM [Entities1] AS [e] +WHERE [e].[IntA] IN ( + SELECT CAST([i].[value] AS int) AS [value] FROM OPENJSON(@__ids_0) AS [i] - WHERE CAST([i].[value] AS int) = [e].[IntA]) +) """, // """ @@ -1953,10 +2049,10 @@ WHERE CAST([i].[value] AS int) = [e].[IntA]) SELECT [e].[Id] FROM [Entities1] AS [e] -WHERE EXISTS ( - SELECT 1 +WHERE [e].[IntA] IN ( + SELECT CAST([i].[value] AS int) AS [value] FROM OPENJSON(@__ids2_0) AS [i] - WHERE CAST([i].[value] AS int) = [e].[IntA]) +) """, // """ @@ -1975,10 +2071,10 @@ WHERE CAST([i].[value] AS int) = [e].[IntA]) SELECT [e].[Id] FROM [Entities1] AS [e] -WHERE EXISTS ( - SELECT 1 +WHERE [e].[IntA] IN ( + SELECT CAST([i].[value] AS int) AS [value] FROM OPENJSON(@__ids3_0) AS [i] - WHERE CAST([i].[value] AS int) = [e].[IntA]) +) """, // """ @@ -1997,10 +2093,10 @@ WHERE CAST([i].[value] AS int) = [e].[IntA]) SELECT [e].[Id] FROM [Entities1] AS [e] -WHERE EXISTS ( - SELECT 1 +WHERE [e].[IntA] IN ( + SELECT CAST([i].[value] AS int) AS [value] FROM OPENJSON(@__ids4_0) AS [i] - WHERE CAST([i].[value] AS int) = [e].[IntA]) +) """, // """ diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/PrimitiveCollectionsQueryOldSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/PrimitiveCollectionsQueryOldSqlServerTest.cs index 0748763df67..69813a78a1f 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/PrimitiveCollectionsQueryOldSqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/PrimitiveCollectionsQueryOldSqlServerTest.cs @@ -193,31 +193,31 @@ public override Task Parameter_collection_Count(bool async) public override async Task Parameter_collection_of_ints_Contains(bool async) { - await base.Parameter_collection_of_nullable_ints_Contains(async); + await base.Parameter_collection_of_nullable_ints_Contains_int(async); AssertSql( """ SELECT [p].[Id], [p].[Bool], [p].[Bools], [p].[DateTime], [p].[DateTimes], [p].[Enum], [p].[Enums], [p].[Int], [p].[Ints], [p].[NullableInt], [p].[NullableInts], [p].[String], [p].[Strings] FROM [PrimitiveCollectionsEntity] AS [p] -WHERE [p].[NullableInt] IN (10, 999) +WHERE [p].[Int] IN (10, 999) """); } - public override async Task Parameter_collection_of_nullable_ints_Contains(bool async) + public override async Task Parameter_collection_of_nullable_ints_Contains_int(bool async) { - await base.Parameter_collection_of_nullable_ints_Contains(async); + await base.Parameter_collection_of_nullable_ints_Contains_int(async); AssertSql( """ SELECT [p].[Id], [p].[Bool], [p].[Bools], [p].[DateTime], [p].[DateTimes], [p].[Enum], [p].[Enums], [p].[Int], [p].[Ints], [p].[NullableInt], [p].[NullableInts], [p].[String], [p].[Strings] FROM [PrimitiveCollectionsEntity] AS [p] -WHERE [p].[NullableInt] IN (10, 999) +WHERE [p].[Int] IN (10, 999) """); } - public override async Task Parameter_collection_of_nullable_ints_Contains_null(bool async) + public override async Task Parameter_collection_of_nullable_ints_Contains_nullable_int(bool async) { - await base.Parameter_collection_of_nullable_ints_Contains_null(async); + await base.Parameter_collection_of_nullable_ints_Contains_nullable_int(async); AssertSql( """ @@ -296,6 +296,9 @@ public override Task Column_collection_of_nullable_ints_Contains(bool async) public override Task Column_collection_of_nullable_ints_Contains_null(bool async) => AssertTranslationFailed(() => base.Column_collection_of_nullable_ints_Contains_null(async)); + public override Task Column_collection_of_strings_contains_null(bool async) + => AssertTranslationFailed(() => base.Column_collection_of_strings_contains_null(async)); + public override Task Column_collection_of_bools_Contains(bool async) => AssertTranslationFailed(() => base.Column_collection_of_bools_Contains(async)); diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/PrimitiveCollectionsQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/PrimitiveCollectionsQuerySqlServerTest.cs index 6dff82ec517..9580116c856 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/PrimitiveCollectionsQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/PrimitiveCollectionsQuerySqlServerTest.cs @@ -152,10 +152,10 @@ public override async Task Inline_collection_Contains_with_all_parameters(bool a SELECT [p].[Id], [p].[Bool], [p].[Bools], [p].[DateTime], [p].[DateTimes], [p].[Enum], [p].[Enums], [p].[Int], [p].[Ints], [p].[NullableInt], [p].[NullableInts], [p].[String], [p].[Strings] FROM [PrimitiveCollectionsEntity] AS [p] -WHERE EXISTS ( - SELECT 1 +WHERE [p].[Id] IN ( + SELECT CAST([p0].[value] AS int) AS [value] FROM OPENJSON(@__p_0) AS [p0] - WHERE CAST([p0].[value] AS int) = [p].[Id]) +) """); } @@ -217,16 +217,16 @@ public override async Task Parameter_collection_of_ints_Contains(bool async) SELECT [p].[Id], [p].[Bool], [p].[Bools], [p].[DateTime], [p].[DateTimes], [p].[Enum], [p].[Enums], [p].[Int], [p].[Ints], [p].[NullableInt], [p].[NullableInts], [p].[String], [p].[Strings] FROM [PrimitiveCollectionsEntity] AS [p] -WHERE EXISTS ( - SELECT 1 +WHERE [p].[Int] IN ( + SELECT CAST([i].[value] AS int) AS [value] FROM OPENJSON(@__ints_0) AS [i] - WHERE CAST([i].[value] AS int) = [p].[Int]) +) """); } - public override async Task Parameter_collection_of_nullable_ints_Contains(bool async) + public override async Task Parameter_collection_of_nullable_ints_Contains_int(bool async) { - await base.Parameter_collection_of_nullable_ints_Contains(async); + await base.Parameter_collection_of_nullable_ints_Contains_int(async); AssertSql( """ @@ -234,16 +234,16 @@ public override async Task Parameter_collection_of_nullable_ints_Contains(bool a SELECT [p].[Id], [p].[Bool], [p].[Bools], [p].[DateTime], [p].[DateTimes], [p].[Enum], [p].[Enums], [p].[Int], [p].[Ints], [p].[NullableInt], [p].[NullableInts], [p].[String], [p].[Strings] FROM [PrimitiveCollectionsEntity] AS [p] -WHERE EXISTS ( - SELECT 1 +WHERE [p].[Int] IN ( + SELECT CAST([n].[value] AS int) AS [value] FROM OPENJSON(@__nullableInts_0) AS [n] - WHERE CAST([n].[value] AS int) = [p].[NullableInt] OR ([n].[value] IS NULL AND [p].[NullableInt] IS NULL)) +) """); } - public override async Task Parameter_collection_of_nullable_ints_Contains_null(bool async) + public override async Task Parameter_collection_of_nullable_ints_Contains_nullable_int(bool async) { - await base.Parameter_collection_of_nullable_ints_Contains_null(async); + await base.Parameter_collection_of_nullable_ints_Contains_nullable_int(async); AssertSql( """ @@ -285,10 +285,10 @@ public override async Task Parameter_collection_of_DateTimes_Contains(bool async SELECT [p].[Id], [p].[Bool], [p].[Bools], [p].[DateTime], [p].[DateTimes], [p].[Enum], [p].[Enums], [p].[Int], [p].[Ints], [p].[NullableInt], [p].[NullableInts], [p].[String], [p].[Strings] FROM [PrimitiveCollectionsEntity] AS [p] -WHERE EXISTS ( - SELECT 1 +WHERE [p].[DateTime] IN ( + SELECT CAST([d].[value] AS datetime) AS [value] FROM OPENJSON(@__dateTimes_0) AS [d] - WHERE CAST([d].[value] AS datetime) = [p].[DateTime]) +) """); } @@ -302,10 +302,10 @@ public override async Task Parameter_collection_of_bools_Contains(bool async) SELECT [p].[Id], [p].[Bool], [p].[Bools], [p].[DateTime], [p].[DateTimes], [p].[Enum], [p].[Enums], [p].[Int], [p].[Ints], [p].[NullableInt], [p].[NullableInts], [p].[String], [p].[Strings] FROM [PrimitiveCollectionsEntity] AS [p] -WHERE EXISTS ( - SELECT 1 +WHERE [p].[Bool] IN ( + SELECT CAST([b].[value] AS bit) AS [value] FROM OPENJSON(@__bools_0) AS [b] - WHERE CAST([b].[value] AS bit) = [p].[Bool]) +) """); } @@ -319,10 +319,10 @@ public override async Task Parameter_collection_of_enums_Contains(bool async) SELECT [p].[Id], [p].[Bool], [p].[Bools], [p].[DateTime], [p].[DateTimes], [p].[Enum], [p].[Enums], [p].[Int], [p].[Ints], [p].[NullableInt], [p].[NullableInts], [p].[String], [p].[Strings] FROM [PrimitiveCollectionsEntity] AS [p] -WHERE EXISTS ( - SELECT 1 +WHERE [p].[Enum] IN ( + SELECT CAST([e].[value] AS int) AS [value] FROM OPENJSON(@__enums_0) AS [e] - WHERE CAST([e].[value] AS int) = [p].[Enum]) +) """); } @@ -334,10 +334,10 @@ public override async Task Parameter_collection_null_Contains(bool async) """ SELECT [p].[Id], [p].[Bool], [p].[Bools], [p].[DateTime], [p].[DateTimes], [p].[Enum], [p].[Enums], [p].[Int], [p].[Ints], [p].[NullableInt], [p].[NullableInts], [p].[String], [p].[Strings] FROM [PrimitiveCollectionsEntity] AS [p] -WHERE EXISTS ( - SELECT 1 +WHERE [p].[Int] IN ( + SELECT CAST([i].[value] AS int) AS [value] FROM OPENJSON(N'[]') AS [i] - WHERE CAST([i].[value] AS int) = [p].[Int]) +) """); } @@ -349,10 +349,10 @@ public override async Task Column_collection_of_ints_Contains(bool async) """ SELECT [p].[Id], [p].[Bool], [p].[Bools], [p].[DateTime], [p].[DateTimes], [p].[Enum], [p].[Enums], [p].[Int], [p].[Ints], [p].[NullableInt], [p].[NullableInts], [p].[String], [p].[Strings] FROM [PrimitiveCollectionsEntity] AS [p] -WHERE EXISTS ( - SELECT 1 +WHERE 10 IN ( + SELECT CAST([i].[value] AS int) FROM OPENJSON([p].[Ints]) AS [i] - WHERE CAST([i].[value] AS int) = 10) +) """); } @@ -364,10 +364,10 @@ public override async Task Column_collection_of_nullable_ints_Contains(bool asyn """ SELECT [p].[Id], [p].[Bool], [p].[Bools], [p].[DateTime], [p].[DateTimes], [p].[Enum], [p].[Enums], [p].[Int], [p].[Ints], [p].[NullableInt], [p].[NullableInts], [p].[String], [p].[Strings] FROM [PrimitiveCollectionsEntity] AS [p] -WHERE EXISTS ( - SELECT 1 +WHERE 10 IN ( + SELECT CAST([n].[value] AS int) FROM OPENJSON([p].[NullableInts]) AS [n] - WHERE CAST([n].[value] AS int) = 10) +) """); } @@ -386,9 +386,9 @@ FROM OPENJSON([p].[NullableInts]) AS [n] """); } - public override async Task Column_collection_of_bools_Contains(bool async) + public override async Task Column_collection_of_strings_contains_null(bool async) { - await base.Column_collection_of_bools_Contains(async); + await base.Column_collection_of_strings_contains_null(async); AssertSql( """ @@ -396,8 +396,23 @@ public override async Task Column_collection_of_bools_Contains(bool async) FROM [PrimitiveCollectionsEntity] AS [p] WHERE EXISTS ( SELECT 1 + FROM OPENJSON([p].[Strings]) AS [s] + WHERE [s].[value] IS NULL) +"""); + } + + public override async Task Column_collection_of_bools_Contains(bool async) + { + await base.Column_collection_of_bools_Contains(async); + + AssertSql( +""" +SELECT [p].[Id], [p].[Bool], [p].[Bools], [p].[DateTime], [p].[DateTimes], [p].[Enum], [p].[Enums], [p].[Int], [p].[Ints], [p].[NullableInt], [p].[NullableInts], [p].[String], [p].[Strings] +FROM [PrimitiveCollectionsEntity] AS [p] +WHERE CAST(1 AS bit) IN ( + SELECT CAST([b].[value] AS bit) FROM OPENJSON([p].[Bools]) AS [b] - WHERE CAST([b].[value] AS bit) = CAST(1 AS bit)) +) """); } @@ -572,14 +587,11 @@ public override async Task Column_collection_Take(bool async) """ SELECT [p].[Id], [p].[Bool], [p].[Bools], [p].[DateTime], [p].[DateTimes], [p].[Enum], [p].[Enums], [p].[Int], [p].[Ints], [p].[NullableInt], [p].[NullableInts], [p].[String], [p].[Strings] FROM [PrimitiveCollectionsEntity] AS [p] -WHERE EXISTS ( - SELECT 1 - FROM ( - SELECT TOP(2) CAST([i].[value] AS int) AS [c], CAST([i].[key] AS int) AS [c0] - FROM OPENJSON([p].[Ints]) AS [i] - ORDER BY CAST([i].[key] AS int) - ) AS [t] - WHERE [t].[c] = 11) +WHERE 11 IN ( + SELECT TOP(2) CAST([i].[value] AS int) + FROM OPENJSON([p].[Ints]) AS [i] + ORDER BY CAST([i].[key] AS int) +) """); } @@ -591,15 +603,12 @@ public override async Task Column_collection_Skip_Take(bool async) """ SELECT [p].[Id], [p].[Bool], [p].[Bools], [p].[DateTime], [p].[DateTimes], [p].[Enum], [p].[Enums], [p].[Int], [p].[Ints], [p].[NullableInt], [p].[NullableInts], [p].[String], [p].[Strings] FROM [PrimitiveCollectionsEntity] AS [p] -WHERE EXISTS ( - SELECT 1 - FROM ( - SELECT CAST([i].[value] AS int) AS [c], CAST([i].[key] AS int) AS [c0] - FROM OPENJSON([p].[Ints]) AS [i] - ORDER BY CAST([i].[key] AS int) - OFFSET 1 ROWS FETCH NEXT 2 ROWS ONLY - ) AS [t] - WHERE [t].[c] = 11) +WHERE 11 IN ( + SELECT CAST([i].[value] AS int) + FROM OPENJSON([p].[Ints]) AS [i] + ORDER BY CAST([i].[key] AS int) + OFFSET 1 ROWS FETCH NEXT 2 ROWS ONLY +) """); } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/QueryBugsTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/QueryBugsTest.cs index 34d2f662f12..bc5c98a4123 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/QueryBugsTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/QueryBugsTest.cs @@ -186,40 +186,40 @@ public async Task Where_contains_DateTime_literals(bool async) SELECT [d].[Id], [d].[DateTime], [d].[DateTime2], [d].[DateTime2_0], [d].[DateTime2_1], [d].[DateTime2_2], [d].[DateTime2_3], [d].[DateTime2_4], [d].[DateTime2_5], [d].[DateTime2_6], [d].[DateTime2_7], [d].[SmallDateTime] FROM [Dates] AS [d] -WHERE EXISTS ( - SELECT 1 +WHERE [d].[SmallDateTime] IN ( + SELECT CAST([d0].[value] AS smalldatetime) AS [value] FROM OPENJSON(@__dateTimes_0) AS [d0] - WHERE CAST([d0].[value] AS smalldatetime) = [d].[SmallDateTime]) AND EXISTS ( - SELECT 1 +) AND [d].[DateTime] IN ( + SELECT CAST([d1].[value] AS datetime) AS [value] FROM OPENJSON(@__dateTimes_0_1) AS [d1] - WHERE CAST([d1].[value] AS datetime) = [d].[DateTime]) AND EXISTS ( - SELECT 1 +) AND [d].[DateTime2] IN ( + SELECT CAST([d2].[value] AS datetime2) AS [value] FROM OPENJSON(@__dateTimes_0_2) AS [d2] - WHERE CAST([d2].[value] AS datetime2) = [d].[DateTime2]) AND EXISTS ( - SELECT 1 +) AND [d].[DateTime2_0] IN ( + SELECT CAST([d3].[value] AS datetime2(0)) AS [value] FROM OPENJSON(@__dateTimes_0_3) AS [d3] - WHERE CAST([d3].[value] AS datetime2(0)) = [d].[DateTime2_0]) AND EXISTS ( - SELECT 1 +) AND [d].[DateTime2_1] IN ( + SELECT CAST([d4].[value] AS datetime2(1)) AS [value] FROM OPENJSON(@__dateTimes_0_4) AS [d4] - WHERE CAST([d4].[value] AS datetime2(1)) = [d].[DateTime2_1]) AND EXISTS ( - SELECT 1 +) AND [d].[DateTime2_2] IN ( + SELECT CAST([d5].[value] AS datetime2(2)) AS [value] FROM OPENJSON(@__dateTimes_0_5) AS [d5] - WHERE CAST([d5].[value] AS datetime2(2)) = [d].[DateTime2_2]) AND EXISTS ( - SELECT 1 +) AND [d].[DateTime2_3] IN ( + SELECT CAST([d6].[value] AS datetime2(3)) AS [value] FROM OPENJSON(@__dateTimes_0_6) AS [d6] - WHERE CAST([d6].[value] AS datetime2(3)) = [d].[DateTime2_3]) AND EXISTS ( - SELECT 1 +) AND [d].[DateTime2_4] IN ( + SELECT CAST([d7].[value] AS datetime2(4)) AS [value] FROM OPENJSON(@__dateTimes_0_7) AS [d7] - WHERE CAST([d7].[value] AS datetime2(4)) = [d].[DateTime2_4]) AND EXISTS ( - SELECT 1 +) AND [d].[DateTime2_5] IN ( + SELECT CAST([d8].[value] AS datetime2(5)) AS [value] FROM OPENJSON(@__dateTimes_0_8) AS [d8] - WHERE CAST([d8].[value] AS datetime2(5)) = [d].[DateTime2_5]) AND EXISTS ( - SELECT 1 +) AND [d].[DateTime2_6] IN ( + SELECT CAST([d9].[value] AS datetime2(6)) AS [value] FROM OPENJSON(@__dateTimes_0_9) AS [d9] - WHERE CAST([d9].[value] AS datetime2(6)) = [d].[DateTime2_6]) AND EXISTS ( - SELECT 1 +) AND [d].[DateTime2_7] IN ( + SELECT CAST([d10].[value] AS datetime2(7)) AS [value] FROM OPENJSON(@__dateTimes_0_10) AS [d10] - WHERE CAST([d10].[value] AS datetime2(7)) = [d].[DateTime2_7]) +) """); } @@ -2091,10 +2091,11 @@ FROM [Entities] AS [e] SELECT [e].[Id], [e].[Name] FROM [Entities] AS [e] -WHERE EXISTS ( - SELECT 1 +WHERE [e].[Id] IN ( + SELECT [e0].[Id] FROM [Entities] AS [e0] - WHERE [e0].[Id] = @__id_0 AND [e0].[Id] = [e].[Id]) + WHERE [e0].[Id] = @__id_0 +) """, // """ @@ -2102,10 +2103,11 @@ FROM [Entities] AS [e0] SELECT [e].[Id], [e].[Name] FROM [Entities] AS [e] -WHERE EXISTS ( - SELECT 1 +WHERE [e].[Id] IN ( + SELECT [e0].[Id] FROM [Entities] AS [e0] - WHERE [e0].[Id] = @__id_0 AND [e0].[Id] = [e].[Id]) + WHERE [e0].[Id] = @__id_0 +) """); } } @@ -3922,10 +3924,10 @@ public virtual async Task DateTime_Contains_with_smalldatetime_generates_correct SELECT [r].[Id], [r].[MyTime] FROM [ReproEntity] AS [r] -WHERE EXISTS ( - SELECT 1 +WHERE [r].[MyTime] IN ( + SELECT CAST([t].[value] AS smalldatetime) AS [value] FROM OPENJSON(@__testDateList_0) AS [t] - WHERE CAST([t].[value] AS smalldatetime) = [r].[MyTime]) +) """); } } @@ -3973,6 +3975,8 @@ public virtual async Task Nested_contains_with_enum() var keys = new List { Guid.Parse("0a47bcb7-a1cb-4345-8944-c58f82d6aac7"), key }; var todoTypes = new List { MyContext12732.TodoType.foo0 }; + // Note that in this query, the outer Contains really has no type mapping, neither for its source (collection parameter), nor + // for its item (the conditional expression returns key, which is also a parameter). The default type mapping must be applied. var query = context.Todos .Where(x => keys.Contains(todoTypes.Contains(x.Type) ? key : key)) .ToList(); @@ -3981,18 +3985,18 @@ public virtual async Task Nested_contains_with_enum() AssertSql( """ -@__keys_0='["0a47bcb7-a1cb-4345-8944-c58f82d6aac7","5f221fb9-66f4-442a-92c9-d97ed5989cc7"]' (Size = 4000) @__key_2='5f221fb9-66f4-442a-92c9-d97ed5989cc7' +@__keys_0='["0a47bcb7-a1cb-4345-8944-c58f82d6aac7","5f221fb9-66f4-442a-92c9-d97ed5989cc7"]' (Size = 4000) SELECT [t].[Id], [t].[Type] FROM [Todos] AS [t] -WHERE EXISTS ( - SELECT 1 +WHERE CASE + WHEN [t].[Type] = 0 THEN @__key_2 + ELSE @__key_2 +END IN ( + SELECT CAST([k].[value] AS uniqueidentifier) AS [value] FROM OPENJSON(@__keys_0) AS [k] - WHERE CAST([k].[value] AS uniqueidentifier) = CASE - WHEN [t].[Type] = 0 THEN @__key_2 - ELSE @__key_2 - END) +) """); } } @@ -8894,10 +8898,10 @@ public virtual async Task Query_filter_with_contains_evaluates_correctly() SELECT [e].[Id], [e].[Name] FROM [Entities] AS [e] -WHERE NOT EXISTS ( - SELECT 1 +WHERE [e].[Id] NOT IN ( + SELECT CAST([e0].[value] AS int) AS [value] FROM OPENJSON(@__ef_filter___ids_0) AS [e0] - WHERE CAST([e0].[value] AS int) = [e].[Id]) +) """); } } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/QueryFilterFuncletizationSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/QueryFilterFuncletizationSqlServerTest.cs index 0b5d828b399..4a3adbbe67e 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/QueryFilterFuncletizationSqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/QueryFilterFuncletizationSqlServerTest.cs @@ -96,10 +96,10 @@ public override void DbContext_list_is_parameterized() """ SELECT [l].[Id], [l].[Tenant] FROM [ListFilter] AS [l] -WHERE EXISTS ( - SELECT 1 +WHERE [l].[Tenant] IN ( + SELECT CAST([e].[value] AS int) AS [value] FROM OPENJSON(N'[]') AS [e] - WHERE CAST([e].[value] AS int) = [l].[Tenant]) +) """, // """ @@ -107,10 +107,10 @@ WHERE CAST([e].[value] AS int) = [l].[Tenant]) SELECT [l].[Id], [l].[Tenant] FROM [ListFilter] AS [l] -WHERE EXISTS ( - SELECT 1 +WHERE [l].[Tenant] IN ( + SELECT CAST([e].[value] AS int) AS [value] FROM OPENJSON(@__ef_filter__TenantIds_0) AS [e] - WHERE CAST([e].[value] AS int) = [l].[Tenant]) +) """, // """ @@ -118,10 +118,10 @@ WHERE CAST([e].[value] AS int) = [l].[Tenant]) SELECT [l].[Id], [l].[Tenant] FROM [ListFilter] AS [l] -WHERE EXISTS ( - SELECT 1 +WHERE [l].[Tenant] IN ( + SELECT CAST([e].[value] AS int) AS [value] FROM OPENJSON(@__ef_filter__TenantIds_0) AS [e] - WHERE CAST([e].[value] AS int) = [l].[Tenant]) +) """, // """ @@ -129,10 +129,10 @@ WHERE CAST([e].[value] AS int) = [l].[Tenant]) SELECT [l].[Id], [l].[Tenant] FROM [ListFilter] AS [l] -WHERE EXISTS ( - SELECT 1 +WHERE [l].[Tenant] IN ( + SELECT CAST([e].[value] AS int) AS [value] FROM OPENJSON(@__ef_filter__TenantIds_0) AS [e] - WHERE CAST([e].[value] AS int) = [l].[Tenant]) +) """); } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/SimpleQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/SimpleQuerySqlServerTest.cs index 9b24b8e6149..47447b5838e 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/SimpleQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/SimpleQuerySqlServerTest.cs @@ -135,10 +135,11 @@ WHEN EXISTS ( SELECT 1 FROM [Memberships] AS [m] INNER JOIN [Users] AS [u0] ON [m].[UserId] = [u0].[Id] - WHERE EXISTS ( - SELECT 1 + WHERE [m].[GroupId] IN ( + SELECT [m0].[GroupId] FROM [Memberships] AS [m0] - WHERE [m0].[UserId] = @__currentUserId_0 AND [m0].[GroupId] = [m].[GroupId]) AND [u0].[Id] = [u].[Id]) THEN CAST(1 AS bit) + WHERE [m0].[UserId] = @__currentUserId_0 + ) AND [u0].[Id] = [u].[Id]) THEN CAST(1 AS bit) ELSE CAST(0 AS bit) END AS [HasAccess] FROM [Users] AS [u] @@ -183,10 +184,11 @@ WHEN EXISTS ( SELECT 1 FROM [Memberships] AS [m] INNER JOIN [Users] AS [u0] ON [m].[UserId] = [u0].[Id] - WHERE EXISTS ( - SELECT 1 + WHERE [m].[GroupId] IN ( + SELECT [m0].[GroupId] FROM [Memberships] AS [m0] - WHERE [m0].[UserId] = @__currentUserId_0 AND [m0].[GroupId] = [m].[GroupId]) AND [u0].[Id] = [u].[Id]) THEN CAST(1 AS bit) + WHERE [m0].[UserId] = @__currentUserId_0 + ) AND [u0].[Id] = [u].[Id]) THEN CAST(1 AS bit) ELSE CAST(0 AS bit) END AS [HasAccess] FROM [Users] AS [u] @@ -562,12 +564,12 @@ public virtual async Task Muliple_occurrences_of_FromSql_in_group_by_aggregate(b SELECT [d].[Id] AS [Key], COUNT(*) AS [Aggregate] FROM [DemoEntities] AS [d] -WHERE EXISTS ( - SELECT 1 +WHERE [d].[Id] IN ( + SELECT [m].[Id] FROM ( SELECT * FROM DemoEntities WHERE Id = @p0 ) AS [m] - WHERE [m].[Id] = [d].[Id]) +) GROUP BY [d].[Id] """); } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/SqlQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/SqlQuerySqlServerTest.cs index 2d2b9ecc76a..9af4d2a0e32 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/SqlQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/SqlQuerySqlServerTest.cs @@ -141,17 +141,17 @@ public override async Task SqlQueryRaw_composed_contains(bool async) await base.SqlQueryRaw_composed_contains(async); AssertSql( - """ +""" SELECT [m].[Address], [m].[City], [m].[CompanyName], [m].[ContactName], [m].[ContactTitle], [m].[Country], [m].[CustomerID], [m].[Fax], [m].[Phone], [m].[Region], [m].[PostalCode] FROM ( SELECT * FROM "Customers" ) AS [m] -WHERE EXISTS ( - SELECT 1 +WHERE [m].[CustomerID] IN ( + SELECT [m0].[CustomerID] FROM ( SELECT * FROM "Orders" ) AS [m0] - WHERE [m0].[CustomerID] = [m].[CustomerID]) +) """); } @@ -553,7 +553,7 @@ public override async Task SqlQuery_parameterization_issue_12213(bool async) await base.SqlQuery_parameterization_issue_12213(async); AssertSql( - """ +""" p0='10300' SELECT [m].[OrderID] @@ -562,7 +562,7 @@ SELECT [m].[OrderID] ) AS [m] """, // - """ +""" @__max_1='10400' p0='10300' @@ -570,15 +570,15 @@ SELECT [m].[OrderID] FROM ( SELECT * FROM "Orders" ) AS [m] -WHERE [m].[OrderID] <= @__max_1 AND EXISTS ( - SELECT 1 +WHERE [m].[OrderID] <= @__max_1 AND [m].[OrderID] IN ( + SELECT [m0].[OrderID] FROM ( SELECT * FROM "Orders" WHERE "OrderID" >= @p0 ) AS [m0] - WHERE [m0].[OrderID] = [m].[OrderID]) +) """, // - """ +""" @__max_1='10400' p0='10300' @@ -586,12 +586,12 @@ SELECT [m].[OrderID] FROM ( SELECT * FROM "Orders" ) AS [m] -WHERE [m].[OrderID] <= @__max_1 AND EXISTS ( - SELECT 1 +WHERE [m].[OrderID] <= @__max_1 AND [m].[OrderID] IN ( + SELECT [m0].[OrderID] FROM ( SELECT * FROM "Orders" WHERE "OrderID" >= @p0 ) AS [m0] - WHERE [m0].[OrderID] = [m].[OrderID]) +) """); } @@ -645,19 +645,19 @@ public override async Task SqlQueryRaw_in_subquery_with_dbParameter(bool async) await base.SqlQueryRaw_in_subquery_with_dbParameter(async); AssertSql( - """ +""" @city='London' (Nullable = false) (Size = 6) SELECT [m].[CustomerID], [m].[EmployeeID], [m].[Freight], [m].[OrderDate], [m].[OrderID], [m].[RequiredDate], [m].[ShipAddress], [m].[ShipCity], [m].[ShipCountry], [m].[ShipName], [m].[ShipPostalCode], [m].[ShipRegion], [m].[ShipVia], [m].[ShippedDate] FROM ( SELECT * FROM "Orders" ) AS [m] -WHERE EXISTS ( - SELECT 1 +WHERE [m].[CustomerID] IN ( + SELECT [m0].[CustomerID] FROM ( SELECT * FROM "Customers" WHERE "City" = @city ) AS [m0] - WHERE [m0].[CustomerID] = [m].[CustomerID]) +) """); } @@ -666,19 +666,19 @@ public override async Task SqlQueryRaw_in_subquery_with_positional_dbParameter_w await base.SqlQueryRaw_in_subquery_with_positional_dbParameter_without_name(async); AssertSql( - """ +""" p0='London' (Nullable = false) (Size = 6) SELECT [m].[CustomerID], [m].[EmployeeID], [m].[Freight], [m].[OrderDate], [m].[OrderID], [m].[RequiredDate], [m].[ShipAddress], [m].[ShipCity], [m].[ShipCountry], [m].[ShipName], [m].[ShipPostalCode], [m].[ShipRegion], [m].[ShipVia], [m].[ShippedDate] FROM ( SELECT * FROM "Orders" ) AS [m] -WHERE EXISTS ( - SELECT 1 +WHERE [m].[CustomerID] IN ( + SELECT [m0].[CustomerID] FROM ( SELECT * FROM "Customers" WHERE "City" = @p0 ) AS [m0] - WHERE [m0].[CustomerID] = [m].[CustomerID]) +) """); } @@ -687,19 +687,19 @@ public override async Task SqlQueryRaw_in_subquery_with_positional_dbParameter_w await base.SqlQueryRaw_in_subquery_with_positional_dbParameter_with_name(async); AssertSql( - """ +""" @city='London' (Nullable = false) (Size = 6) SELECT [m].[CustomerID], [m].[EmployeeID], [m].[Freight], [m].[OrderDate], [m].[OrderID], [m].[RequiredDate], [m].[ShipAddress], [m].[ShipCity], [m].[ShipCountry], [m].[ShipName], [m].[ShipPostalCode], [m].[ShipRegion], [m].[ShipVia], [m].[ShippedDate] FROM ( SELECT * FROM "Orders" ) AS [m] -WHERE EXISTS ( - SELECT 1 +WHERE [m].[CustomerID] IN ( + SELECT [m0].[CustomerID] FROM ( SELECT * FROM "Customers" WHERE "City" = @city ) AS [m0] - WHERE [m0].[CustomerID] = [m].[CustomerID]) +) """); } @@ -708,7 +708,7 @@ public override async Task SqlQueryRaw_with_dbParameter_mixed_in_subquery(bool a await base.SqlQueryRaw_with_dbParameter_mixed_in_subquery(async); AssertSql( - """ +""" p0='London' (Size = 4000) @title='Sales Representative' (Nullable = false) (Size = 20) @@ -716,15 +716,15 @@ public override async Task SqlQueryRaw_with_dbParameter_mixed_in_subquery(bool a FROM ( SELECT * FROM "Orders" ) AS [m] -WHERE EXISTS ( - SELECT 1 +WHERE [m].[CustomerID] IN ( + SELECT [m0].[CustomerID] FROM ( SELECT * FROM "Customers" WHERE "City" = @p0 AND "ContactTitle" = @title ) AS [m0] - WHERE [m0].[CustomerID] = [m].[CustomerID]) +) """, // - """ +""" @city='London' (Nullable = false) (Size = 6) p1='Sales Representative' (Size = 4000) @@ -732,12 +732,12 @@ SELECT 1 FROM ( SELECT * FROM "Orders" ) AS [m] -WHERE EXISTS ( - SELECT 1 +WHERE [m].[CustomerID] IN ( + SELECT [m0].[CustomerID] FROM ( SELECT * FROM "Customers" WHERE "City" = @city AND "ContactTitle" = @p1 ) AS [m0] - WHERE [m0].[CustomerID] = [m].[CustomerID]) +) """); } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/TPCGearsOfWarQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/TPCGearsOfWarQuerySqlServerTest.cs index 043be0c6f33..82d63243b8f 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/TPCGearsOfWarQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/TPCGearsOfWarQuerySqlServerTest.cs @@ -3573,16 +3573,13 @@ UNION ALL SELECT [o].[Nickname], [o].[SquadId] FROM [Officers] AS [o] ) AS [t0] ON [t].[GearNickName] = [t0].[Nickname] AND [t].[GearSquadId] = [t0].[SquadId] -WHERE ([t].[Note] <> N'K.I.A.' OR [t].[Note] IS NULL) AND EXISTS ( - SELECT 1 - FROM ( - SELECT [g0].[Nickname], [g0].[SquadId], [g0].[AssignedCityName], [g0].[CityOfBirthName], [g0].[FullName], [g0].[HasSoulPatch], [g0].[LeaderNickname], [g0].[LeaderSquadId], [g0].[Rank], N'Gear' AS [Discriminator] - FROM [Gears] AS [g0] - UNION ALL - SELECT [o0].[Nickname], [o0].[SquadId], [o0].[AssignedCityName], [o0].[CityOfBirthName], [o0].[FullName], [o0].[HasSoulPatch], [o0].[LeaderNickname], [o0].[LeaderSquadId], [o0].[Rank], N'Officer' AS [Discriminator] - FROM [Officers] AS [o0] - ) AS [t1] - WHERE [t1].[SquadId] = [t0].[SquadId]) +WHERE ([t].[Note] <> N'K.I.A.' OR [t].[Note] IS NULL) AND [t0].[SquadId] IN ( + SELECT [g0].[SquadId] + FROM [Gears] AS [g0] + UNION ALL + SELECT [o0].[SquadId] + FROM [Officers] AS [o0] +) """); } @@ -4148,10 +4145,10 @@ public override async Task Contains_with_local_nullable_guid_list_closure(bool a SELECT [t].[Id], [t].[GearNickName], [t].[GearSquadId], [t].[IssueDate], [t].[Note] FROM [Tags] AS [t] -WHERE EXISTS ( - SELECT 1 +WHERE [t].[Id] IN ( + SELECT CAST([i].[value] AS uniqueidentifier) AS [value] FROM OPENJSON(@__ids_0) AS [i] - WHERE CAST([i].[value] AS uniqueidentifier) = [t].[Id]) +) """); } @@ -8963,10 +8960,10 @@ public override async Task DateTimeOffset_Contains_Less_than_Greater_than(bool a SELECT [m].[Id], [m].[CodeName], [m].[Date], [m].[Duration], [m].[Rating], [m].[Time], [m].[Timeline] FROM [Missions] AS [m] -WHERE @__start_0 <= CAST(CONVERT(date, [m].[Timeline]) AS datetimeoffset) AND [m].[Timeline] < @__end_1 AND EXISTS ( - SELECT 1 +WHERE @__start_0 <= CAST(CONVERT(date, [m].[Timeline]) AS datetimeoffset) AND [m].[Timeline] < @__end_1 AND [m].[Timeline] IN ( + SELECT CAST([d].[value] AS datetimeoffset) AS [value] FROM OPENJSON(@__dates_2) AS [d] - WHERE CAST([d].[value] AS datetimeoffset) = [m].[Timeline]) +) """); } @@ -9844,10 +9841,10 @@ UNION ALL FROM [Officers] AS [o] ) AS [t] ORDER BY CASE - WHEN EXISTS ( - SELECT 1 + WHEN [t].[SquadId] IN ( + SELECT CAST([i].[value] AS int) AS [value] FROM OPENJSON(@__ids_0) AS [i] - WHERE CAST([i].[value] AS int) = [t].[SquadId]) THEN CAST(1 AS bit) + ) THEN CAST(1 AS bit) ELSE CAST(0 AS bit) END """); @@ -10514,16 +10511,13 @@ UNION ALL SELECT [l0].[Name], [l0].[LocustHordeId], [l0].[ThreatLevel], [l0].[ThreatLevelByte], [l0].[ThreatLevelNullableByte], [l0].[DefeatedByNickname], [l0].[DefeatedBySquadId], [l0].[HighCommandId], N'LocustCommander' AS [Discriminator] FROM [LocustCommanders] AS [l0] ) AS [t] -WHERE EXISTS ( - SELECT 1 - FROM ( - SELECT [l1].[Name], [l1].[LocustHordeId], [l1].[ThreatLevel], [l1].[ThreatLevelByte], [l1].[ThreatLevelNullableByte], NULL AS [DefeatedByNickname], NULL AS [DefeatedBySquadId], NULL AS [HighCommandId], N'LocustLeader' AS [Discriminator] - FROM [LocustLeaders] AS [l1] - UNION ALL - SELECT [l2].[Name], [l2].[LocustHordeId], [l2].[ThreatLevel], [l2].[ThreatLevelByte], [l2].[ThreatLevelNullableByte], [l2].[DefeatedByNickname], [l2].[DefeatedBySquadId], [l2].[HighCommandId], N'LocustCommander' AS [Discriminator] - FROM [LocustCommanders] AS [l2] - ) AS [t0] - WHERE [t0].[ThreatLevelByte] = [t].[ThreatLevelByte]) +WHERE [t].[ThreatLevelByte] IN ( + SELECT [l1].[ThreatLevelByte] + FROM [LocustLeaders] AS [l1] + UNION ALL + SELECT [l2].[ThreatLevelByte] + FROM [LocustCommanders] AS [l2] +) """); } @@ -10544,10 +10538,10 @@ FROM [LocustCommanders] AS [l0] WHERE EXISTS ( SELECT 1 FROM ( - SELECT [l1].[Name], [l1].[LocustHordeId], [l1].[ThreatLevel], [l1].[ThreatLevelByte], [l1].[ThreatLevelNullableByte], NULL AS [DefeatedByNickname], NULL AS [DefeatedBySquadId], NULL AS [HighCommandId], N'LocustLeader' AS [Discriminator] + SELECT [l1].[ThreatLevelNullableByte] FROM [LocustLeaders] AS [l1] UNION ALL - SELECT [l2].[Name], [l2].[LocustHordeId], [l2].[ThreatLevel], [l2].[ThreatLevelByte], [l2].[ThreatLevelNullableByte], [l2].[DefeatedByNickname], [l2].[DefeatedBySquadId], [l2].[HighCommandId], N'LocustCommander' AS [Discriminator] + SELECT [l2].[ThreatLevelNullableByte] FROM [LocustCommanders] AS [l2] ) AS [t0] WHERE [t0].[ThreatLevelNullableByte] = [t].[ThreatLevelNullableByte] OR ([t0].[ThreatLevelNullableByte] IS NULL AND [t].[ThreatLevelNullableByte] IS NULL)) @@ -10571,10 +10565,10 @@ FROM [LocustCommanders] AS [l0] WHERE EXISTS ( SELECT 1 FROM ( - SELECT [l1].[Name], [l1].[LocustHordeId], [l1].[ThreatLevel], [l1].[ThreatLevelByte], [l1].[ThreatLevelNullableByte], NULL AS [DefeatedByNickname], NULL AS [DefeatedBySquadId], NULL AS [HighCommandId], N'LocustLeader' AS [Discriminator] + SELECT [l1].[ThreatLevelNullableByte] FROM [LocustLeaders] AS [l1] UNION ALL - SELECT [l2].[Name], [l2].[LocustHordeId], [l2].[ThreatLevel], [l2].[ThreatLevelByte], [l2].[ThreatLevelNullableByte], [l2].[DefeatedByNickname], [l2].[DefeatedBySquadId], [l2].[HighCommandId], N'LocustCommander' AS [Discriminator] + SELECT [l2].[ThreatLevelNullableByte] FROM [LocustCommanders] AS [l2] ) AS [t0] WHERE [t0].[ThreatLevelNullableByte] IS NULL) @@ -10598,10 +10592,10 @@ FROM [LocustCommanders] AS [l0] WHERE EXISTS ( SELECT 1 FROM ( - SELECT [l1].[Name], [l1].[LocustHordeId], [l1].[ThreatLevel], [l1].[ThreatLevelByte], [l1].[ThreatLevelNullableByte], NULL AS [DefeatedByNickname], NULL AS [DefeatedBySquadId], NULL AS [HighCommandId], N'LocustLeader' AS [Discriminator] + SELECT [l1].[ThreatLevelNullableByte] FROM [LocustLeaders] AS [l1] UNION ALL - SELECT [l2].[Name], [l2].[LocustHordeId], [l2].[ThreatLevel], [l2].[ThreatLevelByte], [l2].[ThreatLevelNullableByte], [l2].[DefeatedByNickname], [l2].[DefeatedBySquadId], [l2].[HighCommandId], N'LocustCommander' AS [Discriminator] + SELECT [l2].[ThreatLevelNullableByte] FROM [LocustCommanders] AS [l2] ) AS [t0] WHERE [t0].[ThreatLevelNullableByte] IS NULL) @@ -10651,16 +10645,13 @@ UNION ALL SELECT [o].[Nickname], [o].[SquadId], [o].[AssignedCityName], [o].[CityOfBirthName], [o].[FullName], [o].[HasSoulPatch], [o].[LeaderNickname], [o].[LeaderSquadId], [o].[Rank], N'Officer' AS [Discriminator] FROM [Officers] AS [o] ) AS [t0] - WHERE EXISTS ( - SELECT 1 - FROM ( - SELECT [l1].[Name], [l1].[LocustHordeId], [l1].[ThreatLevel], [l1].[ThreatLevelByte], [l1].[ThreatLevelNullableByte], NULL AS [DefeatedByNickname], NULL AS [DefeatedBySquadId], NULL AS [HighCommandId], N'LocustLeader' AS [Discriminator] - FROM [LocustLeaders] AS [l1] - UNION ALL - SELECT [l2].[Name], [l2].[LocustHordeId], [l2].[ThreatLevel], [l2].[ThreatLevelByte], [l2].[ThreatLevelNullableByte], [l2].[DefeatedByNickname], [l2].[DefeatedBySquadId], [l2].[HighCommandId], N'LocustCommander' AS [Discriminator] - FROM [LocustCommanders] AS [l2] - ) AS [t2] - WHERE [t2].[ThreatLevelByte] = [t].[ThreatLevelByte]) + WHERE [t].[ThreatLevelByte] IN ( + SELECT [l1].[ThreatLevelByte] + FROM [LocustLeaders] AS [l1] + UNION ALL + SELECT [l2].[ThreatLevelByte] + FROM [LocustCommanders] AS [l2] + ) ) AS [t1] """); } @@ -10689,16 +10680,13 @@ UNION ALL SELECT [o].[Nickname], [o].[SquadId], [o].[AssignedCityName], [o].[CityOfBirthName], [o].[FullName], [o].[HasSoulPatch], [o].[LeaderNickname], [o].[LeaderSquadId], [o].[Rank], N'Officer' AS [Discriminator] FROM [Officers] AS [o] ) AS [t0] - WHERE NOT EXISTS ( - SELECT 1 - FROM ( - SELECT [l1].[Name], [l1].[LocustHordeId], [l1].[ThreatLevel], [l1].[ThreatLevelByte], [l1].[ThreatLevelNullableByte], NULL AS [DefeatedByNickname], NULL AS [DefeatedBySquadId], NULL AS [HighCommandId], N'LocustLeader' AS [Discriminator] - FROM [LocustLeaders] AS [l1] - UNION ALL - SELECT [l2].[Name], [l2].[LocustHordeId], [l2].[ThreatLevel], [l2].[ThreatLevelByte], [l2].[ThreatLevelNullableByte], [l2].[DefeatedByNickname], [l2].[DefeatedBySquadId], [l2].[HighCommandId], N'LocustCommander' AS [Discriminator] - FROM [LocustCommanders] AS [l2] - ) AS [t2] - WHERE [t2].[ThreatLevelByte] = [t].[ThreatLevelByte]) + WHERE [t].[ThreatLevelByte] NOT IN ( + SELECT [l1].[ThreatLevelByte] + FROM [LocustLeaders] AS [l1] + UNION ALL + SELECT [l2].[ThreatLevelByte] + FROM [LocustCommanders] AS [l2] + ) ) AS [t1] """); } @@ -10729,10 +10717,10 @@ FROM [Officers] AS [o] WHERE EXISTS ( SELECT 1 FROM ( - SELECT [l1].[Name], [l1].[LocustHordeId], [l1].[ThreatLevel], [l1].[ThreatLevelByte], [l1].[ThreatLevelNullableByte], NULL AS [DefeatedByNickname], NULL AS [DefeatedBySquadId], NULL AS [HighCommandId], N'LocustLeader' AS [Discriminator] + SELECT [l1].[ThreatLevelNullableByte] FROM [LocustLeaders] AS [l1] UNION ALL - SELECT [l2].[Name], [l2].[LocustHordeId], [l2].[ThreatLevel], [l2].[ThreatLevelByte], [l2].[ThreatLevelNullableByte], [l2].[DefeatedByNickname], [l2].[DefeatedBySquadId], [l2].[HighCommandId], N'LocustCommander' AS [Discriminator] + SELECT [l2].[ThreatLevelNullableByte] FROM [LocustCommanders] AS [l2] ) AS [t2] WHERE [t2].[ThreatLevelNullableByte] = [t].[ThreatLevelNullableByte] OR ([t2].[ThreatLevelNullableByte] IS NULL AND [t].[ThreatLevelNullableByte] IS NULL)) @@ -10766,10 +10754,10 @@ FROM [Officers] AS [o] WHERE NOT EXISTS ( SELECT 1 FROM ( - SELECT [l1].[Name], [l1].[LocustHordeId], [l1].[ThreatLevel], [l1].[ThreatLevelByte], [l1].[ThreatLevelNullableByte], NULL AS [DefeatedByNickname], NULL AS [DefeatedBySquadId], NULL AS [HighCommandId], N'LocustLeader' AS [Discriminator] + SELECT [l1].[ThreatLevelNullableByte] FROM [LocustLeaders] AS [l1] UNION ALL - SELECT [l2].[Name], [l2].[LocustHordeId], [l2].[ThreatLevel], [l2].[ThreatLevelByte], [l2].[ThreatLevelNullableByte], [l2].[DefeatedByNickname], [l2].[DefeatedBySquadId], [l2].[HighCommandId], N'LocustCommander' AS [Discriminator] + SELECT [l2].[ThreatLevelNullableByte] FROM [LocustCommanders] AS [l2] ) AS [t2] WHERE [t2].[ThreatLevelNullableByte] = [t].[ThreatLevelNullableByte] OR ([t2].[ThreatLevelNullableByte] IS NULL AND [t].[ThreatLevelNullableByte] IS NULL)) @@ -11975,10 +11963,10 @@ UNION ALL SELECT [o].[Nickname], [o].[SquadId], [o].[AssignedCityName], [o].[CityOfBirthName], [o].[FullName], [o].[HasSoulPatch], [o].[LeaderNickname], [o].[LeaderSquadId], [o].[Rank], N'Officer' AS [Discriminator] FROM [Officers] AS [o] ) AS [t] -WHERE [t].[HasSoulPatch] = CAST(1 AS bit) AND EXISTS ( - SELECT 1 +WHERE [t].[HasSoulPatch] = CAST(1 AS bit) AND [t].[HasSoulPatch] IN ( + SELECT CAST([v].[value] AS bit) AS [value] FROM OPENJSON(@__values_0) AS [v] - WHERE CAST([v].[value] AS bit) = [t].[HasSoulPatch]) +) """); } @@ -11998,10 +11986,10 @@ UNION ALL SELECT [o].[Nickname], [o].[SquadId], [o].[AssignedCityName], [o].[CityOfBirthName], [o].[FullName], [o].[HasSoulPatch], [o].[LeaderNickname], [o].[LeaderSquadId], [o].[Rank], N'Officer' AS [Discriminator] FROM [Officers] AS [o] ) AS [t] -WHERE [t].[HasSoulPatch] = CAST(1 AS bit) AND EXISTS ( - SELECT 1 +WHERE [t].[HasSoulPatch] = CAST(1 AS bit) AND [t].[HasSoulPatch] IN ( + SELECT CAST([v].[value] AS bit) AS [value] FROM OPENJSON(@__values_0) AS [v] - WHERE CAST([v].[value] AS bit) = [t].[HasSoulPatch]) +) """); } @@ -13285,8 +13273,8 @@ FROM [Officers] AS [o0] ) AS [t3] INNER JOIN [Squads] AS [s0] ON [t3].[SquadId] = [s0].[Id] INNER JOIN [Cities] AS [c] ON [t3].[CityOfBirthName] = [c].[Name] - WHERE EXISTS ( - SELECT 1 + WHERE N'Marcus' IN ( + SELECT [t4].[Nickname] FROM ( SELECT [g1].[Nickname], [g1].[SquadId], [g1].[AssignedCityName], [g1].[CityOfBirthName], [g1].[FullName], [g1].[HasSoulPatch], [g1].[LeaderNickname], [g1].[LeaderSquadId], [g1].[Rank], N'Gear' AS [Discriminator] FROM [Gears] AS [g1] @@ -13300,7 +13288,7 @@ UNION ALL SELECT [o2].[Nickname], [o2].[SquadId], [o2].[AssignedCityName], [o2].[CityOfBirthName], [o2].[FullName], [o2].[HasSoulPatch], [o2].[LeaderNickname], [o2].[LeaderSquadId], [o2].[Rank], N'Officer' AS [Discriminator] FROM [Officers] AS [o2] ) AS [t4] - WHERE [t4].[Nickname] = N'Marcus') AND ([s].[Name] = [s0].[Name] OR ([s].[Name] IS NULL AND [s0].[Name] IS NULL))) AS [SumOfLengths] + ) AND ([s].[Name] = [s0].[Name] OR ([s].[Name] IS NULL AND [s0].[Name] IS NULL))) AS [SumOfLengths] FROM ( SELECT [g].[SquadId] FROM [Gears] AS [g] @@ -13309,8 +13297,8 @@ SELECT [o].[SquadId] FROM [Officers] AS [o] ) AS [t] INNER JOIN [Squads] AS [s] ON [t].[SquadId] = [s].[Id] -WHERE EXISTS ( - SELECT 1 +WHERE N'Marcus' IN ( + SELECT [t0].[Nickname] FROM ( SELECT [g3].[Nickname], [g3].[SquadId], [g3].[AssignedCityName], [g3].[CityOfBirthName], [g3].[FullName], [g3].[HasSoulPatch], [g3].[LeaderNickname], [g3].[LeaderSquadId], [g3].[Rank], N'Gear' AS [Discriminator] FROM [Gears] AS [g3] @@ -13324,7 +13312,7 @@ UNION ALL SELECT [o4].[Nickname], [o4].[SquadId], [o4].[AssignedCityName], [o4].[CityOfBirthName], [o4].[FullName], [o4].[HasSoulPatch], [o4].[LeaderNickname], [o4].[LeaderSquadId], [o4].[Rank], N'Officer' AS [Discriminator] FROM [Officers] AS [o4] ) AS [t0] - WHERE [t0].[Nickname] = N'Marcus') +) GROUP BY [s].[Name] """); } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/TPTGearsOfWarQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/TPTGearsOfWarQuerySqlServerTest.cs index 7849b346fe6..069db1c2802 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/TPTGearsOfWarQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/TPTGearsOfWarQuerySqlServerTest.cs @@ -3058,11 +3058,11 @@ LEFT JOIN ( SELECT [g].[Nickname], [g].[SquadId] FROM [Gears] AS [g] ) AS [t0] ON [t].[GearNickName] = [t0].[Nickname] AND [t].[GearSquadId] = [t0].[SquadId] -WHERE ([t].[Note] <> N'K.I.A.' OR [t].[Note] IS NULL) AND EXISTS ( - SELECT 1 +WHERE ([t].[Note] <> N'K.I.A.' OR [t].[Note] IS NULL) AND [t0].[SquadId] IN ( + SELECT [g0].[SquadId] FROM [Gears] AS [g0] LEFT JOIN [Officers] AS [o0] ON [g0].[Nickname] = [o0].[Nickname] AND [g0].[SquadId] = [o0].[SquadId] - WHERE [g0].[SquadId] = [t0].[SquadId]) +) """); } @@ -3569,10 +3569,10 @@ public override async Task Contains_with_local_nullable_guid_list_closure(bool a SELECT [t].[Id], [t].[GearNickName], [t].[GearSquadId], [t].[IssueDate], [t].[Note] FROM [Tags] AS [t] -WHERE EXISTS ( - SELECT 1 +WHERE [t].[Id] IN ( + SELECT CAST([i].[value] AS uniqueidentifier) AS [value] FROM OPENJSON(@__ids_0) AS [i] - WHERE CAST([i].[value] AS uniqueidentifier) = [t].[Id]) +) """); } @@ -7626,10 +7626,10 @@ public override async Task DateTimeOffset_Contains_Less_than_Greater_than(bool a SELECT [m].[Id], [m].[CodeName], [m].[Date], [m].[Duration], [m].[Rating], [m].[Time], [m].[Timeline] FROM [Missions] AS [m] -WHERE @__start_0 <= CAST(CONVERT(date, [m].[Timeline]) AS datetimeoffset) AND [m].[Timeline] < @__end_1 AND EXISTS ( - SELECT 1 +WHERE @__start_0 <= CAST(CONVERT(date, [m].[Timeline]) AS datetimeoffset) AND [m].[Timeline] < @__end_1 AND [m].[Timeline] IN ( + SELECT CAST([d].[value] AS datetimeoffset) AS [value] FROM OPENJSON(@__dates_2) AS [d] - WHERE CAST([d].[value] AS datetimeoffset) = [m].[Timeline]) +) """); } @@ -8436,10 +8436,10 @@ END AS [Discriminator] FROM [Gears] AS [g] LEFT JOIN [Officers] AS [o] ON [g].[Nickname] = [o].[Nickname] AND [g].[SquadId] = [o].[SquadId] ORDER BY CASE - WHEN EXISTS ( - SELECT 1 + WHEN [g].[SquadId] IN ( + SELECT CAST([i].[value] AS int) AS [value] FROM OPENJSON(@__ids_0) AS [i] - WHERE CAST([i].[value] AS int) = [g].[SquadId]) THEN CAST(1 AS bit) + ) THEN CAST(1 AS bit) ELSE CAST(0 AS bit) END """); @@ -9034,11 +9034,11 @@ WHEN [l0].[Name] IS NOT NULL THEN N'LocustCommander' END AS [Discriminator] FROM [LocustLeaders] AS [l] LEFT JOIN [LocustCommanders] AS [l0] ON [l].[Name] = [l0].[Name] -WHERE EXISTS ( - SELECT 1 +WHERE [l].[ThreatLevelByte] IN ( + SELECT [l1].[ThreatLevelByte] FROM [LocustLeaders] AS [l1] LEFT JOIN [LocustCommanders] AS [l2] ON [l1].[Name] = [l2].[Name] - WHERE [l1].[ThreatLevelByte] = [l].[ThreatLevelByte]) +) """); } @@ -9133,11 +9133,11 @@ WHEN [o].[Nickname] IS NOT NULL THEN N'Officer' END AS [Discriminator] FROM [Gears] AS [g] LEFT JOIN [Officers] AS [o] ON [g].[Nickname] = [o].[Nickname] AND [g].[SquadId] = [o].[SquadId] - WHERE EXISTS ( - SELECT 1 + WHERE [l].[ThreatLevelByte] IN ( + SELECT [l1].[ThreatLevelByte] FROM [LocustLeaders] AS [l1] LEFT JOIN [LocustCommanders] AS [l2] ON [l1].[Name] = [l2].[Name] - WHERE [l1].[ThreatLevelByte] = [l].[ThreatLevelByte]) + ) ) AS [t] """); } @@ -9157,11 +9157,11 @@ WHEN [o].[Nickname] IS NOT NULL THEN N'Officer' END AS [Discriminator] FROM [Gears] AS [g] LEFT JOIN [Officers] AS [o] ON [g].[Nickname] = [o].[Nickname] AND [g].[SquadId] = [o].[SquadId] - WHERE NOT EXISTS ( - SELECT 1 + WHERE [l].[ThreatLevelByte] NOT IN ( + SELECT [l1].[ThreatLevelByte] FROM [LocustLeaders] AS [l1] LEFT JOIN [LocustCommanders] AS [l2] ON [l1].[Name] = [l2].[Name] - WHERE [l1].[ThreatLevelByte] = [l].[ThreatLevelByte]) + ) ) AS [t] """); } @@ -10264,10 +10264,10 @@ WHEN [o].[Nickname] IS NOT NULL THEN N'Officer' END AS [Discriminator] FROM [Gears] AS [g] LEFT JOIN [Officers] AS [o] ON [g].[Nickname] = [o].[Nickname] AND [g].[SquadId] = [o].[SquadId] -WHERE [g].[HasSoulPatch] = CAST(1 AS bit) AND EXISTS ( - SELECT 1 +WHERE [g].[HasSoulPatch] = CAST(1 AS bit) AND [g].[HasSoulPatch] IN ( + SELECT CAST([v].[value] AS bit) AS [value] FROM OPENJSON(@__values_0) AS [v] - WHERE CAST([v].[value] AS bit) = [g].[HasSoulPatch]) +) """); } @@ -10284,10 +10284,10 @@ WHEN [o].[Nickname] IS NOT NULL THEN N'Officer' END AS [Discriminator] FROM [Gears] AS [g] LEFT JOIN [Officers] AS [o] ON [g].[Nickname] = [o].[Nickname] AND [g].[SquadId] = [o].[SquadId] -WHERE [g].[HasSoulPatch] = CAST(1 AS bit) AND EXISTS ( - SELECT 1 +WHERE [g].[HasSoulPatch] = CAST(1 AS bit) AND [g].[HasSoulPatch] IN ( + SELECT CAST([v].[value] AS bit) AS [value] FROM OPENJSON(@__values_0) AS [v] - WHERE CAST([v].[value] AS bit) = [g].[HasSoulPatch]) +) """); } @@ -11436,8 +11436,8 @@ FROM [Gears] AS [g2] LEFT JOIN [Officers] AS [o2] ON [g2].[Nickname] = [o2].[Nickname] AND [g2].[SquadId] = [o2].[SquadId] INNER JOIN [Squads] AS [s0] ON [g2].[SquadId] = [s0].[Id] INNER JOIN [Cities] AS [c] ON [g2].[CityOfBirthName] = [c].[Name] - WHERE EXISTS ( - SELECT 1 + WHERE N'Marcus' IN ( + SELECT [t0].[Nickname] FROM ( SELECT [g3].[Nickname], [g3].[SquadId], [g3].[AssignedCityName], [g3].[CityOfBirthName], [g3].[FullName], [g3].[HasSoulPatch], [g3].[LeaderNickname], [g3].[LeaderSquadId], [g3].[Rank], CASE WHEN [o3].[Nickname] IS NOT NULL THEN N'Officer' @@ -11451,11 +11451,11 @@ END AS [Discriminator] FROM [Gears] AS [g4] LEFT JOIN [Officers] AS [o4] ON [g4].[Nickname] = [o4].[Nickname] AND [g4].[SquadId] = [o4].[SquadId] ) AS [t0] - WHERE [t0].[Nickname] = N'Marcus') AND ([s].[Name] = [s0].[Name] OR ([s].[Name] IS NULL AND [s0].[Name] IS NULL))) AS [SumOfLengths] + ) AND ([s].[Name] = [s0].[Name] OR ([s].[Name] IS NULL AND [s0].[Name] IS NULL))) AS [SumOfLengths] FROM [Gears] AS [g] INNER JOIN [Squads] AS [s] ON [g].[SquadId] = [s].[Id] -WHERE EXISTS ( - SELECT 1 +WHERE N'Marcus' IN ( + SELECT [t].[Nickname] FROM ( SELECT [g0].[Nickname], [g0].[SquadId], [g0].[AssignedCityName], [g0].[CityOfBirthName], [g0].[FullName], [g0].[HasSoulPatch], [g0].[LeaderNickname], [g0].[LeaderSquadId], [g0].[Rank], CASE WHEN [o0].[Nickname] IS NOT NULL THEN N'Officer' @@ -11469,7 +11469,7 @@ END AS [Discriminator] FROM [Gears] AS [g1] LEFT JOIN [Officers] AS [o1] ON [g1].[Nickname] = [o1].[Nickname] AND [g1].[SquadId] = [o1].[SquadId] ) AS [t] - WHERE [t].[Nickname] = N'Marcus') +) GROUP BY [s].[Name] """); } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/TemporalGearsOfWarQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/TemporalGearsOfWarQuerySqlServerTest.cs index 3da4aa91429..966ebefd564 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/TemporalGearsOfWarQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/TemporalGearsOfWarQuerySqlServerTest.cs @@ -1632,10 +1632,10 @@ public override async Task Where_bool_column_or_Contains(bool async) SELECT [g].[Nickname], [g].[SquadId], [g].[AssignedCityName], [g].[CityOfBirthName], [g].[Discriminator], [g].[FullName], [g].[HasSoulPatch], [g].[LeaderNickname], [g].[LeaderSquadId], [g].[PeriodEnd], [g].[PeriodStart], [g].[Rank] FROM [Gears] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [g] -WHERE [g].[HasSoulPatch] = CAST(1 AS bit) AND EXISTS ( - SELECT 1 +WHERE [g].[HasSoulPatch] = CAST(1 AS bit) AND [g].[HasSoulPatch] IN ( + SELECT CAST([v].[value] AS bit) AS [value] FROM OPENJSON(@__values_0) AS [v] - WHERE CAST([v].[value] AS bit) = [g].[HasSoulPatch]) +) """); } @@ -2947,10 +2947,10 @@ public override async Task Subquery_projecting_non_nullable_scalar_contains_non_ CROSS APPLY ( SELECT [g].[Nickname], [g].[SquadId], [g].[AssignedCityName], [g].[CityOfBirthName], [g].[Discriminator], [g].[FullName], [g].[HasSoulPatch], [g].[LeaderNickname], [g].[LeaderSquadId], [g].[PeriodEnd], [g].[PeriodStart], [g].[Rank] FROM [Gears] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [g] - WHERE NOT EXISTS ( - SELECT 1 + WHERE [l].[ThreatLevelByte] NOT IN ( + SELECT [l0].[ThreatLevelByte] FROM [LocustLeaders] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [l0] - WHERE [l0].[ThreatLevelByte] = [l].[ThreatLevelByte]) + ) ) AS [t] """); } @@ -3513,10 +3513,10 @@ public override async Task Contains_on_collection_of_byte_subquery(bool async) """ SELECT [l].[Name], [l].[Discriminator], [l].[LocustHordeId], [l].[PeriodEnd], [l].[PeriodStart], [l].[ThreatLevel], [l].[ThreatLevelByte], [l].[ThreatLevelNullableByte], [l].[DefeatedByNickname], [l].[DefeatedBySquadId], [l].[HighCommandId] FROM [LocustLeaders] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [l] -WHERE EXISTS ( - SELECT 1 +WHERE [l].[ThreatLevelByte] IN ( + SELECT [l0].[ThreatLevelByte] FROM [LocustLeaders] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [l0] - WHERE [l0].[ThreatLevelByte] = [l].[ThreatLevelByte]) +) """); } @@ -4062,10 +4062,10 @@ public override async Task Subquery_projecting_non_nullable_scalar_contains_non_ CROSS APPLY ( SELECT [g].[Nickname], [g].[SquadId], [g].[AssignedCityName], [g].[CityOfBirthName], [g].[Discriminator], [g].[FullName], [g].[HasSoulPatch], [g].[LeaderNickname], [g].[LeaderSquadId], [g].[PeriodEnd], [g].[PeriodStart], [g].[Rank] FROM [Gears] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [g] - WHERE EXISTS ( - SELECT 1 + WHERE [l].[ThreatLevelByte] IN ( + SELECT [l0].[ThreatLevelByte] FROM [LocustLeaders] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [l0] - WHERE [l0].[ThreatLevelByte] = [l].[ThreatLevelByte]) + ) ) AS [t] """); } @@ -6163,10 +6163,10 @@ public override async Task OrderBy_Contains_empty_list(bool async) SELECT [g].[Nickname], [g].[SquadId], [g].[AssignedCityName], [g].[CityOfBirthName], [g].[Discriminator], [g].[FullName], [g].[HasSoulPatch], [g].[LeaderNickname], [g].[LeaderSquadId], [g].[PeriodEnd], [g].[PeriodStart], [g].[Rank] FROM [Gears] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [g] ORDER BY CASE - WHEN EXISTS ( - SELECT 1 + WHEN [g].[SquadId] IN ( + SELECT CAST([i].[value] AS int) AS [value] FROM OPENJSON(@__ids_0) AS [i] - WHERE CAST([i].[value] AS int) = [g].[SquadId]) THEN CAST(1 AS bit) + ) THEN CAST(1 AS bit) ELSE CAST(0 AS bit) END """); @@ -6283,10 +6283,10 @@ public override async Task DateTimeOffset_Contains_Less_than_Greater_than(bool a SELECT [m].[Id], [m].[BriefingDocument], [m].[BriefingDocumentFileExtension], [m].[CodeName], [m].[Date], [m].[Duration], [m].[PeriodEnd], [m].[PeriodStart], [m].[Rating], [m].[Time], [m].[Timeline] FROM [Missions] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [m] -WHERE @__start_0 <= CAST(CONVERT(date, [m].[Timeline]) AS datetimeoffset) AND [m].[Timeline] < @__end_1 AND EXISTS ( - SELECT 1 +WHERE @__start_0 <= CAST(CONVERT(date, [m].[Timeline]) AS datetimeoffset) AND [m].[Timeline] < @__end_1 AND [m].[Timeline] IN ( + SELECT CAST([d].[value] AS datetimeoffset) AS [value] FROM OPENJSON(@__dates_2) AS [d] - WHERE CAST([d].[value] AS datetimeoffset) = [m].[Timeline]) +) """); } @@ -6637,10 +6637,10 @@ public override async Task Optional_navigation_type_compensation_works_with_cont SELECT [t].[Id], [t].[GearNickName], [t].[GearSquadId], [t].[IssueDate], [t].[Note], [t].[PeriodEnd], [t].[PeriodStart] FROM [Tags] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [t] LEFT JOIN [Gears] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [g] ON [t].[GearNickName] = [g].[Nickname] AND [t].[GearSquadId] = [g].[SquadId] -WHERE ([t].[Note] <> N'K.I.A.' OR [t].[Note] IS NULL) AND EXISTS ( - SELECT 1 +WHERE ([t].[Note] <> N'K.I.A.' OR [t].[Note] IS NULL) AND [g].[SquadId] IN ( + SELECT [g0].[SquadId] FROM [Gears] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [g0] - WHERE [g0].[SquadId] = [g].[SquadId]) +) """); } @@ -8303,10 +8303,10 @@ public override async Task Contains_with_local_nullable_guid_list_closure(bool a SELECT [t].[Id], [t].[GearNickName], [t].[GearSquadId], [t].[IssueDate], [t].[Note], [t].[PeriodEnd], [t].[PeriodStart] FROM [Tags] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [t] -WHERE EXISTS ( - SELECT 1 +WHERE [t].[Id] IN ( + SELECT CAST([i].[value] AS uniqueidentifier) AS [value] FROM OPENJSON(@__ids_0) AS [i] - WHERE CAST([i].[value] AS uniqueidentifier) = [t].[Id]) +) """); } @@ -8961,10 +8961,10 @@ public override async Task Where_bool_column_and_Contains(bool async) SELECT [g].[Nickname], [g].[SquadId], [g].[AssignedCityName], [g].[CityOfBirthName], [g].[Discriminator], [g].[FullName], [g].[HasSoulPatch], [g].[LeaderNickname], [g].[LeaderSquadId], [g].[PeriodEnd], [g].[PeriodStart], [g].[Rank] FROM [Gears] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [g] -WHERE [g].[HasSoulPatch] = CAST(1 AS bit) AND EXISTS ( - SELECT 1 +WHERE [g].[HasSoulPatch] = CAST(1 AS bit) AND [g].[HasSoulPatch] IN ( + SELECT CAST([v].[value] AS bit) AS [value] FROM OPENJSON(@__values_0) AS [v] - WHERE CAST([v].[value] AS bit) = [g].[HasSoulPatch]) +) """); } @@ -10003,8 +10003,8 @@ SELECT COALESCE(SUM(CAST(LEN([c].[Location]) AS int)), 0) FROM [Gears] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [g2] INNER JOIN [Squads] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [s0] ON [g2].[SquadId] = [s0].[Id] INNER JOIN [Cities] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [c] ON [g2].[CityOfBirthName] = [c].[Name] - WHERE EXISTS ( - SELECT 1 + WHERE N'Marcus' IN ( + SELECT [t0].[Nickname] FROM ( SELECT [g3].[Nickname], [g3].[SquadId], [g3].[AssignedCityName], [g3].[CityOfBirthName], [g3].[Discriminator], [g3].[FullName], [g3].[HasSoulPatch], [g3].[LeaderNickname], [g3].[LeaderSquadId], [g3].[PeriodEnd], [g3].[PeriodStart], [g3].[Rank] FROM [Gears] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [g3] @@ -10012,11 +10012,11 @@ UNION ALL SELECT [g4].[Nickname], [g4].[SquadId], [g4].[AssignedCityName], [g4].[CityOfBirthName], [g4].[Discriminator], [g4].[FullName], [g4].[HasSoulPatch], [g4].[LeaderNickname], [g4].[LeaderSquadId], [g4].[PeriodEnd], [g4].[PeriodStart], [g4].[Rank] FROM [Gears] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [g4] ) AS [t0] - WHERE [t0].[Nickname] = N'Marcus') AND ([s].[Name] = [s0].[Name] OR ([s].[Name] IS NULL AND [s0].[Name] IS NULL))) AS [SumOfLengths] + ) AND ([s].[Name] = [s0].[Name] OR ([s].[Name] IS NULL AND [s0].[Name] IS NULL))) AS [SumOfLengths] FROM [Gears] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [g] INNER JOIN [Squads] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [s] ON [g].[SquadId] = [s].[Id] -WHERE EXISTS ( - SELECT 1 +WHERE N'Marcus' IN ( + SELECT [t].[Nickname] FROM ( SELECT [g0].[Nickname], [g0].[SquadId], [g0].[AssignedCityName], [g0].[CityOfBirthName], [g0].[Discriminator], [g0].[FullName], [g0].[HasSoulPatch], [g0].[LeaderNickname], [g0].[LeaderSquadId], [g0].[PeriodEnd], [g0].[PeriodStart], [g0].[Rank] FROM [Gears] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [g0] @@ -10024,7 +10024,7 @@ UNION ALL SELECT [g1].[Nickname], [g1].[SquadId], [g1].[AssignedCityName], [g1].[CityOfBirthName], [g1].[Discriminator], [g1].[FullName], [g1].[HasSoulPatch], [g1].[LeaderNickname], [g1].[LeaderSquadId], [g1].[PeriodEnd], [g1].[PeriodStart], [g1].[Rank] FROM [Gears] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [g1] ) AS [t] - WHERE [t].[Nickname] = N'Marcus') +) GROUP BY [s].[Name] """); } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/UdfDbFunctionSqlServerTests.cs b/test/EFCore.SqlServer.FunctionalTests/Query/UdfDbFunctionSqlServerTests.cs index aeb735fce1b..a61cfc6485b 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/UdfDbFunctionSqlServerTests.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/UdfDbFunctionSqlServerTests.cs @@ -935,22 +935,22 @@ SELECT COALESCE(SUM(CAST(LEN([c2].[FirstName]) AS int)), 0) FROM [Orders] AS [o0] INNER JOIN [Customers] AS [c1] ON [o0].[CustomerId] = [c1].[Id] INNER JOIN [Customers] AS [c2] ON [o0].[CustomerId] = [c2].[Id] - WHERE NOT EXISTS ( - SELECT 1 + WHERE 25 NOT IN ( + SELECT [g0].[CustomerId] FROM [dbo].[GetOrdersWithMultipleProducts](( SELECT TOP(1) [c3].[Id] FROM [Customers] AS [c3] ORDER BY [c3].[Id])) AS [g0] - WHERE [g0].[CustomerId] = 25) AND ([c0].[LastName] = [c1].[LastName] OR ([c0].[LastName] IS NULL AND [c1].[LastName] IS NULL))) AS [SumOfLengths] + ) AND ([c0].[LastName] = [c1].[LastName] OR ([c0].[LastName] IS NULL AND [c1].[LastName] IS NULL))) AS [SumOfLengths] FROM [Orders] AS [o] INNER JOIN [Customers] AS [c0] ON [o].[CustomerId] = [c0].[Id] -WHERE NOT EXISTS ( - SELECT 1 +WHERE 25 NOT IN ( + SELECT [g].[CustomerId] FROM [dbo].[GetOrdersWithMultipleProducts](( SELECT TOP(1) [c].[Id] FROM [Customers] AS [c] ORDER BY [c].[Id])) AS [g] - WHERE [g].[CustomerId] = 25) +) GROUP BY [c0].[LastName] """); } diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/GearsOfWarQuerySqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/GearsOfWarQuerySqliteTest.cs index be87fb50ab5..74e138fd71b 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Query/GearsOfWarQuerySqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Query/GearsOfWarQuerySqliteTest.cs @@ -1904,10 +1904,10 @@ public override async Task Correlated_collection_with_complex_order_by_funcletiz SELECT "g"."Nickname", "g"."SquadId", "w"."Name", "w"."Id" FROM "Gears" AS "g" LEFT JOIN "Weapons" AS "w" ON "g"."FullName" = "w"."OwnerFullName" -ORDER BY EXISTS ( - SELECT 1 +ORDER BY COALESCE("g"."Nickname" IN ( + SELECT "n"."value" FROM json_each(@__nicknames_0) AS "n" - WHERE "n"."value" = "g"."Nickname") DESC, "g"."Nickname", "g"."SquadId" +), 0) DESC, "g"."Nickname", "g"."SquadId" """); } @@ -2168,10 +2168,10 @@ public override async Task Where_bool_column_and_Contains(bool async) SELECT "g"."Nickname", "g"."SquadId", "g"."AssignedCityName", "g"."CityOfBirthName", "g"."Discriminator", "g"."FullName", "g"."HasSoulPatch", "g"."LeaderNickname", "g"."LeaderSquadId", "g"."Rank" FROM "Gears" AS "g" -WHERE "g"."HasSoulPatch" AND EXISTS ( - SELECT 1 +WHERE "g"."HasSoulPatch" AND "g"."HasSoulPatch" IN ( + SELECT "v"."value" FROM json_each(@__values_0) AS "v" - WHERE "v"."value" = "g"."HasSoulPatch") +) """); } @@ -2408,10 +2408,10 @@ public override async Task Optional_navigation_type_compensation_works_with_cont SELECT "t"."Id", "t"."GearNickName", "t"."GearSquadId", "t"."IssueDate", "t"."Note" FROM "Tags" AS "t" LEFT JOIN "Gears" AS "g" ON "t"."GearNickName" = "g"."Nickname" AND "t"."GearSquadId" = "g"."SquadId" -WHERE ("t"."Note" <> 'K.I.A.' OR "t"."Note" IS NULL) AND EXISTS ( - SELECT 1 +WHERE ("t"."Note" <> 'K.I.A.' OR "t"."Note" IS NULL) AND "g"."SquadId" IN ( + SELECT "g0"."SquadId" FROM "Gears" AS "g0" - WHERE "g0"."SquadId" = "g"."SquadId") +) """); } @@ -4170,10 +4170,10 @@ public override async Task Contains_on_collection_of_byte_subquery(bool async) """ SELECT "l"."Name", "l"."Discriminator", "l"."LocustHordeId", "l"."ThreatLevel", "l"."ThreatLevelByte", "l"."ThreatLevelNullableByte", "l"."DefeatedByNickname", "l"."DefeatedBySquadId", "l"."HighCommandId" FROM "LocustLeaders" AS "l" -WHERE EXISTS ( - SELECT 1 +WHERE "l"."ThreatLevelByte" IN ( + SELECT "l0"."ThreatLevelByte" FROM "LocustLeaders" AS "l0" - WHERE "l0"."ThreatLevelByte" = "l"."ThreatLevelByte") +) """); } @@ -4505,10 +4505,10 @@ public override async Task Where_bool_column_or_Contains(bool async) SELECT "g"."Nickname", "g"."SquadId", "g"."AssignedCityName", "g"."CityOfBirthName", "g"."Discriminator", "g"."FullName", "g"."HasSoulPatch", "g"."LeaderNickname", "g"."LeaderSquadId", "g"."Rank" FROM "Gears" AS "g" -WHERE "g"."HasSoulPatch" AND EXISTS ( - SELECT 1 +WHERE "g"."HasSoulPatch" AND "g"."HasSoulPatch" IN ( + SELECT "v"."value" FROM json_each(@__values_0) AS "v" - WHERE "v"."value" = "g"."HasSoulPatch") +) """); } @@ -5594,10 +5594,10 @@ public override async Task OrderBy_Contains_empty_list(bool async) SELECT "g"."Nickname", "g"."SquadId", "g"."AssignedCityName", "g"."CityOfBirthName", "g"."Discriminator", "g"."FullName", "g"."HasSoulPatch", "g"."LeaderNickname", "g"."LeaderSquadId", "g"."Rank" FROM "Gears" AS "g" -ORDER BY EXISTS ( - SELECT 1 +ORDER BY "g"."SquadId" IN ( + SELECT "i"."value" FROM json_each(@__ids_0) AS "i" - WHERE "i"."value" = "g"."SquadId") +) """); } @@ -7976,10 +7976,10 @@ public override async Task Contains_with_local_nullable_guid_list_closure(bool a SELECT "t"."Id", "t"."GearNickName", "t"."GearSquadId", "t"."IssueDate", "t"."Note" FROM "Tags" AS "t" -WHERE EXISTS ( - SELECT 1 +WHERE "t"."Id" IN ( + SELECT upper("i"."value") AS "value" FROM json_each(@__ids_0) AS "i" - WHERE upper("i"."value") = "t"."Id") +) """); } @@ -9463,8 +9463,8 @@ SELECT COALESCE(SUM(length("c"."Location")), 0) FROM "Gears" AS "g2" INNER JOIN "Squads" AS "s0" ON "g2"."SquadId" = "s0"."Id" INNER JOIN "Cities" AS "c" ON "g2"."CityOfBirthName" = "c"."Name" - WHERE EXISTS ( - SELECT 1 + WHERE 'Marcus' IN ( + SELECT "t0"."Nickname" FROM ( SELECT "g3"."Nickname", "g3"."SquadId", "g3"."AssignedCityName", "g3"."CityOfBirthName", "g3"."Discriminator", "g3"."FullName", "g3"."HasSoulPatch", "g3"."LeaderNickname", "g3"."LeaderSquadId", "g3"."Rank" FROM "Gears" AS "g3" @@ -9472,11 +9472,11 @@ UNION ALL SELECT "g4"."Nickname", "g4"."SquadId", "g4"."AssignedCityName", "g4"."CityOfBirthName", "g4"."Discriminator", "g4"."FullName", "g4"."HasSoulPatch", "g4"."LeaderNickname", "g4"."LeaderSquadId", "g4"."Rank" FROM "Gears" AS "g4" ) AS "t0" - WHERE "t0"."Nickname" = 'Marcus') AND ("s"."Name" = "s0"."Name" OR ("s"."Name" IS NULL AND "s0"."Name" IS NULL))) AS "SumOfLengths" + ) AND ("s"."Name" = "s0"."Name" OR ("s"."Name" IS NULL AND "s0"."Name" IS NULL))) AS "SumOfLengths" FROM "Gears" AS "g" INNER JOIN "Squads" AS "s" ON "g"."SquadId" = "s"."Id" -WHERE EXISTS ( - SELECT 1 +WHERE 'Marcus' IN ( + SELECT "t"."Nickname" FROM ( SELECT "g0"."Nickname", "g0"."SquadId", "g0"."AssignedCityName", "g0"."CityOfBirthName", "g0"."Discriminator", "g0"."FullName", "g0"."HasSoulPatch", "g0"."LeaderNickname", "g0"."LeaderSquadId", "g0"."Rank" FROM "Gears" AS "g0" @@ -9484,7 +9484,7 @@ UNION ALL SELECT "g1"."Nickname", "g1"."SquadId", "g1"."AssignedCityName", "g1"."CityOfBirthName", "g1"."Discriminator", "g1"."FullName", "g1"."HasSoulPatch", "g1"."LeaderNickname", "g1"."LeaderSquadId", "g1"."Rank" FROM "Gears" AS "g1" ) AS "t" - WHERE "t"."Nickname" = 'Marcus') +) GROUP BY "s"."Name" """); } diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/NullSemanticsQuerySqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/NullSemanticsQuerySqliteTest.cs index a24f9c503f7..f1d0e1adc6d 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Query/NullSemanticsQuerySqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Query/NullSemanticsQuerySqliteTest.cs @@ -8,10 +8,108 @@ namespace Microsoft.EntityFrameworkCore.Query; public class NullSemanticsQuerySqliteTest : NullSemanticsQueryTestBase { - public NullSemanticsQuerySqliteTest(NullSemanticsQuerySqliteFixture fixture) + // ReSharper disable once UnusedParameter.Local + public NullSemanticsQuerySqliteTest(NullSemanticsQuerySqliteFixture fixture, ITestOutputHelper testOutputHelper) : base(fixture) { Fixture.TestSqlLoggerFactory.Clear(); + //Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper); + } + + public override async Task Null_semantics_contains_non_nullable_item_with_non_nullable_subquery(bool async) + { + await base.Null_semantics_contains_non_nullable_item_with_non_nullable_subquery(async); + + AssertSql( +""" +SELECT "e"."Id" +FROM "Entities1" AS "e" +WHERE "e"."StringA" IN ( + SELECT "e0"."StringA" + FROM "Entities2" AS "e0" +) +""", + // +""" +SELECT "e"."Id" +FROM "Entities1" AS "e" +WHERE "e"."StringA" NOT IN ( + SELECT "e0"."StringA" + FROM "Entities2" AS "e0" +) +"""); + } + + public override async Task Null_semantics_contains_nullable_item_with_non_nullable_subquery(bool async) + { + await base.Null_semantics_contains_nullable_item_with_non_nullable_subquery(async); + + AssertSql( +""" +SELECT "e"."Id" +FROM "Entities1" AS "e" +WHERE "e"."NullableStringA" IN ( + SELECT "e0"."StringA" + FROM "Entities2" AS "e0" +) +""", + // +""" +SELECT "e"."Id" +FROM "Entities1" AS "e" +WHERE "e"."NullableStringA" NOT IN ( + SELECT "e0"."StringA" + FROM "Entities2" AS "e0" +) OR "e"."NullableStringA" IS NULL +"""); + } + + public override async Task Null_semantics_contains_non_nullable_item_with_nullable_subquery(bool async) + { + await base.Null_semantics_contains_non_nullable_item_with_nullable_subquery(async); + + AssertSql( +""" +SELECT "e"."Id" +FROM "Entities1" AS "e" +WHERE "e"."StringA" IN ( + SELECT "e0"."NullableStringA" + FROM "Entities2" AS "e0" +) +""", + // +""" +SELECT "e"."Id" +FROM "Entities1" AS "e" +WHERE NOT (COALESCE("e"."StringA" IN ( + SELECT "e0"."NullableStringA" + FROM "Entities2" AS "e0" +), 0)) +"""); + } + + public override async Task Null_semantics_contains_nullable_item_with_nullable_subquery(bool async) + { + await base.Null_semantics_contains_nullable_item_with_nullable_subquery(async); + + AssertSql( +""" +SELECT "e"."Id" +FROM "Entities1" AS "e" +WHERE EXISTS ( + SELECT 1 + FROM "Entities2" AS "e0" + WHERE "e0"."NullableStringA" = "e"."NullableStringA" OR ("e0"."NullableStringA" IS NULL AND "e"."NullableStringA" IS NULL)) +""", + // +""" +SELECT "e"."Id" +FROM "Entities1" AS "e" +WHERE NOT EXISTS ( + SELECT 1 + FROM "Entities2" AS "e0" + WHERE "e0"."NullableStringA" = "e"."NullableStringA" OR ("e0"."NullableStringA" IS NULL AND "e"."NullableStringA" IS NULL)) +"""); } public override async Task Bool_equal_nullable_bool_HasValue(bool async) diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/PrimitiveCollectionsQuerySqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/PrimitiveCollectionsQuerySqliteTest.cs index 4d632724f6d..1fc9e9cf550 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Query/PrimitiveCollectionsQuerySqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Query/PrimitiveCollectionsQuerySqliteTest.cs @@ -154,10 +154,10 @@ public override async Task Inline_collection_Contains_with_all_parameters(bool a SELECT "p"."Id", "p"."Bool", "p"."Bools", "p"."DateTime", "p"."DateTimes", "p"."Enum", "p"."Enums", "p"."Int", "p"."Ints", "p"."NullableInt", "p"."NullableInts", "p"."String", "p"."Strings" FROM "PrimitiveCollectionsEntity" AS "p" -WHERE EXISTS ( - SELECT 1 +WHERE "p"."Id" IN ( + SELECT "p0"."value" FROM json_each(@__p_0) AS "p0" - WHERE "p0"."value" = "p"."Id") +) """); } @@ -219,16 +219,16 @@ public override async Task Parameter_collection_of_ints_Contains(bool async) SELECT "p"."Id", "p"."Bool", "p"."Bools", "p"."DateTime", "p"."DateTimes", "p"."Enum", "p"."Enums", "p"."Int", "p"."Ints", "p"."NullableInt", "p"."NullableInts", "p"."String", "p"."Strings" FROM "PrimitiveCollectionsEntity" AS "p" -WHERE EXISTS ( - SELECT 1 +WHERE "p"."Int" IN ( + SELECT "i"."value" FROM json_each(@__ints_0) AS "i" - WHERE "i"."value" = "p"."Int") +) """); } - public override async Task Parameter_collection_of_nullable_ints_Contains(bool async) + public override async Task Parameter_collection_of_nullable_ints_Contains_int(bool async) { - await base.Parameter_collection_of_nullable_ints_Contains(async); + await base.Parameter_collection_of_nullable_ints_Contains_int(async); AssertSql( """ @@ -236,16 +236,16 @@ public override async Task Parameter_collection_of_nullable_ints_Contains(bool a SELECT "p"."Id", "p"."Bool", "p"."Bools", "p"."DateTime", "p"."DateTimes", "p"."Enum", "p"."Enums", "p"."Int", "p"."Ints", "p"."NullableInt", "p"."NullableInts", "p"."String", "p"."Strings" FROM "PrimitiveCollectionsEntity" AS "p" -WHERE EXISTS ( - SELECT 1 +WHERE "p"."Int" IN ( + SELECT "n"."value" FROM json_each(@__nullableInts_0) AS "n" - WHERE "n"."value" = "p"."NullableInt" OR ("n"."value" IS NULL AND "p"."NullableInt" IS NULL)) +) """); } - public override async Task Parameter_collection_of_nullable_ints_Contains_null(bool async) + public override async Task Parameter_collection_of_nullable_ints_Contains_nullable_int(bool async) { - await base.Parameter_collection_of_nullable_ints_Contains_null(async); + await base.Parameter_collection_of_nullable_ints_Contains_nullable_int(async); AssertSql( """ @@ -287,10 +287,10 @@ public override async Task Parameter_collection_of_DateTimes_Contains(bool async SELECT "p"."Id", "p"."Bool", "p"."Bools", "p"."DateTime", "p"."DateTimes", "p"."Enum", "p"."Enums", "p"."Int", "p"."Ints", "p"."NullableInt", "p"."NullableInts", "p"."String", "p"."Strings" FROM "PrimitiveCollectionsEntity" AS "p" -WHERE EXISTS ( - SELECT 1 +WHERE "p"."DateTime" IN ( + SELECT datetime("d"."value") AS "value" FROM json_each(@__dateTimes_0) AS "d" - WHERE datetime("d"."value") = "p"."DateTime") +) """); } @@ -304,10 +304,10 @@ public override async Task Parameter_collection_of_bools_Contains(bool async) SELECT "p"."Id", "p"."Bool", "p"."Bools", "p"."DateTime", "p"."DateTimes", "p"."Enum", "p"."Enums", "p"."Int", "p"."Ints", "p"."NullableInt", "p"."NullableInts", "p"."String", "p"."Strings" FROM "PrimitiveCollectionsEntity" AS "p" -WHERE EXISTS ( - SELECT 1 +WHERE "p"."Bool" IN ( + SELECT "b"."value" FROM json_each(@__bools_0) AS "b" - WHERE "b"."value" = "p"."Bool") +) """); } @@ -321,10 +321,10 @@ public override async Task Parameter_collection_of_enums_Contains(bool async) SELECT "p"."Id", "p"."Bool", "p"."Bools", "p"."DateTime", "p"."DateTimes", "p"."Enum", "p"."Enums", "p"."Int", "p"."Ints", "p"."NullableInt", "p"."NullableInts", "p"."String", "p"."Strings" FROM "PrimitiveCollectionsEntity" AS "p" -WHERE EXISTS ( - SELECT 1 +WHERE "p"."Enum" IN ( + SELECT "e"."value" FROM json_each(@__enums_0) AS "e" - WHERE "e"."value" = "p"."Enum") +) """); } @@ -336,10 +336,10 @@ public override async Task Parameter_collection_null_Contains(bool async) """ SELECT "p"."Id", "p"."Bool", "p"."Bools", "p"."DateTime", "p"."DateTimes", "p"."Enum", "p"."Enums", "p"."Int", "p"."Ints", "p"."NullableInt", "p"."NullableInts", "p"."String", "p"."Strings" FROM "PrimitiveCollectionsEntity" AS "p" -WHERE EXISTS ( - SELECT 1 +WHERE "p"."Int" IN ( + SELECT "i"."value" FROM json_each('[]') AS "i" - WHERE "i"."value" = "p"."Int") +) """); } @@ -351,10 +351,10 @@ public override async Task Column_collection_of_ints_Contains(bool async) """ SELECT "p"."Id", "p"."Bool", "p"."Bools", "p"."DateTime", "p"."DateTimes", "p"."Enum", "p"."Enums", "p"."Int", "p"."Ints", "p"."NullableInt", "p"."NullableInts", "p"."String", "p"."Strings" FROM "PrimitiveCollectionsEntity" AS "p" -WHERE EXISTS ( - SELECT 1 +WHERE 10 IN ( + SELECT "i"."value" FROM json_each("p"."Ints") AS "i" - WHERE "i"."value" = 10) +) """); } @@ -366,10 +366,10 @@ public override async Task Column_collection_of_nullable_ints_Contains(bool asyn """ SELECT "p"."Id", "p"."Bool", "p"."Bools", "p"."DateTime", "p"."DateTimes", "p"."Enum", "p"."Enums", "p"."Int", "p"."Ints", "p"."NullableInt", "p"."NullableInts", "p"."String", "p"."Strings" FROM "PrimitiveCollectionsEntity" AS "p" -WHERE EXISTS ( - SELECT 1 +WHERE 10 IN ( + SELECT "n"."value" FROM json_each("p"."NullableInts") AS "n" - WHERE "n"."value" = 10) +) """); } @@ -388,9 +388,9 @@ FROM json_each("p"."NullableInts") AS "n" """); } - public override async Task Column_collection_of_bools_Contains(bool async) + public override async Task Column_collection_of_strings_contains_null(bool async) { - await base.Column_collection_of_bools_Contains(async); + await base.Column_collection_of_strings_contains_null(async); AssertSql( """ @@ -398,8 +398,23 @@ public override async Task Column_collection_of_bools_Contains(bool async) FROM "PrimitiveCollectionsEntity" AS "p" WHERE EXISTS ( SELECT 1 + FROM json_each("p"."Strings") AS "s" + WHERE "s"."value" IS NULL) +"""); + } + + public override async Task Column_collection_of_bools_Contains(bool async) + { + await base.Column_collection_of_bools_Contains(async); + + AssertSql( +""" +SELECT "p"."Id", "p"."Bool", "p"."Bools", "p"."DateTime", "p"."DateTimes", "p"."Enum", "p"."Enums", "p"."Int", "p"."Ints", "p"."NullableInt", "p"."NullableInts", "p"."String", "p"."Strings" +FROM "PrimitiveCollectionsEntity" AS "p" +WHERE 1 IN ( + SELECT "b"."value" FROM json_each("p"."Bools") AS "b" - WHERE "b"."value") +) """); } @@ -559,15 +574,12 @@ public override async Task Column_collection_Take(bool async) """ SELECT "p"."Id", "p"."Bool", "p"."Bools", "p"."DateTime", "p"."DateTimes", "p"."Enum", "p"."Enums", "p"."Int", "p"."Ints", "p"."NullableInt", "p"."NullableInts", "p"."String", "p"."Strings" FROM "PrimitiveCollectionsEntity" AS "p" -WHERE EXISTS ( - SELECT 1 - FROM ( - SELECT "i"."value", "i"."key" - FROM json_each("p"."Ints") AS "i" - ORDER BY "i"."key" - LIMIT 2 - ) AS "t" - WHERE "t"."value" = 11) +WHERE 11 IN ( + SELECT "i"."value" + FROM json_each("p"."Ints") AS "i" + ORDER BY "i"."key" + LIMIT 2 +) """); } @@ -579,15 +591,12 @@ public override async Task Column_collection_Skip_Take(bool async) """ SELECT "p"."Id", "p"."Bool", "p"."Bools", "p"."DateTime", "p"."DateTimes", "p"."Enum", "p"."Enums", "p"."Int", "p"."Ints", "p"."NullableInt", "p"."NullableInts", "p"."String", "p"."Strings" FROM "PrimitiveCollectionsEntity" AS "p" -WHERE EXISTS ( - SELECT 1 - FROM ( - SELECT "i"."value", "i"."key" - FROM json_each("p"."Ints") AS "i" - ORDER BY "i"."key" - LIMIT 2 OFFSET 1 - ) AS "t" - WHERE "t"."value" = 11) +WHERE 11 IN ( + SELECT "i"."value" + FROM json_each("p"."Ints") AS "i" + ORDER BY "i"."key" + LIMIT 2 OFFSET 1 +) """); }