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

388 Make Retrieve accept a parameters argument #521

Open
wants to merge 5 commits into
base: develop-2.0
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 2 additions & 0 deletions Cql/CodeGeneration.NET/CSharpLibrarySetToStreamsWriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
using System.Reflection;
using System.Text;
using Hl7.Cql.Compiler;
using Hl7.Cql.Operators;
using Microsoft.Extensions.Options;

namespace Hl7.Cql.CodeGeneration.NET
Expand Down Expand Up @@ -71,6 +72,7 @@ private static HashSet<string> BuildUsings(TypeResolver typeResolver)
typeof(IValueSetFacade).Namespace!,
typeof(Iso8601.DateIso8601).Namespace!,
typeof(PropertyInfo).Namespace!,
typeof(RetrieveParameters).Namespace!,
};

foreach (var @using in typeResolver.ModelNamespaces)
Expand Down
1 change: 1 addition & 0 deletions Cql/CoreTests/CSharp/CqlBooleanTest-1.0.000.g.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using Hl7.Cql.ValueSets;
using Hl7.Cql.Iso8601;
using System.Reflection;
using Hl7.Cql.Operators;
using Hl7.Fhir.Model;
using Range = Hl7.Fhir.Model.Range;
using Task = Hl7.Fhir.Model.Task;
Expand Down
1 change: 1 addition & 0 deletions Cql/CoreTests/CSharp/FHIRConversionTest-2023.0.0.g.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using Hl7.Cql.ValueSets;
using Hl7.Cql.Iso8601;
using System.Reflection;
using Hl7.Cql.Operators;
using Hl7.Fhir.Model;
using Range = Hl7.Fhir.Model.Range;
using Task = Hl7.Fhir.Model.Task;
Expand Down
1 change: 1 addition & 0 deletions Cql/CoreTests/CSharp/FHIRHelpers-4.0.1.g.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using Hl7.Cql.ValueSets;
using Hl7.Cql.Iso8601;
using System.Reflection;
using Hl7.Cql.Operators;
using Hl7.Fhir.Model;
using Range = Hl7.Fhir.Model.Range;
using Task = Hl7.Fhir.Model.Task;
Expand Down
3 changes: 2 additions & 1 deletion Cql/CoreTests/CSharp/TestRetrieve-1.0.1.g.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using Hl7.Cql.ValueSets;
using Hl7.Cql.Iso8601;
using System.Reflection;
using Hl7.Cql.Operators;
using Hl7.Fhir.Model;
using Range = Hl7.Fhir.Model.Range;
using Task = Hl7.Fhir.Model.Task;
Expand Down Expand Up @@ -191,7 +192,7 @@ public object MeasurementPeriod() =>

