Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support 'case' conditional function #2356

Draft
wants to merge 1 commit into
base: release-7.x
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions src/Microsoft.OData.Core/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -1454,6 +1462,7 @@ Microsoft.OData.UriParser.ISyntacticTreeVisitor<T>.Visit(Microsoft.OData.UriPars
Microsoft.OData.UriParser.ISyntacticTreeVisitor<T>.Visit(Microsoft.OData.UriParser.AllToken tokenIn) -> T
Microsoft.OData.UriParser.ISyntacticTreeVisitor<T>.Visit(Microsoft.OData.UriParser.AnyToken tokenIn) -> T
Microsoft.OData.UriParser.ISyntacticTreeVisitor<T>.Visit(Microsoft.OData.UriParser.BinaryOperatorToken tokenIn) -> T
Microsoft.OData.UriParser.ISyntacticTreeVisitor<T>.Visit(Microsoft.OData.UriParser.CaseParameterToken tokenIn) -> T
Microsoft.OData.UriParser.ISyntacticTreeVisitor<T>.Visit(Microsoft.OData.UriParser.CountSegmentToken tokenIn) -> T
Microsoft.OData.UriParser.ISyntacticTreeVisitor<T>.Visit(Microsoft.OData.UriParser.CustomQueryOptionToken tokenIn) -> T
Microsoft.OData.UriParser.ISyntacticTreeVisitor<T>.Visit(Microsoft.OData.UriParser.DottedIdentifierToken tokenIn) -> T
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -2000,6 +2011,10 @@ override Microsoft.OData.UriParser.BinaryOperatorNode.Accept<T>(Microsoft.OData.
override Microsoft.OData.UriParser.BinaryOperatorNode.TypeReference.get -> Microsoft.OData.Edm.IEdmTypeReference
override Microsoft.OData.UriParser.BinaryOperatorToken.Accept<T>(Microsoft.OData.UriParser.ISyntacticTreeVisitor<T> visitor) -> T
override Microsoft.OData.UriParser.BinaryOperatorToken.Kind.get -> Microsoft.OData.UriParser.QueryTokenKind
override Microsoft.OData.UriParser.CaseParameterNode.Accept<T>(Microsoft.OData.UriParser.QueryNodeVisitor<T> visitor) -> T
override Microsoft.OData.UriParser.CaseParameterNode.Kind.get -> Microsoft.OData.UriParser.QueryNodeKind
override Microsoft.OData.UriParser.CaseParameterToken.Accept<T>(Microsoft.OData.UriParser.ISyntacticTreeVisitor<T> visitor) -> T
override Microsoft.OData.UriParser.CaseParameterToken.Kind.get -> Microsoft.OData.UriParser.QueryTokenKind
override Microsoft.OData.UriParser.CollectionComplexNode.Accept<T>(Microsoft.OData.UriParser.QueryNodeVisitor<T> visitor) -> T
override Microsoft.OData.UriParser.CollectionComplexNode.CollectionType.get -> Microsoft.OData.Edm.IEdmCollectionTypeReference
override Microsoft.OData.UriParser.CollectionComplexNode.ItemStructuredType.get -> Microsoft.OData.Edm.IEdmStructuredTypeReference
Expand Down Expand Up @@ -2435,6 +2450,7 @@ virtual Microsoft.OData.UriParser.QueryNodeVisitor<T>.Visit(Microsoft.OData.UriP
virtual Microsoft.OData.UriParser.QueryNodeVisitor<T>.Visit(Microsoft.OData.UriParser.AllNode nodeIn) -> T
virtual Microsoft.OData.UriParser.QueryNodeVisitor<T>.Visit(Microsoft.OData.UriParser.AnyNode nodeIn) -> T
virtual Microsoft.OData.UriParser.QueryNodeVisitor<T>.Visit(Microsoft.OData.UriParser.BinaryOperatorNode nodeIn) -> T
virtual Microsoft.OData.UriParser.QueryNodeVisitor<T>.Visit(Microsoft.OData.UriParser.CaseParameterNode nodeIn) -> T
virtual Microsoft.OData.UriParser.QueryNodeVisitor<T>.Visit(Microsoft.OData.UriParser.CollectionComplexNode nodeIn) -> T
virtual Microsoft.OData.UriParser.QueryNodeVisitor<T>.Visit(Microsoft.OData.UriParser.CollectionConstantNode nodeIn) -> T
virtual Microsoft.OData.UriParser.QueryNodeVisitor<T>.Visit(Microsoft.OData.UriParser.CollectionFunctionCallNode nodeIn) -> T
Expand Down
5 changes: 4 additions & 1 deletion src/Microsoft.OData.Core/Uri/ExpressionConstants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -120,9 +120,12 @@ internal static class ExpressionConstants
/// <summary> "cast" function </summary>
internal const string UnboundFunctionCast = "cast";

/// <summary> "isof function </summary>
/// <summary> "isof" function </summary>
internal const string UnboundFunctionIsOf = "isof";

/// <summary> "case" function </summary>
internal const string UnboundFunctionCase = "case";

/// <summary> Spatial length function </summary>
internal const string UnboundFunctionLength = "geo.length";

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ internal sealed class FunctionCallBinder : BinderBase
{
ExpressionConstants.UnboundFunctionCast,
ExpressionConstants.UnboundFunctionIsOf,
ExpressionConstants.UnboundFunctionCase
};

/// <summary>
Expand Down Expand Up @@ -678,6 +679,9 @@ private SingleValueNode CreateUnboundFunctionNode(string functionCallTokenName,
break;
}

case ExpressionConstants.UnboundFunctionCase:
break;

default:
{
break;
Expand Down
22 changes: 22 additions & 0 deletions src/Microsoft.OData.Core/UriParser/Binders/MetadataBinder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -241,6 +244,25 @@ protected virtual QueryNode BindFunctionParameter(FunctionParameterToken token)
return this.Bind(token.ValueToken);
}

/// <summary>
/// Bind a case parameter token
/// </summary>
/// <param name="token">The token to bind.</param>
/// <returns>A semantically bound CaseParameterNode.</returns>
/// <exception cref="ODataException"></exception>
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));
}

