Skip to content

Conversation

@gathogojr
Copy link
Contributor

@gathogojr gathogojr commented Nov 14, 2025

Issues

This pull request fixes #3385.

Description

This pull request adds first-class support for passing collection-valued properties and collection-valued literals as parameters to functions invoked inside the $compute query option, and fixes a parse-order defect that prevented selecting a computed property in $select when defined via $compute.

Before these changes:

  • Functions used in $compute could only reliably accept single-valued parameters (primitive/scalar).
  • Supplying a collection-valued property caused an exception to be thrown.
  • Supplying a collection-valued literal (e.g. [1,2,3]) was not consistently recognized as a semantic collection.
  • $select referencing a newly introduced alias from $compute (e.g. &$select=Name,Bonus) could throw, because $select/$expand was parsed before $compute, so the alias and function call hadn’t been materialized yet.

Key enhancements

  1. Collection-valued property parameters in $compute

    • You can now pass a collection-valued structural property directly to a custom function (registered via CustomUriFunctions.AddCustomUriFunction) or model function inside $compute.
    • Example: $compute=calculateTaxablePay(Salary,OvertimeHours) as TaxablePay&$select=Name,Salary,TaxablePay.
  2. Collection-valued literal parameters

    • Functions invoked in $compute can accept a bracketed collection literal as a parameter.
    • Example: $$compute=calculateSalaryBand(Salary,[5000,7000,9000]) as SalaryBand&$select=Name,SalaryBand.
    • Literal parsing supports numeric sequences.
  3. Edm function collection parameters

    • Edm functions registered accept collection-valued arguments (properties or literals) the same way as custom URI functions.
    • Example: $compute=Default.CalculateOvertimePay(salary=Salary,overtimeHours=OvertimeHours) as OvertimePay&$select=Name,Salary,OvertimePay.
    • Example: $compute=Default.GetRating(scale=[1,2,3,4,5]) as Rating&$select=Name,Rating.
  4. Parse order fix in ODataUriParser.ParseUri

    • ParseCompute() is now invoked before ParseSelectAndExpand().
    • This ensures computed aliases are available when $select is processed, eliminating the previous exception when selecting a computed property.

Internal changes

  • Function call binding extended to recognize and type-promote collection arguments coming from:
    • Collection-valued properties.
    • Bracketed collection literals in function parameter lists.
  • Type promotion utilities updated to interpret bracketed collection literal text only when the target parameter is a collection.
  • Ordering change in ParseUri prevents premature $select binding on non-existent aliases.
  • Metadata binding utilities enhanced to convert element types in collection constants where necessary.

Backward compatibility

  • Existing queries without collection-valued parameters in $compute are unaffected.
  • The revised call order (ParseCompute before ParseSelectExpand) only resolves previously failing scenarios; no semantic regressions expected.

Checklist (Uncheck if it is not completed)

  • Test cases added
  • Build and test with one-click build and test script passed

Additional work necessary

  • Update to AspNetCoreOData to take advantage of this change

@gathogojr gathogojr force-pushed the fix/3385-support-collection-as-uri-function-parameter branch 3 times, most recently from 1b0ab84 to a06f317 Compare November 19, 2025 14:04
@gathogojr gathogojr force-pushed the fix/3385-support-collection-as-uri-function-parameter branch 2 times, most recently from 6504a2e to 13346b1 Compare November 20, 2025 12:37
@gathogojr gathogojr force-pushed the fix/3385-support-collection-as-uri-function-parameter branch from 13346b1 to 2cb423d Compare November 20, 2025 13:38
/// Promotes types of arguments to match signature if possible.
/// </summary>
/// <param name="signature">The signature to match the types to.</param>
/// <param name="argumentNodes">The types to promote.</param>
Copy link
Member

Choose a reason for hiding this comment

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

Should we update this comment as well

Copy link
Member

@WanjohiSammy WanjohiSammy left a comment

Choose a reason for hiding this comment

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

LGTM

OrderByClause orderBy = this.ParseOrderBy();
SearchClause search = this.ParseSearch();
ApplyClause apply = this.ParseApply();
ComputeClause compute = this.ParseCompute();
Copy link
Member

Choose a reason for hiding this comment

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

Why not move 'ParseApply()'?

ParseApply() should be called first

Copy link
Member

@xuzhg xuzhg left a comment

Choose a reason for hiding this comment

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

:shipit:

for (int i = 0; i < candidate.Value.ArgumentTypes.Length; i++)
{
if (!CanPromoteNodeTo(argumentNodes[i], argumentTypes[i], candidate.Value.ArgumentTypes[i]))
if (!CanPromoteNodeTo(argumentMetadata[i].Node, argumentMetadata[i].TypeReference, candidate.Value.ArgumentTypes[i]))
Copy link
Contributor

Choose a reason for hiding this comment

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

What does this code do?

{
object result = ODataUriUtils.ConvertFromUriLiteral(
value: constantNode.LiteralText,
version: ODataVersion.V4,
Copy link
Contributor

Choose a reason for hiding this comment

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

Will the behaviour be the same for v4.01?

if(enumType.TryParse(memberIntegralValue, out IEdmEnumMember enumMember))
{
string literalText = ODataUriUtils.ConvertToUriLiteral(enumMember.Name, default(ODataVersion));
string literalText = ODataUriUtils.ConvertToUriLiteral(enumMember.Name, default);
Copy link
Contributor

Choose a reason for hiding this comment

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

Why this change?

if (convertedNode == null)
{
// Try to keep original literal text if meaningful
string literal = item.LiteralText ?? ODataUriUtils.ConvertToUriLiteral(item.Value, ODataVersion.V4);
Copy link
Contributor

Choose a reason for hiding this comment

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

Will the behaviour be the same on v4.01?

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.

Error when adding custom computed property from $compute to $select query option

4 participants