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

Personal/garamamo/compartmentsearchexpression #212

Merged
merged 20 commits into from
Nov 28, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
4c6b2bc
Compartment Lookup work.
garamamo Sep 14, 2018
8763fac
PR Comments incorporated.
garamamo Sep 19, 2018
6a7a779
Merge commit.
garamamo Oct 25, 2018
e32bb8f
Merge branch 'master' of https://github.com/Microsoft/fhir-server int…
garamamo Oct 31, 2018
e7a8984
Added more unit tests.
garamamo Oct 31, 2018
64785fe
null check for searchindices.
garamamo Nov 1, 2018
f4a9d0e
Fixed some inconsistent namings, tab and csproj issues.
garamamo Nov 6, 2018
f45b6c4
Merge branch 'master' of https://github.com/Microsoft/fhir-server int…
garamamo Nov 7, 2018
e7589d2
Merge branch 'master' of https://github.com/Microsoft/fhir-server int…
garamamo Nov 7, 2018
74382bf
Compartment Query Work.
garamamo Nov 9, 2018
f359a9c
Tests added.
garamamo Nov 9, 2018
24337e3
Some comment fixes.
garamamo Nov 13, 2018
dfa912e
PR comments addressed.
garamamo Nov 14, 2018
923153c
Merge branch 'master' of https://github.com/Microsoft/fhir-server int…
garamamo Nov 14, 2018
83842a2
Fixed some format and added a test.
garamamo Nov 15, 2018
cb09f4e
Merge branch 'master' of https://github.com/Microsoft/fhir-server int…
garamamo Nov 15, 2018
eaaa494
Updated Read to Search in the route name plus some nit changes.
garamamo Nov 15, 2018
5ece60d
Merge branch 'master' of https://github.com/Microsoft/fhir-server int…
garamamo Nov 19, 2018
9f6371a
Added expression for CompartmentSearch and updated query builder.
garamamo Nov 19, 2018
9306cdd
Added unit tests.
garamamo Nov 20, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,17 @@ public static void ValidateMissingFieldExpression(
Assert.Equal(expectedFieldName, mfExpression.FieldName);
}

public static void ValidateCompartmentSearchExpression(
Expression expression,
CompartmentType compartmentType,
string compartmentId)
{
CompartmentSearchExpression compartmentSearchExpression = Assert.IsType<CompartmentSearchExpression>(expression);

Assert.Equal(compartmentType, compartmentSearchExpression.CompartmentType);
Assert.Equal(compartmentId, compartmentSearchExpression.CompartmentId);
}

public static IEnumerable<object[]> GetEnumAsMemberData<TEnum>(Predicate<TEnum> predicate = null)
{
return Enum.GetNames(typeof(TEnum))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -243,11 +243,133 @@ public void GivenASearchParamWithInvalidValue_WhenCreated_ThenSearchParamShouldB
Assert.Equal(queryParameters.Take(1), options.UnsupportedSearchParams);
}

[Theory]
[InlineData(ResourceType.Patient, CompartmentType.Patient, "123")]
[InlineData(ResourceType.Appointment, CompartmentType.Device, "abc")]
[InlineData(ResourceType.Patient, CompartmentType.Encounter, "aaa")]
[InlineData(ResourceType.Condition, CompartmentType.Practitioner, "9aa")]
[InlineData(ResourceType.Patient, CompartmentType.RelatedPerson, "fdsfasfasfdas")]
[InlineData(ResourceType.Claim, CompartmentType.Encounter, "ksd;/fkds;kfsd;kf")]
public void GivenAValidCompartmentSearch_WhenCreated_ThenCorrectCompartmentSearchExpressionShouldBeGenerated(ResourceType resourceType, CompartmentType compartmentType, string compartmentId)
{
SearchOptions options = CreateSearchOptions(
resourceType: resourceType.ToString(),
queryParameters: null,
compartmentType.ToString(),
compartmentId);

Assert.NotNull(options);
ValidateMultiaryExpression(
options.Expression,
MultiaryOperator.And,
e => ValidateResourceTypeSearchParameterExpression(e, resourceType.ToString()),
e => ValidateCompartmentSearchExpression(e, compartmentType, compartmentId));
}

