Skip to content
Permalink

Comparing changes

This is a direct comparison between two commits made in this repository or its related repositories. View the default comparison for this range or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: FirelyTeam/firely-cql-sdk
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: bc4104aeae0b6853012f7133e3e33f988867f1a2
Choose a base ref
..
head repository: FirelyTeam/firely-cql-sdk
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: 997a0862151c53707e8ec466447a53a183ccc98e
Choose a head ref
Showing with 746 additions and 496 deletions.
  1. +6 −2 Cql/CoreTests/Input/ValueSets/intensional-value-set.json
  2. +2 −3 Cql/CoreTests/QueriesTest.cs
  3. +98 −0 Cql/CoreTests/ValueSetFacadeTests.cs
  4. +126 −86 Cql/CoreTests/ValueSetTests.cs
  5. +2 −2 Cql/Cql.Abstractions/Abstractions/IEquivalenceComparer.cs
  6. +1 −1 Cql/Cql.Abstractions/PublicAPI.Unshipped.txt
  7. +5 −7 Cql/Cql.Firely/Comparers/IdentifierComparer.cs
  8. +13 −1 Cql/Cql.Firely/PublicAPI.Unshipped.txt
  9. +0 −146 Cql/Cql.Firely/ValueSetLoader.cs
  10. +213 −0 Cql/Cql.Firely/ValueSetSource.cs
  11. +37 −10 Cql/Cql.Runtime/Comparers/CqlCodeCqlComparer.cs
  12. +3 −2 Cql/Cql.Runtime/Comparers/CqlCodeCqlEquivalentComparer.cs
  13. 0 Cql/Cql.Runtime/{Converfsion → Conversion}/ConversionConstants.cs
  14. 0 Cql/Cql.Runtime/{Converfsion → Conversion}/IUnitConverter.cs
  15. 0 Cql/Cql.Runtime/{Converfsion → Conversion}/TypeConverter.cs
  16. 0 Cql/Cql.Runtime/{Converfsion → Conversion}/UcumConversionExtensions.cs
  17. 0 Cql/Cql.Runtime/{Converfsion → Conversion}/UnitConverter.cs
  18. +1 −1 Cql/Cql.Runtime/Operators/CqlOperators.ListOperators.cs
  19. +0 −1 Cql/Cql.Runtime/Operators/CqlOperators.cs
  20. +27 −4 Cql/Cql.Runtime/PublicAPI.Unshipped.txt
  21. +45 −30 Cql/Cql.Runtime/ValueSets/CqlCodeExtensions.cs
  22. +8 −49 Cql/Cql.Runtime/ValueSets/CqlValueSetFacade.cs
  23. +51 −88 Cql/Cql.Runtime/ValueSets/HashValueSetDictionary.cs
  24. +18 −6 Cql/Cql.Runtime/ValueSets/IValueSetDictionary.cs
  25. +12 −5 Cql/Cql.Runtime/ValueSets/IValueSetFacade.cs
  26. +50 −13 Cql/Cql.Runtime/ValueSets/InMemoryValueSet.cs
  27. +13 −26 Cql/Cql.Runtime/ValueSets/ValueSetUnion.cs
  28. +8 −6 Cql/CqlToElmTests/(tests)/RetrieveTest.cs
  29. +1 −1 Demo/CLI/Helpers/ResourceHelper.cs
  30. +2 −2 Demo/cql-demo.props
  31. +1 −1 cql-base.props
  32. +1 −1 cql-sdk.props
  33. +1 −1 submodules/Firely.Cql.Sdk.Integration.Runner
  34. +1 −1 submodules/Ncqa.DQIC
8 changes: 6 additions & 2 deletions Cql/CoreTests/Input/ValueSets/intensional-value-set.json
Original file line number Diff line number Diff line change
@@ -7,10 +7,14 @@
"include": [
{
"valueSet": [
"https://www.ncqa.org/fhir/valueset/2.16.840.1.113883.3.464.1004.1009",
"https://www.ncqa.org/fhir/valueset/2.16.840.1.113883.3.464.1004.1009"
]
},
{
"valueSet": [
"https://www.ncqa.org/fhir/valueset/2.16.840.1.113883.3.464.1004.1013"
]
}
]
}
}
}
5 changes: 2 additions & 3 deletions Cql/CoreTests/QueriesTest.cs
Original file line number Diff line number Diff line change
@@ -24,8 +24,7 @@ public static void Initialize(TestContext context)
var definitions = libraryExpressionBuilderScoped.ProcessLibrary(elmPackage);
QueriesDefinitions = definitions.CompileAll();
ValueSets = new HashValueSetDictionary();
ValueSets.Add("http://hl7.org/fhir/ValueSet/example-expansion",
new CqlCode("code", "system", null, null));
ValueSets.Add("http://hl7.org/fhir/ValueSet/example-expansion", [new CqlCode("code", "system")]);


