diff --git a/src/Microsoft.AspNetCore.OData/Microsoft.AspNetCore.OData.xml b/src/Microsoft.AspNetCore.OData/Microsoft.AspNetCore.OData.xml index 2d2db69af..d227d0ce6 100644 --- a/src/Microsoft.AspNetCore.OData/Microsoft.AspNetCore.OData.xml +++ b/src/Microsoft.AspNetCore.OData/Microsoft.AspNetCore.OData.xml @@ -7639,6 +7639,11 @@ A value that corresponds to allowing the $apply query option. + + + A value that corresponds to allowing the $compute query option. + + A value that corresponds to the default query options supported. @@ -9704,6 +9709,11 @@ Gets the . + + + Gets the . + + Gets the . @@ -10343,6 +10353,42 @@ The that contains all the query application related settings. The new after the filter query has been applied to. + + + This defines a $compute OData query option for querying. + The $compute system query option allows clients to define computed properties that can be used in a $select or within a $filter or $orderby expression. + Computed properties SHOULD be included as dynamic properties in the result and MUST be included if $select is specified with the computed property name, or star (*). + + + + + Initialize a new instance of based on the raw $compute value and + an EdmModel from . + + The raw value for $filter query. It can be null or empty. + The which contains the and some type information + The which is used to parse the query option. + + + + Gets the given . + + + + + ClrType for result of transformations + + + + + Gets the parsed for this query option. + + + + + Gets the raw $compute value. + + Represents the value of the $count query option and exposes a way to retrieve the number of entities that satisfy a query. @@ -10546,6 +10592,11 @@ Gets the raw $apply query value from the incoming request Uri if exists. + + + Gets the raw $compute query value from the incoming request Uri if exists. + + Gets the raw $orderby query value from the incoming request Uri if exists. diff --git a/src/Microsoft.AspNetCore.OData/PublicAPI.Unshipped.txt b/src/Microsoft.AspNetCore.OData/PublicAPI.Unshipped.txt index e881e74cb..90c16045b 100644 --- a/src/Microsoft.AspNetCore.OData/PublicAPI.Unshipped.txt +++ b/src/Microsoft.AspNetCore.OData/PublicAPI.Unshipped.txt @@ -734,6 +734,7 @@ Microsoft.AspNetCore.OData.Query.AllowedLogicalOperators.Or = 1 -> Microsoft.Asp Microsoft.AspNetCore.OData.Query.AllowedQueryOptions Microsoft.AspNetCore.OData.Query.AllowedQueryOptions.All = Microsoft.AspNetCore.OData.Query.AllowedQueryOptions.DeltaToken | Microsoft.AspNetCore.OData.Query.AllowedQueryOptions.Supported -> Microsoft.AspNetCore.OData.Query.AllowedQueryOptions Microsoft.AspNetCore.OData.Query.AllowedQueryOptions.Apply = 1024 -> Microsoft.AspNetCore.OData.Query.AllowedQueryOptions +Microsoft.AspNetCore.OData.Query.AllowedQueryOptions.Compute = 2048 -> Microsoft.AspNetCore.OData.Query.AllowedQueryOptions Microsoft.AspNetCore.OData.Query.AllowedQueryOptions.Count = 64 -> Microsoft.AspNetCore.OData.Query.AllowedQueryOptions Microsoft.AspNetCore.OData.Query.AllowedQueryOptions.DeltaToken = 512 -> Microsoft.AspNetCore.OData.Query.AllowedQueryOptions Microsoft.AspNetCore.OData.Query.AllowedQueryOptions.Expand = 2 -> Microsoft.AspNetCore.OData.Query.AllowedQueryOptions @@ -744,7 +745,7 @@ Microsoft.AspNetCore.OData.Query.AllowedQueryOptions.OrderBy = 8 -> Microsoft.As Microsoft.AspNetCore.OData.Query.AllowedQueryOptions.Select = 4 -> Microsoft.AspNetCore.OData.Query.AllowedQueryOptions Microsoft.AspNetCore.OData.Query.AllowedQueryOptions.Skip = 32 -> Microsoft.AspNetCore.OData.Query.AllowedQueryOptions Microsoft.AspNetCore.OData.Query.AllowedQueryOptions.SkipToken = 256 -> Microsoft.AspNetCore.OData.Query.AllowedQueryOptions -Microsoft.AspNetCore.OData.Query.AllowedQueryOptions.Supported = Microsoft.AspNetCore.OData.Query.AllowedQueryOptions.Filter | Microsoft.AspNetCore.OData.Query.AllowedQueryOptions.Expand | Microsoft.AspNetCore.OData.Query.AllowedQueryOptions.Select | Microsoft.AspNetCore.OData.Query.AllowedQueryOptions.OrderBy | Microsoft.AspNetCore.OData.Query.AllowedQueryOptions.Top | Microsoft.AspNetCore.OData.Query.AllowedQueryOptions.Skip | Microsoft.AspNetCore.OData.Query.AllowedQueryOptions.Count | Microsoft.AspNetCore.OData.Query.AllowedQueryOptions.Format | Microsoft.AspNetCore.OData.Query.AllowedQueryOptions.SkipToken | Microsoft.AspNetCore.OData.Query.AllowedQueryOptions.Apply -> Microsoft.AspNetCore.OData.Query.AllowedQueryOptions +Microsoft.AspNetCore.OData.Query.AllowedQueryOptions.Supported = Microsoft.AspNetCore.OData.Query.AllowedQueryOptions.Filter | Microsoft.AspNetCore.OData.Query.AllowedQueryOptions.Expand | Microsoft.AspNetCore.OData.Query.AllowedQueryOptions.Select | Microsoft.AspNetCore.OData.Query.AllowedQueryOptions.OrderBy | Microsoft.AspNetCore.OData.Query.AllowedQueryOptions.Top | Microsoft.AspNetCore.OData.Query.AllowedQueryOptions.Skip | Microsoft.AspNetCore.OData.Query.AllowedQueryOptions.Count | Microsoft.AspNetCore.OData.Query.AllowedQueryOptions.Format | Microsoft.AspNetCore.OData.Query.AllowedQueryOptions.SkipToken | Microsoft.AspNetCore.OData.Query.AllowedQueryOptions.Apply | Microsoft.AspNetCore.OData.Query.AllowedQueryOptions.Compute -> Microsoft.AspNetCore.OData.Query.AllowedQueryOptions Microsoft.AspNetCore.OData.Query.AllowedQueryOptions.Top = 16 -> Microsoft.AspNetCore.OData.Query.AllowedQueryOptions Microsoft.AspNetCore.OData.Query.ApplyQueryOption Microsoft.AspNetCore.OData.Query.ApplyQueryOption.ApplyClause.get -> Microsoft.OData.UriParser.Aggregation.ApplyClause @@ -753,6 +754,12 @@ Microsoft.AspNetCore.OData.Query.ApplyQueryOption.ApplyTo(System.Linq.IQueryable Microsoft.AspNetCore.OData.Query.ApplyQueryOption.Context.get -> Microsoft.AspNetCore.OData.Query.ODataQueryContext Microsoft.AspNetCore.OData.Query.ApplyQueryOption.RawValue.get -> string Microsoft.AspNetCore.OData.Query.ApplyQueryOption.ResultClrType.get -> System.Type +Microsoft.AspNetCore.OData.Query.ComputeQueryOption +Microsoft.AspNetCore.OData.Query.ComputeQueryOption.ComputeClause.get -> Microsoft.OData.UriParser.ComputeClause +Microsoft.AspNetCore.OData.Query.ComputeQueryOption.ComputeQueryOption(string rawValue, Microsoft.AspNetCore.OData.Query.ODataQueryContext context, Microsoft.OData.UriParser.ODataQueryOptionParser queryOptionParser) -> void +Microsoft.AspNetCore.OData.Query.ComputeQueryOption.Context.get -> Microsoft.AspNetCore.OData.Query.ODataQueryContext +Microsoft.AspNetCore.OData.Query.ComputeQueryOption.RawValue.get -> string +Microsoft.AspNetCore.OData.Query.ComputeQueryOption.ResultClrType.get -> System.Type Microsoft.AspNetCore.OData.Query.Container.IPropertyMapper Microsoft.AspNetCore.OData.Query.Container.IPropertyMapper.MapProperty(string propertyName) -> string Microsoft.AspNetCore.OData.Query.Container.ITruncatedCollection @@ -922,6 +929,7 @@ Microsoft.AspNetCore.OData.Query.ODataQueryContext.Path.get -> Microsoft.OData.U Microsoft.AspNetCore.OData.Query.ODataQueryContext.RequestContainer.get -> System.IServiceProvider Microsoft.AspNetCore.OData.Query.ODataQueryOptions Microsoft.AspNetCore.OData.Query.ODataQueryOptions.Apply.get -> Microsoft.AspNetCore.OData.Query.ApplyQueryOption +Microsoft.AspNetCore.OData.Query.ODataQueryOptions.Compute.get -> Microsoft.AspNetCore.OData.Query.ComputeQueryOption Microsoft.AspNetCore.OData.Query.ODataQueryOptions.Context.get -> Microsoft.AspNetCore.OData.Query.ODataQueryContext Microsoft.AspNetCore.OData.Query.ODataQueryOptions.Count.get -> Microsoft.AspNetCore.OData.Query.CountQueryOption Microsoft.AspNetCore.OData.Query.ODataQueryOptions.Filter.get -> Microsoft.AspNetCore.OData.Query.FilterQueryOption @@ -963,6 +971,7 @@ Microsoft.AspNetCore.OData.Query.ODataQuerySettings.TimeZone.get -> System.TimeZ Microsoft.AspNetCore.OData.Query.ODataQuerySettings.TimeZone.set -> void Microsoft.AspNetCore.OData.Query.ODataRawQueryOptions Microsoft.AspNetCore.OData.Query.ODataRawQueryOptions.Apply.get -> string +Microsoft.AspNetCore.OData.Query.ODataRawQueryOptions.Compute.get -> string Microsoft.AspNetCore.OData.Query.ODataRawQueryOptions.Count.get -> string Microsoft.AspNetCore.OData.Query.ODataRawQueryOptions.DeltaToken.get -> string Microsoft.AspNetCore.OData.Query.ODataRawQueryOptions.Expand.get -> string diff --git a/src/Microsoft.AspNetCore.OData/Query/AllowedQueryOptions.cs b/src/Microsoft.AspNetCore.OData/Query/AllowedQueryOptions.cs index 20e123a3a..0e37564d1 100644 --- a/src/Microsoft.AspNetCore.OData/Query/AllowedQueryOptions.cs +++ b/src/Microsoft.AspNetCore.OData/Query/AllowedQueryOptions.cs @@ -75,14 +75,19 @@ public enum AllowedQueryOptions /// Apply = 0x400, + /// + /// A value that corresponds to allowing the $compute query option. + /// + Compute = 0x800, + /// /// A value that corresponds to the default query options supported. /// - Supported = Filter | OrderBy | Top | Skip | SkipToken | Count | Select | Expand | Format | Apply, + Supported = Filter | OrderBy | Top | Skip | SkipToken | Count | Select | Expand | Format | Apply | Compute, /// /// A value that corresponds to allowing all query options. /// - All = Filter | Expand | Select | OrderBy | Top | Skip | Count | Format | SkipToken | DeltaToken | Apply + All = Filter | Expand | Select | OrderBy | Top | Skip | Count | Format | SkipToken | DeltaToken | Apply | Compute } } diff --git a/src/Microsoft.AspNetCore.OData/Query/ODataQueryOptions.cs b/src/Microsoft.AspNetCore.OData/Query/ODataQueryOptions.cs index fc6b921a8..7261bbc6f 100644 --- a/src/Microsoft.AspNetCore.OData/Query/ODataQueryOptions.cs +++ b/src/Microsoft.AspNetCore.OData/Query/ODataQueryOptions.cs @@ -110,6 +110,11 @@ public ODataQueryOptions(ODataQueryContext context, HttpRequest request) /// public ApplyQueryOption Apply { get; private set; } + /// + /// Gets the . + /// + public ComputeQueryOption Compute { get; private set; } + /// /// Gets the . /// @@ -973,6 +978,11 @@ private void BuildQueryOptions(IDictionary queryParameters) RawValues.Apply = kvp.Value; Apply = new ApplyQueryOption(kvp.Value, Context, _queryOptionParser); break; + case "$compute": + ThrowIfEmpty(kvp.Value, "$compute"); + RawValues.Compute = kvp.Value; + Compute = new ComputeQueryOption(kvp.Value, Context, _queryOptionParser); + break; default: // we don't throw if we can't recognize the query break; diff --git a/src/Microsoft.AspNetCore.OData/Query/Query/ComputeQueryOption.cs b/src/Microsoft.AspNetCore.OData/Query/Query/ComputeQueryOption.cs new file mode 100644 index 000000000..a69fbc463 --- /dev/null +++ b/src/Microsoft.AspNetCore.OData/Query/Query/ComputeQueryOption.cs @@ -0,0 +1,110 @@ +//----------------------------------------------------------------------------- +// +// Copyright (c) .NET Foundation and Contributors. All rights reserved. +// See License.txt in the project root for license information. +// +//------------------------------------------------------------------------------ + +using System; +using System.Collections.Generic; +using Microsoft.OData.Edm; +using Microsoft.OData.UriParser; + +namespace Microsoft.AspNetCore.OData.Query +{ + /// + /// This defines a $compute OData query option for querying. + /// The $compute system query option allows clients to define computed properties that can be used in a $select or within a $filter or $orderby expression. + /// Computed properties SHOULD be included as dynamic properties in the result and MUST be included if $select is specified with the computed property name, or star (*). + /// + public class ComputeQueryOption + { + private ComputeClause _computeClause; + private ODataQueryOptionParser _queryOptionParser; + + /// + /// Initialize a new instance of based on the raw $compute value and + /// an EdmModel from . + /// + /// The raw value for $filter query. It can be null or empty. + /// The which contains the and some type information + /// The which is used to parse the query option. + public ComputeQueryOption(string rawValue, ODataQueryContext context, ODataQueryOptionParser queryOptionParser) + { + if (string.IsNullOrEmpty(rawValue)) + { + throw Error.ArgumentNullOrEmpty(nameof(rawValue)); + } + + if (context == null) + { + throw Error.ArgumentNull(nameof(context)); + } + + if (queryOptionParser == null) + { + throw Error.ArgumentNull(nameof(queryOptionParser)); + } + + Context = context; + RawValue = rawValue; + _queryOptionParser = queryOptionParser; + ResultClrType = Context.ElementClrType; + } + + // This constructor is intended for unit testing only. + internal ComputeQueryOption(string rawValue, ODataQueryContext context) + { + if (string.IsNullOrEmpty(rawValue)) + { + throw Error.ArgumentNullOrEmpty(nameof(rawValue)); + } + + if (context == null) + { + throw Error.ArgumentNull(nameof(context)); + } + + Context = context; + RawValue = rawValue; + + _queryOptionParser = new ODataQueryOptionParser( + context.Model, + context.ElementType, + context.NavigationSource, + new Dictionary { { "$compute", rawValue } }, + context.RequestContainer); + } + + /// + /// Gets the given . + /// + public ODataQueryContext Context { get; } + + /// + /// ClrType for result of transformations + /// + public Type ResultClrType { get; } + + /// + /// Gets the parsed for this query option. + /// + public ComputeClause ComputeClause + { + get + { + if (_computeClause == null) + { + _computeClause = _queryOptionParser.ParseCompute(); + } + + return _computeClause; + } + } + + /// + /// Gets the raw $compute value. + /// + public string RawValue { get; } + } +} diff --git a/src/Microsoft.AspNetCore.OData/Query/Query/ODataRawQueryOptions.cs b/src/Microsoft.AspNetCore.OData/Query/Query/ODataRawQueryOptions.cs index 8e5b6a434..94225045c 100644 --- a/src/Microsoft.AspNetCore.OData/Query/Query/ODataRawQueryOptions.cs +++ b/src/Microsoft.AspNetCore.OData/Query/Query/ODataRawQueryOptions.cs @@ -22,6 +22,11 @@ public class ODataRawQueryOptions /// public string Apply { get; internal set; } + /// + /// Gets the raw $compute query value from the incoming request Uri if exists. + /// + public string Compute { get; internal set; } + /// /// Gets the raw $orderby query value from the incoming request Uri if exists. /// diff --git a/src/Microsoft.AspNetCore.OData/Query/Validator/ODataQueryValidator.cs b/src/Microsoft.AspNetCore.OData/Query/Validator/ODataQueryValidator.cs index 5d6d81f49..908e0e736 100644 --- a/src/Microsoft.AspNetCore.OData/Query/Validator/ODataQueryValidator.cs +++ b/src/Microsoft.AspNetCore.OData/Query/Validator/ODataQueryValidator.cs @@ -34,6 +34,14 @@ public virtual void Validate(ODataQueryOptions options, ODataValidationSettings } // Validate each query options + if (options.Compute != null) + { + if (options.Compute.ComputeClause != null) + { + ValidateQueryOptionAllowed(AllowedQueryOptions.Compute, validationSettings.AllowedQueryOptions); + } + } + if (options.Apply != null) { if (options.Apply.ApplyClause != null) diff --git a/test/Microsoft.AspNetCore.OData.Tests/PublicApi/Microsoft.AspNetCore.OData.PublicApi.Net5.bsl b/test/Microsoft.AspNetCore.OData.Tests/PublicApi/Microsoft.AspNetCore.OData.PublicApi.Net5.bsl index ce8c98fc8..042cb4adb 100644 --- a/test/Microsoft.AspNetCore.OData.Tests/PublicApi/Microsoft.AspNetCore.OData.PublicApi.Net5.bsl +++ b/test/Microsoft.AspNetCore.OData.Tests/PublicApi/Microsoft.AspNetCore.OData.PublicApi.Net5.bsl @@ -1161,8 +1161,9 @@ public enum Microsoft.AspNetCore.OData.Query.AllowedLogicalOperators : int { FlagsAttribute(), ] public enum Microsoft.AspNetCore.OData.Query.AllowedQueryOptions : int { - All = 2047 + All = 4095 Apply = 1024 + Compute = 2048 Count = 64 DeltaToken = 512 Expand = 2 @@ -1173,7 +1174,7 @@ public enum Microsoft.AspNetCore.OData.Query.AllowedQueryOptions : int { Select = 4 Skip = 32 SkipToken = 256 - Supported = 1535 + Supported = 3583 Top = 16 } @@ -1231,6 +1232,15 @@ public class Microsoft.AspNetCore.OData.Query.ApplyQueryOption { public System.Linq.IQueryable ApplyTo (System.Linq.IQueryable query, Microsoft.AspNetCore.OData.Query.ODataQuerySettings querySettings) } +public class Microsoft.AspNetCore.OData.Query.ComputeQueryOption { + public ComputeQueryOption (string rawValue, Microsoft.AspNetCore.OData.Query.ODataQueryContext context, Microsoft.OData.UriParser.ODataQueryOptionParser queryOptionParser) + + Microsoft.OData.UriParser.ComputeClause ComputeClause { public get; } + Microsoft.AspNetCore.OData.Query.ODataQueryContext Context { public get; } + string RawValue { public get; } + System.Type ResultClrType { public get; } +} + public class Microsoft.AspNetCore.OData.Query.CountQueryOption { public CountQueryOption (string rawValue, Microsoft.AspNetCore.OData.Query.ODataQueryContext context, Microsoft.OData.UriParser.ODataQueryOptionParser queryOptionParser) @@ -1351,6 +1361,7 @@ public class Microsoft.AspNetCore.OData.Query.ODataQueryOptions { public ODataQueryOptions (Microsoft.AspNetCore.OData.Query.ODataQueryContext context, Microsoft.AspNetCore.Http.HttpRequest request) Microsoft.AspNetCore.OData.Query.ApplyQueryOption Apply { public get; } + Microsoft.AspNetCore.OData.Query.ComputeQueryOption Compute { public get; } Microsoft.AspNetCore.OData.Query.ODataQueryContext Context { public get; } Microsoft.AspNetCore.OData.Query.CountQueryOption Count { public get; } Microsoft.AspNetCore.OData.Query.FilterQueryOption Filter { public get; } @@ -1420,6 +1431,7 @@ public class Microsoft.AspNetCore.OData.Query.ODataRawQueryOptions { public ODataRawQueryOptions () string Apply { public get; } + string Compute { public get; } string Count { public get; } string DeltaToken { public get; } string Expand { public get; } diff --git a/test/Microsoft.AspNetCore.OData.Tests/PublicApi/Microsoft.AspNetCore.OData.PublicApi.NetCore31.bsl b/test/Microsoft.AspNetCore.OData.Tests/PublicApi/Microsoft.AspNetCore.OData.PublicApi.NetCore31.bsl index ce8c98fc8..042cb4adb 100644 --- a/test/Microsoft.AspNetCore.OData.Tests/PublicApi/Microsoft.AspNetCore.OData.PublicApi.NetCore31.bsl +++ b/test/Microsoft.AspNetCore.OData.Tests/PublicApi/Microsoft.AspNetCore.OData.PublicApi.NetCore31.bsl @@ -1161,8 +1161,9 @@ public enum Microsoft.AspNetCore.OData.Query.AllowedLogicalOperators : int { FlagsAttribute(), ] public enum Microsoft.AspNetCore.OData.Query.AllowedQueryOptions : int { - All = 2047 + All = 4095 Apply = 1024 + Compute = 2048 Count = 64 DeltaToken = 512 Expand = 2 @@ -1173,7 +1174,7 @@ public enum Microsoft.AspNetCore.OData.Query.AllowedQueryOptions : int { Select = 4 Skip = 32 SkipToken = 256 - Supported = 1535 + Supported = 3583 Top = 16 } @@ -1231,6 +1232,15 @@ public class Microsoft.AspNetCore.OData.Query.ApplyQueryOption { public System.Linq.IQueryable ApplyTo (System.Linq.IQueryable query, Microsoft.AspNetCore.OData.Query.ODataQuerySettings querySettings) } +public class Microsoft.AspNetCore.OData.Query.ComputeQueryOption { + public ComputeQueryOption (string rawValue, Microsoft.AspNetCore.OData.Query.ODataQueryContext context, Microsoft.OData.UriParser.ODataQueryOptionParser queryOptionParser) + + Microsoft.OData.UriParser.ComputeClause ComputeClause { public get; } + Microsoft.AspNetCore.OData.Query.ODataQueryContext Context { public get; } + string RawValue { public get; } + System.Type ResultClrType { public get; } +} + public class Microsoft.AspNetCore.OData.Query.CountQueryOption { public CountQueryOption (string rawValue, Microsoft.AspNetCore.OData.Query.ODataQueryContext context, Microsoft.OData.UriParser.ODataQueryOptionParser queryOptionParser) @@ -1351,6 +1361,7 @@ public class Microsoft.AspNetCore.OData.Query.ODataQueryOptions { public ODataQueryOptions (Microsoft.AspNetCore.OData.Query.ODataQueryContext context, Microsoft.AspNetCore.Http.HttpRequest request) Microsoft.AspNetCore.OData.Query.ApplyQueryOption Apply { public get; } + Microsoft.AspNetCore.OData.Query.ComputeQueryOption Compute { public get; } Microsoft.AspNetCore.OData.Query.ODataQueryContext Context { public get; } Microsoft.AspNetCore.OData.Query.CountQueryOption Count { public get; } Microsoft.AspNetCore.OData.Query.FilterQueryOption Filter { public get; } @@ -1420,6 +1431,7 @@ public class Microsoft.AspNetCore.OData.Query.ODataRawQueryOptions { public ODataRawQueryOptions () string Apply { public get; } + string Compute { public get; } string Count { public get; } string DeltaToken { public get; } string Expand { public get; } diff --git a/test/Microsoft.AspNetCore.OData.Tests/Query/Query/ComputeQueryOptionTests.cs b/test/Microsoft.AspNetCore.OData.Tests/Query/Query/ComputeQueryOptionTests.cs new file mode 100644 index 000000000..8ea19be07 --- /dev/null +++ b/test/Microsoft.AspNetCore.OData.Tests/Query/Query/ComputeQueryOptionTests.cs @@ -0,0 +1,127 @@ +//----------------------------------------------------------------------------- +// +// Copyright (c) .NET Foundation and Contributors. All rights reserved. +// See License.txt in the project root for license information. +// +//------------------------------------------------------------------------------ + +using System.Collections.Generic; +using System.Linq; +using Microsoft.AspNetCore.OData.Query; +using Microsoft.AspNetCore.OData.TestCommon; +using Microsoft.AspNetCore.OData.Tests.Commons; +using Microsoft.OData.Edm; +using Microsoft.OData.ModelBuilder; +using Microsoft.OData.UriParser; +using Xunit; + +namespace Microsoft.AspNetCore.OData.Tests.Query +{ + public class ComputeQueryOptionTests + { + private static IEdmModel _model = GetModel(); + + [Fact] + public void CtorComputeQueryOption_ThrowsArgumentNull_ForInputParameter() + { + // Arrange & Act & Assert + ExceptionAssert.ThrowsArgumentNullOrEmpty(() => new ComputeQueryOption(null, null, null), "rawValue"); + ExceptionAssert.ThrowsArgumentNullOrEmpty(() => new ComputeQueryOption(string.Empty, null, null), "rawValue"); + + // Arrange & Act & Assert + ExceptionAssert.ThrowsArgumentNull(() => new ComputeQueryOption("groupby", null, null), "context"); + + // Arrange & Act & Assert + ODataQueryContext context = new ODataQueryContext(EdmCoreModel.Instance, typeof(int)); + ExceptionAssert.ThrowsArgumentNull(() => new ComputeQueryOption("groupby", context, null), "queryOptionParser"); + } + + [Fact] + public void CtorComputeQueryOption_CanConstructValidComputeQuery() + { + // Arrange + IEdmModel model = _model; + ODataQueryContext context = new ODataQueryContext(model, typeof(ComputeCustomer)); + + // Act + ComputeQueryOption compute = new ComputeQueryOption("Price mul Qty as Total", context); + + // Assert + Assert.Same(context, compute.Context); + Assert.Equal("Price mul Qty as Total", compute.RawValue); + } + + [Fact] + public void CtorComputeQueryOption_GetQueryNodeParsesQuery() + { + // Arrange + IEdmModel model = _model; + ODataQueryContext context = new ODataQueryContext(model, typeof(ComputeCustomer)) { RequestContainer = new MockServiceProvider() }; + + // Act + ComputeQueryOption compute = new ComputeQueryOption("Price mul Qty as Total,Price mul 2.0 as Tax", context); + ComputeClause computeClause = compute.ComputeClause; + + // Assert + Assert.Equal(2, computeClause.ComputedItems.Count()); + + Assert.Collection(computeClause.ComputedItems, + e => + { + Assert.Equal("Total", e.Alias); + + Assert.Equal(QueryNodeKind.BinaryOperator, e.Expression.Kind); + BinaryOperatorNode binaryNode = e.Expression as BinaryOperatorNode; + Assert.Equal(BinaryOperatorKind.Multiply, binaryNode.OperatorKind); + Assert.Equal(QueryNodeKind.Convert, binaryNode.Right.Kind); + ConvertNode convertNode = (ConvertNode)binaryNode.Right; + Assert.Equal("Qty", ((SingleValuePropertyAccessNode)convertNode.Source).Property.Name); + + Assert.Equal(QueryNodeKind.SingleValuePropertyAccess, binaryNode.Left.Kind); + var propertyAccessNode = binaryNode.Left as SingleValuePropertyAccessNode; + Assert.Equal("Price", propertyAccessNode.Property.Name); + }, + e => + { + Assert.Equal("Tax", e.Alias); + + Assert.Equal(QueryNodeKind.BinaryOperator, e.Expression.Kind); + BinaryOperatorNode binaryNode = e.Expression as BinaryOperatorNode; + Assert.Equal(BinaryOperatorKind.Multiply, binaryNode.OperatorKind); + Assert.Equal(QueryNodeKind.Constant, binaryNode.Right.Kind); + Assert.Equal(2.0, ((ConstantNode)binaryNode.Right).Value); + + Assert.Equal(QueryNodeKind.SingleValuePropertyAccess, binaryNode.Left.Kind); + var propertyAccessNode = binaryNode.Left as SingleValuePropertyAccessNode; + Assert.Equal("Price", propertyAccessNode.Property.Name); + }); + } + + private static IEdmModel GetModel() + { + ODataConventionModelBuilder builder = new ODataConventionModelBuilder(); + builder.EntitySet("Customers"); + return builder.GetEdmModel(); + } + } + + public class ComputeCustomer + { + public int Id { get; set; } + + public string Name { get; set; } + + public int Age { get; set; } + + public double Price { get; set; } + + public int Qty { get; set; } + + public IDictionary Dynamics { get; set; } + } + + public class ComputeAddress + { + public string Street { get; set; } + } +} diff --git a/test/Microsoft.AspNetCore.OData.Tests/Query/Validator/ODataQueryValidatorTest.cs b/test/Microsoft.AspNetCore.OData.Tests/Query/Validator/ODataQueryValidatorTest.cs index f5b7ba833..b78ee8920 100644 --- a/test/Microsoft.AspNetCore.OData.Tests/Query/Validator/ODataQueryValidatorTest.cs +++ b/test/Microsoft.AspNetCore.OData.Tests/Query/Validator/ODataQueryValidatorTest.cs @@ -8,14 +8,12 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Net.Http; using Microsoft.AspNetCore.OData.Query; using Microsoft.AspNetCore.OData.Query.Validator; using Microsoft.AspNetCore.OData.TestCommon; using Microsoft.AspNetCore.OData.Tests.Commons; using Microsoft.AspNetCore.OData.Tests.Extensions; using Microsoft.OData; -using Microsoft.OData.ModelBuilder.Config; using Moq; using Xunit; @@ -47,6 +45,7 @@ public static TheoryDataSet SupportedQueryO { AllowedQueryOptions.Skip, "$skip=5", "Skip" }, { AllowedQueryOptions.Top, "$top=10", "Top" }, { AllowedQueryOptions.Apply, "$apply=groupby((Name))", "Apply" }, + { AllowedQueryOptions.Compute, "$compute=AmountSpent mul 2 as DoubleAmount", "Compute" }, { AllowedQueryOptions.SkipToken, "$skiptoken=__skip__", "SkipToken" }, }; }