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

Updated ODataUriParser to allow select and expand property names that… #2437

Open
wants to merge 1 commit into
base: release-7.x
Choose a base branch
from

Conversation

corranrogue9
Copy link
Contributor

… conflict with reserved keywords

Issues

This pull request fixes #2410.

Description

The SelectExpandParser used by the ODataUriParser takes a two-pass approach to parsing the select and expand options. The first pass is intended to be syntactic only, while the second pass applies semantics. For code re-use, The syntactic pass leverages the ExpressionLexer. However, ExpressionLexer applies some semantics while parsing when a perceived "identifier" token is reached. I have added a flag in ExpressionLexer to optionally disable these semantics, and updated the SelectExpandParser to disable this behavior for its lexer.

Checklist (Uncheck if it is not completed)

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

Additional work necessary

If documentation update is needed, please add "Docs Needed" label to the issue and provide details about the required document change in the issue.

@pull-request-quantifier-deprecated

This PR has 293 quantified lines of changes. In general, a change size of upto 200 lines is ideal for the best PR experience!


Quantification details

Label      : Large
Size       : +291 -2
Percentile : 69.3%

Total files changed: 3

Change summary by file extension:
.cs : +291 -2

Change counts above are quantified counts, based on the PullRequestQuantifier customizations.

Why proper sizing of changes matters

Optimal pull request sizes drive a better predictable PR flow as they strike a
balance between between PR complexity and PR review overhead. PRs within the
optimal size (typical small, or medium sized PRs) mean:

  • Fast and predictable releases to production:
    • Optimal size changes are more likely to be reviewed faster with fewer
      iterations.
    • Similarity in low PR complexity drives similar review times.
  • Review quality is likely higher as complexity is lower:
    • Bugs are more likely to be detected.
    • Code inconsistencies are more likely to be detetcted.
  • Knowledge sharing is improved within the participants:
    • Small portions can be assimilated better.
  • Better engineering practices are exercised:
    • Solving big problems by dividing them in well contained, smaller problems.
    • Exercising separation of concerns within the code changes.

