-
Notifications
You must be signed in to change notification settings - Fork 349
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
Fix type casting exceptions in 'isof' and 'cast' calls. #3117
base: main
Are you sure you want to change the base?
Changes from all commits
1f406c8
981b428
a460cbf
bab5e32
df8bfe6
4d38f5f
8483730
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 |
---|---|---|
|
@@ -769,6 +769,31 @@ public void IsOfFunctionWorksWithOrWithoutSingleQuotesOnType() | |
singleValueFunctionCallNode.Parameters.ElementAt(1).ShouldBeConstantQueryNode("Edm.String"); | ||
} | ||
|
||
[Theory] | ||
[InlineData("isof(Fully.Qualified.Namespace.Employee)")] | ||
[InlineData("isof('Fully.Qualified.Namespace.Employee')")] | ||
public void IsOfFunctionWithOneParameter_WithOrWithoutSingleQuotesOnTypeParameter_WorksAsExpected(string filterQuery) | ||
{ | ||
// Arrange & Act | ||
FilterClause filter = ParseFilter(filterQuery, HardCodedTestModel.TestModel, HardCodedTestModel.GetPersonType(), HardCodedTestModel.GetPeopleSet()); | ||
|
||
// Assert | ||
SingleValueFunctionCallNode singleValueFunctionCallNode = filter.Expression.ShouldBeSingleValueFunctionCallQueryNode("isof"); | ||
ResourceRangeVariableReferenceNode rangeVariableReference = singleValueFunctionCallNode.Parameters.ElementAt(0).ShouldBeResourceRangeVariableReferenceNode("$it"); | ||
Assert.Equal("Fully.Qualified.Namespace.Person", rangeVariableReference.GetEdmTypeReference().FullName()); // $it is of type Person | ||
|
||
if(singleValueFunctionCallNode.Parameters.ElementAt(1) is ConstantNode) | ||
{ | ||
var constantNode = singleValueFunctionCallNode.Parameters.ElementAt(1).ShouldBeConstantQueryNode("Fully.Qualified.Namespace.Employee"); | ||
Assert.Equal("Fully.Qualified.Namespace.Employee", constantNode.Value); | ||
} | ||
else | ||
{ | ||
var singleResourceCastNode = singleValueFunctionCallNode.Parameters.ElementAt(1).ShouldBeSingleResourceCastNode(HardCodedTestModel.GetEmployeeTypeReference()); | ||
Assert.Equal("Fully.Qualified.Namespace.Employee", singleResourceCastNode.TypeReference.FullName()); | ||
} | ||
} | ||
|
||
[Fact] | ||
public void IsOfFunctionWithOneParameter_WithSingleQuotesOnTypeParameter_ShouldBeConstantQueryNode() | ||
{ | ||
|
@@ -805,6 +830,32 @@ public void IsOfFunctionWithOneParameter_WithoutSingleQuotesOnTypeParameter_Shou | |
Assert.Equal("Fully.Qualified.Namespace.Employee", singleResourceCastNode.TypeReference.FullName()); // Fully.Qualified.Namespace.Employee is the type parameter | ||
} | ||
|
||
[Theory] | ||
[InlineData("isof(MyAddress, 'Fully.Qualified.Namespace.HomeAddress')")] | ||
[InlineData("isof(MyAddress, Fully.Qualified.Namespace.HomeAddress)")] | ||
public void IsOfFunctionWithTwoParameters_WithSingleQuotesOnTypeParameter_WorksAsExpected(string filterQuery) | ||
{ | ||
// Arrange & Act | ||
FilterClause filter = ParseFilter(filterQuery, HardCodedTestModel.TestModel, HardCodedTestModel.GetPersonType(), HardCodedTestModel.GetPeopleSet()); | ||
|
||
// Assert | ||
SingleValueFunctionCallNode singleValueFunctionCallNode = filter.Expression.ShouldBeSingleValueFunctionCallQueryNode("isof"); | ||
SingleComplexNode singleComplexNode = singleValueFunctionCallNode.Parameters.ElementAt(0).ShouldBeSingleComplexNode(HardCodedTestModel.GetPersonAddressProp()); | ||
Assert.Equal("MyAddress", singleComplexNode.Property.Name); // MyAddress is the property name | ||
Assert.Equal("Fully.Qualified.Namespace.Address", singleComplexNode.GetEdmTypeReference().FullName()); // MyAddress is of type Address | ||
|
||
if (singleValueFunctionCallNode.Parameters.ElementAt(1) is ConstantNode) | ||
{ | ||
var constantNode = singleValueFunctionCallNode.Parameters.ElementAt(1).ShouldBeConstantQueryNode("Fully.Qualified.Namespace.HomeAddress"); | ||
Assert.Equal("Fully.Qualified.Namespace.HomeAddress", constantNode.Value); // 'Fully.Qualified.Namespace.Employee' is the type parameter | ||
} | ||
else | ||
{ | ||
var singleResourceCastNode = singleValueFunctionCallNode.Parameters.ElementAt(1).ShouldBeSingleResourceCastNode(HardCodedTestModel.GetHomeAddressReference()); | ||
Assert.Equal("Fully.Qualified.Namespace.HomeAddress", singleResourceCastNode.TypeReference.FullName()); // Fully.Qualified.Namespace.HomeAddress is the type parameter | ||
} | ||
} | ||
|
||
[Fact] | ||
public void IsOfFunctionWithTwoParameters_WithSingleQuotesOnTypeParameter_ShouldBeConstantQueryNode() | ||
{ | ||
|
@@ -844,27 +895,31 @@ public void IsOfFunctionWithTwoParameters_WithoutSingleQuotesOnTypeParameter_Sho | |
} | ||
|
||
[Theory] | ||
[InlineData("isof(Fully.Qualified.Namespace.Pet1)", "Fully.Qualified.Namespace.Pet1")] | ||
[InlineData("cast(Fully.Qualified.Namespace.HomeAddress)/City eq 'City1'", "Fully.Qualified.Namespace.HomeAddress")] | ||
public void IsOfAndCastFunctionsWithSingleParameterWithoutSingleQuotes_WithIncorrectType_ThrowException(string filterQuery, string fullyQualifiedTypeName) | ||
[InlineData("isof(Fully.Qualified.Namespace.Pet1)")] | ||
[InlineData("isof(MyAddress,Fully.Qualified.Namespace.Pet1)")] | ||
[InlineData("isof(null,Fully.Qualified.Namespace.Person)")] | ||
[InlineData("isof('',Fully.Qualified.Namespace.Person)")] | ||
public void IsOfFunctionsWithUnquotedTypeParameter_WithIncorrectType_DoesNotThrowException(string filterQuery) | ||
{ | ||
// Arrange & Act | ||
Action test = () => ParseFilter(filterQuery, HardCodedTestModel.TestModel, HardCodedTestModel.GetPersonType(), HardCodedTestModel.GetPeopleSet()); | ||
var exception = Record.Exception(() => ParseFilter(filterQuery, HardCodedTestModel.TestModel, HardCodedTestModel.GetPersonType(), HardCodedTestModel.GetPeopleSet())); | ||
|
||
// Assert | ||
test.Throws<ODataException>(Strings.MetadataBinder_HierarchyNotFollowed(fullyQualifiedTypeName, "Fully.Qualified.Namespace.Person")); | ||
Assert.Null(exception); | ||
} | ||
|
||
[Theory] | ||
[InlineData("isof(MyAddress,Fully.Qualified.Namespace.Pet1)", "Fully.Qualified.Namespace.Pet1")] | ||
[InlineData("cast(MyAddress,Fully.Qualified.Namespace.Employee)/WorkID eq 345", "Fully.Qualified.Namespace.Employee")] | ||
public void IsOfAndCastFunctionsWithTwoParameterWhereTypeParameterIsWithoutSingleQuotes_WithIncorrectType_ThrowException(string filterQuery, string fullyQualifiedTypeName) | ||
[InlineData("cast(Fully.Qualified.Namespace.HomeAddress)/City eq 'City1'")] | ||
[InlineData("cast(MyAddress,Fully.Qualified.Namespace.Employee)/WorkID eq 345")] | ||
[InlineData("cast(null,Fully.Qualified.Namespace.Employee)/WorkID eq 345")] | ||
[InlineData("cast('',Fully.Qualified.Namespace.Employee)/WorkID eq 345")] | ||
public void CastFunctionWithUnquotedTypeParameter_WithIncorrectType_DoesNotThrowException(string filterQuery) | ||
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. What is the expected behaviour of 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 is what is happening currently with
For
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. Thanks for this added context. Can you also shed light on the behaviour with respect to handling of types where a cast is not possible? Ideally, from the user's endpoint the behaviour should be the same regardless of whether they user quoted or non-quoted syntax, and regardless of our internal implementation details which you have highlighted. So could you clarify how we handle both supported and invalid cast scenarios in both quoted and unquoted variants? In which cases are we throwing an exception? Is the exception thrown consistent? Is that the expected behaviour? My expectation is that 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. With the current behavior, ODL does not throw exception or to be more specific, it doesn't check if target type is related to source type. It only bind the Let's look at this query:
AspNetCore will try to relate And then CreatePropertyAccessExpression will throw an expression when trying to get the Property Name from a null expression. An exception like below will be thrown:
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. Is this behaviour consistent with how cast is handled when the type param is quoted? 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.
|
||
{ | ||
// Arrange & Act | ||
Action test = () => ParseFilter(filterQuery, HardCodedTestModel.TestModel, HardCodedTestModel.GetPersonType(), HardCodedTestModel.GetPeopleSet()); | ||
var exception = Record.Exception(() => ParseFilter(filterQuery, HardCodedTestModel.TestModel, HardCodedTestModel.GetPersonType(), HardCodedTestModel.GetPeopleSet())); | ||
|
||
// Assert | ||
test.Throws<ODataException>(Strings.MetadataBinder_HierarchyNotFollowed(fullyQualifiedTypeName, "Fully.Qualified.Namespace.Address")); | ||
Assert.Null(exception); | ||
} | ||
|
||
[Fact] | ||
|
@@ -890,14 +945,31 @@ public void CastFunctionWorksForEnum() | |
bon.Right.ShouldBeEnumNode(HardCodedTestModel.TestModel.FindType("Fully.Qualified.Namespace.ColorPattern") as IEdmEnumType, 2L); | ||
} | ||
|
||
[Fact] | ||
public void CastFunctionWorksForCastFromNullToEnum() | ||
[Theory] | ||
[InlineData("cast(null, Fully.Qualified.Namespace.ColorPattern) eq Fully.Qualified.Namespace.ColorPattern'blue'")] | ||
[InlineData("cast(null, 'Fully.Qualified.Namespace.ColorPattern') eq Fully.Qualified.Namespace.ColorPattern'blue'")] | ||
public void CastFunctionWorksForCastFromNullToEnum(string filterQuery) | ||
{ | ||
FilterClause filter = ParseFilter("cast(null, Fully.Qualified.Namespace.ColorPattern) eq Fully.Qualified.Namespace.ColorPattern'blue'", HardCodedTestModel.TestModel, HardCodedTestModel.GetPersonType(), HardCodedTestModel.GetPeopleSet()); | ||
FilterClause filter = ParseFilter(filterQuery, HardCodedTestModel.TestModel, HardCodedTestModel.GetPersonType(), HardCodedTestModel.GetPeopleSet()); | ||
var bon = filter.Expression.ShouldBeBinaryOperatorNode(BinaryOperatorKind.Equal); | ||
var singleFunctionCallNode = bon.Left.ShouldBeSingleValueFunctionCallQueryNode("cast"); | ||
Assert.Null(Assert.IsType<ConstantNode>(singleFunctionCallNode.Parameters.ElementAt(0)).Value); | ||
singleFunctionCallNode.Parameters.ElementAt(1).ShouldBeConstantQueryNode("Fully.Qualified.Namespace.ColorPattern"); | ||
|
||
string fullyQualifiedTypeName; | ||
QueryNode secondParameterNode = singleFunctionCallNode.Parameters.ElementAt(1); | ||
if(secondParameterNode is SingleResourceCastNode singleResourceCastNode) | ||
{ | ||
secondParameterNode.ShouldBeSingleCastNode(HardCodedTestModel.GetColorPatternTypeReference()); | ||
fullyQualifiedTypeName = singleResourceCastNode.TypeReference.FullName(); | ||
} | ||
else | ||
{ | ||
ConstantNode constantNode = secondParameterNode.ShouldBeConstantQueryNode("Fully.Qualified.Namespace.ColorPattern"); | ||
fullyQualifiedTypeName = constantNode.Value as string; | ||
} | ||
|
||
Assert.Equal("Fully.Qualified.Namespace.ColorPattern", fullyQualifiedTypeName); | ||
Assert.Equal("Fully.Qualified.Namespace.ColorPattern'blue'", Assert.IsType<ConstantNode>(bon.Right).LiteralText); | ||
bon.Right.ShouldBeEnumNode(HardCodedTestModel.TestModel.FindType("Fully.Qualified.Namespace.ColorPattern") as IEdmEnumType, 2L); | ||
} | ||
|
||
|
@@ -912,16 +984,28 @@ public void LiteralTextShouldNeverBeNullForConstantNodeOfDottedIdentifier() | |
Assert.Equal("Fully.Qualified.Namespace.ColorPattern'blue'", Assert.IsType<ConstantNode>(bon.Right).LiteralText); | ||
} | ||
|
||
[Fact] | ||
public void CastFunctionProducesAnEntityType() | ||
[Theory] | ||
[InlineData("cast(MyDog, 'Fully.Qualified.Namespace.Dog')/Color eq 'blue'")] | ||
[InlineData("cast(MyDog, Fully.Qualified.Namespace.Dog)/Color eq 'blue'")] | ||
public void CastFunctionProducesAnEntityType(string filterQuery) | ||
{ | ||
FilterClause filter = ParseFilter("cast(MyDog, 'Fully.Qualified.Namespace.Dog')/Color eq 'blue'", HardCodedTestModel.TestModel, HardCodedTestModel.GetPersonType(), HardCodedTestModel.GetPeopleSet()); | ||
FilterClause filter = ParseFilter(filterQuery, HardCodedTestModel.TestModel, HardCodedTestModel.GetPersonType(), HardCodedTestModel.GetPeopleSet()); | ||
SingleResourceFunctionCallNode function = filter.Expression.ShouldBeBinaryOperatorNode(BinaryOperatorKind.Equal) | ||
.Left.ShouldBeSingleValuePropertyAccessQueryNode(HardCodedTestModel.GetDogColorProp()) | ||
.Source.ShouldBeSingleResourceFunctionCallNode("cast"); | ||
Assert.Equal(2, function.Parameters.Count()); | ||
function.Parameters.ElementAt(0).ShouldBeSingleNavigationNode(HardCodedTestModel.GetPersonMyDogNavProp()); | ||
function.Parameters.ElementAt(1).ShouldBeConstantQueryNode("Fully.Qualified.Namespace.Dog"); | ||
if(function.Parameters.ElementAt(1) is SingleResourceCastNode) | ||
{ | ||
var singleResourceCastNode = function.Parameters.ElementAt(1).ShouldBeSingleCastNode(HardCodedTestModel.GetDogTypeReference()); | ||
Assert.Equal("Fully.Qualified.Namespace.Dog", singleResourceCastNode.TypeReference.FullName()); | ||
} | ||
else | ||
{ | ||
var constantNode = function.Parameters.ElementAt(1).ShouldBeConstantQueryNode("Fully.Qualified.Namespace.Dog"); | ||
Assert.Equal("Fully.Qualified.Namespace.Dog", constantNode.Value as string); | ||
} | ||
|
||
Assert.IsType<BinaryOperatorNode>(filter.Expression).Right.ShouldBeConstantQueryNode("blue"); | ||
} | ||
|
||
|
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.
Is this behaviour consistent with how
isof
is handled when the type param is quoted?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.
Same applied to
isof
as discussed in #3117 (comment), however, the method inAspNetCore
that is responsible to create LINQ is different. Here is the method Expression BindIsOf(SingleValueFunctionCallNode node, QueryBinderContext context). I will also update this method to allow cast toSingleResourceCastNode
as it currently only support cast toConstantNode