-
Notifications
You must be signed in to change notification settings - Fork 352
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
Add Dollar it in BuildUri response #1991
Changes from 10 commits
718c958
9cee5ea
a1df6b2
985eed6
558ffba
12fdbe8
f8930bf
e0fc7f2
cf66923
f60c37c
5234ec1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -10,6 +10,7 @@ namespace Microsoft.OData | |
using System.Collections.Generic; | ||
using System.Diagnostics; | ||
using System.Globalization; | ||
using System.Linq; | ||
using System.Text; | ||
using System.Text.RegularExpressions; | ||
using Microsoft.OData.Edm; | ||
|
@@ -470,7 +471,240 @@ internal String TranslateNode(QueryNode node) | |
internal String TranslateFilterClause(FilterClause filterClause) | ||
{ | ||
Debug.Assert(filterClause != null, "filterClause != null"); | ||
return this.TranslateNode(filterClause.Expression); | ||
|
||
return BindNode(filterClause.Expression, filterClause.RangeVariable as ResourceRangeVariable); | ||
} | ||
|
||
/// <summary> | ||
/// We return the <see cref="ResourceRangeVariableReferenceNode"/> within a <see cref="QueryNode"/> | ||
/// </summary> | ||
/// <param name="node">The node to extract the ResourceRangeVariableReferenceNode.</param> | ||
/// <returns>The extracted ResourceRangeVariableReferenceNode.</returns> | ||
private ResourceRangeVariableReferenceNode GetResourceRangeVariableReferenceNode(QueryNode node) | ||
{ | ||
switch(node.Kind) | ||
{ | ||
case QueryNodeKind.SingleValuePropertyAccess: | ||
SingleValuePropertyAccessNode singleValuePropertyAccessNode = node as SingleValuePropertyAccessNode; | ||
return GetResourceRangeVariableReferenceNode(singleValuePropertyAccessNode.Source); | ||
|
||
case QueryNodeKind.Convert: | ||
ConvertNode convertNode = node as ConvertNode; | ||
return GetResourceRangeVariableReferenceNode(convertNode.Source); | ||
|
||
case QueryNodeKind.Any: | ||
AnyNode anyNode = node as AnyNode; | ||
return GetResourceRangeVariableReferenceNode(anyNode.Source); | ||
|
||
case QueryNodeKind.SingleValueFunctionCall: | ||
SingleValueFunctionCallNode singleValueFunctionCallNode = node as SingleValueFunctionCallNode; | ||
return GetResourceRangeVariableReferenceNode(singleValueFunctionCallNode.Parameters.First()); | ||
|
||
case QueryNodeKind.ResourceRangeVariableReference: | ||
return node as ResourceRangeVariableReferenceNode; | ||
|
||
case QueryNodeKind.SingleValueOpenPropertyAccess: | ||
SingleValueOpenPropertyAccessNode singleValueOpenPropertyAccessNode = node as SingleValueOpenPropertyAccessNode; | ||
return GetResourceRangeVariableReferenceNode(singleValueOpenPropertyAccessNode.Source); | ||
|
||
case QueryNodeKind.SingleComplexNode: | ||
SingleComplexNode singleComplexNode = node as SingleComplexNode; | ||
return GetResourceRangeVariableReferenceNode(singleComplexNode.Source); | ||
|
||
case QueryNodeKind.CollectionComplexNode: | ||
CollectionComplexNode collectionComplexNode = node as CollectionComplexNode; | ||
return GetResourceRangeVariableReferenceNode(collectionComplexNode.Source); | ||
} | ||
|
||
return null; | ||
} | ||
|
||
/// <summary> | ||
/// Bind $it to the <see cref="QueryNode"/> translated string. | ||
/// </summary> | ||
/// <param name="node">node to bind.</param> | ||
/// <param name="filterClauseRangeVariable">The <see cref="FilterClause"/> range variable.</param> | ||
/// <returns>The translated string with $it binding.</returns> | ||
private string BindNode(QueryNode node, ResourceRangeVariable filterClauseRangeVariable) | ||
{ | ||
BinaryOperatorNode binaryNode = node as BinaryOperatorNode; | ||
InNode inNode = node as InNode; | ||
AnyNode anyNode = node as AnyNode; | ||
SingleValueFunctionCallNode singleValueFunctionCallNode = node as SingleValueFunctionCallNode; | ||
SingleValueOpenPropertyAccessNode singleValueOpenPropertyAccessNode = node as SingleValueOpenPropertyAccessNode; | ||
|
||
if (binaryNode != null) | ||
{ | ||
return BindBinaryOperatorNode(binaryNode, filterClauseRangeVariable); | ||
} | ||
else if (inNode != null) | ||
{ | ||
return BindInNode(inNode, filterClauseRangeVariable); | ||
} | ||
else if (anyNode != null) | ||
{ | ||
return BindAnyNode(anyNode, filterClauseRangeVariable); | ||
} | ||
else if (singleValueFunctionCallNode != null) | ||
{ | ||
return BindSingleValueFunctionCallNode(singleValueFunctionCallNode, filterClauseRangeVariable); | ||
} | ||
else if (singleValueOpenPropertyAccessNode != null) | ||
{ | ||
return BindSingleValueOpenPropertyAccess(singleValueOpenPropertyAccessNode, filterClauseRangeVariable); | ||
} | ||
else | ||
{ | ||
return this.TranslateNode(node); | ||
} | ||
} | ||
|
||
/// <summary> | ||
/// Bind $it to the <see cref="BinaryOperatorNode"/> translated string. | ||
/// </summary> | ||
/// <param name="node">node to bind.</param> | ||
/// <param name="filterClauseRangeVariable">The <see cref="FilterClause"/> range variable.</param> | ||
/// <returns>The translated string with $it binding.</returns> | ||
private string BindBinaryOperatorNode(BinaryOperatorNode node, ResourceRangeVariable filterClauseRangeVariable) | ||
{ | ||
string translatedNode = this.TranslateNode(node); | ||
ResourceRangeVariableReferenceNode leftRangeVariableNode = GetResourceRangeVariableReferenceNode(node.Left); | ||
ResourceRangeVariableReferenceNode rightRangeVariableNode = GetResourceRangeVariableReferenceNode(node.Right); | ||
|
||
BinaryOperatorKind binaryNodeKind = node.OperatorKind; | ||
|
||
string binaryOperator = this.BinaryOperatorNodeToString(binaryNodeKind); | ||
string[] binarySeparator = { binaryOperator }; | ||
string[] substrings = translatedNode.Trim().Split(binarySeparator, StringSplitOptions.RemoveEmptyEntries); | ||
string leftBinary = substrings[0].Trim(); | ||
string rightBinary = substrings[1].Trim(); | ||
|
||
if (leftRangeVariableNode != null && IsDifferentSource(filterClauseRangeVariable, leftRangeVariableNode)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
So this logic is assuming that, if the range variable is a different source, the range variable must be $it, right? In which case, $this would work because it's the same type as the filterClauseRangeVariable so it will just fall through? How would this logic change when if we supported $root? Minimally, we need to document these assumptions and add tests (if they don't already exist) for these different cases. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good catch! |
||
{ | ||
leftBinary = "$it/" + leftBinary; | ||
KenitoInc marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
|
||
if (rightRangeVariableNode != null && IsDifferentSource(filterClauseRangeVariable, rightRangeVariableNode)) | ||
{ | ||
rightBinary = "$it/" + rightBinary; | ||
} | ||
|
||
return leftBinary + ' ' + binaryOperator + ' ' + rightBinary; | ||
} | ||
|
||
/// <summary> | ||
/// Bind $it to the <see cref="InNode"/> translated string. | ||
/// </summary> | ||
/// <param name="node">node to bind.</param> | ||
/// <param name="filterClauseRangeVariable">The <see cref="FilterClause"/> range variable.</param> | ||
/// <returns>The translated string with $it binding.</returns> | ||
private string BindInNode(InNode node, ResourceRangeVariable filterClauseRangeVariable) | ||
{ | ||
string translatedNode = this.TranslateNode(node); | ||
ResourceRangeVariableReferenceNode leftRangeVariableNode = GetResourceRangeVariableReferenceNode(node.Left); | ||
|
||
string inOperator = ExpressionConstants.KeywordIn; | ||
string[] inSeparator = { inOperator }; | ||
string[] substrings = translatedNode.Trim().Split(inSeparator, StringSplitOptions.RemoveEmptyEntries); | ||
string leftIn = substrings[0]; | ||
string rightIn = substrings[1]; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This logic only needs to execute if the sources are different, so it would be better to move it within your if statement. |
||
|
||
if (leftRangeVariableNode != null && IsDifferentSource(filterClauseRangeVariable, leftRangeVariableNode)) | ||
{ | ||
leftIn = "$it/" + leftIn; | ||
translatedNode = leftIn + inOperator + rightIn; | ||
} | ||
KenitoInc marked this conversation as resolved.
Show resolved
Hide resolved
|
||
return translatedNode; | ||
} | ||
|
||
/// <summary> | ||
/// Bind $it to the <see cref="AnyNode"/> translated string. | ||
/// </summary> | ||
/// <param name="node">node to bind.</param> | ||
/// <param name="filterClauseRangeVariable">The <see cref="FilterClause"/> range variable.</param> | ||
/// <returns>The translated string with $it binding.</returns> | ||
private string BindAnyNode(AnyNode node, ResourceRangeVariable filterClauseRangeVariable) | ||
{ | ||
string translatedNode = this.TranslateNode(node); | ||
ResourceRangeVariableReferenceNode leftRangeVariableNode = GetResourceRangeVariableReferenceNode(node); | ||
|
||
const string slashAny = "/any"; | ||
KenitoInc marked this conversation as resolved.
Show resolved
Hide resolved
|
||
string[] anySeparator = { slashAny }; | ||
string[] substrings = translatedNode.Trim().Split(anySeparator, StringSplitOptions.RemoveEmptyEntries); | ||
string leftAnyNodeSubstring = substrings[0]; | ||
string rightAnyNodeSubstring = substrings[1]; | ||
|
||
if (leftRangeVariableNode != null && IsDifferentSource(filterClauseRangeVariable, leftRangeVariableNode)) | ||
{ | ||
leftAnyNodeSubstring = "$it/" + leftAnyNodeSubstring; | ||
translatedNode = leftAnyNodeSubstring + slashAny + rightAnyNodeSubstring; | ||
} | ||
KenitoInc marked this conversation as resolved.
Show resolved
Hide resolved
|
||
return translatedNode; | ||
} | ||
|
||
/// <summary> | ||
/// Bind $it to the <see cref="SingleValueFunctionCallNode"/> translated string. | ||
/// </summary> | ||
/// <param name="node">node to bind.</param> | ||
/// <param name="filterClauseRangeVariable">The <see cref="FilterClause"/> range variable.</param> | ||
/// <returns>The translated string with $it binding.</returns> | ||
private string BindSingleValueFunctionCallNode(SingleValueFunctionCallNode node, ResourceRangeVariable filterClauseRangeVariable) | ||
{ | ||
string translatedNode = this.TranslateNode(node); | ||
ResourceRangeVariableReferenceNode firstParameterRangeVariableNode = GetResourceRangeVariableReferenceNode(node); | ||
|
||
char[] separators = { '(', ')' }; | ||
string[] subtrings = translatedNode.Trim().Split(separators, StringSplitOptions.RemoveEmptyEntries); | ||
string withinBrackets = subtrings[1]; | ||
string[] parameters = withinBrackets.Trim().Split(','); | ||
KenitoInc marked this conversation as resolved.
Show resolved
Hide resolved
|
||
string leftParameter = parameters[0]; | ||
string rightParameter = parameters.Length == 2 ? parameters[1] : String.Empty; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. all of this logic should be moved inside the conditional and only executed in the case that we are inserting the $it. |
||
|
||
if (firstParameterRangeVariableNode != null && IsDifferentSource(filterClauseRangeVariable, firstParameterRangeVariableNode)) | ||
{ | ||
leftParameter = "$it/" + leftParameter; | ||
translatedNode = parameters.Length == 1 ? subtrings[0] + '(' + leftParameter + ')' : subtrings[0] + '(' + leftParameter + ',' + rightParameter + ')'; | ||
} | ||
KenitoInc marked this conversation as resolved.
Show resolved
Hide resolved
|
||
return translatedNode; | ||
} | ||
|
||
/// <summary> | ||
/// Bind $it to the <see cref="SingleValueOpenPropertyAccessNode"/> translated string. | ||
/// </summary> | ||
/// <param name="node">node to bind.</param> | ||
/// <param name="filterClauseRangeVariable">The <see cref="FilterClause"/> range variable.</param> | ||
/// <returns>The translated string with $it binding.</returns> | ||
private string BindSingleValueOpenPropertyAccess(SingleValueOpenPropertyAccessNode node, ResourceRangeVariable filterClauseRangeVariable) | ||
{ | ||
string translatedNode = this.TranslateNode(node); | ||
ResourceRangeVariableReferenceNode rangeVariableNode = GetResourceRangeVariableReferenceNode(node); | ||
|
||
if (rangeVariableNode != null && IsDifferentSource(filterClauseRangeVariable, rangeVariableNode)) | ||
{ | ||
translatedNode = "$it/" + translatedNode; | ||
} | ||
KenitoInc marked this conversation as resolved.
Show resolved
Hide resolved
|
||
return translatedNode; | ||
} | ||
|
||
/// <summary> | ||
/// Check whether Navigation source of the FilterClause rangeVariable is different from the Expression rangeVariable. | ||
/// </summary> | ||
/// <param name="filterClauseRangeVariable"><see cref="FilterClause"/> rangeVariable.</param> | ||
/// <param name="rangeVariableReferenceNode">The rangeVariable of the expression.</param> | ||
/// <returns>If Navigation Source are different, returns true. Otherwise false.</returns> | ||
private static bool IsDifferentSource(ResourceRangeVariable filterClauseRangeVariable, ResourceRangeVariableReferenceNode rangeVariableReferenceNode) | ||
{ | ||
Debug.Assert(filterClauseRangeVariable != null, "filterClause != null"); | ||
Debug.Assert(rangeVariableReferenceNode != null, "rangeVariableReferenceNode != null"); | ||
|
||
if (filterClauseRangeVariable.NavigationSource != rangeVariableReferenceNode.NavigationSource) | ||
{ | ||
return true; | ||
} | ||
else | ||
{ | ||
return false; | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. could simplify to: |
||
} | ||
|
||
/// <summary>Translates a <see cref="OrderByClause"/> into a string.</summary> | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -652,6 +652,46 @@ public void SelectWithNestedExpandShouldWork() | |
Assert.Equal("http://gobbledygook/People?$select=" + Uri.EscapeDataString("Name,MyOpenAddress/Test") + "&$expand=" + Uri.EscapeDataString("MyDog($filter=Color eq 'Brown';$orderby=Color;$search=Color)"), actualUri.OriginalString); | ||
} | ||
|
||
[Fact] | ||
public void ExpandWithDollarItInFilterBinaryOperatorShouldWork() | ||
{ | ||
Uri queryUri = new Uri("People?$expand=MyDog($select=Color;$expand=LionsISaw($filter=ID1 eq $it/ID))", UriKind.Relative); | ||
Uri actualUri = UriBuilder(queryUri, ODataUrlKeyDelimiter.Parentheses, settings); | ||
Assert.Equal("http://gobbledygook/People?$expand=" + Uri.EscapeDataString("MyDog($select=Color;$expand=LionsISaw($filter=ID1 eq $it/ID))"), actualUri.OriginalString); | ||
} | ||
|
||
[Fact] | ||
public void ExpandWithDollarItInFilterComplexBinaryOperatorShouldWork() | ||
{ | ||
Uri queryUri = new Uri("People?$expand=MyDog($filter=$it/MyAddress/City eq 'Seattle')", UriKind.Relative); | ||
Uri actualUri = UriBuilder(queryUri, ODataUrlKeyDelimiter.Parentheses, settings); | ||
Assert.Equal("http://gobbledygook/People?$expand=" + Uri.EscapeDataString("MyDog($filter=$it/MyAddress/City eq 'Seattle')"), actualUri.OriginalString); | ||
} | ||
|
||
[Fact] | ||
public void ExpandWithDollarItInFilterInOperatorShouldWork() | ||
{ | ||
Uri queryUri = new Uri("People?$expand=MyDog($filter=$it/ID in ['1', '2', '3'])", UriKind.Relative); | ||
Uri actualUri = UriBuilder(queryUri, ODataUrlKeyDelimiter.Parentheses, settings); | ||
Assert.Equal("http://gobbledygook/People?$expand=" + Uri.EscapeDataString("MyDog($filter=$it/ID in ['1', '2', '3'])"), actualUri.OriginalString); | ||
} | ||
|
||
[Fact] | ||
public void ExpandWithDollarItInFilterAnyOperatorShouldWork() | ||
{ | ||
Uri queryUri = new Uri("People?$expand=MyDog($filter=$it/PreviousAddresses/any(d:d/City eq 'Seattle'))", UriKind.Relative); | ||
Uri actualUri = UriBuilder(queryUri, ODataUrlKeyDelimiter.Parentheses, settings); | ||
Assert.Equal("http://gobbledygook/People?$expand=" + Uri.EscapeDataString("MyDog($filter=$it/PreviousAddresses/any(d:d/City eq 'Seattle'))"), actualUri.OriginalString); | ||
} | ||
|
||
[Fact] | ||
public void ExpandWithDollarItInFilterFunctionCallShouldWork() | ||
{ | ||
Uri queryUri = new Uri("People?$expand=MyDog($filter=endswith($it/Name,'abcd'))", UriKind.Relative); | ||
Uri actualUri = UriBuilder(queryUri, ODataUrlKeyDelimiter.Parentheses, settings); | ||
Assert.Equal("http://gobbledygook/People?$expand=" + Uri.EscapeDataString("MyDog($filter=endswith($it/Name,'abcd'))"), actualUri.OriginalString); | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. do we have variants of each of these that don't use $it, and instead assume the range variable of the filter clause? |
||
|
||
[Fact] | ||
public void ExpandWithNestedQueryOptionsShouldWork() | ||
{ | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Alternatively,
I however don't know if the cost of doing all 5 explicit conversions using the
as
operator regardless is significant. In this case it might not matter since the most common scenario is handled by theelse
block and all the explicit conversions will have been done either way.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let me hear the opinions of others on this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I had the same thought -- we want to limit the number of casts.
Can we switch on QueryNode.Kind (or QueryNode.InternalKind)?
In reply to: 574336006 [](ancestors = 574336006)