-
Notifications
You must be signed in to change notification settings - Fork 521
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Search expression visitor updates (#491)
- Loading branch information
1 parent
420c5b0
commit c09f9ef
Showing
20 changed files
with
447 additions
and
108 deletions.
There are no files selected for viewing
79 changes: 79 additions & 0 deletions
79
...crosoft.Health.Fhir.Core.UnitTests/Features/Search/Expressions/ExpressionRewriterTests.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
// ------------------------------------------------------------------------------------------------- | ||
// Copyright (c) Microsoft Corporation. All rights reserved. | ||
// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. | ||
// ------------------------------------------------------------------------------------------------- | ||
|
||
using Microsoft.Health.Fhir.Core.Features.Search.Expressions; | ||
using Microsoft.Health.Fhir.Core.Models; | ||
using Xunit; | ||
|
||
namespace Microsoft.Health.Fhir.Core.UnitTests.Features.Search.Expressions | ||
{ | ||
public class ExpressionRewriterTests | ||
{ | ||
[Fact] | ||
public void GivenANoopRewriter_WhenVisiting_ReturnsTheSameExpressionInstances() | ||
{ | ||
var expressionRewriter = new NoopRewriter(); | ||
|
||
void VerifyVisit(Expression expression) | ||
{ | ||
Assert.Same(expression, expression.AcceptVisitor(expressionRewriter, null)); | ||
} | ||
|
||
var simpleExpression1 = Expression.Equals(FieldName.Number, null, 1M); | ||
var simpleExpression2 = Expression.Equals(FieldName.Number, null, 5M); | ||
VerifyVisit(simpleExpression1); | ||
VerifyVisit(Expression.SearchParameter(new SearchParameterInfo("my-param"), simpleExpression1)); | ||
VerifyVisit(Expression.Chained("Observation", "subject", "Patient", simpleExpression1)); | ||
VerifyVisit(Expression.CompartmentSearch("Patient", "x")); | ||
VerifyVisit(Expression.Missing(FieldName.Quantity, null)); | ||
VerifyVisit(Expression.MissingSearchParameter(new SearchParameterInfo("my-param"), true)); | ||
VerifyVisit(Expression.Or(simpleExpression1, simpleExpression2)); | ||
VerifyVisit(Expression.StringEquals(FieldName.String, null, "Bob", true)); | ||
} | ||
|
||
[Fact] | ||
public void GivenARewriterThatTurnsValuesIntoRanges_WhenVisiting_ReplacesSubexpressions() | ||
{ | ||
var expressionRewriter = new RangeRewriter(); | ||
|
||
void VerifyVisit(string expected, Expression inputExpression) | ||
{ | ||
Assert.Equal(expected, inputExpression.AcceptVisitor(expressionRewriter, null).ToString()); | ||
} | ||
|
||
var simpleExpression1 = Expression.Equals(FieldName.Number, null, 1M); | ||
var simpleExpression2 = Expression.Equals(FieldName.Number, null, 5M); | ||
string expectedAndString1 = "(And (FieldGreaterThanOrEqual Number 0) (FieldLessThanOrEqual Number 2))"; | ||
string expectedAndString2 = "(And (FieldGreaterThanOrEqual Number 4) (FieldLessThanOrEqual Number 6))"; | ||
|
||
VerifyVisit(expectedAndString1, simpleExpression1); | ||
|
||
VerifyVisit($"(Param my-param {expectedAndString1})", Expression.SearchParameter(new SearchParameterInfo("my-param"), simpleExpression1)); | ||
|
||
VerifyVisit($"(Chain subject:Patient {expectedAndString1})", Expression.Chained("Observation", "subject", "Patient", simpleExpression1)); | ||
VerifyVisit($"(Or {expectedAndString1} {expectedAndString2})", Expression.Or(simpleExpression1, simpleExpression2)); | ||
} | ||
|
||
public class NoopRewriter : ExpressionRewriter<object> | ||
{ | ||
} | ||
|
||
public class RangeRewriter : ExpressionRewriter<object> | ||
{ | ||
public override Expression VisitBinary(BinaryExpression expression, object context) | ||
{ | ||
// turn "Field = x" into "Field >= (x-1) and Field < (x+1) | ||
if (expression.BinaryOperator == BinaryOperator.Equal && expression.Value is decimal decimalValue) | ||
{ | ||
return Expression.And( | ||
Expression.GreaterThanOrEqual(fieldName: expression.FieldName, expression.ComponentIndex, decimalValue - 1), | ||
Expression.LessThanOrEqual(fieldName: expression.FieldName, expression.ComponentIndex, decimalValue + 1)); | ||
} | ||
|
||
return expression; | ||
} | ||
} | ||
} | ||
} |
46 changes: 46 additions & 0 deletions
46
...crosoft.Health.Fhir.Core.UnitTests/Features/Search/Expressions/ExpressionToStringTests.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
// ------------------------------------------------------------------------------------------------- | ||
// Copyright (c) Microsoft Corporation. All rights reserved. | ||
// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. | ||
// ------------------------------------------------------------------------------------------------- | ||
|
||
using System; | ||
using Microsoft.Health.Fhir.Core.Features.Search.Expressions; | ||
using Microsoft.Health.Fhir.Core.Models; | ||
using Xunit; | ||
|
||
namespace Microsoft.Health.Fhir.Core.UnitTests.Features.Search.Expressions | ||
{ | ||
public class ExpressionToStringTests | ||
{ | ||
[Fact] | ||
public void GivenAnExpression_WhenCallingToString_ReturnsAnUnderstandableString() | ||
{ | ||
VerifyExpression("(FieldEqual Quantity 1)", Expression.Equals(FieldName.Quantity, null, 1)); | ||
VerifyExpression("(FieldEqual QuantityCode 'a')", Expression.Equals(FieldName.QuantityCode, null, "a")); | ||
VerifyExpression("(FieldEqual [0].QuantityCode 'a')", Expression.Equals(FieldName.QuantityCode, 0, "a")); | ||
|
||
VerifyExpression("(StringEquals TokenText 'a')", Expression.StringEquals(FieldName.TokenText, null, "a", false)); | ||
VerifyExpression("(StringEqualsIgnoreCase TokenText 'a')", Expression.StringEquals(FieldName.TokenText, null, "a", true)); | ||
VerifyExpression("(StringEqualsIgnoreCase [0].TokenText 'a')", Expression.StringEquals(FieldName.TokenText, 0, "a", true)); | ||
|
||
VerifyExpression("(Param my-param (FieldEqual Quantity 'a'))", Expression.SearchParameter(new SearchParameterInfo("my-param"), Expression.Equals(FieldName.Quantity, null, "a"))); | ||
|
||
VerifyExpression("(MissingParam my-param)", Expression.MissingSearchParameter(new SearchParameterInfo("my-param"), true)); | ||
VerifyExpression("(NotMissingParam my-param)", Expression.MissingSearchParameter(new SearchParameterInfo("my-param"), false)); | ||
|
||
VerifyExpression("(And (FieldGreaterThan Quantity 1) (FieldLessThan Quantity 10))", Expression.And(Expression.GreaterThan(FieldName.Quantity, null, 1), Expression.LessThan(FieldName.Quantity, null, 10))); | ||
|
||
VerifyExpression("(MissingField Quantity)", Expression.Missing(FieldName.Quantity, null)); | ||
VerifyExpression("(MissingField [0].Quantity)", Expression.Missing(FieldName.Quantity, 0)); | ||
|
||
VerifyExpression("(Compartment Patient 'x')", Expression.CompartmentSearch("Patient", "x")); | ||
|
||
VerifyExpression("(Chain subject:Patient (FieldGreaterThan DateTimeEnd 2000-01-01T00:00:00.0000000))", Expression.Chained("Observation", "subject", "Patient", Expression.GreaterThan(FieldName.DateTimeEnd, null, new DateTime(2000, 1, 1)))); | ||
} | ||
|
||
private static void VerifyExpression(string expected, Expression expression) | ||
{ | ||
Assert.Equal(expected, expression.ToString()); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
30 changes: 30 additions & 0 deletions
30
src/Microsoft.Health.Fhir.Core/Features/Search/Expressions/ExpressionExtensions.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
// ------------------------------------------------------------------------------------------------- | ||
// Copyright (c) Microsoft Corporation. All rights reserved. | ||
// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. | ||
// ------------------------------------------------------------------------------------------------- | ||
|
||
using EnsureThat; | ||
|
||
namespace Microsoft.Health.Fhir.Core.Features.Search.Expressions | ||
{ | ||
public static class ExpressionExtensions | ||
{ | ||
/// <summary> | ||
/// Calls <see cref="Expression.AcceptVisitor{TContext,TOutput}"/> using | ||
/// <see cref="IExpressionVisitorWithInitialContext{TContext,TOutput}.InitialContext"/> | ||
/// as with the context argument | ||
/// </summary> | ||
/// <typeparam name="TContext">The type of the context parameter</typeparam> | ||
/// <typeparam name="TOutput">The return type of the visitor</typeparam> | ||
/// <param name="expression">The expression</param> | ||
/// <param name="visitor">The visitor</param> | ||
/// <returns>The output from the visit.</returns> | ||
public static TOutput AcceptVisitor<TContext, TOutput>(this Expression expression, IExpressionVisitorWithInitialContext<TContext, TOutput> visitor) | ||
{ | ||
EnsureArg.IsNotNull(expression, nameof(expression)); | ||
EnsureArg.IsNotNull(visitor, nameof(visitor)); | ||
|
||
return expression.AcceptVisitor(visitor, visitor.InitialContext); | ||
} | ||
} | ||
} |
Oops, something went wrong.