What can I do to optimize my changes

  • Use the PullRequestQuantifier to quantify your PR accurately
    • Create a context profile for your repo using the context generator
    • Exclude files that are not necessary to be reviewed or do not increase the review complexity. Example: Autogenerated code, docs, project IDE setting files, binaries, etc. Check out the Excluded section from your prquantifier.yaml context profile.
    • Understand your typical change complexity, drive towards the desired complexity by adjusting the label mapping in your prquantifier.yaml context profile.
    • Only use the labels that matter to you, see context specification to customize your prquantifier.yaml context profile.
  • Change your engineering behaviors
    • For PRs that fall outside of the desired spectrum, review the details and check if:
      • Your PR could be split in smaller, self-contained PRs instead
      • Your PR only solves one particular issue. (For example, don't refactor and code new features in the same PR).

How to interpret the change counts in git diff output

  • One line was added: +1 -0
  • One line was deleted: +0 -1
  • One line was modified: +1 -1 (git diff doesn't know about modified, it will
    interpret that line like one addition plus one deletion)
  • Change percentiles: Change characteristics (addition, deletion, modification)
    of this PR in relation to all other PRs within the repository.


Was this comment helpful? 👍  :ok_hand:  :thumbsdown: (Email)
Customize PullRequestQuantifier for this repository.

@@ -81,7 +81,7 @@ internal sealed class SelectExpandParser

// Sets up our lexer. We don't turn useSemicolonDelimiter on since the parsing code for expand options,
// which is the only thing that needs it, is in a different class that uses it's own lexer.
this.lexer = clauseToParse != null ? new ExpressionLexer(clauseToParse, false /*moveToFirstToken*/, false /*useSemicolonDelimiter*/) : null;
this.lexer = clauseToParse != null ? new ExpressionLexer(clauseToParse, false, false, false, false) : null;
Copy link
Member

Choose a reason for hiding this comment

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

false

so, only in select and expand, it's false, in others it's true?

We don't allow: $filter=true eq true, if one of the 'true' is for the property?

Copy link
Contributor Author

@corranrogue9 corranrogue9 Jun 22, 2022

Choose a reason for hiding this comment

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

There's a lot going on here. For $filter=true eq true, that's the same as just saying $filter=true or not providing the filter at all. We interpret the "true" literal as a boolean expression, which as far as I can tell is in accordance with the standard. If there is a property named "true", the above is still accurate, but the client can disambiguate that they are referring to the property by using $it, as in $filter=$it/true eq true. The reality is that we also have a bug in that case as well. I talked with Mike yesterday and we decided that we would address the $filter issue separately, since it implies a larger change (specifically, ExpressionLexer applies semantics, but many query options require a syntax-only pass first; this syntax-only pass using ExpressionLexer has issues across all of the query option implementations).

var parser = new ODataUriParser(model, new Uri("/foos?$select=null", UriKind.Relative));
var odataException = Assert.Throws<ODataException>(() => parser.ParseUri());

Assert.Equal("Could not find a property named 'null' on type 'foobar.foo'.", odataException.Message);
Copy link
Member

Choose a reason for hiding this comment

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

Should have another test where the type is defined as open -- in which case, no error should be returned.

@@ -108,6 +113,19 @@ internal ExpressionLexer(string expression, bool moveToFirstToken, bool useSemic
/// <param name="useSemicolonDelimiter">If true, the lexer will tokenize based on semicolons as well.</param>
/// <param name="parsingFunctionParameters">Whether the lexer is being used to parse function parameters. If true, will allow/recognize parameter aliases and typed nulls.</param>
internal ExpressionLexer(string expression, bool moveToFirstToken, bool useSemicolonDelimiter, bool parsingFunctionParameters)
Copy link
Member

Choose a reason for hiding this comment

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

This is internal -- how many places do we call this existing method from? Would it be easier to add the parameter to the existing overload and explicitly pass "true", rather than define a new overload?

Copy link
Contributor

Choose a reason for hiding this comment

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

Something unrelated --
I have in the past considered making the ExpressionLexer public and customizable.

/// <summary>
/// Whether or not to interpret the semantics of a token that appears to be an identifier (i.e. consider if it is a keyword instead of an identifier)
/// </summary>
private readonly bool applySemanticsToIdentifiers;
Copy link
Contributor

Choose a reason for hiding this comment

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

Where are we setting this?
I think we should have this in the ODataUriParserSettings.

Copy link
Member

Choose a reason for hiding this comment

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

how can customer config this?

Copy link
Contributor

@habbes habbes left a comment

Choose a reason for hiding this comment

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

Looks good to me. Left minor suggestions to improve legibility.

@@ -108,6 +113,19 @@ internal ExpressionLexer(string expression, bool moveToFirstToken, bool useSemic
/// <param name="useSemicolonDelimiter">If true, the lexer will tokenize based on semicolons as well.</param>
/// <param name="parsingFunctionParameters">Whether the lexer is being used to parse function parameters. If true, will allow/recognize parameter aliases and typed nulls.</param>
internal ExpressionLexer(string expression, bool moveToFirstToken, bool useSemicolonDelimiter, bool parsingFunctionParameters)
: this(expression, moveToFirstToken, useSemicolonDelimiter, parsingFunctionParameters, true)
Copy link
Contributor

Choose a reason for hiding this comment

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

For better legibility:

Suggested change
: this(expression, moveToFirstToken, useSemicolonDelimiter, parsingFunctionParameters, true)
: this(expression, moveToFirstToken, useSemicolonDelimiter, parsingFunctionParameters, applySemanticsToIdentifiers: true)

@@ -81,7 +81,7 @@ internal sealed class SelectExpandParser

// Sets up our lexer. We don't turn useSemicolonDelimiter on since the parsing code for expand options,
// which is the only thing that needs it, is in a different class that uses it's own lexer.
this.lexer = clauseToParse != null ? new ExpressionLexer(clauseToParse, false /*moveToFirstToken*/, false /*useSemicolonDelimiter*/) : null;
this.lexer = clauseToParse != null ? new ExpressionLexer(clauseToParse, false, false, false, false) : null;
Copy link
Contributor

Choose a reason for hiding this comment

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

Hard to tell what all these false params are for, suggestion to use named parameters in such cases:

Suggested change
this.lexer = clauseToParse != null ? new ExpressionLexer(clauseToParse, false, false, false, false) : null;
this.lexer = clauseToParse != null ? new ExpressionLexer(clauseToParse, moveToFirstToken: false, useSemicolonDelimiter: false, parsingFunctionParameters: false, applySemanticsToIdentifiers: false) : null;

}

/// <summary>
/// Selects a property named "null" when such a property doesn't exist on the entity
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 comment should specify that this returns an error. On first reading, I assumed it actually does allow you to $select a property name "null" without any issues, even when it doesn't exist.

}

/// <summary>
/// Expands a property named "null" when such a property doesn't exist on the entity
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 comment should mention that this results in an exception.

@xuzhg
Copy link
Member

xuzhg commented Jul 25, 2022

Others look good to me

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Property named "true" in $select throws exception
5 participants