private Patient Patient_Value()
{
IEnumerable<Patient> a_ = context.Operators.RetrieveByValueSet<Patient>(default, default);
IEnumerable<Patient> a_ = context.Operators.Retrieve<Patient>(new RetrieveParameters(default, default, default, "http://hl7.org/fhir/StructureDefinition/Patient"));
Patient b_ = context.Operators.SingletonFrom<Patient>(a_);

return b_;
Expand Down
1 change: 1 addition & 0 deletions Cql/CoreTests/CSharp/TestRetrieveInclude-1.0.1.g.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using Hl7.Cql.ValueSets;
using Hl7.Cql.Iso8601;
using System.Reflection;
using Hl7.Cql.Operators;
using Hl7.Fhir.Model;
using Range = Hl7.Fhir.Model.Range;
using Task = Hl7.Fhir.Model.Task;
Expand Down
27 changes: 16 additions & 11 deletions Cql/CoreTests/Fhir/DataSourceTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Linq;
using Hl7.Cql.Operators;

namespace CoreTests.Fhir
{
Expand All @@ -17,27 +18,29 @@ public void FiltersOnResourceType()
{
var dr = buildDataSource();

var results = dr.RetrieveByCodes<Patient>();
var results = dr.Retrieve<Patient>(null);
results.Should().HaveCount(2);
results.Should().AllBeOfType<Patient>();
}

private static RetrieveParameters Pars(params CqlCode[] codes) => new(null, null, codes, null);

[TestMethod]
public void FiltersOnDefaultProp()
{
var dr = buildDataSource();

var results = dr.RetrieveByCodes<Observation>(new[] { new CqlCode("x", "http://nu.nl", null, null) });
var results = dr.Retrieve<Observation>(Pars(new CqlCode("x", "http://nu.nl")));
results.Should().HaveCount(1);
results.Should().AllBeOfType<Observation>().And.AllSatisfy(o => o.Code.Coding[0].Code.Should().Be("x"));

results = dr.RetrieveByCodes<Observation>(new[] { new CqlCode("z", "http://nu.nl", null, null) });
results = dr.Retrieve<Observation>(Pars(new CqlCode("z", "http://nu.nl")));
results.Should().BeEmpty();

Assert.ThrowsException<InvalidOperationException>(() =>
dr.RetrieveByCodes<Patient>(new[] { new CqlCode("x", "http://nu.nl", null, null) }));
Assert.ThrowsException<InvalidOperationException>(
() => dr.Retrieve<Patient>(Pars(new CqlCode("x", "http://nu.nl"))));

results = dr.RetrieveByCodes<Observation>(new[] { new CqlCode("x", "http://nu.nl", null, null), new CqlCode("y", "http://nu.nl", null, null) });
results = dr.Retrieve<Observation>(Pars(new CqlCode("x", "http://nu.nl"), new CqlCode("y", "http://nu.nl")));
results.Should().HaveCount(2);
results.Should().AllBeOfType<Observation>().And.AllSatisfy(o => o.Code.Coding[0].System.Should().Be("http://nu.nl"));
}
Expand All @@ -47,16 +50,18 @@ public void FiltersOnSpecificProp()
{
var dr = buildDataSource();
var model = new FhirTypeResolver(ModelInfo.ModelInspector);
var genderProp = model.GetProperty(model.ResolveType("{http://hl7.org/fhir}Patient"), "gender");
var genderProp = model.GetProperty(model.ResolveType("{http://hl7.org/fhir}Patient")!, "gender");
genderProp.Should().NotBeNull();

var results = dr.RetrieveByCodes<Patient>(new[] { new CqlCode("male", "http://hl7.org/fhir/administrative-gender", null, null) }, genderProp);
var results = dr.Retrieve<Patient>(new RetrieveParameters(genderProp, null,
[new CqlCode("male", "http://hl7.org/fhir/administrative-gender")], null));
results.Should().HaveCount(1);
results.Should().AllBeOfType<Patient>().And.AllSatisfy(p => p.Gender.Should().Be(AdministrativeGender.Male));

var activeProp = model.GetProperty(model.ResolveType("{http://hl7.org/fhir}Patient"), "active");
var activeProp = model.GetProperty(model.ResolveType("{http://hl7.org/fhir}Patient")!, "active");
Assert.ThrowsException<NotSupportedException>(() =>
dr.RetrieveByCodes<Patient>(new[] { new CqlCode("male", "http://hl7.org/fhir/administrative-gender", null, null) }, activeProp).ToList());
dr.Retrieve<Patient>(new RetrieveParameters(activeProp, null,
[new CqlCode("male", "http://hl7.org/fhir/administrative-gender")], null)).ToList());
}

private BundleDataSource buildDataSource()
Expand All @@ -75,4 +80,4 @@ private BundleDataSource buildDataSource()
return new BundleDataSource(bundle, new HashValueSetDictionary()); // we're not calling IsInValueSet, so we can pass an empty dict
}
}
}
}
15 changes: 4 additions & 11 deletions Cql/CoreTests/ModelTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -57,24 +57,17 @@ private class UnitTestPatient
public DateIso8601 birthDate { get; set; }
}

private class UnitTestDataSource : IDataSource
private class UnitTestDataSource(IEnumerable<object> data) : IDataSource
{
public UnitTestDataSource(IEnumerable<object> data)
{
Data = data?.ToList() ?? new List<object>();
}

public IList<object> Data { get; }
public IList<object> Data { get; } = data?.ToList() ?? new List<object>();

#if VNEXT
public event EventHandler DataChanged;
#endif

public IEnumerable<T> RetrieveByCodes<T>(IEnumerable<CqlCode> codes = null, PropertyInfo _ = null) where T : class =>
public IEnumerable<T> Retrieve<T>(RetrieveParameters _) where T : class =>
Data.OfType<T>();

public IEnumerable<T> RetrieveByValueSet<T>(CqlValueSet valueSet = null, PropertyInfo _ = null) where T : class =>
Data.OfType<T>();
}