/// <summary>
/// Binds an InnerPathToken.
/// </summary>
Expand Down
111 changes: 111 additions & 0 deletions src/Microsoft.OData.Core/UriParser/Parsers/CaseParameterParser.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
//---------------------------------------------------------------------
// <copyright file="FunctionParameterParser.cs" company="Microsoft">
// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.
// </copyright>
//---------------------------------------------------------------------

namespace Microsoft.OData.UriParser
{
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using ODataErrorStrings = Microsoft.OData.Strings;

/// <summary>
/// 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.
/// </summary>
internal static class CaseParameterParser
{
/// <summary>
/// Tries to parse a collection of function parameters. Allows path and filter to share the core algorithm while representing parameters differently.
/// </summary>
/// <param name="parser">The UriQueryExpressionParser to read from.</param>
/// <param name="splitParameters">The parameters if they were successfully split.</param>
/// <returns>Whether the parameters could be split.</returns>
internal static bool TrySplitCaseParameters(this UriQueryExpressionParser parser, out ICollection<CaseParameterToken> splitParameters)
{
return parser.TrySplitCaseParameters(ExpressionTokenKind.CloseParen, out splitParameters);
}

/// <summary>
/// Tries to parse a collection of function parameters for path.
/// </summary>
/// <param name="parenthesisExpression">The contents of the parentheses portion of the current path segment.</param>
/// <param name="configuration">The ODataUriParserConfiguration to create a UriQueryExpressionParser.</param>
/// <param name="splitParameters">The parameters if they were successfully split.</param>
/// <returns>Whether the parameters could be split.</returns>
internal static bool TrySplitOperationParameters(string parenthesisExpression, ODataUriParserConfiguration configuration, out ICollection<FunctionParameterToken> 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;
}

/// <summary>
/// Tries to parse a collection of function parameters. Allows path and filter to share the core algorithm while representing parameters differently.
/// </summary>
/// <param name="parser">The UriQueryExpressionParser to read from.</param>
/// <param name="endTokenKind">The token kind that marks the end of the parameters.</param>
/// <param name="splitParameters">The parameters if they were successfully split.</param>
/// <returns>Whether the parameters could be split.</returns>
private static bool TrySplitCaseParameters(this UriQueryExpressionParser parser, ExpressionTokenKind endTokenKind, out ICollection<CaseParameterToken> splitParameters)
{
Debug.Assert(parser != null, "parser != null");
var lexer = parser.Lexer;
var parameters = new List<CaseParameterToken>();
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;
}
}
}
12 changes: 11 additions & 1 deletion src/Microsoft.OData.Core/UriParser/Parsers/FunctionCallParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,17 @@ private List<FunctionParameterToken> ReadArgumentsAsPositionalValues()
List<FunctionParameterToken> argList = new List<FunctionParameterToken>();
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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -715,6 +715,15 @@ internal QueryToken ParseExpression()
return token;
}


//internal QueryToken ParseCaseParameter()
//{
// this.RecurseEnter();
// QueryToken token = this.ParseLogicalOr();
// this.RecurseLeave();
// return token;
//}

/// <summary>
/// Creates a new <see cref="ExpressionLexer"/> for the given filter, orderby or apply expression.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
//---------------------------------------------------------------------
// <copyright file="CaseParameterNode.cs" company="Microsoft">
// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.
// </copyright>
//---------------------------------------------------------------------

namespace Microsoft.OData.UriParser
{
#region Namespaces

using Microsoft.OData;
using Microsoft.OData.Edm;
using ODataErrorStrings = Microsoft.OData.Strings;

#endregion Namespaces

/// <summary>
/// Query node representing a binary operator.
/// </summary>
public sealed class CaseParameterNode : QueryNode
{
/// <summary>
/// Create a CaseParameterNode
/// </summary>
/// <param name="condition">The condition operand.</param>
/// <param name="value">The value operand.</param>
/// <exception cref="System.ArgumentNullException">Throws if the left or right inputs are null.</exception>
/// <exception cref="ODataException">Throws if the two operands don't have the same type.</exception>
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:");
}
}

/// <summary>
/// Gets the Condition operand.
/// </summary>
public QueryNode Condition { get; }

/// <summary>
/// Gets the value operand.
/// </summary>
public QueryNode Value { get; }

public override QueryNodeKind Kind => QueryNodeKind.CaseParameter;

/// <summary>
/// Gets the kind of this node.
/// </summary>
internal override InternalQueryNodeKind InternalKind
{
get
{
return InternalQueryNodeKind.CaseParameter;
}
}

/// <summary>
/// Accept a <see cref="QueryNodeVisitor{T}"/> that walks a tree of <see cref="QueryNode"/>s.
/// </summary>
/// <typeparam name="T">Type that the visitor will return after visiting this token.</typeparam>
/// <param name="visitor">An implementation of the visitor interface.</param>
/// <returns>An object whose type is determined by the type parameter of the visitor.</returns>
/// <exception cref="System.ArgumentNullException">throws if the input visitor is null.</exception>
public override T Accept<T>(QueryNodeVisitor<T> visitor)
{
ExceptionUtils.CheckArgumentNotNull(visitor, "visitor");
return visitor.Visit(this);
}
}
}
Loading