From 039b10c78265165dad8980588f97e3ca8d93721c Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Sat, 22 Jun 2024 23:54:34 +0200 Subject: [PATCH] Implement Cosmos alias management Closes #33894 --- .../Query/Internal/CosmosAliasManager.cs | 238 ++++++++++++++++++ .../Internal/CosmosQueryCompilationContext.cs | 11 + .../CosmosQueryTranslationPostprocessor.cs | 7 +- ...mosQueryTranslationPostprocessorFactory.cs | 2 +- ...yableMethodTranslatingExpressionVisitor.cs | 128 ++++++---- .../Internal/Expressions/SelectExpression.cs | 80 ++---- .../Query/Internal/ISqlExpressionFactory.cs | 24 -- .../Query/Internal/SqlExpressionFactory.cs | 66 ----- ...onalQueryCompilationContextDependencies.cs | 2 +- .../Query/SqlAliasManager.cs | 36 +-- .../Query/FromSqlQueryCosmosTest.cs | 115 ++++----- ...aredPrimitiveCollectionsQueryCosmosTest.cs | 83 +++--- ...thwindAggregateOperatorsQueryCosmosTest.cs | 31 +-- .../NorthwindFunctionsQueryCosmosTest.cs | 2 +- .../Query/NorthwindWhereQueryCosmosTest.cs | 9 - .../Query/OwnedQueryCosmosTest.cs | 88 +++---- .../PrimitiveCollectionsQueryCosmosTest.cs | 90 +++---- 17 files changed, 579 insertions(+), 433 deletions(-) create mode 100644 src/EFCore.Cosmos/Query/Internal/CosmosAliasManager.cs diff --git a/src/EFCore.Cosmos/Query/Internal/CosmosAliasManager.cs b/src/EFCore.Cosmos/Query/Internal/CosmosAliasManager.cs new file mode 100644 index 00000000000..3e686495b86 --- /dev/null +++ b/src/EFCore.Cosmos/Query/Internal/CosmosAliasManager.cs @@ -0,0 +1,238 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections; + +namespace Microsoft.EntityFrameworkCore.Cosmos.Query.Internal; + +/// +/// A stateful manager for SQL aliases, capable of generating uniquified source aliases and rewriting them in post-processing. +/// An instance of is valid for a single query compilation, and is owned by +/// . +/// +/// +/// 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. +/// +public class CosmosAliasManager +{ + /// + /// Maps alias prefixes to the highest number postfix currently in use. + /// + /// + /// 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. + /// + private readonly Dictionary _aliases = new(); + + /// + /// Generates an alias based on the given . + /// All aliases produced by a given instance of are unique. + /// + /// + /// An expression to use as the starting point for the alias; this method knows a number of well-known expression types and can + /// generate appropriate aliases for them. A number postfix will be appended to it as necessary. + /// + /// + /// If isn't a well-known expression type, this fallback string will be used. + /// + /// A fully unique alias within the context of this translation process. + /// + /// 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. + /// + public virtual string GenerateSourceAlias(Expression expression, string? fallback = null) + => GenerateSourceAlias(expression switch + { + IAccessExpression { PropertyName: string propertyName } => propertyName, + FromSqlExpression => "sql", + SqlFunctionExpression { Name: "ARRAY_SLICE", Arguments: [var array, ..] } => GenerateSourceAlias(array), + ObjectFunctionExpression { Name: "ARRAY_SLICE", Arguments: [var array, ..] } => GenerateSourceAlias(array), + SqlFunctionExpression { Name: var name } => name, + ObjectFunctionExpression { Name: var name } => name, + ArrayConstantExpression => "array", + + _ => fallback ?? "value" + }); + + /// + /// Generates an alias based on the given . + /// All aliases produced by a given instance of are unique. + /// + /// + /// A name (e.g. of a container) to use as the starting point for the alias; a number postfix will be appended to it as necessary. + /// + /// A fully unique alias within the context of this translation process. + /// + /// 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. + /// + public virtual string GenerateSourceAlias(string name) + { + var firstChar = char.ToLowerInvariant(name[0]); + + if (_aliases.TryGetValue(firstChar, out var counter)) + { + return firstChar.ToString() + counter.Value++; + } + + _aliases[firstChar] = new MutableInt { Value = 0 }; + return firstChar.ToString(); + } + + /// + /// Performs a post-processing pass over aliases in the provided SQL tree, closing any gaps. + /// + /// The SQL tree to post-process. + /// + /// 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. + /// + public virtual Expression PostprocessAliases(Expression expression) + { + // To post-process (finalize) source aliases in the tree, we visit it to see which aliases are actually in use. + // We then remap those alias, e.g. closing any gaps caused by tables getting pruned, etc. + // Finally, we revisit the tree in order to apply the remapped aliases. + + var sourceAliases = SourceAliasCollector.Collect(expression); + + var aliasRewritingMap = RemapSourceAliases(sourceAliases); + + return aliasRewritingMap is null + ? expression + : SourceAliasRewriter.Rewrite(expression, aliasRewritingMap); + } + + /// + /// Given the list of source aliases currently in use in the SQL tree, produces a remapping for aliases within that list. + /// Can be used to e.g. close gaps for sources which have been pruned, etc. + /// + /// + /// 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 virtual Dictionary? RemapSourceAliases(IReadOnlySet usedAliases) + { + // Aliases consist of a single character, followed by a counter for uniquification. + // We process the collected aliases above into a bitmap that represents, for each alias char, which numbers have been seen. + // Note that since a0 is the 2nd uniquified alias (a is the first), the bits are off-by-one, with position 0 representing + // a, position 1 representing a0, and so on. + Dictionary aliasBitmaps = new(); + + foreach (var alias in usedAliases) + { + var aliasBase = alias[0]; + var aliasNum = alias.Length == 1 ? 0 : int.Parse(alias[1..]) + 1; + + if (aliasBitmaps.TryGetValue(aliasBase, out var bitmap)) + { + if (bitmap.Length < aliasNum + 1) + { + bitmap.Length = aliasNum + 1; + } + } + else + { + bitmap = aliasBitmaps[aliasBase] = new(aliasNum + 1); + } + + bitmap[aliasNum] = true; + } + + Dictionary? aliasRewritingMap = null; + foreach (var (aliasBase, bitmap) in aliasBitmaps) + { + if (bitmap.HasAllSet()) + { + // There are no gaps, no need to do any rewriting of the aliases for this alias base + continue; + } + + var numHoles = 0; + for (var i = 0; i < bitmap.Length; i++) + { + if (!bitmap[i]) + { + numHoles++; + } + else if (numHoles > 0) + { + var oldAlias = aliasBase + (i == 0 ? "" : (i - 1).ToString()); + var j = i - numHoles; + var newAlias = aliasBase + (j == 0 ? "" : (j - 1).ToString()); + + aliasRewritingMap ??= new(); + aliasRewritingMap[oldAlias] = newAlias; + } + } + } + + return aliasRewritingMap; + } + + private sealed class SourceAliasCollector : ExpressionVisitor + { + private readonly HashSet _sourceAliases = new(); + + internal static HashSet Collect(Expression expression) + { + var collector = new SourceAliasCollector(); + collector.Visit(expression); + return collector._sourceAliases; + } + + protected override Expression VisitExtension(Expression node) + { + switch (node) + { + case ShapedQueryExpression shapedQuery: + return shapedQuery.UpdateQueryExpression(Visit(shapedQuery.QueryExpression)); + + case SourceExpression { Alias: string alias }: + _sourceAliases.Add(alias); + return base.VisitExtension(node); + + default: + return base.VisitExtension(node); + } + } + } + + private sealed class SourceAliasRewriter(IReadOnlyDictionary aliasRewritingMap) : ExpressionVisitor + { + internal static Expression Rewrite(Expression expression, IReadOnlyDictionary aliasRewritingMap) + => new SourceAliasRewriter(aliasRewritingMap).Visit(expression); + + protected override Expression VisitExtension(Expression node) + => node switch + { + ShapedQueryExpression shapedQuery => shapedQuery.UpdateQueryExpression(Visit(shapedQuery.QueryExpression)), + + SourceExpression { Alias: string alias } source when aliasRewritingMap.TryGetValue(alias, out var newAlias) + => base.VisitExtension(new SourceExpression(source.Expression, newAlias, source.WithIn)), + ScalarReferenceExpression reference when aliasRewritingMap.TryGetValue(reference.Name, out var newAlias) + => new ScalarReferenceExpression(newAlias, reference.Type, reference.TypeMapping), + ObjectReferenceExpression reference when aliasRewritingMap.TryGetValue(reference.Name, out var newAlias) + => new ObjectReferenceExpression(reference.EntityType, newAlias), + + _ => base.VisitExtension(node) + }; + } + + private sealed class MutableInt + { + internal int Value; + } +} diff --git a/src/EFCore.Cosmos/Query/Internal/CosmosQueryCompilationContext.cs b/src/EFCore.Cosmos/Query/Internal/CosmosQueryCompilationContext.cs index 1b0d35baf5d..bee3bdb6b5b 100644 --- a/src/EFCore.Cosmos/Query/Internal/CosmosQueryCompilationContext.cs +++ b/src/EFCore.Cosmos/Query/Internal/CosmosQueryCompilationContext.cs @@ -30,4 +30,15 @@ public class CosmosQueryCompilationContext(QueryCompilationContextDependencies d /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public virtual PartitionKey? PartitionKeyValueFromExtension { get; internal set; } + + /// + /// A manager for aliases, capable of generate uniquified source aliases. + /// + /// + /// 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. + /// + public virtual CosmosAliasManager AliasManager { get; } = new(); } diff --git a/src/EFCore.Cosmos/Query/Internal/CosmosQueryTranslationPostprocessor.cs b/src/EFCore.Cosmos/Query/Internal/CosmosQueryTranslationPostprocessor.cs index 7742bc50617..afd87a9d63b 100644 --- a/src/EFCore.Cosmos/Query/Internal/CosmosQueryTranslationPostprocessor.cs +++ b/src/EFCore.Cosmos/Query/Internal/CosmosQueryTranslationPostprocessor.cs @@ -12,7 +12,7 @@ namespace Microsoft.EntityFrameworkCore.Cosmos.Query.Internal; public class CosmosQueryTranslationPostprocessor( QueryTranslationPostprocessorDependencies dependencies, ISqlExpressionFactory sqlExpressionFactory, - QueryCompilationContext queryCompilationContext) + CosmosQueryCompilationContext queryCompilationContext) : QueryTranslationPostprocessor(dependencies, queryCompilationContext) { /// @@ -31,8 +31,9 @@ public override Expression Process(Expression query) selectExpression.ApplyProjection(); } - query = new CosmosValueConverterCompensatingExpressionVisitor(sqlExpressionFactory).Visit(query); + var afterValueConverterCompensation = new CosmosValueConverterCompensatingExpressionVisitor(sqlExpressionFactory).Visit(query); + var afterAliases = queryCompilationContext.AliasManager.PostprocessAliases(afterValueConverterCompensation); - return query; + return afterAliases; } } diff --git a/src/EFCore.Cosmos/Query/Internal/CosmosQueryTranslationPostprocessorFactory.cs b/src/EFCore.Cosmos/Query/Internal/CosmosQueryTranslationPostprocessorFactory.cs index fb4260b9b53..313976e3e92 100644 --- a/src/EFCore.Cosmos/Query/Internal/CosmosQueryTranslationPostprocessorFactory.cs +++ b/src/EFCore.Cosmos/Query/Internal/CosmosQueryTranslationPostprocessorFactory.cs @@ -29,5 +29,5 @@ public virtual QueryTranslationPostprocessor Create(QueryCompilationContext quer => new CosmosQueryTranslationPostprocessor( Dependencies, sqlExpressionFactory, - queryCompilationContext); + ((CosmosQueryCompilationContext)queryCompilationContext)); } diff --git a/src/EFCore.Cosmos/Query/Internal/CosmosQueryableMethodTranslatingExpressionVisitor.cs b/src/EFCore.Cosmos/Query/Internal/CosmosQueryableMethodTranslatingExpressionVisitor.cs index 1bc4e87cd2a..e4aa5b15cba 100644 --- a/src/EFCore.Cosmos/Query/Internal/CosmosQueryableMethodTranslatingExpressionVisitor.cs +++ b/src/EFCore.Cosmos/Query/Internal/CosmosQueryableMethodTranslatingExpressionVisitor.cs @@ -25,8 +25,9 @@ public class CosmosQueryableMethodTranslatingExpressionVisitor : QueryableMethod private readonly IMethodCallTranslatorProvider _methodCallTranslatorProvider; private readonly CosmosSqlTranslatingExpressionVisitor _sqlTranslator; private readonly CosmosProjectionBindingExpressionVisitor _projectionBindingExpressionVisitor; + private readonly CosmosAliasManager _aliasManager; private bool _subquery; - private ReadItemInfo? _readItemExpression; + private ReadItemInfo? _readItemInfo; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -57,6 +58,7 @@ public CosmosQueryableMethodTranslatingExpressionVisitor( this); _projectionBindingExpressionVisitor = new CosmosProjectionBindingExpressionVisitor(_queryCompilationContext.Model, this, _sqlTranslator, _typeMappingSource); + _aliasManager = queryCompilationContext.AliasManager; _subquery = false; } @@ -84,6 +86,7 @@ protected CosmosQueryableMethodTranslatingExpressionVisitor( parentVisitor); _projectionBindingExpressionVisitor = new CosmosProjectionBindingExpressionVisitor(_queryCompilationContext.Model, this, _sqlTranslator, _typeMappingSource); + _aliasManager = parentVisitor._aliasManager; _subquery = true; } @@ -225,7 +228,7 @@ public override Expression Translate(Expression expression) (property, parameter) => (property, parameter)) .ToDictionary(tuple => tuple.property, tuple => tuple.parameter); - _readItemExpression = new ReadItemInfo(entityType, propertyParameterList, clrType); + _readItemInfo = new ReadItemInfo(entityType, propertyParameterList, clrType); } } } @@ -359,11 +362,14 @@ protected override Expression VisitExtension(Expression extensionExpression) AddTranslationErrorDetails(CosmosStrings.NonCorrelatedSubqueriesNotSupported); return QueryCompilationContext.NotTranslatedExpression; - case FromSqlQueryRootExpression fromSqlQueryRootExpression: - return CreateShapedQueryExpression( - fromSqlQueryRootExpression.EntityType, - _sqlExpressionFactory.Select( - fromSqlQueryRootExpression.EntityType, fromSqlQueryRootExpression.Sql, fromSqlQueryRootExpression.Argument)); + case FromSqlQueryRootExpression fromSqlQueryRoot: + var entityType = fromSqlQueryRoot.EntityType; + var fromSql = new FromSqlExpression(entityType.ClrType, fromSqlQueryRoot.Sql, fromSqlQueryRoot.Argument); + var alias = _aliasManager.GenerateSourceAlias(fromSql); + var selectExpression = new SelectExpression( + new SourceExpression(fromSql, alias), + new EntityProjectionExpression(new ObjectReferenceExpression(entityType, alias), entityType)); + return CreateShapedQueryExpression(entityType, selectExpression); default: return base.VisitExtension(extensionExpression); @@ -404,13 +410,49 @@ protected override QueryableMethodTranslatingExpressionVisitor CreateSubqueryVis /// doing so can result in application failures when updating to a new Entity Framework Core release. /// protected override ShapedQueryExpression CreateShapedQueryExpression(IEntityType entityType) - => CreateShapedQueryExpression( - entityType, - _readItemExpression == null - ? _sqlExpressionFactory.Select(entityType) - : _sqlExpressionFactory.ReadItem(entityType, _readItemExpression)); + { + Check.DebugAssert(!entityType.IsOwned(), "Can't create ShapedQueryExpression for owned entity type"); + + var alias = _aliasManager.GenerateSourceAlias("c"); + var selectExpression = new SelectExpression( + new SourceExpression(new ObjectReferenceExpression(entityType, "root"), alias), + new EntityProjectionExpression(new ObjectReferenceExpression(entityType, alias), entityType), + _readItemInfo); + + // Add discriminator predicate + var concreteEntityTypes = entityType.GetConcreteDerivedTypesInclusive().ToList(); + if (concreteEntityTypes.Count == 1) + { + var concreteEntityType = concreteEntityTypes[0]; + var discriminatorProperty = concreteEntityType.FindDiscriminatorProperty(); + if (discriminatorProperty != null) + { + var discriminatorColumn = ((EntityProjectionExpression)selectExpression.GetMappedProjection(new ProjectionMember())) + .BindProperty(discriminatorProperty, clientEval: false); + + selectExpression.ApplyPredicate( + _sqlExpressionFactory.Equal( + (SqlExpression)discriminatorColumn, + _sqlExpressionFactory.Constant(concreteEntityType.GetDiscriminatorValue()))); + } + } + else + { + var discriminatorProperty = concreteEntityTypes[0].FindDiscriminatorProperty(); + Check.DebugAssert(discriminatorProperty is not null, "Missing discriminator property in hierarchy"); + var discriminatorColumn = ((EntityProjectionExpression)selectExpression.GetMappedProjection(new ProjectionMember())) + .BindProperty(discriminatorProperty, clientEval: false); - private ShapedQueryExpression CreateShapedQueryExpression(IEntityType entityType, Expression queryExpression) + selectExpression.ApplyPredicate( + _sqlExpressionFactory.In( + (SqlExpression)discriminatorColumn, + concreteEntityTypes.Select(et => _sqlExpressionFactory.Constant(et.GetDiscriminatorValue())).ToArray())); + } + + return CreateShapedQueryExpression(entityType, selectExpression); + } + + private ShapedQueryExpression CreateShapedQueryExpression(IEntityType entityType, SelectExpression queryExpression) { if (!entityType.IsOwned()) { @@ -1088,7 +1130,7 @@ protected override ShapedQueryExpression TranslateSelect(ShapedQueryExpression s if (Visit(collectionSelectorBody) is ShapedQueryExpression inner) { var select = (SelectExpression)source.QueryExpression; - var shaper = select.AddJoin(inner, source.ShaperExpression); + var shaper = select.AddJoin(inner, source.ShaperExpression, _aliasManager); return TranslateTwoParameterSelector(source.UpdateShaperExpression(shaper), resultSelector); } @@ -1196,11 +1238,11 @@ protected override ShapedQueryExpression TranslateSelect(ShapedQueryExpression s var slice = _sqlExpressionFactory.Function("ARRAY_SLICE", [scalarArray, translatedCount], arrayType, arrayTypeMapping); - // TODO: Proper alias management (#33894). Ideally reach into the source of the original SelectExpression and use that alias. + var alias = _aliasManager.GenerateSourceAlias(slice); var translatedSelect = SelectExpression.CreateForCollection( slice, - "i", - new ScalarReferenceExpression("i", element.Type, element.TypeMapping)); + alias, + new ScalarReferenceExpression(alias, element.Type, element.TypeMapping)); return source.UpdateQueryExpression(translatedSelect); } @@ -1208,14 +1250,13 @@ protected override ShapedQueryExpression TranslateSelect(ShapedQueryExpression s case not null when projectedStructuralTypeShaper is not null: { var arrayType = typeof(IEnumerable<>).MakeGenericType(projectedStructuralTypeShaper.Type); - - // TODO: Proper alias management (#33894). var slice = new ObjectFunctionExpression("ARRAY_SLICE", [array, translatedCount], arrayType); + var alias = _aliasManager.GenerateSourceAlias(slice); var translatedSelect = SelectExpression.CreateForCollection( slice, - "i", + alias, new EntityProjectionExpression( - new ObjectReferenceExpression((IEntityType)projectedStructuralTypeShaper.StructuralType, "i"), + new ObjectReferenceExpression((IEntityType)projectedStructuralTypeShaper.StructuralType, alias), (IEntityType)projectedStructuralTypeShaper.StructuralType)); return source.Update( translatedSelect, @@ -1322,29 +1363,29 @@ protected override ShapedQueryExpression TranslateSelect(ShapedQueryExpression s "ARRAY_SLICE", [scalarArray, TranslateExpression(Expression.Constant(0))!, translatedCount], scalarArray.Type, scalarArray.TypeMapping); - // TODO: Proper alias management (#33894). Ideally reach into the source of the original SelectExpression and use that alias. + var alias = _aliasManager.GenerateSourceAlias(slice); select = SelectExpression.CreateForCollection( slice, - "i", - new ScalarReferenceExpression("i", element.Type, element.TypeMapping)); + alias, + new ScalarReferenceExpression(alias, element.Type, element.TypeMapping)); return source.UpdateQueryExpression(select); } // ElementAtOrDefault over an array os structural types case not null when projectedStructuralTypeShaper is not null: { - // TODO: Proper alias management (#33894). // Take() is composed over Skip(), combine the two together to a single ARRAY_SLICE() var slice = array is ObjectFunctionExpression { Name: "ARRAY_SLICE", Arguments: [var nestedArray, var skipCount] } previousSlice ? previousSlice.Update([nestedArray, skipCount, translatedCount]) : new ObjectFunctionExpression( "ARRAY_SLICE", [array, TranslateExpression(Expression.Constant(0))!, translatedCount], projectedStructuralTypeShaper.Type); + var alias = _aliasManager.GenerateSourceAlias(slice); var translatedSelect = SelectExpression.CreateForCollection( slice, - "i", + alias, new EntityProjectionExpression( - new ObjectReferenceExpression((IEntityType)projectedStructuralTypeShaper.StructuralType, "i"), + new ObjectReferenceExpression((IEntityType)projectedStructuralTypeShaper.StructuralType, alias), (IEntityType)projectedStructuralTypeShaper.StructuralType)); return source.Update( translatedSelect, @@ -1590,13 +1631,14 @@ when methodCallExpression.TryGetIndexerArguments(_queryCompilationContext.Model, // Maybe have it return the StructuralTypeShaperExpression instead, and only when binding from within SqlTranslatingEV, // wrap with ERE? // Check: how is this currently working in relational? + + var sourceAlias = _aliasManager.GenerateSourceAlias(property.Name); + switch (translatedExpression) { case StructuralTypeShaperExpression shaper when property is INavigation { IsCollection: true }: { - // TODO: Alias management #33894 var targetEntityType = (IEntityType)shaper.StructuralType; - var sourceAlias = "t"; var projection = new EntityProjectionExpression( new ObjectReferenceExpression(targetEntityType, sourceAlias), targetEntityType); var select = SelectExpression.CreateForCollection( @@ -1614,11 +1656,10 @@ when methodCallExpression.TryGetIndexerArguments(_queryCompilationContext.Model, case SqlExpression sqlExpression when property is IProperty { IsPrimitiveCollection: true }: { var elementClrType = sqlExpression.Type.GetSequenceType(); - // TODO: Do proper alias management: #33894 var select = SelectExpression.CreateForCollection( sqlExpression, - "i", - new ScalarReferenceExpression("i", elementClrType, sqlExpression.TypeMapping!.ElementTypeMapping!)); + sourceAlias, + new ScalarReferenceExpression(sourceAlias, elementClrType, sqlExpression.TypeMapping!.ElementTypeMapping!)); return CreateShapedQueryExpression(select, elementClrType); } } @@ -1635,7 +1676,7 @@ when methodCallExpression.TryGetIndexerArguments(_queryCompilationContext.Model, /// protected override ShapedQueryExpression? TranslateInlineQueryRoot(InlineQueryRootExpression inlineQueryRootExpression) { - // The below produces an InlineArrayExpression ([1,2,3]), wrapped by a SelectExpression (SELECT VALUE [1,2,3]). + // The below produces an ArrayConstantExpression ([1,2,3]), wrapped by a SelectExpression (SELECT VALUE [1,2,3]). // This is because a bare inline array can only appear in the projection. For example, the following is wrong: // SELECT i FROM i IN [1,2,3] (syntax error) var values = inlineQueryRootExpression.Values; @@ -1658,11 +1699,11 @@ when methodCallExpression.TryGetIndexerArguments(_queryCompilationContext.Model, var arrayTypeMapping = _typeMappingSource.FindMapping(typeof(IEnumerable<>).MakeGenericType(elementClrType)); var inlineArray = new ArrayConstantExpression(elementClrType, translatedItems, arrayTypeMapping); - // TODO: Do proper alias management: #33894 + var sourceAlias = _aliasManager.GenerateSourceAlias(inlineArray); var select = SelectExpression.CreateForCollection( inlineArray, - "i", - new ScalarReferenceExpression("i", elementClrType, elementTypeMapping)); + sourceAlias, + new ScalarReferenceExpression(sourceAlias, elementClrType, elementTypeMapping)); return CreateShapedQueryExpression(select, elementClrType); } @@ -1688,11 +1729,11 @@ when methodCallExpression.TryGetIndexerArguments(_queryCompilationContext.Model, var elementTypeMapping = _typeMappingSource.FindMapping(elementClrType)!; var sqlParameterExpression = new SqlParameterExpression(parameterQueryRootExpression.ParameterExpression, arrayTypeMapping); - // TODO: Do proper alias management: #33894 + var sourceAlias = _aliasManager.GenerateSourceAlias(sqlParameterExpression.Name.TrimStart('_')); var select = SelectExpression.CreateForCollection( sqlParameterExpression, - "i", - new ScalarReferenceExpression("i", elementClrType, elementTypeMapping)); + sourceAlias, + new ScalarReferenceExpression(sourceAlias, elementClrType, elementTypeMapping)); return CreateShapedQueryExpression(select, elementClrType); } @@ -1761,11 +1802,10 @@ when methodCallExpression.TryGetIndexerArguments(_queryCompilationContext.Model, && (sqlProjection1.TypeMapping ?? sqlProjection2.TypeMapping) is CosmosTypeMapping typeMapping) { var arrayTypeMapping = _typeMappingSource.FindMapping(arrayType, _queryCompilationContext.Model, typeMapping); - - // TODO: Proper alias management (#33894). var translation = _sqlExpressionFactory.Function(functionName, [array1, array2], arrayType, arrayTypeMapping); + var alias = _aliasManager.GenerateSourceAlias(translation); var select = SelectExpression.CreateForCollection( - translation, "i", new ScalarReferenceExpression("i", projection1.Type, typeMapping)); + translation, alias, new ScalarReferenceExpression(alias, projection1.Type, typeMapping)); return source1.UpdateQueryExpression(select); } @@ -1774,10 +1814,10 @@ when methodCallExpression.TryGetIndexerArguments(_queryCompilationContext.Model, && source2.ShaperExpression is StructuralTypeShaperExpression { StructuralType: var structuralType2 } && structuralType1 == structuralType2) { - // TODO: Proper alias management (#33894). var translation = new ObjectFunctionExpression(functionName, [array1, array2], arrayType); + var alias = _aliasManager.GenerateSourceAlias(translation); var select = SelectExpression.CreateForCollection( - translation, "i", new ObjectReferenceExpression((IEntityType)structuralType1, "i")); + translation, alias, new ObjectReferenceExpression((IEntityType)structuralType1, alias)); return CreateShapedQueryExpression(select, structuralType1.ClrType); } } diff --git a/src/EFCore.Cosmos/Query/Internal/Expressions/SelectExpression.cs b/src/EFCore.Cosmos/Query/Internal/Expressions/SelectExpression.cs index 56a96abf42e..af87947caf3 100644 --- a/src/EFCore.Cosmos/Query/Internal/Expressions/SelectExpression.cs +++ b/src/EFCore.Cosmos/Query/Internal/Expressions/SelectExpression.cs @@ -16,8 +16,6 @@ namespace Microsoft.EntityFrameworkCore.Cosmos.Query.Internal; [DebuggerDisplay("{PrintShortSql(), nq}")] public class SelectExpression : Expression, IPrintableExpression { - private const string RootAlias = "c"; - private IDictionary _projectionMapping = new Dictionary(); private readonly List _sources = []; private readonly List _projection = []; @@ -31,38 +29,7 @@ public class SelectExpression : Expression, IPrintableExpression /// 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. /// - public SelectExpression(IEntityType entityType, ReadItemInfo readItemInfo) - : this(entityType) - { - ReadItemInfo = readItemInfo; - } - - /// - /// 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. - /// - public SelectExpression(IEntityType entityType) - { - // TODO: Redo aliasing - _sources = [new SourceExpression(new ObjectReferenceExpression(entityType, "root"), RootAlias)]; - _projectionMapping[new ProjectionMember()] - = new EntityProjectionExpression(new ObjectReferenceExpression(entityType, RootAlias), entityType); - } - - /// - /// 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. - /// - public SelectExpression(IEntityType entityType, string sql, Expression argument) - { - var fromSql = new FromSqlExpression(entityType.ClrType, sql, argument); - _sources = [new SourceExpression(fromSql, RootAlias)]; - _projectionMapping[new ProjectionMember()] = new EntityProjectionExpression(new ObjectReferenceExpression(entityType, RootAlias), entityType); - } + public virtual ReadItemInfo? ReadItemInfo { get; init; } /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -80,14 +47,6 @@ public SelectExpression( _orderings = orderings; } - /// - /// 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. - /// - public virtual ReadItemInfo? ReadItemInfo { get; init; } - /// /// 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 @@ -103,14 +62,11 @@ public SelectExpression(Expression projection) /// 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. /// - public SelectExpression(SourceExpression source, Expression projection) + public SelectExpression(SourceExpression source, Expression projection, ReadItemInfo? readItemInfo = null) { _sources.Add(source); _projectionMapping[new ProjectionMember()] = projection; - } - - private SelectExpression() - { + ReadItemInfo = readItemInfo; } /// @@ -140,10 +96,8 @@ [new ProjectionExpression(sourceExpression, null!)], var source = new SourceExpression(sourceExpression, sourceAlias, withIn: true); - return new SelectExpression + return new SelectExpression(source, projection) { - _sources = { source }, - _projectionMapping = { [new ProjectionMember()] = projection }, UsesSingleValueProjection = true }; } @@ -498,16 +452,29 @@ public virtual void ReverseOrderings() /// 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. /// - public virtual Expression AddJoin(ShapedQueryExpression inner, Expression outerShaper) + public virtual Expression AddJoin(ShapedQueryExpression inner, Expression outerShaper, CosmosAliasManager aliasManager) { var (innerSelect, innerShaper) = ((SelectExpression)inner.QueryExpression, inner.ShaperExpression); - // TODO: Proper alias management (#33894). // Create a new source (JOIN) for the server side of the query; if the inner query represents a bare array, unwrap it and // add the JOIN directly - var joinSource = inner.TryExtractArray(out var bareArray) && SourceExpression.IsCompatible(bareArray) - ? new SourceExpression(bareArray, "a", withIn: true) - : new SourceExpression(innerSelect, "a"); + SourceExpression? joinSource = null; + string sourceAlias; + if (inner.TryExtractArray(out var bareArray) && SourceExpression.IsCompatible(bareArray)) + { + sourceAlias = aliasManager.GenerateSourceAlias(bareArray); + + if (SourceExpression.IsCompatible(bareArray)) + { + joinSource = new SourceExpression(bareArray, sourceAlias, withIn: true); + } + } + else + { + sourceAlias = aliasManager.GenerateSourceAlias("join"); + } + + joinSource ??= new SourceExpression(innerSelect, sourceAlias); // Make the necessary modifications to the shaper side, projecting out a TransparentIdentifier (outer/inner) var transparentIdentifierType = TransparentIdentifierFactory.Create(outerShaper.Type, innerShaper.Type); @@ -658,7 +625,8 @@ protected override Expression VisitChildren(ExpressionVisitor visitor) Predicate = predicate, Offset = offset, Limit = limit, - IsDistinct = IsDistinct + IsDistinct = IsDistinct, + UsesSingleValueProjection = UsesSingleValueProjection }; return newSelectExpression; diff --git a/src/EFCore.Cosmos/Query/Internal/ISqlExpressionFactory.cs b/src/EFCore.Cosmos/Query/Internal/ISqlExpressionFactory.cs index abd4c69d034..6529ac44bea 100644 --- a/src/EFCore.Cosmos/Query/Internal/ISqlExpressionFactory.cs +++ b/src/EFCore.Cosmos/Query/Internal/ISqlExpressionFactory.cs @@ -304,28 +304,4 @@ SqlConditionalExpression Condition( /// doing so can result in application failures when updating to a new Entity Framework Core release. /// SqlConstantExpression Constant(object? value, CoreTypeMapping? typeMapping = null); - - /// - /// 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. - /// - SelectExpression Select(IEntityType entityType); - - /// - /// 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. - /// - SelectExpression Select(IEntityType entityType, string sql, Expression argument); - - /// - /// 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. - /// - SelectExpression ReadItem(IEntityType entityType, ReadItemInfo argument); } diff --git a/src/EFCore.Cosmos/Query/Internal/SqlExpressionFactory.cs b/src/EFCore.Cosmos/Query/Internal/SqlExpressionFactory.cs index a47640efdd1..953ca08c48d 100644 --- a/src/EFCore.Cosmos/Query/Internal/SqlExpressionFactory.cs +++ b/src/EFCore.Cosmos/Query/Internal/SqlExpressionFactory.cs @@ -629,70 +629,4 @@ public virtual InExpression In(SqlExpression item, SqlParameterExpression values /// public virtual SqlConstantExpression Constant(object? value, CoreTypeMapping? typeMapping = null) => new(Expression.Constant(value), typeMapping); - - /// - /// 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. - /// - public virtual SelectExpression Select(IEntityType entityType) - { - var selectExpression = new SelectExpression(entityType); - AddDiscriminator(selectExpression, entityType); - - return selectExpression; - } - - /// - /// 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. - /// - public virtual SelectExpression ReadItem(IEntityType entityType, ReadItemInfo readItemInfo) - { - var selectExpression = new SelectExpression(entityType, readItemInfo); - AddDiscriminator(selectExpression, entityType); - - return selectExpression; - } - - /// - /// 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. - /// - public virtual SelectExpression Select(IEntityType entityType, string sql, Expression argument) - => new(entityType, sql, argument); - - private void AddDiscriminator(SelectExpression selectExpression, IEntityType entityType) - { - var concreteEntityTypes = entityType.GetConcreteDerivedTypesInclusive().ToList(); - - if (concreteEntityTypes.Count == 1) - { - var concreteEntityType = concreteEntityTypes[0]; - var discriminatorProperty = concreteEntityType.FindDiscriminatorProperty(); - if (discriminatorProperty != null) - { - var discriminatorColumn = ((EntityProjectionExpression)selectExpression.GetMappedProjection(new ProjectionMember())) - .BindProperty(discriminatorProperty, clientEval: false); - - selectExpression.ApplyPredicate( - Equal((SqlExpression)discriminatorColumn, Constant(concreteEntityType.GetDiscriminatorValue()))); - } - } - else - { - var discriminatorProperty = concreteEntityTypes[0].FindDiscriminatorProperty(); - Check.DebugAssert(discriminatorProperty is not null, "Missing discriminator property in hierarchy"); - var discriminatorColumn = ((EntityProjectionExpression)selectExpression.GetMappedProjection(new ProjectionMember())) - .BindProperty(discriminatorProperty, clientEval: false); - - selectExpression.ApplyPredicate( - In((SqlExpression)discriminatorColumn, concreteEntityTypes.Select(et => Constant(et.GetDiscriminatorValue())).ToArray())); - } - } } diff --git a/src/EFCore.Relational/Query/RelationalQueryCompilationContextDependencies.cs b/src/EFCore.Relational/Query/RelationalQueryCompilationContextDependencies.cs index 51c49e52697..afecb979d90 100644 --- a/src/EFCore.Relational/Query/RelationalQueryCompilationContextDependencies.cs +++ b/src/EFCore.Relational/Query/RelationalQueryCompilationContextDependencies.cs @@ -51,7 +51,7 @@ public RelationalQueryCompilationContextDependencies(ISqlAliasManagerFactory sql => SqlAliasManagerFactory = sqlAliasManagerFactory; /// - /// The current context. + /// A manager for SQL aliases, capable of generate uniquified table aliases. /// public ISqlAliasManagerFactory SqlAliasManagerFactory { get; init; } } diff --git a/src/EFCore.Relational/Query/SqlAliasManager.cs b/src/EFCore.Relational/Query/SqlAliasManager.cs index 977253c3974..4d1f247eea7 100644 --- a/src/EFCore.Relational/Query/SqlAliasManager.cs +++ b/src/EFCore.Relational/Query/SqlAliasManager.cs @@ -23,8 +23,7 @@ public class SqlAliasManager /// All aliases produced by a given instance of are unique. /// /// - /// A name (e.g. of a table) to use as the starting point for the aliasA base for the alias; a number postfix will be appended to it - /// as necessary. + /// A name (e.g. of a table) to use as the starting point for the alias; a number postfix will be appended to it as necessary. /// /// A fully unique alias within the context of this translation process. public virtual string GenerateTableAlias(string name) @@ -95,8 +94,8 @@ public virtual Expression PostprocessAliases(Expression expression) protected virtual Dictionary? RemapTableAliases(IReadOnlySet usedAliases) { // Aliases consist of a single character, followed by a counter for uniquification. - // We construct process the collected aliases above into a bitmap that represents, for each alias char, which numbers have been - // seen. Note that since a0 is the 2nd uniquified alias (a is the first), the bits are off-by-one, with position 0 representing + // We process the collected aliases above into a bitmap that represents, for each alias char, which numbers have been seen. + // Note that since a0 is the 2nd uniquified alias (a is the first), the bits are off-by-one, with position 0 representing // a, position 1 representing a0, and so on. Dictionary aliasBitmaps = new(); @@ -169,10 +168,6 @@ protected override Expression VisitExtension(Expression node) case ShapedQueryExpression shapedQuery: return shapedQuery.UpdateQueryExpression(Visit(shapedQuery.QueryExpression)); - case ColumnExpression { TableAlias: var alias }: - _tableAliases.Add(alias); - return base.VisitExtension(node); - case TableExpressionBase { Alias: string alias }: _tableAliases.Add(alias); return base.VisitExtension(node); @@ -189,28 +184,19 @@ internal static Expression Rewrite(Expression expression, IReadOnlyDictionary new TableAliasRewriter(aliasRewritingMap).Visit(expression); protected override Expression VisitExtension(Expression node) - { - switch (node) + => node switch { - case ShapedQueryExpression shapedQuery: - return shapedQuery.UpdateQueryExpression(Visit(shapedQuery.QueryExpression)); + ShapedQueryExpression shapedQuery => shapedQuery.UpdateQueryExpression(Visit(shapedQuery.QueryExpression)), // Note that this skips joins (which wrap the table that has the actual alias), as well as the top-level select - case TableExpressionBase { Alias: string alias } table: - if (aliasRewritingMap.TryGetValue(alias, out var newAlias)) - { - table = table.WithAlias(newAlias); - } - - return base.VisitExtension(table); + TableExpressionBase { Alias: string alias } table when aliasRewritingMap.TryGetValue(alias, out var newAlias) + => base.VisitExtension(table.WithAlias(newAlias)), - case ColumnExpression column when aliasRewritingMap.TryGetValue(column.TableAlias, out var newTableAlias): - return new ColumnExpression(column.Name, newTableAlias, column.Type, column.TypeMapping, column.IsNullable); + ColumnExpression column when aliasRewritingMap.TryGetValue(column.TableAlias, out var newTableAlias) + => new ColumnExpression(column.Name, newTableAlias, column.Type, column.TypeMapping, column.IsNullable), - default: - return base.VisitExtension(node); - } - } + _ => base.VisitExtension(node) + }; } private sealed class MutableInt diff --git a/test/EFCore.Cosmos.FunctionalTests/Query/FromSqlQueryCosmosTest.cs b/test/EFCore.Cosmos.FunctionalTests/Query/FromSqlQueryCosmosTest.cs index 7aba1c99fbe..8f5be39c0bc 100644 --- a/test/EFCore.Cosmos.FunctionalTests/Query/FromSqlQueryCosmosTest.cs +++ b/test/EFCore.Cosmos.FunctionalTests/Query/FromSqlQueryCosmosTest.cs @@ -40,10 +40,10 @@ public Task FromSqlRaw_queryable_simple(bool async) AssertSql( """ -SELECT c +SELECT s FROM ( SELECT * FROM root c WHERE c["Discriminator"] = "Customer" AND c["ContactName"] LIKE '%z%' -) c +) s """); }); @@ -83,10 +83,10 @@ public Task FromSqlRaw_queryable_simple_columns_out_of_order(bool async) AssertSql( """ -SELECT c +SELECT s FROM ( SELECT c["id"], c["Discriminator"], c["Region"], c["PostalCode"], c["Phone"], c["Fax"], c["CustomerID"], c["Country"], c["ContactTitle"], c["ContactName"], c["CompanyName"], c["City"], c["Address"] FROM root c WHERE c["Discriminator"] = "Customer" -) c +) s """); }); @@ -111,10 +111,10 @@ public Task FromSqlRaw_queryable_simple_columns_out_of_order_and_extra_columns(b AssertSql( """ -SELECT c +SELECT s FROM ( SELECT c["id"], c["Discriminator"], c["Region"], c["PostalCode"], c["PostalCode"] AS Foo, c["Phone"], c["Fax"], c["CustomerID"], c["Country"], c["ContactTitle"], c["ContactName"], c["CompanyName"], c["City"], c["Address"] FROM root c WHERE c["Discriminator"] = "Customer" -) c +) s """); }); @@ -141,11 +141,11 @@ public Task FromSqlRaw_queryable_composed(bool async) AssertSql( """ -SELECT c +SELECT s FROM ( SELECT * FROM root c WHERE c["Discriminator"] = "Customer" -) c -WHERE CONTAINS(c["ContactName"], "z") +) s +WHERE CONTAINS(s["ContactName"], "z") """); }); @@ -167,17 +167,20 @@ public virtual Task FromSqlRaw_queryable_composed_after_removing_whitespaces(boo Assert.Equal(14, actual.Length); AssertSql( - @"SELECT c + """ +SELECT s FROM ( - " - + @" + +""" + " " + """ + SELECT - * FROM root c WHERE c[""Discriminator""] = ""Customer"" -) c -WHERE CONTAINS(c[""ContactName""], ""z"")"); + * FROM root c WHERE c["Discriminator"] = "Customer" +) s +WHERE CONTAINS(s["ContactName"], "z") +"""); }); [ConditionalTheory] @@ -219,11 +222,11 @@ public Task FromSqlRaw_queryable_composed_compiled(bool async) AssertSql( """ -SELECT c +SELECT s FROM ( SELECT * FROM root c WHERE c["Discriminator"] = "Customer" -) c -WHERE CONTAINS(c["ContactName"], "z") +) s +WHERE CONTAINS(s["ContactName"], "z") """); }); @@ -264,11 +267,11 @@ public virtual Task FromSqlRaw_queryable_composed_compiled_with_parameter(bool a AssertSql( """ -SELECT c +SELECT s FROM ( SELECT * FROM root c WHERE c["Discriminator"] = "Customer" AND c["CustomerID"] = "CONSH" -) c -WHERE CONTAINS(c["ContactName"], "z") +) s +WHERE CONTAINS(s["ContactName"], "z") """); }); @@ -295,12 +298,12 @@ FROM root c AssertSql( """ -SELECT c +SELECT s FROM ( SELECT * FROM root c WHERE c["Discriminator"] = "Customer" AND c["City"] = 'London' -) c +) s """); }); @@ -328,13 +331,13 @@ FROM root c AssertSql( """ -SELECT c +SELECT s FROM ( SELECT * FROM root c WHERE c["Discriminator"] = "Customer" -) c -WHERE (c["City"] = "London") +) s +WHERE (s["City"] = "London") """); }); @@ -366,10 +369,10 @@ public Task FromSqlRaw_queryable_with_parameters(bool async) @p0='London' @p1='Sales Representative' -SELECT c +SELECT s FROM ( SELECT * FROM root c WHERE c["Discriminator"] = "Customer" AND c["City"] = @p0 AND c["ContactTitle"] = @p1 -) c +) s """); }); @@ -398,10 +401,10 @@ public Task FromSqlRaw_queryable_with_parameters_inline(bool async) @p0='London' @p1='Sales Representative' -SELECT c +SELECT s FROM ( SELECT * FROM root c WHERE c["Discriminator"] = "Customer" AND c["City"] = @p0 AND c["ContactTitle"] = @p1 -) c +) s """); }); @@ -428,10 +431,10 @@ public Task FromSqlRaw_queryable_with_null_parameter(bool async) """ @p0=null -SELECT c +SELECT s FROM ( SELECT * FROM root c WHERE c["Discriminator"] = "Employee" AND c["ReportsTo"] = @p0 OR (IS_NULL(c["ReportsTo"]) AND IS_NULL(@p0)) -) c +) s """); }); @@ -463,11 +466,11 @@ public Task FromSqlRaw_queryable_with_parameters_and_closure(bool async) @p0='London' @__contactTitle_1='Sales Representative' -SELECT c +SELECT s FROM ( SELECT * FROM root c WHERE c["Discriminator"] = "Customer" AND c["City"] = @p0 -) c -WHERE (c["ContactTitle"] = @__contactTitle_1) +) s +WHERE (s["ContactTitle"] = @__contactTitle_1) """); }); @@ -500,17 +503,17 @@ public virtual Task FromSqlRaw_queryable_simple_cache_key_includes_query_string( AssertSql( """ -SELECT c +SELECT s FROM ( SELECT * FROM root c WHERE c["Discriminator"] = "Customer" AND c["City"] = 'London' -) c +) s """, // """ -SELECT c +SELECT s FROM ( SELECT * FROM root c WHERE c["Discriminator"] = "Customer" AND c["City"] = 'Seattle' -) c +) s """); }); @@ -554,20 +557,20 @@ public virtual Task FromSqlRaw_queryable_with_parameters_cache_key_includes_para @p0='London' @p1='Sales Representative' -SELECT c +SELECT s FROM ( SELECT * FROM root c WHERE c["Discriminator"] = "Customer" AND c["City"] = @p0 AND c["ContactTitle"] = @p1 -) c +) s """, // """ @p0='Madrid' @p1='Accounting Manager' -SELECT c +SELECT s FROM ( SELECT * FROM root c WHERE c["Discriminator"] = "Customer" AND c["City"] = @p0 AND c["ContactTitle"] = @p1 -) c +) s """); }); @@ -592,10 +595,10 @@ public virtual Task FromSqlRaw_queryable_simple_as_no_tracking_not_composed(bool AssertSql( """ -SELECT c +SELECT s FROM ( SELECT * FROM root c WHERE c["Discriminator"] = "Customer" -) c +) s """); }); @@ -622,12 +625,12 @@ FROM root c AssertSql( """ -SELECT c["ProductName"] +SELECT s["ProductName"] FROM ( SELECT * FROM root c WHERE c["Discriminator"] = "Product" AND NOT c["Discontinued"] AND ((c["UnitsInStock"] + c["UnitsOnOrder"]) < c["ReorderLevel"]) -) c +) s """); }); @@ -651,11 +654,11 @@ public virtual Task FromSqlRaw_composed_with_nullable_predicate(bool async) AssertSql( """ -SELECT c +SELECT s FROM ( SELECT * FROM root c WHERE c["Discriminator"] = "Customer" -) c -WHERE (c["ContactName"] = c["CompanyName"]) +) s +WHERE (s["ContactName"] = s["CompanyName"]) """); }); @@ -701,10 +704,10 @@ public virtual Task FromSqlRaw_queryable_simple_projection_not_composed(bool asy AssertSql( """ -SELECT c["CustomerID"], c["City"] +SELECT s["CustomerID"], s["City"] FROM ( SELECT * FROM root c WHERE c["Discriminator"] = "Customer" -) c +) s """); }); @@ -745,10 +748,10 @@ await AssertQuery( @p0='London' @p1='Sales Representative' -SELECT c +SELECT s FROM ( SELECT * FROM root c WHERE c["City"] = @p0 AND c["ContactTitle"] = @p1 -) c +) s """); }); @@ -769,10 +772,10 @@ await AssertQuery( @p0='London' @p1='Sales Representative' -SELECT c +SELECT s FROM ( SELECT * FROM root c WHERE c["City"] = @p0 AND c["ContactTitle"] = @p1 -) c +) s """); }); diff --git a/test/EFCore.Cosmos.FunctionalTests/Query/NonSharedPrimitiveCollectionsQueryCosmosTest.cs b/test/EFCore.Cosmos.FunctionalTests/Query/NonSharedPrimitiveCollectionsQueryCosmosTest.cs index c2396f72b87..4b6929c2649 100644 --- a/test/EFCore.Cosmos.FunctionalTests/Query/NonSharedPrimitiveCollectionsQueryCosmosTest.cs +++ b/test/EFCore.Cosmos.FunctionalTests/Query/NonSharedPrimitiveCollectionsQueryCosmosTest.cs @@ -17,8 +17,8 @@ SELECT c FROM root c WHERE ((c["Discriminator"] = "TestEntity") AND (( SELECT VALUE COUNT(1) - FROM i IN c["SomeArray"] - WHERE (i = "a")) = 2)) + FROM s IN c["SomeArray"] + WHERE (s = "a")) = 2)) OFFSET 0 LIMIT 2 """); } @@ -33,8 +33,8 @@ SELECT c FROM root c WHERE ((c["Discriminator"] = "TestEntity") AND (( SELECT VALUE COUNT(1) - FROM i IN c["SomeArray"] - WHERE (i = 1)) = 2)) + FROM s IN c["SomeArray"] + WHERE (s = 1)) = 2)) OFFSET 0 LIMIT 2 """); } @@ -49,8 +49,8 @@ SELECT c FROM root c WHERE ((c["Discriminator"] = "TestEntity") AND (( SELECT VALUE COUNT(1) - FROM i IN c["SomeArray"] - WHERE (i = 1)) = 2)) + FROM s IN c["SomeArray"] + WHERE (s = 1)) = 2)) OFFSET 0 LIMIT 2 """); } @@ -65,8 +65,8 @@ SELECT c FROM root c WHERE ((c["Discriminator"] = "TestEntity") AND (( SELECT VALUE COUNT(1) - FROM i IN c["SomeArray"] - WHERE (i = 1)) = 2)) + FROM s IN c["SomeArray"] + WHERE (s = 1)) = 2)) OFFSET 0 LIMIT 2 """); } @@ -89,8 +89,8 @@ SELECT c FROM root c WHERE ((c["Discriminator"] = "TestEntity") AND (( SELECT VALUE COUNT(1) - FROM i IN c["SomeArray"] - WHERE (i = 1.0)) = 2)) + FROM s IN c["SomeArray"] + WHERE (s = 1.0)) = 2)) OFFSET 0 LIMIT 2 """); } @@ -105,8 +105,8 @@ SELECT c FROM root c WHERE ((c["Discriminator"] = "TestEntity") AND (( SELECT VALUE COUNT(1) - FROM i IN c["SomeArray"] - WHERE (i = 1.0)) = 2)) + FROM s IN c["SomeArray"] + WHERE (s = 1.0)) = 2)) OFFSET 0 LIMIT 2 """); } @@ -121,8 +121,8 @@ SELECT c FROM root c WHERE ((c["Discriminator"] = "TestEntity") AND (( SELECT VALUE COUNT(1) - FROM i IN c["SomeArray"] - WHERE (i = 1.0)) = 2)) + FROM s IN c["SomeArray"] + WHERE (s = 1.0)) = 2)) OFFSET 0 LIMIT 2 """); } @@ -137,8 +137,8 @@ SELECT c FROM root c WHERE ((c["Discriminator"] = "TestEntity") AND (( SELECT VALUE COUNT(1) - FROM i IN c["SomeArray"] - WHERE (i = "2023-01-01T12:30:00")) = 2)) + FROM s IN c["SomeArray"] + WHERE (s = "2023-01-01T12:30:00")) = 2)) OFFSET 0 LIMIT 2 """); } @@ -153,8 +153,8 @@ SELECT c FROM root c WHERE ((c["Discriminator"] = "TestEntity") AND (( SELECT VALUE COUNT(1) - FROM i IN c["SomeArray"] - WHERE (i = "2023-01-01T12:30:00.123")) = 2)) + FROM s IN c["SomeArray"] + WHERE (s = "2023-01-01T12:30:00.123")) = 2)) OFFSET 0 LIMIT 2 """); } @@ -169,8 +169,8 @@ SELECT c FROM root c WHERE ((c["Discriminator"] = "TestEntity") AND (( SELECT VALUE COUNT(1) - FROM i IN c["SomeArray"] - WHERE (i = "2023-01-01T12:30:00.123456")) = 2)) + FROM s IN c["SomeArray"] + WHERE (s = "2023-01-01T12:30:00.123456")) = 2)) OFFSET 0 LIMIT 2 """); } @@ -185,8 +185,8 @@ SELECT c FROM root c WHERE ((c["Discriminator"] = "TestEntity") AND (( SELECT VALUE COUNT(1) - FROM i IN c["SomeArray"] - WHERE (i = "2023-01-01")) = 2)) + FROM s IN c["SomeArray"] + WHERE (s = "2023-01-01")) = 2)) OFFSET 0 LIMIT 2 """); } @@ -201,8 +201,8 @@ SELECT c FROM root c WHERE ((c["Discriminator"] = "TestEntity") AND (( SELECT VALUE COUNT(1) - FROM i IN c["SomeArray"] - WHERE (i = "12:30:00")) = 2)) + FROM s IN c["SomeArray"] + WHERE (s = "12:30:00")) = 2)) OFFSET 0 LIMIT 2 """); } @@ -217,8 +217,8 @@ SELECT c FROM root c WHERE ((c["Discriminator"] = "TestEntity") AND (( SELECT VALUE COUNT(1) - FROM i IN c["SomeArray"] - WHERE (i = "12:30:00.123")) = 2)) + FROM s IN c["SomeArray"] + WHERE (s = "12:30:00.123")) = 2)) OFFSET 0 LIMIT 2 """); } @@ -233,8 +233,8 @@ SELECT c FROM root c WHERE ((c["Discriminator"] = "TestEntity") AND (( SELECT VALUE COUNT(1) - FROM i IN c["SomeArray"] - WHERE (i = "12:30:00.123456")) = 2)) + FROM s IN c["SomeArray"] + WHERE (s = "12:30:00.123456")) = 2)) OFFSET 0 LIMIT 2 """); } @@ -249,8 +249,8 @@ SELECT c FROM root c WHERE ((c["Discriminator"] = "TestEntity") AND (( SELECT VALUE COUNT(1) - FROM i IN c["SomeArray"] - WHERE (i = "2023-01-01T12:30:00+02:00")) = 2)) + FROM s IN c["SomeArray"] + WHERE (s = "2023-01-01T12:30:00+02:00")) = 2)) OFFSET 0 LIMIT 2 """); } @@ -265,8 +265,8 @@ SELECT c FROM root c WHERE ((c["Discriminator"] = "TestEntity") AND (( SELECT VALUE COUNT(1) - FROM i IN c["SomeArray"] - WHERE (i = true)) = 2)) + FROM s IN c["SomeArray"] + WHERE (s = true)) = 2)) OFFSET 0 LIMIT 2 """); } @@ -281,8 +281,8 @@ SELECT c FROM root c WHERE ((c["Discriminator"] = "TestEntity") AND (( SELECT VALUE COUNT(1) - FROM i IN c["SomeArray"] - WHERE (i = "dc8c903d-d655-4144-a0fd-358099d40ae1")) = 2)) + FROM s IN c["SomeArray"] + WHERE (s = "dc8c903d-d655-4144-a0fd-358099d40ae1")) = 2)) OFFSET 0 LIMIT 2 """); } @@ -292,13 +292,14 @@ public override async Task Array_of_byte_array() // TODO await Assert.ThrowsAsync(() => base.Array_of_byte_array()); - AssertSql(""" + AssertSql( + """ SELECT c FROM root c WHERE ((c["Discriminator"] = "TestEntity") AND (( SELECT VALUE COUNT(1) - FROM i IN c["SomeArray"] - WHERE (i = "AQI=")) = 2)) + FROM s IN c["SomeArray"] + WHERE (s = "AQI=")) = 2)) OFFSET 0 LIMIT 2 """); } @@ -313,8 +314,8 @@ SELECT c FROM root c WHERE ((c["Discriminator"] = "TestEntity") AND (( SELECT VALUE COUNT(1) - FROM i IN c["SomeArray"] - WHERE (i = 0)) = 2)) + FROM s IN c["SomeArray"] + WHERE (s = 0)) = 2)) OFFSET 0 LIMIT 2 """); } @@ -365,8 +366,8 @@ SELECT c FROM root c WHERE ((c["Discriminator"] = "TestEntity") AND (( SELECT VALUE COUNT(1) - FROM i IN (SELECT VALUE [1, 2, 3]) - WHERE (i > c["Id"])) = 1)) + FROM a IN (SELECT VALUE [1, 2, 3]) + WHERE (a > c["Id"])) = 1)) OFFSET 0 LIMIT 2 """); } diff --git a/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindAggregateOperatorsQueryCosmosTest.cs b/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindAggregateOperatorsQueryCosmosTest.cs index 044e501f7ff..e29f1608e15 100644 --- a/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindAggregateOperatorsQueryCosmosTest.cs +++ b/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindAggregateOperatorsQueryCosmosTest.cs @@ -1659,8 +1659,8 @@ SELECT c FROM root c WHERE ((c["Discriminator"] = "Customer") AND EXISTS ( SELECT 1 - FROM i IN (SELECT VALUE ["ABCDE", "ALFKI"]) - WHERE ((i != null) AND (i = c["CustomerID"])))) + FROM a IN (SELECT VALUE ["ABCDE", "ALFKI"]) + WHERE ((a != null) AND (a = c["CustomerID"])))) """); }); @@ -1678,8 +1678,8 @@ SELECT c FROM root c WHERE ((c["Discriminator"] = "Customer") AND EXISTS ( SELECT 1 - FROM i IN (SELECT VALUE @__p_0) - WHERE ((i != null) AND (i = c["CustomerID"])))) + FROM p IN (SELECT VALUE @__p_0) + WHERE ((p != null) AND (p = c["CustomerID"])))) """, // """ @@ -1689,8 +1689,8 @@ SELECT c FROM root c WHERE ((c["Discriminator"] = "Customer") AND EXISTS ( SELECT 1 - FROM i IN (SELECT VALUE @__p_0) - WHERE ((i != null) AND (i = c["CustomerID"])))) + FROM p IN (SELECT VALUE @__p_0) + WHERE ((p != null) AND (p = c["CustomerID"])))) """); }); @@ -1761,8 +1761,7 @@ public override Task Contains_with_local_ordered_enumerable_inline(bool async) SELECT c FROM root c WHERE ((c["Discriminator"] = "Customer") AND c["CustomerID"] IN ("ABCDE", "ALFKI")) -""" - ); +"""); }); public override Task Contains_with_local_ordered_enumerable_inline_closure_mix(bool async) @@ -1810,8 +1809,7 @@ FROM root c SELECT c FROM root c WHERE ((c["Discriminator"] = "Customer") AND ARRAY_CONTAINS(@__ids_0, c["CustomerID"])) -""" - ); +"""); }); public override Task Contains_with_local_object_read_only_collection_closure(bool async) @@ -1827,8 +1825,7 @@ public override Task Contains_with_local_object_read_only_collection_closure(boo SELECT c FROM root c WHERE ((c["Discriminator"] = "Customer") AND ARRAY_CONTAINS(@__ids_0, c["CustomerID"])) -""" - ); +"""); }); public override Task Contains_with_local_ordered_read_only_collection_all_null(bool async) @@ -1858,8 +1855,7 @@ public override Task Contains_with_local_read_only_collection_inline(bool async) SELECT c FROM root c WHERE ((c["Discriminator"] = "Customer") AND c["CustomerID"] IN ("ABCDE", "ALFKI")) -""" - ); +"""); }); public override Task Contains_with_local_read_only_collection_inline_closure_mix(bool async) @@ -1876,15 +1872,14 @@ SELECT c FROM root c WHERE ((c["Discriminator"] = "Customer") AND ARRAY_CONTAINS(@__AsReadOnly_0, c["CustomerID"])) """, - // - """ + // + """ @__AsReadOnly_0='["ABCDE","ANATR"]' SELECT c FROM root c WHERE ((c["Discriminator"] = "Customer") AND ARRAY_CONTAINS(@__AsReadOnly_0, c["CustomerID"])) -""" - ); +"""); }); public override Task Contains_with_local_collection_false(bool async) diff --git a/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindFunctionsQueryCosmosTest.cs b/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindFunctionsQueryCosmosTest.cs index ff9ad39f9ea..8b94e8ddd97 100644 --- a/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindFunctionsQueryCosmosTest.cs +++ b/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindFunctionsQueryCosmosTest.cs @@ -2020,7 +2020,7 @@ public override Task String_Contains_negated_in_predicate(bool async) await base.String_Contains_negated_in_predicate(a); AssertSql( -""" + """ SELECT c FROM root c WHERE ((c["Discriminator"] = "Customer") AND NOT(CONTAINS(c["CompanyName"], c["ContactName"]))) diff --git a/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindWhereQueryCosmosTest.cs b/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindWhereQueryCosmosTest.cs index f9db421c40e..d48b83e5b54 100644 --- a/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindWhereQueryCosmosTest.cs +++ b/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindWhereQueryCosmosTest.cs @@ -495,15 +495,6 @@ SELECT c FROM root c WHERE ((c["Discriminator"] = "Customer") AND (c["City"] = @__city_0)) """); - - Assert.Equal( - """ --- @__city_0='London' -SELECT c -FROM root c -WHERE ((c["Discriminator"] = "Customer") AND (c["City"] = @__city_0)) -""", queryString, ignoreLineEndingDifferences: true, - ignoreWhiteSpaceDifferences: true); }); return null; diff --git a/test/EFCore.Cosmos.FunctionalTests/Query/OwnedQueryCosmosTest.cs b/test/EFCore.Cosmos.FunctionalTests/Query/OwnedQueryCosmosTest.cs index befaeddd1ba..f5537bf5faa 100644 --- a/test/EFCore.Cosmos.FunctionalTests/Query/OwnedQueryCosmosTest.cs +++ b/test/EFCore.Cosmos.FunctionalTests/Query/OwnedQueryCosmosTest.cs @@ -57,9 +57,9 @@ public override async Task Navigation_rewrite_on_owned_collection_with_compositi AssertSql( """ SELECT (ARRAY( - SELECT VALUE (t["Id"] != 42) - FROM t IN c["Orders"] - ORDER BY t["Id"])[0] ?? false) AS c + SELECT VALUE (o["Id"] != 42) + FROM o IN c["Orders"] + ORDER BY o["Id"])[0] ?? false) AS c FROM root c WHERE c["Discriminator"] IN ("OwnedPerson", "Branch", "LeafB", "LeafA") ORDER BY c["Id"] @@ -247,9 +247,9 @@ public override Task SelectMany_on_owned_collection(bool async) AssertSql( """ -SELECT a +SELECT o AS o0 FROM root c -JOIN a IN c["Orders"] +JOIN o IN c["Orders"] WHERE c["Discriminator"] IN ("OwnedPerson", "Branch", "LeafB", "LeafA") """); }); @@ -267,10 +267,10 @@ public override Task SelectMany_with_result_selector(bool async) SELECT VALUE { "PersonId" : c["Id"], - "OrderId" : a["Id"] + "OrderId" : o["Id"] } FROM root c -JOIN a IN c["Orders"] +JOIN o IN c["Orders"] WHERE c["Discriminator"] IN ("OwnedPerson", "Branch", "LeafB", "LeafA") """); }); @@ -388,10 +388,10 @@ await AssertQuery( // TODO: The following should project out a["Details"], not a: #34067 AssertSql( """ -SELECT a["Details"] +SELECT o["Details"] FROM root c -JOIN a IN c["Orders"] -WHERE (c["Discriminator"] IN ("OwnedPerson", "Branch", "LeafB", "LeafA") AND (ARRAY_LENGTH(a["Details"]) = 1)) +JOIN o IN c["Orders"] +WHERE (c["Discriminator"] IN ("OwnedPerson", "Branch", "LeafB", "LeafA") AND (ARRAY_LENGTH(o["Details"]) = 1)) ORDER BY c["Id"] """); }); @@ -415,10 +415,10 @@ await AssertQuery( AssertSql( """ -SELECT a +SELECT o AS o0 FROM root c -JOIN a IN c["Orders"] -WHERE (c["Discriminator"] IN ("OwnedPerson", "Branch", "LeafB", "LeafA") AND (ARRAY_LENGTH(a["Details"]) = 1)) +JOIN o IN c["Orders"] +WHERE (c["Discriminator"] IN ("OwnedPerson", "Branch", "LeafB", "LeafA") AND (ARRAY_LENGTH(o["Details"]) = 1)) ORDER BY c["Id"] """); }); @@ -442,10 +442,10 @@ await AssertQuery( AssertSql( """ -SELECT a +SELECT o AS o0 FROM root c -JOIN a IN c["Orders"] -WHERE (c["Discriminator"] IN ("OwnedPerson", "Branch", "LeafB", "LeafA") AND (ARRAY_LENGTH(a["Details"]) = 1)) +JOIN o IN c["Orders"] +WHERE (c["Discriminator"] IN ("OwnedPerson", "Branch", "LeafB", "LeafA") AND (ARRAY_LENGTH(o["Details"]) = 1)) ORDER BY c["Id"] """); }); @@ -469,10 +469,10 @@ await AssertQuery( AssertSql( """ -SELECT a["Details"] +SELECT o["Details"] FROM root c -JOIN a IN c["Orders"] -WHERE (c["Discriminator"] IN ("OwnedPerson", "Branch", "LeafB", "LeafA") AND (ARRAY_LENGTH(a["Details"]) = 1)) +JOIN o IN c["Orders"] +WHERE (c["Discriminator"] IN ("OwnedPerson", "Branch", "LeafB", "LeafA") AND (ARRAY_LENGTH(o["Details"]) = 1)) ORDER BY c["Id"] """); }); @@ -496,10 +496,10 @@ await AssertQuery( AssertSql( """ -SELECT a +SELECT o AS o0 FROM root c -JOIN a IN c["Orders"] -WHERE (c["Discriminator"] IN ("OwnedPerson", "Branch", "LeafB", "LeafA") AND (ARRAY_LENGTH(a["Details"]) = 1)) +JOIN o IN c["Orders"] +WHERE (c["Discriminator"] IN ("OwnedPerson", "Branch", "LeafB", "LeafA") AND (ARRAY_LENGTH(o["Details"]) = 1)) ORDER BY c["Id"] """); }); @@ -741,8 +741,8 @@ SELECT c["Name"] FROM root c WHERE (c["Discriminator"] IN ("OwnedPerson", "Branch", "LeafB", "LeafA") AND (( SELECT VALUE COUNT(1) - FROM t IN c["Orders"] - WHERE (DateTimePart("yyyy", t["OrderDate"]) = 2018)) = 1)) + FROM o IN c["Orders"] + WHERE (DateTimePart("yyyy", o["OrderDate"]) = 2018)) = 1)) """); }); @@ -806,10 +806,10 @@ OFFSET 0 LIMIT 2 """ @__p_0='1' -SELECT a +SELECT o AS o0 FROM root c -JOIN a IN c["Orders"] -WHERE (c["Discriminator"] IN ("OwnedPerson", "Branch", "LeafB", "LeafA") AND (a["ClientId"] = @__p_0)) +JOIN o IN c["Orders"] +WHERE (c["Discriminator"] IN ("OwnedPerson", "Branch", "LeafB", "LeafA") AND (o["ClientId"] = @__p_0)) """); }); @@ -1091,8 +1091,8 @@ SELECT c FROM root c WHERE (c["Discriminator"] IN ("OwnedPerson", "Branch", "LeafB", "LeafA") AND EXISTS ( SELECT 1 - FROM t IN c["Orders"] - WHERE (t["Id"] = -30))) + FROM o IN c["Orders"] + WHERE (o["Id"] = -30))) """); }); @@ -1110,8 +1110,8 @@ SELECT c FROM root c WHERE (c["Discriminator"] IN ("OwnedPerson", "Branch", "LeafB", "LeafA") AND EXISTS ( SELECT 1 - FROM t IN c["Orders"] - WHERE (t["Id"] = -30))) + FROM o IN c["Orders"] + WHERE (o["Id"] = -30))) """); }); @@ -1163,9 +1163,9 @@ public override async Task OrderBy_ElementAt_over_owned_collection(bool async) SELECT c FROM root c WHERE (c["Discriminator"] IN ("OwnedPerson", "Branch", "LeafB", "LeafA") AND (ARRAY( - SELECT VALUE t["Id"] - FROM t IN c["Orders"] - ORDER BY t["Id"])[1] = -10)) + SELECT VALUE o["Id"] + FROM o IN c["Orders"] + ORDER BY o["Id"])[1] = -10)) """); } } @@ -1199,9 +1199,9 @@ public override Task FirstOrDefault_over_owned_collection(bool async) SELECT c FROM root c WHERE (c["Discriminator"] IN ("OwnedPerson", "Branch", "LeafB", "LeafA") AND (DateTimePart("yyyy", (ARRAY( - SELECT VALUE t["OrderDate"] - FROM t IN c["Orders"] - WHERE (t["Id"] > -20))[0] ?? "0001-01-01T00:00:00")) = 2018)) + SELECT VALUE o["OrderDate"] + FROM o IN c["Orders"] + WHERE (o["Id"] > -20))[0] ?? "0001-01-01T00:00:00")) = 2018)) """); }); @@ -1232,12 +1232,12 @@ public override Task Union_over_owned_collection(bool async) SELECT c FROM root c WHERE (c["Discriminator"] IN ("OwnedPerson", "Branch", "LeafB", "LeafA") AND (ARRAY_LENGTH(SetUnion(ARRAY( - SELECT VALUE t - FROM t IN c["Orders"] - WHERE (t["Id"] = -10)), ARRAY( - SELECT VALUE t - FROM t IN c["Orders"] - WHERE (t["Id"] = -11)))) = 2)) + SELECT VALUE o + FROM o IN c["Orders"] + WHERE (o["Id"] = -10)), ARRAY( + SELECT VALUE o0 + FROM o0 IN c["Orders"] + WHERE (o0["Id"] = -11)))) = 2)) """); }); @@ -1265,6 +1265,8 @@ public override DbContextOptionsBuilder AddOptions(DbContextOptionsBuilder build protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext context) { + modelBuilder.HasDefaultContainer("OwnedQueryTest"); + modelBuilder.Entity( eb => { diff --git a/test/EFCore.Cosmos.FunctionalTests/Query/PrimitiveCollectionsQueryCosmosTest.cs b/test/EFCore.Cosmos.FunctionalTests/Query/PrimitiveCollectionsQueryCosmosTest.cs index 7f65fc88cd7..aa642d076b3 100644 --- a/test/EFCore.Cosmos.FunctionalTests/Query/PrimitiveCollectionsQueryCosmosTest.cs +++ b/test/EFCore.Cosmos.FunctionalTests/Query/PrimitiveCollectionsQueryCosmosTest.cs @@ -73,8 +73,8 @@ SELECT c FROM root c WHERE ((c["Discriminator"] = "PrimitiveCollectionsEntity") AND (( SELECT VALUE COUNT(1) - FROM i IN (SELECT VALUE []) - WHERE (i > c["Id"])) = 1)) + FROM a IN (SELECT VALUE []) + WHERE (a > c["Id"])) = 1)) """); }); @@ -90,8 +90,8 @@ SELECT c FROM root c WHERE ((c["Discriminator"] = "PrimitiveCollectionsEntity") AND (( SELECT VALUE COUNT(1) - FROM i IN (SELECT VALUE [2]) - WHERE (i > c["Id"])) = 1)) + FROM a IN (SELECT VALUE [2]) + WHERE (a > c["Id"])) = 1)) """); }); @@ -107,8 +107,8 @@ SELECT c FROM root c WHERE ((c["Discriminator"] = "PrimitiveCollectionsEntity") AND (( SELECT VALUE COUNT(1) - FROM i IN (SELECT VALUE [2, 999]) - WHERE (i > c["Id"])) = 1)) + FROM a IN (SELECT VALUE [2, 999]) + WHERE (a > c["Id"])) = 1)) """); }); @@ -124,8 +124,8 @@ SELECT c FROM root c WHERE ((c["Discriminator"] = "PrimitiveCollectionsEntity") AND (( SELECT VALUE COUNT(1) - FROM i IN (SELECT VALUE [2, 999, 1000]) - WHERE (i > c["Id"])) = 2)) + FROM a IN (SELECT VALUE [2, 999, 1000]) + WHERE (a > c["Id"])) = 2)) """); }); @@ -303,8 +303,8 @@ public override Task Inline_collection_Min_with_two_values(bool async) SELECT c FROM root c WHERE ((c["Discriminator"] = "PrimitiveCollectionsEntity") AND (( - SELECT VALUE MIN(i) - FROM i IN (SELECT VALUE [30, c["Int"]])) = 30)) + SELECT VALUE MIN(a) + FROM a IN (SELECT VALUE [30, c["Int"]])) = 30)) """); }); @@ -319,8 +319,8 @@ public override Task Inline_collection_List_Min_with_two_values(bool async) SELECT c FROM root c WHERE ((c["Discriminator"] = "PrimitiveCollectionsEntity") AND (( - SELECT VALUE MIN(i) - FROM i IN (SELECT VALUE [30, c["Int"]])) = 30)) + SELECT VALUE MIN(a) + FROM a IN (SELECT VALUE [30, c["Int"]])) = 30)) """); }); @@ -335,8 +335,8 @@ public override Task Inline_collection_Max_with_two_values(bool async) SELECT c FROM root c WHERE ((c["Discriminator"] = "PrimitiveCollectionsEntity") AND (( - SELECT VALUE MAX(i) - FROM i IN (SELECT VALUE [30, c["Int"]])) = 30)) + SELECT VALUE MAX(a) + FROM a IN (SELECT VALUE [30, c["Int"]])) = 30)) """); }); @@ -351,8 +351,8 @@ public override Task Inline_collection_List_Max_with_two_values(bool async) SELECT c FROM root c WHERE ((c["Discriminator"] = "PrimitiveCollectionsEntity") AND (( - SELECT VALUE MAX(i) - FROM i IN (SELECT VALUE [30, c["Int"]])) = 30)) + SELECT VALUE MAX(a) + FROM a IN (SELECT VALUE [30, c["Int"]])) = 30)) """); }); @@ -369,8 +369,8 @@ public override Task Inline_collection_Min_with_three_values(bool async) SELECT c FROM root c WHERE ((c["Discriminator"] = "PrimitiveCollectionsEntity") AND (( - SELECT VALUE MIN(i) - FROM i IN (SELECT VALUE [30, c["Int"], @__i_0])) = 25)) + SELECT VALUE MIN(a) + FROM a IN (SELECT VALUE [30, c["Int"], @__i_0])) = 25)) """); }); @@ -387,8 +387,8 @@ public override Task Inline_collection_List_Min_with_three_values(bool async) SELECT c FROM root c WHERE ((c["Discriminator"] = "PrimitiveCollectionsEntity") AND (( - SELECT VALUE MIN(i) - FROM i IN (SELECT VALUE [30, c["Int"], @__i_0])) = 25)) + SELECT VALUE MIN(a) + FROM a IN (SELECT VALUE [30, c["Int"], @__i_0])) = 25)) """); }); @@ -405,8 +405,8 @@ public override Task Inline_collection_Max_with_three_values(bool async) SELECT c FROM root c WHERE ((c["Discriminator"] = "PrimitiveCollectionsEntity") AND (( - SELECT VALUE MAX(i) - FROM i IN (SELECT VALUE [30, c["Int"], @__i_0])) = 35)) + SELECT VALUE MAX(a) + FROM a IN (SELECT VALUE [30, c["Int"], @__i_0])) = 35)) """); }); @@ -423,8 +423,8 @@ public override Task Inline_collection_List_Max_with_three_values(bool async) SELECT c FROM root c WHERE ((c["Discriminator"] = "PrimitiveCollectionsEntity") AND (( - SELECT VALUE MAX(i) - FROM i IN (SELECT VALUE [30, c["Int"], @__i_0])) = 35)) + SELECT VALUE MAX(a) + FROM a IN (SELECT VALUE [30, c["Int"], @__i_0])) = 35)) """); }); @@ -1307,9 +1307,9 @@ public override Task Column_collection_SelectMany(bool async) AssertSql( """ -SELECT a +SELECT i AS i0 FROM root c -JOIN a IN c["Ints"] +JOIN i IN c["Ints"] WHERE (c["Discriminator"] = "PrimitiveCollectionsEntity") """); }); @@ -1322,12 +1322,12 @@ public override Task Column_collection_SelectMany_with_filter(bool async) AssertSql( """ -SELECT a +SELECT j FROM root c JOIN ( SELECT VALUE i FROM i IN c["Ints"] - WHERE (i > 1)) a + WHERE (i > 1)) j WHERE (c["Discriminator"] = "PrimitiveCollectionsEntity") """); }); @@ -1656,9 +1656,9 @@ public override Task Project_collection_of_datetimes_filtered(bool async) AssertSql( """ SELECT ARRAY( - SELECT VALUE i - FROM i IN c["DateTimes"] - WHERE (DateTimePart("dd", i) != 1)) AS c + SELECT VALUE d + FROM d IN c["DateTimes"] + WHERE (DateTimePart("dd", d) != 1)) AS c FROM root c WHERE (c["Discriminator"] = "PrimitiveCollectionsEntity") ORDER BY c["Id"] @@ -1762,13 +1762,13 @@ public override Task Project_empty_collection_of_nullables_and_collection_only_c SELECT VALUE { "c" : ARRAY( - SELECT VALUE i - FROM i IN c["NullableInts"] + SELECT VALUE n + FROM n IN c["NullableInts"] WHERE false), "c0" : ARRAY( - SELECT VALUE i - FROM i IN c["NullableInts"] - WHERE (i = null)) + SELECT VALUE n0 + FROM n0 IN c["NullableInts"] + WHERE (n0 = null)) } FROM root c WHERE (c["Discriminator"] = "PrimitiveCollectionsEntity") @@ -1794,17 +1794,17 @@ SELECT VALUE SELECT VALUE i FROM i IN c["Ints"]), "c0" : ARRAY( - SELECT VALUE i - FROM i IN c["Ints"] - ORDER BY i DESC), + SELECT VALUE i0 + FROM i0 IN c["Ints"] + ORDER BY i0 DESC), "c1" : ARRAY( - SELECT VALUE i - FROM i IN c["DateTimes"] - WHERE (DateTimePart("dd", i) != 1)), + SELECT VALUE d + FROM d IN c["DateTimes"] + WHERE (DateTimePart("dd", d) != 1)), "c2" : ARRAY( - SELECT VALUE i - FROM i IN c["DateTimes"] - WHERE (i > "2000-01-01T00:00:00")) + SELECT VALUE d0 + FROM d0 IN c["DateTimes"] + WHERE (d0 > "2000-01-01T00:00:00")) } FROM root c WHERE (c["Discriminator"] = "PrimitiveCollectionsEntity")