private class UnitTestTypeResolver : BaseTypeResolver
Expand All @@ -92,4 +85,4 @@ private class UnitTestTypeResolver : BaseTypeResolver
internal override bool ShouldUseSourceObject(Type type, string propertyName) => true;
}
}
}
}
41 changes: 26 additions & 15 deletions Cql/Cql.Compiler/CqlOperatorsBinder.Specific.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using Hl7.Cql.Compiler.Expressions;
Expand Down Expand Up @@ -179,7 +180,8 @@ private Expression BindConvert(
private MethodCallExpression Retrieve(
Expression typeExpression,
Expression valueSetOrCodes,
Expression codePropertyExpression)
Expression codePropertyExpression,
Expression templateId)
{
if (typeExpression is not ConstantExpression ce || ce.Type != typeof(Type))
throw new ArgumentException("First parameter to Retrieve is expected to be a constant Type", nameof(typeExpression));
Expand All @@ -198,46 +200,55 @@ private MethodCallExpression Retrieve(
codePropertyExpression = Expression.Call(typeOf, method, Expression.Constant(propName));
}

return Retrieve(type, valueSetOrCodes, codePropertyExpression);
return Retrieve(type, valueSetOrCodes, codePropertyExpression, templateId);

}


protected MethodCallExpression Retrieve(
Type resourceType,
Expression codes,
Expression codeProperty)
Expression codeProperty,
Expression templateId)
{
MethodInfo? forType = null;
var forType = typeof(ICqlOperators).GetMethod(nameof(ICqlOperators.Retrieve))!.MakeGenericMethod(resourceType);
Expression codeExpression = NullExpression.ForType<IEnumerable<CqlCode>>();
Expression valuesetExpression = NullExpression.ForType<CqlValueSet>();

if (codes.Type == typeof(CqlValueSet))
{
var method = typeof(ICqlOperators).GetMethod(nameof(ICqlOperators.RetrieveByValueSet))!;
forType = method.MakeGenericMethod(resourceType);
}
valuesetExpression = codes;
else if (_typeResolver.IsListType(codes.Type))
{
var elementType = _typeResolver.GetListElementType(codes.Type, true)!;
if (elementType == typeof(CqlCode))
{
var method = typeof(ICqlOperators).GetMethod(nameof(ICqlOperators.RetrieveByCodes))!;
forType = method.MakeGenericMethod(resourceType);
codeExpression = codes;
}

// cql-to-elm blindly calls ToList when an expression ref is used
// for expressions like:
// [Source : "Definition returning List<Code>"]
// this ends up turning the codes expression into a List<List<Code>>
else if (_typeResolver.IsListType(elementType) && _typeResolver.GetListElementType(elementType) == typeof(CqlCode))
{
// call Flatten.
codes = Flatten(codes);
var method = typeof(ICqlOperators).GetMethod(nameof(ICqlOperators.RetrieveByCodes))!;
forType = method.MakeGenericMethod(resourceType);
codeExpression = Flatten(codes);
}
else throw new ArgumentException($"Retrieve statements with an ExpressionRef in the terminology position must be list of {nameof(CqlCode)} or a list of lists of {nameof(CqlCode)}. Instead, the list's element type is {elementType.Name}.", nameof(codes));
}
else
throw new ArgumentException($"Retrieve statements can only accept terminology expressions whose type is {nameof(CqlValueSet)} or {nameof(IEnumerable<CqlCode>)}. The expression provided has a type of {codes.Type.FullName}", nameof(codes));

var call = BindToDirectMethod(forType, codes, codeProperty);
var constructor = typeof(RetrieveParameters).GetConstructors(BindingFlags.Public | BindingFlags.Instance).Single();
var hasFilters = !codeProperty.IsNullConstant() || !codeExpression.IsNullConstant()
|| !valuesetExpression.IsNullConstant()
|| !templateId.IsNullConstant();

Expression createParameters = hasFilters
? Expression.New(constructor, codeProperty, valuesetExpression, codeExpression, templateId)
: NullExpression.ForType<RetrieveParameters>();

var call = BindToDirectMethod(forType, createParameters);
return call;
}

Expand Down Expand Up @@ -314,4 +325,4 @@ private MethodCallExpression SelectManyResults(
var call = BindToBestMethodOverload(nameof(ICqlOperators.SelectManyResults), [source, collectionSelector, resultSelector], [firstGenericArgument, secondGenericArgument, resultSelector.ReturnType])!;
return call;
}
}
}
2 changes: 1 addition & 1 deletion Cql/Cql.Compiler/CqlOperatorsBinder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ public virtual Expression BindToMethod(
"LateBoundProperty" => LateBoundProperty(args[0], args[1], args[2]),
"ListUnion" => Union(args[0], args[1]),
"ResolveValueSet" => ResolveValueSet(args[0]),
"Retrieve" => Retrieve(args[0], args[1], args[2]),
"Retrieve" => Retrieve(args[0], args[1], args[2], args[3]),
"Select" => Select(args[0], args[1]),
"SelectMany" => SelectMany(source: args[0], collectionSelectorLambda: args[1]),
"SelectManyResults" => SelectManyResults(source: args[0], collectionSelectorLambda: args[1], resultSelectorLambda: args[2]),
Expand Down
Loading