elm = new FileInfo(@"Input\ELM\Test\Aggregates-1.0.0.json");
@@ -427,4 +426,4 @@ public void Aggregates_Multisource_query()
Assert.AreEqual(12, result);
}
}
}
}
98 changes: 98 additions & 0 deletions Cql/CoreTests/ValueSetFacadeTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
#nullable enable

using Hl7.Cql.Comparers;
using Hl7.Cql.Primitives;
using Hl7.Cql.ValueSets;

namespace CoreTests
{
[TestClass]
public class ValueSetFacadeTests
{
private static readonly IEnumerable<CqlCode> TestCodesA = new[] {
new CqlCode("a", "http://nu.nl"),
new CqlCode("b", "http://nu.nl"), new CqlCode("B", "http://nu.nl"),
new CqlCode("c", "HTTP://nu.nl"),
new CqlCode("d", null) };

private static readonly IEnumerable<CqlCode> TestCodesB = new[] {
new CqlCode("e", "http://nu.nl", display: "Letters"),
new CqlCode("f", "http://nu.nl", display: "LETTERS"),
new CqlCode("g", null, display: "Letters"),
new CqlCode("h", "http://nu.nl"),new CqlCode("h", "http://nu.nl"),new CqlCode("h", "http://nu.nl", display: "Letters"),
};

private const string nu = "http://nu.nl";
private const string NU = "HTTP://NU.NL";
private const string letters = "Letters";
private const string LETTERS = "LETTERS";

private static IValueSetFacade? _inMemoryVs;
private static IValueSetFacade? _unionVs;
private static IValueSetFacade? _cqlValueSetVs;

[ClassInitialize]
public static void Initialize(TestContext x)
{
var all = TestCodesA.Concat(TestCodesB);
_inMemoryVs = new InMemoryValueSet(all);
_unionVs = new InMemoryValueSet(TestCodesA).Union(new InMemoryValueSet(TestCodesB));

var dict = new HashValueSetDictionary();
dict.Add("valuesetA", all);
_cqlValueSetVs = dict.GetValueSet("valuesetA");
}

[TestMethod]
[DataRow("a", null, null, true, false, false)]
[DataRow("_", null, null, false, false, false)]
[DataRow("A", null, null, true, false, false)]
[DataRow("b", null, null, true, false, false)]
[DataRow("_", null, null, false, false, false)]
[DataRow("f", null, null, true, false, false)]

[DataRow("a", nu, null, true, true, true)]
[DataRow("a", NU, null, true, true, true)]
[DataRow("a", "crap", null, true, false, false)]
[DataRow("d", null, null, true, true, true)] // null is the explicit codesystem here
[DataRow("f", nu, null, true, true, false)]

[DataRow("a", nu, letters, true, true, false)]
[DataRow("e", nu, letters, true, true, true)]
[DataRow("e", NU, letters, true, true, true)]
[DataRow("e", nu, LETTERS, true, true, true)]
[DataRow("e", nu, null, true, true, false)]
[DataRow("e", "crap", null, true, false, false)]
[DataRow("e", nu, "crap", true, true, false)]
[DataRow("g", null, letters, true, true, true)]
[DataRow("g", null, "crap", true, true, false)]

public void FacadeFindsCodeWithDefaultComparer(string code, string? system, string? display, bool resultC, bool resultEquiv, bool resultEqual)
{
test(_inMemoryVs!, "in-mem");
test(_unionVs!, "union");
test(_cqlValueSetVs!, "facade");

void test(IValueSetFacade f, string where)
{
f.IsCodeInValueSet(code).Should().Be(resultC, because: $"we're using {where}.");
f.IsCodeInValueSet(code, system).Should().Be(resultEquiv, because: $"we're using {where}.");
f.IsCodeInValueSet(new CqlCode(code, system, display: display)).Should().Be(resultEqual, because: $"we're using {where}.");
}
}

[TestMethod]
public void EnumerateUniqueCodes()
{
var mf = _unionVs!;
var comparer = new CqlCodeCqlComparer().ToEqualityComparer();

mf.Count().Should().Be(9); // b and B are the same, h should appear with and without description

mf.Count(c => c.code!.ToLower() == "b").Should().Be(1);
mf.Count(c => c.code!.ToLower() == "h").Should().Be(2);
mf.Contains(new CqlCode("h", nu), comparer).Should().BeTrue();
mf.Contains(new CqlCode("h", nu, display: letters), comparer).Should().BeTrue();
}
}
}
212 changes: 126 additions & 86 deletions Cql/CoreTests/ValueSetTests.cs
Original file line number Diff line number Diff line change
@@ -1,90 +1,130 @@
using Hl7.Cql.Fhir;
using Hl7.Cql.Fhir.Extensions;
using Hl7.Fhir.Model;
#nullable enable