[Theory]
[InlineData(CompartmentType.Patient, "123")]
[InlineData(CompartmentType.Device, "abc")]
[InlineData(CompartmentType.Encounter, "aaa")]
[InlineData(CompartmentType.Practitioner, "9aa")]
[InlineData(CompartmentType.RelatedPerson, "fdsfasfasfdas")]
[InlineData(CompartmentType.Encounter, "ksd;/fkds;kfsd;kf")]
public void GivenAValidCompartmentSearchWithNullResourceType_WhenCreated_ThenCorrectCompartmentSearchExpressionShouldBeGenerated(CompartmentType compartmentType, string compartmentId)
{
SearchOptions options = CreateSearchOptions(
resourceType: null,
queryParameters: null,
compartmentType.ToString(),
compartmentId);

Assert.NotNull(options);
ValidateCompartmentSearchExpression(options.Expression, compartmentType, compartmentId);
}

[Theory]
[InlineData(ResourceType.Patient, CompartmentType.Patient, "123")]
[InlineData(ResourceType.Appointment, CompartmentType.Device, "abc")]
[InlineData(ResourceType.Patient, CompartmentType.Encounter, "aaa")]
[InlineData(ResourceType.Condition, CompartmentType.Practitioner, "945934-5934")]
[InlineData(ResourceType.Patient, CompartmentType.RelatedPerson, "hgdfhdfgdf")]
[InlineData(ResourceType.Claim, CompartmentType.Encounter, "ksd;/fkds;kfsd;kf")]
public void GivenSearchParamsWithValidCompartmentSearch_WhenCreated_ThenCorrectCompartmentSearchExpressionShouldBeGenerated(ResourceType resourceType, CompartmentType compartmentType, string compartmentId)
{
const string paramName1 = "address-city";
const string paramName2 = "address-state";
const string value1 = "Seattle";
const string value2 = "WA";

Expression expression1 = Substitute.For<Expression>();
Expression expression2 = Substitute.For<Expression>();

_expressionParser.Parse(resourceType, paramName1, value1).Returns(expression1);
_expressionParser.Parse(resourceType, paramName2, value2).Returns(expression2);

var queryParameters = new[]
{
Tuple.Create(paramName1, value1),
Tuple.Create(paramName2, value2),
};

SearchOptions options = CreateSearchOptions(
resourceType: resourceType.ToString(),
queryParameters: queryParameters,
compartmentType.ToString(),
compartmentId);

Assert.NotNull(options);
Assert.NotNull(options.Expression);

ValidateMultiaryExpression(
options.Expression,
MultiaryOperator.And,
e => ValidateResourceTypeSearchParameterExpression(e, resourceType.ToString()),
e => Assert.Equal(expression1, e),
e => Assert.Equal(expression2, e),
e => ValidateCompartmentSearchExpression(e, compartmentType, compartmentId));
}

[Theory]
[InlineData("abc")]
[InlineData("12223a2424")]
[InlineData("fsdfsdf")]
[InlineData("patients")]
[InlineData("encounter")]
[InlineData("Devices")]
public void GivenInvalidCompartmentType_WhenCreated_ThenExceptionShouldBeThrown(string invalidCompartmentType)
{
InvalidSearchOperationException exception = Assert.Throws<InvalidSearchOperationException>(() => CreateSearchOptions(
resourceType: null,
queryParameters: null,
invalidCompartmentType,
"123"));

Assert.Equal(exception.Message, $"Compartment type {invalidCompartmentType} is invalid.");
}

