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

Add dynamic single-valued property support for filter expressions #1330

Merged

Conversation

gathogojr
Copy link
Contributor

@gathogojr gathogojr commented Oct 25, 2024

Add dynamic single-valued property support for filter expressions

Scenarios:

  • $filter={DynamicSingleValuedProperty}/{DynamicSingleValuedProperty} eq {value}
  • $filter={DynamicSingleValuedProperty}/{DynamicSingleValuedProperty}/{DynamicSingleValuedProperty} eq {value}
  • $filter={DynamicSingleValuedProperty}/{DeclaredSingleValuedProperty}/{DynamicSingleValuedProperty} eq {value}
  • $filter={DynamicSingleValuedProperty}/{DynamicSingleValuedProperty}/{DeclaredSingleValuedProperty} eq {value}
  • $filter={DeclaredSingleValuedProperty}/{DynamicSingleValuedProperty} eq {value}
  • $filter={DeclaredSingleValuedProperty}/{DynamicSingleValuedProperty}/{DynamicSingleValuedProperty} eq {value}
  • $filter={DeclaredSingleValuedProperty}/{DeclaredSingleValuedProperty}/{DynamicSingleValuedProperty} eq {value}
  • $filter={DeclaredSingleValuedProperty}/{DynamicSingleValuedProperty}/{DeclaredSingleValuedProperty} eq {value}
  • $filter={DynamicSingleValuedProperty}/{DynamicSingleValuedProperty} in {collection}
  • $filter={DynamicSingleValuedProperty}/{DynamicSingleValuedProperty}/{DynamicSingleValuedProperty} in {collection}
  • $filter={DynamicSingleValuedProperty}/{DeclaredSingleValuedProperty}/{DynamicSingleValuedProperty} in {collection}
  • $filter={DynamicSingleValuedProperty}/{DynamicSingleValuedProperty}/{DeclaredSingleValuedProperty} in {collection}
  • $filter={DeclaredSingleValuedProperty}/{DynamicSingleValuedProperty} in {collection}
  • $filter={DeclaredSingleValuedProperty}/{DynamicSingleValuedProperty}/{DynamicSingleValuedProperty} in {collection}
  • $filter={DeclaredSingleValuedProperty}/{DeclaredSingleValuedProperty}/{DynamicSingleValuedProperty} in {collection}
  • $filter={DeclaredSingleValuedProperty}/{DynamicSingleValuedProperty}/{DeclaredSingleValuedProperty} in {collection}

@gathogojr gathogojr force-pushed the feature/dynamic-property-support-for-filter branch 2 times, most recently from 1ff9838 to 3a14c42 Compare October 28, 2024 09:02
@gathogojr gathogojr force-pushed the feature/dynamic-property-support-for-filter branch from 3a14c42 to 810cfba Compare October 28, 2024 11:36
WanjohiSammy
WanjohiSammy previously approved these changes Oct 29, 2024
Comment on lines 1529 to 1531
Expression getDeclaredPropertyValueExpr = Expression.Convert(
Expression.Call(getPropertyExpr, "GetValue", Type.EmptyTypes, sourceExpr),
typeof(object));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this convert the result of PropertyInfo.GetValue() into an object? Isn't the return type of GetValue() already object?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@habbes I'll check if the Convert is redundant here

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@habbes Fixed

Comment on lines 1508 to 1518
if (openNode.Source is SingleValueOpenPropertyAccessNode openNodeParent)
{
sourceExpr = BindNestedDynamicPropertyAccessExpression(openNodeParent, context);
}