namespace CoreTests
using Hl7.Cql.Fhir;
using Hl7.Cql.Primitives;
using Hl7.Cql.ValueSets;
using Hl7.Fhir.Specification.Source;
using Hl7.Fhir.Specification.Terminology;

namespace CoreTests;

[TestClass]
public class ValueSetTests
{
[TestClass]
public class ValueSetTests
private static readonly CqlCode[] TestCodesA = new[] {
new CqlCode("a", "http://nu.nl"),
new CqlCode("b", "http://nu.nl"),
new CqlCode("c", "HTTP://nu.nl"),
new CqlCode("d", null) };

private static readonly CqlCode[] TestCodesB = new[] {
new CqlCode("e", "http://nu.nl", display: "Letters"),
new CqlCode("f", "http://nu.nl", display: "LETTERS"),
new CqlCode("g", null, display: "Letters"),
new CqlCode("h", "http://nu.nl"),
new CqlCode("h", "http://nu.nl", display: "Letters"),
};


[TestMethod]
public void CanReturnAllCodeInValueSet()
{
var dict = buildDict();
dict.TryGetCodesInValueSet("http://valuesetA", out var vsA).Should().BeTrue();
allCodesInA(vsA!);

dict.TryGetCodesInValueSet("http://valuesetB", out var vsB).Should().BeTrue();
allCodesInB(vsB!);
}

private void allCodesInA(IEnumerable<CqlCode> vs)
{
TestCodesA.All(c => vs.Contains(c)).Should().BeTrue();
vs.All(c => TestCodesA.Contains(c)).Should().BeTrue();
}

private void allCodesInB(IEnumerable<CqlCode> vs)
{
TestCodesB.All(c => vs.Contains(c)).Should().BeTrue();
vs.All(c => TestCodesB.Contains(c)).Should().BeTrue();
}

[TestMethod]
public void CanGetFacade()
{
var dict = buildDict();
var f = dict.GetValueSet("http://valuesetA");
allCodesInA(f!);
}

private HashValueSetDictionary buildDict()
{
var dict = new HashValueSetDictionary();

dict.Add("http://valuesetA", TestCodesA);
dict.Add("http://valuesetB", TestCodesB);

return dict;
}

[TestMethod]
public void HashDictonaryInternsCodes()
{
[TestMethod]
public void Intensional_Value_Set()
{
string[] files =
[
@"Input\ValueSets\intensional-value-set.json",
@"Input\ValueSets\2.16.840.1.113883.3.464.1004.1009.json",
@"Input\ValueSets\2.16.840.1.113883.3.464.1004.1013.json"
];
var valueSets = files.Select(path =>
{
using var fs = File.OpenRead(path);
var vs = fs.DeserializeJsonToFhir<ValueSet>();
return vs;
}).ToArray();

var loader = new ValueSetLoader(valueSets, false);
var vsd = loader.Load();

Assert.IsTrue(vsd.TryGetCodesInValueSet("https://www.ncqa.org/fhir/valueset/2.16.840.1.113883.3.464.1004.1009", out var codes1009));
Assert.IsTrue(vsd.TryGetCodesInValueSet("https://www.ncqa.org/fhir/valueset/2.16.840.1.113883.3.464.1004.1013", out var codes1013));
Assert.IsTrue(vsd.TryGetCodesInValueSet("https://www.ncqa.org/fhir/valueset/intensional-value-set", out var intensional));

Assert.IsTrue(codes1009.All(c => intensional.Contains(c)));
Assert.IsTrue(codes1013.All(c => intensional.Contains(c)));
}

[TestMethod]
public void Intensional_Value_Set_2_Levels()
{
string[] files =
[
@"Input\ValueSets\intensional-value-set-2.json",
@"Input\ValueSets\intensional-value-set.json",
@"Input\ValueSets\2.16.840.1.113883.3.464.1004.1009.json",
@"Input\ValueSets\2.16.840.1.113883.3.464.1004.1013.json"
];
var valueSets = files.Select(path =>
{
using var fs = File.OpenRead(path);
var vs = fs.DeserializeJsonToFhir<ValueSet>();
return vs;
}).ToArray();

var loader = new ValueSetLoader(valueSets, false);
var vsd = loader.Load();

Assert.IsTrue(vsd.TryGetCodesInValueSet("https://www.ncqa.org/fhir/valueset/2.16.840.1.113883.3.464.1004.1009", out var codes1009));
Assert.IsTrue(vsd.TryGetCodesInValueSet("https://www.ncqa.org/fhir/valueset/2.16.840.1.113883.3.464.1004.1013", out var codes1013));
Assert.IsTrue(vsd.TryGetCodesInValueSet("https://www.ncqa.org/fhir/valueset/intensional-value-set", out var intensional));
Assert.IsTrue(codes1009.All(c => intensional.Contains(c)));
Assert.IsTrue(codes1013.All(c => intensional.Contains(c)));

Assert.IsTrue(vsd.TryGetCodesInValueSet("https://www.ncqa.org/fhir/valueset/intensional-value-set-2", out var intensional2));
Assert.IsTrue(codes1009.All(c => intensional2.Contains(c)));
Assert.IsTrue(codes1013.All(c => intensional2.Contains(c)));

}

[TestMethod]
public void Intensional_Value_Set_Cycle()
{
string[] files =
[
@"Input\ValueSets\intensional-value-set-3.json",
@"Input\ValueSets\intensional-value-set-4.json"

];
var valueSets = files.Select(path =>
{
using var fs = File.OpenRead(path);
var vs = fs.DeserializeJsonToFhir<ValueSet>();
return vs;
}).ToArray();

var loader = new ValueSetLoader(valueSets, false);

Assert.ThrowsException<InvalidOperationException>(() => loader.Load());
}
var dict = new HashValueSetDictionary();

dict.Add("http://valuesetA", [new CqlCode("x", "http://nu.nl", "1.0"), new CqlCode("y", "http://nu.nl")]);
dict.Add("http://valuesetB", [new CqlCode("x", "http://nu.nl"), new CqlCode("y", "http://nu.nl")]);

_ = dict.TryGetCodesInValueSet("http://valuesetA", out var vsA);
_ = dict.TryGetCodesInValueSet("http://valuesetB", out var vsB);

ReferenceEquals(vsA!.Single(c => c.code == "y"), vsB!.Single(c => c.code == "y")).Should().BeTrue();
ReferenceEquals(vsA!.Single(c => c.code == "x"), vsB!.Single(c => c.code == "x")).Should().BeFalse();
}

[TestMethod]
public void Intensional_Value_Set()
{
var resolver = new DirectorySource(@"Input\ValueSets");
var vsd = new ValueSetSource(resolver);

Assert.IsTrue(vsd.TryGetCodesInValueSet("https://www.ncqa.org/fhir/valueset/2.16.840.1.113883.3.464.1004.1009", out var codes1009));
Assert.IsTrue(vsd.TryGetCodesInValueSet("https://www.ncqa.org/fhir/valueset/2.16.840.1.113883.3.464.1004.1013", out var codes1013));
Assert.IsTrue(vsd.TryGetCodesInValueSet("https://www.ncqa.org/fhir/valueset/intensional-value-set", out var intensional));

// Note that this tests uses *reference equality* to test membership, which only works because we're storing
// all codes into the HashValueSetDictionary, which interns all codes.
Assert.IsTrue(codes1009!.All(c => intensional!.Contains(c)));
Assert.IsTrue(codes1013!.All(c => intensional!.Contains(c)));
}

[TestMethod]
public void Intensional_Value_Set_2_Levels()
{
var resolver = new DirectorySource(@"Input\ValueSets");
var vsd = new ValueSetSource(resolver);

Assert.IsTrue(vsd.TryGetCodesInValueSet("https://www.ncqa.org/fhir/valueset/2.16.840.1.113883.3.464.1004.1009", out var codes1009));
Assert.IsTrue(vsd.TryGetCodesInValueSet("https://www.ncqa.org/fhir/valueset/2.16.840.1.113883.3.464.1004.1013", out var codes1013));
Assert.IsTrue(vsd.TryGetCodesInValueSet("https://www.ncqa.org/fhir/valueset/intensional-value-set", out var intensional));

// Note that this tests uses *reference equality* to test membership, which only works because we're storing
// all codes into the HashValueSetDictionary, which interns all codes.
Assert.IsTrue(codes1009!.All(c => intensional!.Contains(c)));
Assert.IsTrue(codes1013!.All(c => intensional!.Contains(c)));

Assert.IsTrue(vsd.TryGetCodesInValueSet("https://www.ncqa.org/fhir/valueset/intensional-value-set-2", out var intensional2));
Assert.IsTrue(codes1009!.All(c => intensional2!.Contains(c)));
Assert.IsTrue(codes1013!.All(c => intensional2!.Contains(c)));

}

[TestMethod]
public void Intensional_Value_Set_Cycle()
{
var resolver = new DirectorySource(@"Input\ValueSets");
var vsd = new ValueSetSource(resolver);

Assert.ThrowsException<TerminologyServiceException>(() => vsd.TryGetCodesInValueSet("https://www.ncqa.org/fhir/valueset/intensional-value-set-3", out _));
}
}
}
4 changes: 2 additions & 2 deletions Cql/Cql.Abstractions/Abstractions/IEquivalenceComparer.cs
Original file line number Diff line number Diff line change
@@ -40,6 +40,6 @@ public interface IEquivalenceComparer<in T>
/// <param name="y">The second object to compare.</param>
/// <param name="precision">The precision to use in this comparison, or <see langword="null"/>.</param>
/// <returns><see langword="true"/> if the objects are equivalent, and <see langword="false"/> if not. Equivalence computations are never <see langword="null"/> and will return <see langword="false"/> in uncertainty situations.</returns>
bool Equivalent(T x, T y, string? precision);
bool Equivalent(T? x, T? y, string? precision);
}
}
}
2 changes: 1 addition & 1 deletion Cql/Cql.Abstractions/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
@@ -43,7 +43,7 @@ Hl7.Cql.Abstractions.ICqlComparer<T>.GetHashCode(T x) -> int
Hl7.Cql.Abstractions.IEquivalenceComparer
Hl7.Cql.Abstractions.IEquivalenceComparer.Equivalent(object? x, object? y, string? precision) -> bool
Hl7.Cql.Abstractions.IEquivalenceComparer<T>
Hl7.Cql.Abstractions.IEquivalenceComparer<T>.Equivalent(T x, T y, string? precision) -> bool
Hl7.Cql.Abstractions.IEquivalenceComparer<T>.Equivalent(T? x, T? y, string? precision) -> bool
Hl7.Cql.Abstractions.IEquivalentable<T>
Hl7.Cql.Abstractions.IEquivalentable<T>.Equivalent(T? other, string? precision) -> bool
Hl7.Cql.Abstractions.ILibrary
12 changes: 5 additions & 7 deletions Cql/Cql.Firely/Comparers/IdentifierComparer.cs
Original file line number Diff line number Diff line change
@@ -40,11 +40,9 @@ public IdentifierComparer(ICqlComparer systemComparer, ICqlComparer valueCompare
protected override bool EquivalentImpl(Identifier x, Identifier y, string? precision) =>
(Compare(x, y, precision) ?? -1) == 0;

public override int GetHashCode(Identifier? x)
{
if (x == null || x.Value == null)
return typeof(Identifier).GetHashCode();
else return $"{x.Value}{x.System}".GetHashCode();
}
public override int GetHashCode(Identifier? x) =>
x?.Value == null
? typeof(Identifier).GetHashCode()
: HashCode.Combine(x.Value, x.System);
}
}
}
Loading