[Theory]
[InlineData(" ")]
[InlineData("")]
[InlineData(" ")]
[InlineData("\t\t")]
public void GivenInvalidCompartmentId_WhenCreated_ThenExceptionShouldBeThrown(string invalidCompartmentId)
{
InvalidSearchOperationException exception = Assert.Throws<InvalidSearchOperationException>(() => CreateSearchOptions(
resourceType: ResourceType.Claim.ToString(),
queryParameters: null,
CompartmentType.Patient.ToString(),
invalidCompartmentId));

Assert.Equal("Compartment id is null or empty.", exception.Message);
}

private SearchOptions CreateSearchOptions(
string resourceType = DefaultResourceType,
IReadOnlyList<Tuple<string, string>> queryParameters = null)
IReadOnlyList<Tuple<string, string>> queryParameters = null,
string compartmentType = null,
string compartmentId = null)
{
return _factory.Create(resourceType, queryParameters);
return _factory.Create(compartmentType, compartmentId, resourceType, queryParameters);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// -------------------------------------------------------------------------------------------------
// 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 EnsureThat;
using Hl7.Fhir.Model;

namespace Microsoft.Health.Fhir.Core.Features.Search.Expressions
{
/// <summary>
/// Represents an expression for search performed for a compartment.
/// </summary>
public class CompartmentSearchExpression : Expression
{
/// <summary>
/// Initializes a new instance of the <see cref="CompartmentSearchExpression"/> class.
/// </summary>
/// <param name="compartmentType">The compartment type.</param>
/// <param name="compartmentId">The compartment id.</param>
public CompartmentSearchExpression(CompartmentType compartmentType, string compartmentId)
{
EnsureArg.IsTrue(Enum.IsDefined(typeof(CompartmentType), compartmentType), nameof(compartmentType));
EnsureArg.IsNotNullOrWhiteSpace(compartmentId, nameof(compartmentId));

CompartmentType = compartmentType;
CompartmentId = compartmentId;
}

/// <summary>
/// The compartment type.
/// </summary>
public CompartmentType CompartmentType { get; }

/// <summary>
/// The compartment id.
/// </summary>
public string CompartmentId { get; }

protected internal override void AcceptVisitor(IExpressionVisitor visitor)
{
EnsureArg.IsNotNull(visitor, nameof(visitor));
visitor.Visit(this);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,17 @@ public static StringExpression StringEquals(FieldName fieldName, int? componentI
return new StringExpression(StringOperator.Equals, fieldName, componentIndex, value, ignoreCase);
}

/// <summary>
/// Creates a <see cref="CompartmentSearchExpression"/> that represents a compartment search operation.
/// </summary>
/// <param name="compartmentType">The compartment type.</param>
/// <param name="compartmentId">The compartment id.</param>
/// <returns>A <see cref="CompartmentSearchExpression"/> that represents a compartment search operation.</returns>
public static CompartmentSearchExpression CompartmentSearch(CompartmentType compartmentType, string compartmentId)
{
return new CompartmentSearchExpression(compartmentType, compartmentId);
}

protected internal abstract void AcceptVisitor(IExpressionVisitor visitor);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -51,5 +51,11 @@ public interface IExpressionVisitor
/// </summary>
/// <param name="expression">The expression to visit.</param>
void Visit(StringExpression expression);

/// <summary>
/// Visits the <see cref="CompartmentSearchExpression"/>.
/// </summary>
/// <param name="expression">The expression to visit.</param>
void Visit(CompartmentSearchExpression expression);
}
}
10 changes: 0 additions & 10 deletions src/Microsoft.Health.Fhir.Core/Features/Search/SearchOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -59,15 +59,5 @@ internal set
/// Gets the list of search parameters that were not used in the search.
/// </summary>
public IReadOnlyList<Tuple<string, string>> UnsupportedSearchParams { get; internal set; }

/// <summary>
/// Gets the compartment type.
/// </summary>
public CompartmentType? CompartmentType { get; internal set; }

/// <summary>
/// Gets the compartment id.
/// </summary>
public string CompartmentId { get; internal set; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,23 @@ public SearchOptions Create(string compartmentType, string compartmentId, string
})
.Where(item => item != null));

if (!string.IsNullOrWhiteSpace(compartmentType))
garamamo marked this conversation as resolved.
Show resolved Hide resolved
{
if (Enum.TryParse(compartmentType, out CompartmentType parsedCompartmentType))
{
if (string.IsNullOrWhiteSpace(compartmentId))
{
throw new InvalidSearchOperationException(Core.Resources.CompartmentIdIsInvalid);
}

searchExpressions.Add(Expression.CompartmentSearch(parsedCompartmentType, compartmentId));
}
else
{
throw new InvalidSearchOperationException(string.Format(Core.Resources.CompartmentTypeIsInvalid, compartmentType));
}
}

if (searchExpressions.Count == 1)
{
options.Expression = searchExpressions[0];
Expand All @@ -156,24 +173,6 @@ public SearchOptions Create(string compartmentType, string compartmentId, string

options.UnsupportedSearchParams = unsupportedSearchParameters;

if (!string.IsNullOrWhiteSpace(compartmentType))
{
if (Enum.TryParse(compartmentType, out CompartmentType parsedCompartmentType))
{
if (string.IsNullOrWhiteSpace(compartmentId))
{
throw new InvalidSearchOperationException(Core.Resources.CompartmentIdIsInvalid);
}

options.CompartmentType = parsedCompartmentType;
options.CompartmentId = compartmentId;
}
else
{
throw new InvalidSearchOperationException(string.Format(Core.Resources.CompartmentTypeIsInvalid, compartmentType));
}
}

return options;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
using System.Globalization;
using System.Text;
using EnsureThat;
using Hl7.Fhir.Model;
using Microsoft.Health.Fhir.Core.Features.Persistence;
using Microsoft.Health.Fhir.Core.Features.Search;
using Microsoft.Health.Fhir.Core.Features.Search.Expressions;
Expand Down Expand Up @@ -56,6 +57,15 @@ internal class ExpressionQueryBuilder : IExpressionVisitor
{ StringOperator.StartsWith, "STARTSWITH" },
};

private static readonly Dictionary<CompartmentType, string> CompartmentTypeToParamName = new Dictionary<CompartmentType, string>
{
{ CompartmentType.Device, KnownResourceWrapperProperties.Device },
{ CompartmentType.Encounter, KnownResourceWrapperProperties.Encounter },
{ CompartmentType.Patient, KnownResourceWrapperProperties.Patient },
{ CompartmentType.Practitioner, KnownResourceWrapperProperties.Practitioner },
{ CompartmentType.RelatedPerson, KnownResourceWrapperProperties.RelatedPerson },
};

private readonly StringBuilder _queryBuilder;
private readonly QueryParameterManager _queryParameterManager;

Expand Down Expand Up @@ -261,6 +271,17 @@ public void Visit(StringExpression expression)
}
}

public void Visit(CompartmentSearchExpression expression)
{
AppendArrayContainsFilter(GetCompartmentIndicesParamName(expression.CompartmentType), expression.CompartmentId);
}

private static string GetCompartmentIndicesParamName(CompartmentType compartmentType)
{
Debug.Assert(CompartmentTypeToParamName.ContainsKey(compartmentType), $"CompartmentType {compartmentType} should have a corresponding index param");
return $"{KnownResourceWrapperProperties.CompartmentIndices}.{CompartmentTypeToParamName[compartmentType]}";
}

private void VisitBinary(string fieldName, BinaryOperator op, object value)
{
string paramName = AddParameterMapping(value);
Expand Down Expand Up @@ -322,5 +343,15 @@ private string AddParameterMapping(object value)

return _queryParameterManager.AddOrGetParameterMapping(value);
}

private void AppendArrayContainsFilter(string name, string value)
{
_queryBuilder
.Append("ARRAY_CONTAINS(")
.Append(SearchValueConstants.RootAliasName).Append(".").Append(name)
.Append(", ")
.Append(_queryParameterManager.AddOrGetParameterMapping(value))
.AppendLine(")");
}
}
}
Loading