if (!(openNode.Source is SingleValueOpenPropertyAccessNode))
{
return BindDynamicPropertyAccessExpression(openNode, context);
}
else
{
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this if statements are a bit confusing or partially redundant. The second if statement seems to be the opposite of the first, and so could probably be handled by an else instead of writing the negated if statement. The else block part of the second if statement could be moved back indentation-wise and the else wrapper removed since the if statement has a return statement:

Suggested change
if (openNode.Source is SingleValueOpenPropertyAccessNode openNodeParent)
{
sourceExpr = BindNestedDynamicPropertyAccessExpression(openNodeParent, context);
}
if (!(openNode.Source is SingleValueOpenPropertyAccessNode))
{
return BindDynamicPropertyAccessExpression(openNode, context);
}
else
{
if (openNode.Source is SingleValueOpenPropertyAccessNode openNodeParent)
{
sourceExpr = BindNestedDynamicPropertyAccessExpression(openNodeParent, context);
}
else
{
return BindDynamicPropertyAccessExpression(openNode, context);
}
// rest of the code...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@habbes I'll try the refactor that you suggested but let me first explain the logic in this method.
Consider either of these two expressions:

  • Products?$filter=DynamicSegment/NestedDynamicSegment1/NestedDynamicSegment2/NestedDynamicSegment3
  • Products?$filter=DeclaredSegment/NestedDynamicSegment1/NestedDynamicSegment2/NestedDynamicSegment3

In both cases, the query binder will first attempt to bind NestedDynamicSegment3 to it's source (i.e. NestedDynamicSegment2). But we don't know the type for NestedDynamicSegment2 because it's also an open node. It's the same challenge if we attempt to bind NestedDynamicSegment2 to it's source (i.e. NestedDynamicSegment3).

For this reason, what this method first does is recursively call itself until we find a node whose source is not an open node.
Meaning recursion will stop if we find a node whose source is not SingleValueOpenPropertyAccessNode.
When we come across such a node, we bind it to its source which would either be a SingleValueNode or a ResourceRangeVariableReferenceNode.

Binding happens from the BindDynamicPropertyAccessExpression method that returns to us the expression that retrieves the dynamic property at the base of the path expression. The expression is the equivalent of:

{productInstance}.containerDict.Item["DynamicSegment"]

for the first expression, and

{productInstance}.DeclaredSegment.containerDict.Item["NestedDynamicSegment"]

for the second expression.

We then extend that expressions such that it can retrieve NestedDynamicSegment2 and NestedDynamicSegment2 as either declared or dynamic properties of the subsequent segment.


if (!(openNode.Source is SingleValueOpenPropertyAccessNode))
{
return BindDynamicPropertyAccessExpression(openNode, context);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is a DeclaredProperty/DynamicProperty scenario?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No. This is DynamicProperty/NestedDeclaredProperty and DynamicProperty/NestedDynamicProperty scenarios.

Note however that based on the output of the parser, both NestedDeclaredProperty and NestedDynamicProperty will be dynamic! To the parser, everything that follows a dynamic segment is a dynamic segment. We can't know at parse time that nested segment might resolve into a declared segment.

Expression.Constant(openNode.Name));

// Get declared property value
Expression getDeclaredPropertyValueExpr = Expression.Convert(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just for my own sanity check, this expression is the equivalent of (object)DynamicParentProperty.GetType().GetProperty("DeclareProperty").GetValue(DynamicParentPropertyInstance)?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@gathogojr Think of it this way...
The call to BindDynamicPropertyAccessExpression resolves into something like:

var dynamicPropertyValue = containerDict.Item["{dynamicPropertyName}"]

Then, the statements in this else block do something like:

Type dynamicPropertyType = dynamicPropertyValue.GetType();

PropertyInfo propertyInfo = dynamicPropertyType.GetProperty("{nestedPropertyName}");

/* ... Check that propertyInfo is not null ... */

object nestedDeclaredPropertyValue = propertyInfo.GetValue(dynamicPropertyValue);

The nestedDeclaredPropertyValue is returned if indeed the nested property is a declared property.

The logic in scenario 2 on the other hand does the equivalent of:

IEdmTypeReference edmTypeReference = context.Model.GetEdmTypeReference(dynamicPropertyType);

PropertyInfo containerPropertyInfo = GetDynamicPropertyContainer(edmTypeReference, nodeTypeKind, context.Model);

/* ... Check that containerPropertyInfo is not null ... */

Dictionary<string, object> nestedContainerDict = containerPropertyInfo.GetValue(dynamicPropertyValue);

object nestedDynamicPropertyValue = nestedContainerDict.Item["{nestedPropertyName}"]

The nestedDynamicPropertyValue is returned if indeed the nested property is a dynamic property property.

habbes
habbes previously approved these changes Oct 29, 2024
@gathogojr gathogojr dismissed stale reviews from habbes and WanjohiSammy via bef2002 October 29, 2024 18:24
@gathogojr gathogojr force-pushed the feature/dynamic-property-support-for-filter branch from cd9ecae to bef2002 Compare October 29, 2024 18:24
@gathogojr gathogojr force-pushed the feature/dynamic-property-support-for-filter branch from bef2002 to 17fc5d6 Compare October 30, 2024 05:24
@gathogojr gathogojr merged commit 75e562d into OData:main Oct 30, 2024
4 checks passed
@gathogojr gathogojr deleted the feature/dynamic-property-support-for-filter branch October 30, 2024 06:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants