From d5f6dae68d3942a9c082a472538fae8050246c39 Mon Sep 17 00:00:00 2001 From: Sam Xu Date: Thu, 17 Mar 2022 17:48:13 -0700 Subject: [PATCH] Support 'case' conditional function --- .../PublicAPI.Unshipped.txt | 16 +++ .../Uri/ExpressionConstants.cs | 5 +- .../UriParser/Binders/FunctionCallBinder.cs | 4 + .../UriParser/Binders/MetadataBinder.cs | 22 ++++ .../UriParser/Parsers/CaseParameterParser.cs | 111 ++++++++++++++++++ .../UriParser/Parsers/FunctionCallParser.cs | 12 +- .../Parsers/UriQueryExpressionParser.cs | 9 ++ .../SemanticAst/CaseParameterNode.cs | 79 +++++++++++++ .../SyntacticAst/CaseParameterToken.cs | 58 +++++++++ .../UriParser/SyntacticAst/QueryTokenKind.cs | 7 +- .../UriParser/TreeNodeKinds/QueryNodeKind.cs | 10 ++ .../Visitors/ISyntacticTreeVisitor.cs | 7 ++ .../UriParser/Visitors/QueryNodeVisitor.cs | 10 ++ .../Visitors/SyntacticTreeVisitor.cs | 10 ++ .../FilterAndOrderByFunctionalTests.cs | 15 +++ 15 files changed, 372 insertions(+), 3 deletions(-) create mode 100644 src/Microsoft.OData.Core/UriParser/Parsers/CaseParameterParser.cs create mode 100644 src/Microsoft.OData.Core/UriParser/SemanticAst/CaseParameterNode.cs create mode 100644 src/Microsoft.OData.Core/UriParser/SyntacticAst/CaseParameterToken.cs diff --git a/src/Microsoft.OData.Core/PublicAPI.Unshipped.txt b/src/Microsoft.OData.Core/PublicAPI.Unshipped.txt index d15221a3be..d1f1cd0ac4 100644 --- a/src/Microsoft.OData.Core/PublicAPI.Unshipped.txt +++ b/src/Microsoft.OData.Core/PublicAPI.Unshipped.txt @@ -1262,6 +1262,14 @@ Microsoft.OData.UriParser.BinaryOperatorToken.BinaryOperatorToken(Microsoft.ODat Microsoft.OData.UriParser.BinaryOperatorToken.Left.get -> Microsoft.OData.UriParser.QueryToken Microsoft.OData.UriParser.BinaryOperatorToken.OperatorKind.get -> Microsoft.OData.UriParser.BinaryOperatorKind Microsoft.OData.UriParser.BinaryOperatorToken.Right.get -> Microsoft.OData.UriParser.QueryToken +Microsoft.OData.UriParser.CaseParameterNode +Microsoft.OData.UriParser.CaseParameterNode.CaseParameterNode(Microsoft.OData.UriParser.SingleValueNode condition, Microsoft.OData.UriParser.QueryNode value) -> void +Microsoft.OData.UriParser.CaseParameterNode.Condition.get -> Microsoft.OData.UriParser.QueryNode +Microsoft.OData.UriParser.CaseParameterNode.Value.get -> Microsoft.OData.UriParser.QueryNode +Microsoft.OData.UriParser.CaseParameterToken +Microsoft.OData.UriParser.CaseParameterToken.CaseParameterToken(Microsoft.OData.UriParser.QueryToken condition, Microsoft.OData.UriParser.QueryToken value) -> void +Microsoft.OData.UriParser.CaseParameterToken.Condition.get -> Microsoft.OData.UriParser.QueryToken +Microsoft.OData.UriParser.CaseParameterToken.Value.get -> Microsoft.OData.UriParser.QueryToken Microsoft.OData.UriParser.CollectionComplexNode Microsoft.OData.UriParser.CollectionComplexNode.CollectionComplexNode(Microsoft.OData.UriParser.SingleResourceNode source, Microsoft.OData.Edm.IEdmProperty property) -> void Microsoft.OData.UriParser.CollectionComplexNode.Property.get -> Microsoft.OData.Edm.IEdmProperty @@ -1454,6 +1462,7 @@ Microsoft.OData.UriParser.ISyntacticTreeVisitor.Visit(Microsoft.OData.UriPars Microsoft.OData.UriParser.ISyntacticTreeVisitor.Visit(Microsoft.OData.UriParser.AllToken tokenIn) -> T Microsoft.OData.UriParser.ISyntacticTreeVisitor.Visit(Microsoft.OData.UriParser.AnyToken tokenIn) -> T Microsoft.OData.UriParser.ISyntacticTreeVisitor.Visit(Microsoft.OData.UriParser.BinaryOperatorToken tokenIn) -> T +Microsoft.OData.UriParser.ISyntacticTreeVisitor.Visit(Microsoft.OData.UriParser.CaseParameterToken tokenIn) -> T Microsoft.OData.UriParser.ISyntacticTreeVisitor.Visit(Microsoft.OData.UriParser.CountSegmentToken tokenIn) -> T Microsoft.OData.UriParser.ISyntacticTreeVisitor.Visit(Microsoft.OData.UriParser.CustomQueryOptionToken tokenIn) -> T Microsoft.OData.UriParser.ISyntacticTreeVisitor.Visit(Microsoft.OData.UriParser.DottedIdentifierToken tokenIn) -> T @@ -1710,6 +1719,7 @@ Microsoft.OData.UriParser.QueryNodeKind.AggregatedCollectionPropertyNode = 31 -> Microsoft.OData.UriParser.QueryNodeKind.All = 14 -> Microsoft.OData.UriParser.QueryNodeKind Microsoft.OData.UriParser.QueryNodeKind.Any = 9 -> Microsoft.OData.UriParser.QueryNodeKind Microsoft.OData.UriParser.QueryNodeKind.BinaryOperator = 4 -> Microsoft.OData.UriParser.QueryNodeKind +Microsoft.OData.UriParser.QueryNodeKind.CaseParameter = 34 -> Microsoft.OData.UriParser.QueryNodeKind Microsoft.OData.UriParser.QueryNodeKind.CollectionComplexNode = 26 -> Microsoft.OData.UriParser.QueryNodeKind Microsoft.OData.UriParser.QueryNodeKind.CollectionConstant = 33 -> Microsoft.OData.UriParser.QueryNodeKind Microsoft.OData.UriParser.QueryNodeKind.CollectionFunctionCall = 18 -> Microsoft.OData.UriParser.QueryNodeKind @@ -1751,6 +1761,7 @@ Microsoft.OData.UriParser.QueryTokenKind.AggregateGroupBy = 26 -> Microsoft.ODat Microsoft.OData.UriParser.QueryTokenKind.All = 19 -> Microsoft.OData.UriParser.QueryTokenKind Microsoft.OData.UriParser.QueryTokenKind.Any = 15 -> Microsoft.OData.UriParser.QueryTokenKind Microsoft.OData.UriParser.QueryTokenKind.BinaryOperator = 3 -> Microsoft.OData.UriParser.QueryTokenKind +Microsoft.OData.UriParser.QueryTokenKind.CaseParameter = 33 -> Microsoft.OData.UriParser.QueryTokenKind Microsoft.OData.UriParser.QueryTokenKind.Compute = 27 -> Microsoft.OData.UriParser.QueryTokenKind Microsoft.OData.UriParser.QueryTokenKind.ComputeExpression = 28 -> Microsoft.OData.UriParser.QueryTokenKind Microsoft.OData.UriParser.QueryTokenKind.CountSegment = 32 -> Microsoft.OData.UriParser.QueryTokenKind @@ -2000,6 +2011,10 @@ override Microsoft.OData.UriParser.BinaryOperatorNode.Accept(Microsoft.OData. override Microsoft.OData.UriParser.BinaryOperatorNode.TypeReference.get -> Microsoft.OData.Edm.IEdmTypeReference override Microsoft.OData.UriParser.BinaryOperatorToken.Accept(Microsoft.OData.UriParser.ISyntacticTreeVisitor visitor) -> T override Microsoft.OData.UriParser.BinaryOperatorToken.Kind.get -> Microsoft.OData.UriParser.QueryTokenKind +override Microsoft.OData.UriParser.CaseParameterNode.Accept(Microsoft.OData.UriParser.QueryNodeVisitor visitor) -> T +override Microsoft.OData.UriParser.CaseParameterNode.Kind.get -> Microsoft.OData.UriParser.QueryNodeKind +override Microsoft.OData.UriParser.CaseParameterToken.Accept(Microsoft.OData.UriParser.ISyntacticTreeVisitor visitor) -> T +override Microsoft.OData.UriParser.CaseParameterToken.Kind.get -> Microsoft.OData.UriParser.QueryTokenKind override Microsoft.OData.UriParser.CollectionComplexNode.Accept(Microsoft.OData.UriParser.QueryNodeVisitor visitor) -> T override Microsoft.OData.UriParser.CollectionComplexNode.CollectionType.get -> Microsoft.OData.Edm.IEdmCollectionTypeReference override Microsoft.OData.UriParser.CollectionComplexNode.ItemStructuredType.get -> Microsoft.OData.Edm.IEdmStructuredTypeReference @@ -2435,6 +2450,7 @@ virtual Microsoft.OData.UriParser.QueryNodeVisitor.Visit(Microsoft.OData.UriP virtual Microsoft.OData.UriParser.QueryNodeVisitor.Visit(Microsoft.OData.UriParser.AllNode nodeIn) -> T virtual Microsoft.OData.UriParser.QueryNodeVisitor.Visit(Microsoft.OData.UriParser.AnyNode nodeIn) -> T virtual Microsoft.OData.UriParser.QueryNodeVisitor.Visit(Microsoft.OData.UriParser.BinaryOperatorNode nodeIn) -> T +virtual Microsoft.OData.UriParser.QueryNodeVisitor.Visit(Microsoft.OData.UriParser.CaseParameterNode nodeIn) -> T virtual Microsoft.OData.UriParser.QueryNodeVisitor.Visit(Microsoft.OData.UriParser.CollectionComplexNode nodeIn) -> T virtual Microsoft.OData.UriParser.QueryNodeVisitor.Visit(Microsoft.OData.UriParser.CollectionConstantNode nodeIn) -> T virtual Microsoft.OData.UriParser.QueryNodeVisitor.Visit(Microsoft.OData.UriParser.CollectionFunctionCallNode nodeIn) -> T diff --git a/src/Microsoft.OData.Core/Uri/ExpressionConstants.cs b/src/Microsoft.OData.Core/Uri/ExpressionConstants.cs index 6f651dce02..0c45188014 100644 --- a/src/Microsoft.OData.Core/Uri/ExpressionConstants.cs +++ b/src/Microsoft.OData.Core/Uri/ExpressionConstants.cs @@ -120,9 +120,12 @@ internal static class ExpressionConstants /// "cast" function internal const string UnboundFunctionCast = "cast"; - /// "isof function + /// "isof" function internal const string UnboundFunctionIsOf = "isof"; + /// "case" function + internal const string UnboundFunctionCase = "case"; + /// Spatial length function internal const string UnboundFunctionLength = "geo.length"; diff --git a/src/Microsoft.OData.Core/UriParser/Binders/FunctionCallBinder.cs b/src/Microsoft.OData.Core/UriParser/Binders/FunctionCallBinder.cs index 844ce6b02e..bd6f564b92 100644 --- a/src/Microsoft.OData.Core/UriParser/Binders/FunctionCallBinder.cs +++ b/src/Microsoft.OData.Core/UriParser/Binders/FunctionCallBinder.cs @@ -30,6 +30,7 @@ internal sealed class FunctionCallBinder : BinderBase { ExpressionConstants.UnboundFunctionCast, ExpressionConstants.UnboundFunctionIsOf, + ExpressionConstants.UnboundFunctionCase }; /// @@ -678,6 +679,9 @@ private SingleValueNode CreateUnboundFunctionNode(string functionCallTokenName, break; } + case ExpressionConstants.UnboundFunctionCase: + break; + default: { break; diff --git a/src/Microsoft.OData.Core/UriParser/Binders/MetadataBinder.cs b/src/Microsoft.OData.Core/UriParser/Binders/MetadataBinder.cs index bac1e17bf9..fed56bc1af 100644 --- a/src/Microsoft.OData.Core/UriParser/Binders/MetadataBinder.cs +++ b/src/Microsoft.OData.Core/UriParser/Binders/MetadataBinder.cs @@ -195,6 +195,9 @@ protected internal QueryNode Bind(QueryToken token) case QueryTokenKind.FunctionParameter: result = this.BindFunctionParameter((FunctionParameterToken)token); break; + case QueryTokenKind.CaseParameter: + result = this.BindCaseParameter((CaseParameterToken)token); + break; case QueryTokenKind.In: result = this.BindIn((InToken)token); break; @@ -241,6 +244,25 @@ protected virtual QueryNode BindFunctionParameter(FunctionParameterToken token) return this.Bind(token.ValueToken); } + /// + /// Bind a case parameter token + /// + /// The token to bind. + /// A semantically bound CaseParameterNode. + /// + protected virtual QueryNode BindCaseParameter(CaseParameterToken token) + { + SingleValueNode condition = Bind(token.Condition) as SingleValueNode; + + if (condition == null) + { + throw new ODataException("TODO:"); + // throw new ODataException(ODataErrorStrings.MetadataBinder_BinaryOperatorOperandNotSingleValue(operatorKind.ToString())); + } + + return new CaseParameterNode(condition, Bind(token.Value)); + } + /// /// Binds an InnerPathToken. /// diff --git a/src/Microsoft.OData.Core/UriParser/Parsers/CaseParameterParser.cs b/src/Microsoft.OData.Core/UriParser/Parsers/CaseParameterParser.cs new file mode 100644 index 0000000000..b11cbbd1a9 --- /dev/null +++ b/src/Microsoft.OData.Core/UriParser/Parsers/CaseParameterParser.cs @@ -0,0 +1,111 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser +{ + using System.Collections.Generic; + using System.Diagnostics; + using System.Linq; + using ODataErrorStrings = Microsoft.OData.Strings; + + /// + /// Component for parsing function parameters in both $filter/$orderby expressions and in paths. + /// TODO: update code that is duplicate between operation and operation import, add more tests. + /// + internal static class CaseParameterParser + { + /// + /// Tries to parse a collection of function parameters. Allows path and filter to share the core algorithm while representing parameters differently. + /// + /// The UriQueryExpressionParser to read from. + /// The parameters if they were successfully split. + /// Whether the parameters could be split. + internal static bool TrySplitCaseParameters(this UriQueryExpressionParser parser, out ICollection splitParameters) + { + return parser.TrySplitCaseParameters(ExpressionTokenKind.CloseParen, out splitParameters); + } + + /// + /// Tries to parse a collection of function parameters for path. + /// + /// The contents of the parentheses portion of the current path segment. + /// The ODataUriParserConfiguration to create a UriQueryExpressionParser. + /// The parameters if they were successfully split. + /// Whether the parameters could be split. + internal static bool TrySplitOperationParameters(string parenthesisExpression, ODataUriParserConfiguration configuration, out ICollection splitParameters) + { + ExpressionLexer lexer = new ExpressionLexer(parenthesisExpression, true /*moveToFirstToken*/, false /*useSemicolonDelimiter*/, true /*parsingFunctionParameters*/); + UriQueryExpressionParser parser = new UriQueryExpressionParser(configuration.Settings.FilterLimit, lexer); + var ret = parser.TrySplitOperationParameters(ExpressionTokenKind.End, out splitParameters); + + // check duplicate names + if (splitParameters.Select(t => t.ParameterName).Distinct().Count() != splitParameters.Count) + { + throw new ODataException(ODataErrorStrings.FunctionCallParser_DuplicateParameterOrEntityKeyName); + } + + return ret; + } + + /// + /// Tries to parse a collection of function parameters. Allows path and filter to share the core algorithm while representing parameters differently. + /// + /// The UriQueryExpressionParser to read from. + /// The token kind that marks the end of the parameters. + /// The parameters if they were successfully split. + /// Whether the parameters could be split. + private static bool TrySplitCaseParameters(this UriQueryExpressionParser parser, ExpressionTokenKind endTokenKind, out ICollection splitParameters) + { + Debug.Assert(parser != null, "parser != null"); + var lexer = parser.Lexer; + var parameters = new List(); + splitParameters = parameters; + + ExpressionToken currentToken = lexer.CurrentToken; + if (currentToken.Kind == endTokenKind) + { + return true; + } + + if (currentToken.Kind != ExpressionTokenKind.Identifier || lexer.PeekNextToken().Kind != ExpressionTokenKind.Equal) + { + return false; + } + + while (currentToken.Kind != endTokenKind) + { + lexer.ValidateToken(ExpressionTokenKind.Identifier); + string identifier = lexer.CurrentToken.GetIdentifier(); + lexer.NextToken(); + + lexer.ValidateToken(ExpressionTokenKind.Equal); + lexer.NextToken(); + + // the below UriQueryExpressionParser.ParseExpression() is able to parse common expression per ABNF: + // functionExprParameter = parameterName EQ ( parameterAlias / parameterValue ) + // parameterValue = arrayOrObject + // / commonExpr + QueryToken parameterValue = parser.ParseExpression(); + parameters.Add(new FunctionParameterToken(identifier, parameterValue)); + + // the above parser.ParseExpression() already moves to the next token, now get CurrentToken checking a comma followed by something + currentToken = lexer.CurrentToken; + if (currentToken.Kind == ExpressionTokenKind.Comma) + { + lexer.NextToken(); + currentToken = lexer.CurrentToken; + if (currentToken.Kind == endTokenKind) + { + // Trailing comma. + throw new ODataException(ODataErrorStrings.ExpressionLexer_SyntaxError(lexer.Position, lexer.ExpressionText)); + } + } + } + + return true; + } + } +} diff --git a/src/Microsoft.OData.Core/UriParser/Parsers/FunctionCallParser.cs b/src/Microsoft.OData.Core/UriParser/Parsers/FunctionCallParser.cs index 2ca1fb76bc..38c3c0ad46 100644 --- a/src/Microsoft.OData.Core/UriParser/Parsers/FunctionCallParser.cs +++ b/src/Microsoft.OData.Core/UriParser/Parsers/FunctionCallParser.cs @@ -182,7 +182,17 @@ private List ReadArgumentsAsPositionalValues() List argList = new List(); while (true) { - argList.Add(new FunctionParameterToken(null, this.parser.ParseExpression())); + QueryToken parameterToken = this.parser.ParseExpression(); + + if (this.Lexer.CurrentToken.Kind == ExpressionTokenKind.Colon) // ':' + { + this.Lexer.NextToken(); + QueryToken caseValueToken = this.parser.ParseExpression(); + + parameterToken = new CaseParameterToken(parameterToken, caseValueToken); + } + + argList.Add(new FunctionParameterToken(null, parameterToken)); if (this.Lexer.CurrentToken.Kind != ExpressionTokenKind.Comma) { break; diff --git a/src/Microsoft.OData.Core/UriParser/Parsers/UriQueryExpressionParser.cs b/src/Microsoft.OData.Core/UriParser/Parsers/UriQueryExpressionParser.cs index a70fc65914..e2fbd2b279 100644 --- a/src/Microsoft.OData.Core/UriParser/Parsers/UriQueryExpressionParser.cs +++ b/src/Microsoft.OData.Core/UriParser/Parsers/UriQueryExpressionParser.cs @@ -715,6 +715,15 @@ internal QueryToken ParseExpression() return token; } + + //internal QueryToken ParseCaseParameter() + //{ + // this.RecurseEnter(); + // QueryToken token = this.ParseLogicalOr(); + // this.RecurseLeave(); + // return token; + //} + /// /// Creates a new for the given filter, orderby or apply expression. /// diff --git a/src/Microsoft.OData.Core/UriParser/SemanticAst/CaseParameterNode.cs b/src/Microsoft.OData.Core/UriParser/SemanticAst/CaseParameterNode.cs new file mode 100644 index 0000000000..52c1f8a8c2 --- /dev/null +++ b/src/Microsoft.OData.Core/UriParser/SemanticAst/CaseParameterNode.cs @@ -0,0 +1,79 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser +{ + #region Namespaces + + using Microsoft.OData; + using Microsoft.OData.Edm; + using ODataErrorStrings = Microsoft.OData.Strings; + + #endregion Namespaces + + /// + /// Query node representing a binary operator. + /// + public sealed class CaseParameterNode : QueryNode + { + /// + /// Create a CaseParameterNode + /// + /// The condition operand. + /// The value operand. + /// Throws if the left or right inputs are null. + /// Throws if the two operands don't have the same type. + public CaseParameterNode(SingleValueNode condition, QueryNode value) + { + ExceptionUtils.CheckArgumentNotNull(condition, "condition"); + ExceptionUtils.CheckArgumentNotNull(value, "value"); + + Condition = condition; + Value = value; + + if (condition.TypeReference == null || !condition.TypeReference.IsBoolean()) + { + throw new ODataException("TODO:"); + } + } + + /// + /// Gets the Condition operand. + /// + public QueryNode Condition { get; } + + /// + /// Gets the value operand. + /// + public QueryNode Value { get; } + + public override QueryNodeKind Kind => QueryNodeKind.CaseParameter; + + /// + /// Gets the kind of this node. + /// + internal override InternalQueryNodeKind InternalKind + { + get + { + return InternalQueryNodeKind.CaseParameter; + } + } + + /// + /// Accept a that walks a tree of s. + /// + /// Type that the visitor will return after visiting this token. + /// An implementation of the visitor interface. + /// An object whose type is determined by the type parameter of the visitor. + /// throws if the input visitor is null. + public override T Accept(QueryNodeVisitor visitor) + { + ExceptionUtils.CheckArgumentNotNull(visitor, "visitor"); + return visitor.Visit(this); + } + } +} \ No newline at end of file diff --git a/src/Microsoft.OData.Core/UriParser/SyntacticAst/CaseParameterToken.cs b/src/Microsoft.OData.Core/UriParser/SyntacticAst/CaseParameterToken.cs new file mode 100644 index 0000000000..14aac39068 --- /dev/null +++ b/src/Microsoft.OData.Core/UriParser/SyntacticAst/CaseParameterToken.cs @@ -0,0 +1,58 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#if ODATA_CLIENT +namespace Microsoft.OData.Client.ALinq.UriParser +#else +namespace Microsoft.OData.UriParser +#endif +{ + /// + /// Lexical token representing the a parameter in case function + /// + public class CaseParameterToken : QueryToken + { + /// + /// Create a CaseParameterToken given the condition, value + /// + /// The associated expression. + /// The parameter denoting source type. + public CaseParameterToken(QueryToken condition, QueryToken value) + { + ExceptionUtils.CheckArgumentNotNull(condition, "operand"); + ExceptionUtils.CheckArgumentNotNull(value, "operand"); + + Condition = condition; + Value = value; + } + + /// + /// The condition token. + /// + public QueryToken Condition { get; } + + /// + /// The value token. + /// + public QueryToken Value { get; } + + /// + /// The kind of the query token. + /// + public override QueryTokenKind Kind => QueryTokenKind.CaseParameter; + + /// + /// Accept a to walk a tree of s. + /// + /// Type that the visitor will return after visiting this token. + /// An implementation of the visitor interface. + /// An object whose type is determined by the type parameter of the visitor. + public override T Accept(ISyntacticTreeVisitor visitor) + { + return visitor.Visit(this); + } + } +} \ No newline at end of file diff --git a/src/Microsoft.OData.Core/UriParser/SyntacticAst/QueryTokenKind.cs b/src/Microsoft.OData.Core/UriParser/SyntacticAst/QueryTokenKind.cs index 56d1b2bc6b..55ac2f96d3 100644 --- a/src/Microsoft.OData.Core/UriParser/SyntacticAst/QueryTokenKind.cs +++ b/src/Microsoft.OData.Core/UriParser/SyntacticAst/QueryTokenKind.cs @@ -159,6 +159,11 @@ public enum QueryTokenKind /// /// $count segment /// - CountSegment = 32 + CountSegment = 32, + + /// + /// case parameter + /// + CaseParameter = 33 } } \ No newline at end of file diff --git a/src/Microsoft.OData.Core/UriParser/TreeNodeKinds/QueryNodeKind.cs b/src/Microsoft.OData.Core/UriParser/TreeNodeKinds/QueryNodeKind.cs index 002e306da1..2fe9dcd3f1 100644 --- a/src/Microsoft.OData.Core/UriParser/TreeNodeKinds/QueryNodeKind.cs +++ b/src/Microsoft.OData.Core/UriParser/TreeNodeKinds/QueryNodeKind.cs @@ -183,6 +183,11 @@ public enum QueryNodeKind /// Node that represents a collection of constants. /// CollectionConstant = InternalQueryNodeKind.CollectionConstant, + + /// + /// + /// + CaseParameter = InternalQueryNodeKind.CaseParameter } /// @@ -359,5 +364,10 @@ internal enum InternalQueryNodeKind /// Node that represents a collection of constants. /// CollectionConstant = 33, + + /// + /// Parameter node used to represent a binary operator. + /// + CaseParameter = 34, } } \ No newline at end of file diff --git a/src/Microsoft.OData.Core/UriParser/Visitors/ISyntacticTreeVisitor.cs b/src/Microsoft.OData.Core/UriParser/Visitors/ISyntacticTreeVisitor.cs index 29ef90e9b3..8bd5d274f2 100644 --- a/src/Microsoft.OData.Core/UriParser/Visitors/ISyntacticTreeVisitor.cs +++ b/src/Microsoft.OData.Core/UriParser/Visitors/ISyntacticTreeVisitor.cs @@ -192,5 +192,12 @@ public interface ISyntacticTreeVisitor /// The GroupByToken to bind /// A T node bound to this GroupByToken T Visit(GroupByToken tokenIn); + + /// + /// Visits a CaseParameterToken + /// + /// The GroupByToken to bind + /// A T node bound to this GroupByToken + T Visit(CaseParameterToken tokenIn); } } \ No newline at end of file diff --git a/src/Microsoft.OData.Core/UriParser/Visitors/QueryNodeVisitor.cs b/src/Microsoft.OData.Core/UriParser/Visitors/QueryNodeVisitor.cs index 5fc9b7ac5e..c45c42acbe 100644 --- a/src/Microsoft.OData.Core/UriParser/Visitors/QueryNodeVisitor.cs +++ b/src/Microsoft.OData.Core/UriParser/Visitors/QueryNodeVisitor.cs @@ -313,5 +313,15 @@ public virtual T Visit(InNode nodeIn) { throw new NotImplementedException(); } + + /// + /// Visit an CaeParameterNode + /// + /// the node to visit + /// Defined by the implementer + public virtual T Visit(CaseParameterNode nodeIn) + { + throw new NotImplementedException(); + } } } \ No newline at end of file diff --git a/src/Microsoft.OData.Core/UriParser/Visitors/SyntacticTreeVisitor.cs b/src/Microsoft.OData.Core/UriParser/Visitors/SyntacticTreeVisitor.cs index 98215f4cb7..af164ed4f3 100644 --- a/src/Microsoft.OData.Core/UriParser/Visitors/SyntacticTreeVisitor.cs +++ b/src/Microsoft.OData.Core/UriParser/Visitors/SyntacticTreeVisitor.cs @@ -288,5 +288,15 @@ public virtual T Visit(ComputeExpressionToken tokenIn) { throw new NotImplementedException(); } + + /// + /// Visits a CaseParameterToken + /// + /// The CaseParameterToken to bind + /// A user defined value + public virtual T Visit(CaseParameterToken tokenIn) + { + throw new NotImplementedException(); + } } } diff --git a/test/FunctionalTests/Microsoft.OData.Core.Tests/ScenarioTests/UriParser/FilterAndOrderByFunctionalTests.cs b/test/FunctionalTests/Microsoft.OData.Core.Tests/ScenarioTests/UriParser/FilterAndOrderByFunctionalTests.cs index 38be4d41fc..bb3fbb06b0 100644 --- a/test/FunctionalTests/Microsoft.OData.Core.Tests/ScenarioTests/UriParser/FilterAndOrderByFunctionalTests.cs +++ b/test/FunctionalTests/Microsoft.OData.Core.Tests/ScenarioTests/UriParser/FilterAndOrderByFunctionalTests.cs @@ -751,6 +751,16 @@ public void NegateAnEntityShouldThrow() parse.Throws(ODataErrorStrings.MetadataBinder_IncompatibleOperandError(HardCodedTestModel.GetPersonMyDogNavProp().Type.FullName(), UnaryOperatorKind.Negate)); } + [Fact] + public void IsOfFunctionWorksWithSingleQuotesOnType222() + { + ComputeClause compute = ParseCompute("case(ID gt 0 :1,ID lt 0 :-1,true :0) as IDValue", HardCodedTestModel.TestModel, HardCodedTestModel.GetPersonType(), HardCodedTestModel.GetPeopleSet()); + // var singleValueFunctionCallNode = filter.Expression.ShouldBeSingleValueFunctionCallQueryNode("isof"); + //singleValueFunctionCallNode.Parameters.ElementAt(0).ShouldBeSingleValuePropertyAccessQueryNode(HardCodedTestModel.GetPersonShoeProp()); + //singleValueFunctionCallNode.Parameters.ElementAt(1).ShouldBeConstantQueryNode("Edm.String"); + Assert.NotNull(compute); + } + [Fact] public void IsOfFunctionWorksWithSingleQuotesOnType() { @@ -2841,5 +2851,10 @@ private static OrderByClause ParseOrderBy(string text, IEdmModel edmModel, IEdmT { return new ODataQueryOptionParser(edmModel, edmType, edmEntitySet, new Dictionary() { { "$orderby", text } }).ParseOrderBy(); } + + private static ComputeClause ParseCompute(string text, IEdmModel edmModel, IEdmType edmType, IEdmNavigationSource edmEntitySet = null) + { + return new ODataQueryOptionParser(edmModel, edmType, edmEntitySet, new Dictionary() { { "$compute", text } }) { Resolver = new ODataUriResolver() { EnableCaseInsensitive = false } }.ParseCompute(); + } } }