From 6592305aee1b210a3df2c74a83ac7a22330df38c Mon Sep 17 00:00:00 2001 From: Peng Chen <67345894+pengchen0692@users.noreply.github.com> Date: Fri, 24 Sep 2021 14:04:10 -0700 Subject: [PATCH] Update QIDO to respect QueryStatus and header to include Erroneous attribute (#1061) Update QIDO to respect QueryStatus and header to include Erroneous attribute --- .../Extensions/HttpResponseExtensionsTests.cs | 31 +++++++++++++- .../Controllers/QueryController.cs | 1 + .../Extensions/HttpResponseExtensions.cs | 16 +++++++ .../Features/Query/QueryParserTests.cs | 42 +++++++++++++++++++ .../Query/QueryResponseBuilderTests.cs | 13 +++--- ...ryServiceTests.cs => QueryServiceTests.cs} | 8 +++- .../DicomCoreResource.Designer.cs | 9 ++++ .../DicomCoreResource.resx | 4 ++ .../Features/Query/Model/QueryExpression.cs | 12 +++++- .../Features/Query/QueryParser.cs | 21 ++++++++-- .../Features/Query/QueryService.cs | 5 ++- .../Messages/Query/QueryResourceResponse.cs | 9 ++-- .../Features/Query/SqlQueryGeneratorTests.cs | 24 +++++------ 13 files changed, 163 insertions(+), 32 deletions(-) rename src/Microsoft.Health.Dicom.Core.UnitTests/Features/Query/{DicomQueryServiceTests.cs => QueryServiceTests.cs} (92%) diff --git a/src/Microsoft.Health.Dicom.Api.UnitTests/Extensions/HttpResponseExtensionsTests.cs b/src/Microsoft.Health.Dicom.Api.UnitTests/Extensions/HttpResponseExtensionsTests.cs index 8950eb7e8d..9bbabdd7c3 100644 --- a/src/Microsoft.Health.Dicom.Api.UnitTests/Extensions/HttpResponseExtensionsTests.cs +++ b/src/Microsoft.Health.Dicom.Api.UnitTests/Extensions/HttpResponseExtensionsTests.cs @@ -16,7 +16,7 @@ namespace Microsoft.Health.Dicom.Api.UnitTests.Extensions public class HttpResponseExtensionsTests { [Fact] - public void AddLocationHeader_GivenNullArguments_ThrowsArgumentNullException() + public void GivenNullParameters_WhenAddLocationHeader_ThenThrowsArgumentNullException() { var context = new DefaultHttpContext(); var uri = new Uri("https://example.host.com/unit/tests?method=AddLocationHeader#GivenNullArguments_ThrowException"); @@ -29,7 +29,7 @@ public void AddLocationHeader_GivenNullArguments_ThrowsArgumentNullException() [InlineData("https://absolute.url:8080/there%20are%20spaces")] [InlineData("/relative/url?with=query&string=and#fragment")] [SuppressMessage("Design", "CA1054:URI-like parameters should not be strings", Justification = "XUnit more easily leverages compile-time values.")] - public void AddLocationHeader_GivenValidArguments_AddsHeader(string url) + public void GivenValidLocationHeader_WhenAddLocationHeader_ThenShouldAddExpectedHeader(string url) { var response = new DefaultHttpContext().Response; var uri = new Uri(url, UriKind.RelativeOrAbsolute); @@ -41,5 +41,32 @@ public void AddLocationHeader_GivenValidArguments_AddsHeader(string url) Assert.Single(headerValue); Assert.Equal(url, headerValue[0]); // Should continue to be escaped! } + + [Fact] + public void GivenNullParameters_WhenTryAddErroneousAttributesHeader_ThenThrowsArgumentNullException() + { + var context = new DefaultHttpContext(); + Assert.Throws(() => HttpResponseExtensions.TryAddErroneousAttributesHeader(null, Array.Empty())); + Assert.Throws(() => HttpResponseExtensions.TryAddErroneousAttributesHeader(context.Response, null)); + } + + [Fact] + public void GivenEmptyErroneousTags_WhenTryAddErroneousAttributesHeader_ThenShouldReturnFalse() + { + var context = new DefaultHttpContext(); + Assert.False(HttpResponseExtensions.TryAddErroneousAttributesHeader(context.Response, Array.Empty())); + } + + [Fact] + public void GivenNonEmptyErroneousTags_WhenTryAddErroneousAttributesHeader_ThenShouldAddExpectedHeader() + { + var context = new DefaultHttpContext(); + var tags = new string[] { "PatientAge", "00011231" }; + Assert.True(HttpResponseExtensions.TryAddErroneousAttributesHeader(context.Response, tags)); + + Assert.True(context.Response.Headers.TryGetValue(HttpResponseExtensions.ErroneousAttributesHeader, out StringValues headerValue)); + Assert.Single(headerValue); + Assert.Equal(string.Join(",", tags), headerValue[0]); // Should continue to be escaped! + } } } diff --git a/src/Microsoft.Health.Dicom.Api/Controllers/QueryController.cs b/src/Microsoft.Health.Dicom.Api/Controllers/QueryController.cs index 36665b358c..0f39620494 100644 --- a/src/Microsoft.Health.Dicom.Api/Controllers/QueryController.cs +++ b/src/Microsoft.Health.Dicom.Api/Controllers/QueryController.cs @@ -171,6 +171,7 @@ public async Task QueryForInstancesInSeriesAsync([FromRoute] stri private IActionResult CreateResult(QueryResourceResponse resourceResponse) { + Response.TryAddErroneousAttributesHeader(resourceResponse.ErroneousTags); if (!resourceResponse.ResponseDataset.Any()) { return NoContent(); diff --git a/src/Microsoft.Health.Dicom.Api/Extensions/HttpResponseExtensions.cs b/src/Microsoft.Health.Dicom.Api/Extensions/HttpResponseExtensions.cs index 942e660d8b..03cbbbc9a2 100644 --- a/src/Microsoft.Health.Dicom.Api/Extensions/HttpResponseExtensions.cs +++ b/src/Microsoft.Health.Dicom.Api/Extensions/HttpResponseExtensions.cs @@ -4,6 +4,7 @@ // ------------------------------------------------------------------------------------------------- using System; +using System.Collections.Generic; using EnsureThat; using Microsoft.AspNetCore.Http; using Microsoft.Net.Http.Headers; @@ -12,6 +13,8 @@ namespace Microsoft.Health.Dicom.Api.Extensions { internal static class HttpResponseExtensions { + public const string ErroneousAttributesHeader = "erroneous-dicom-attributes"; + public static void AddLocationHeader(this HttpResponse response, Uri locationUrl) { EnsureArg.IsNotNull(response, nameof(response)); @@ -19,5 +22,18 @@ public static void AddLocationHeader(this HttpResponse response, Uri locationUrl response.Headers.Add(HeaderNames.Location, Uri.EscapeUriString(locationUrl.ToString())); } + + public static bool TryAddErroneousAttributesHeader(this HttpResponse response, IReadOnlyCollection erroneousAttributes) + { + EnsureArg.IsNotNull(response, nameof(response)); + EnsureArg.IsNotNull(erroneousAttributes, nameof(erroneousAttributes)); + if (erroneousAttributes.Count == 0) + { + return false; + } + + response.Headers.Add(ErroneousAttributesHeader, string.Join(",", erroneousAttributes)); + return true; + } } } diff --git a/src/Microsoft.Health.Dicom.Core.UnitTests/Features/Query/QueryParserTests.cs b/src/Microsoft.Health.Dicom.Core.UnitTests/Features/Query/QueryParserTests.cs index fe952173d0..6129a1bd38 100644 --- a/src/Microsoft.Health.Dicom.Core.UnitTests/Features/Query/QueryParserTests.cs +++ b/src/Microsoft.Health.Dicom.Core.UnitTests/Features/Query/QueryParserTests.cs @@ -336,6 +336,48 @@ public void GivenPatientNameFilterCondition_WithFuzzyMatchingTrue_FuzzyMatchCond Assert.Equal("CoronaPatient", fuzzyCondition.Value); } + [Fact] + public void GivenErroneousTag_WhenParse_ThenShouldBeInList() + { + DicomTag tag1 = DicomTag.PatientAge; + DicomTag tag2 = DicomTag.PatientAddress; + QueryTag[] tags = new QueryTag[] + { + new QueryTag(new ExtendedQueryTagStoreEntry(1, tag1.GetPath(), tag1.GetDefaultVR().Code, null, QueryTagLevel.Instance, ExtendedQueryTagStatus.Ready, QueryStatus.Enabled,1)), // has error + new QueryTag(new ExtendedQueryTagStoreEntry(2, tag2.GetPath(), tag2.GetDefaultVR().Code, null, QueryTagLevel.Instance, ExtendedQueryTagStatus.Ready, QueryStatus.Enabled,0)), // no error + }; + QueryExpression queryExpression = _queryParser.Parse( + CreateParameters( + new Dictionary + { + { tag1.GetFriendlyName(), "CoronaPatient" }, + { tag2.GetPath(), "20200403" }, + }, + QueryResource.AllInstances), + tags); + + Assert.Single(queryExpression.ErroneousTags); + Assert.Equal(queryExpression.ErroneousTags.First(), tag1.GetFriendlyName()); + } + + [Fact] + public void GivenDisabledTag_WhenParse_ThenShouldThrowException() + { + DicomTag tag1 = DicomTag.PatientAge; + QueryTag[] tags = new QueryTag[] + { + new QueryTag(new ExtendedQueryTagStoreEntry(1, tag1.GetPath(), tag1.GetDefaultVR().Code, null, QueryTagLevel.Instance, ExtendedQueryTagStatus.Ready, QueryStatus.Disabled,1)), // disabled + }; + var parameters = CreateParameters( + new Dictionary + { + { tag1.GetFriendlyName(), "CoronaPatient" }, + }, + QueryResource.AllStudies); + + Assert.Throws(() => _queryParser.Parse(parameters, tags)); + } + private void VerifyIncludeFieldsForValidAttributeIds(params string[] values) { QueryExpression queryExpression = _queryParser.Parse( diff --git a/src/Microsoft.Health.Dicom.Core.UnitTests/Features/Query/QueryResponseBuilderTests.cs b/src/Microsoft.Health.Dicom.Core.UnitTests/Features/Query/QueryResponseBuilderTests.cs index 2bd19b4bfa..df0cbef305 100644 --- a/src/Microsoft.Health.Dicom.Core.UnitTests/Features/Query/QueryResponseBuilderTests.cs +++ b/src/Microsoft.Health.Dicom.Core.UnitTests/Features/Query/QueryResponseBuilderTests.cs @@ -3,6 +3,7 @@ // Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. // ------------------------------------------------------------------------------------------------- +using System; using System.Collections.Generic; using System.Linq; using Dicom; @@ -26,7 +27,7 @@ public void GivenStudyLevel_WithIncludeField_ValidReturned() { new StringSingleValueMatchCondition(queryTag, "35"), }; - var query = new QueryExpression(QueryResource.AllStudies, includeField, false, 0, 0, filters); + var query = new QueryExpression(QueryResource.AllStudies, includeField, false, 0, 0, filters, Array.Empty()); var responseBuilder = new QueryResponseBuilder(query); DicomDataset responseDataset = responseBuilder.GenerateResponseDataset(GenerateTestDataSet()); @@ -49,7 +50,7 @@ public void GivenStudySeriesLevel_WithIncludeField_ValidReturned() { new StringSingleValueMatchCondition(queryTag, "35"), }; - var query = new QueryExpression(QueryResource.StudySeries, includeField, false, 0, 0, filters); + var query = new QueryExpression(QueryResource.StudySeries, includeField, false, 0, 0, filters, Array.Empty()); var responseBuilder = new QueryResponseBuilder(query); DicomDataset responseDataset = responseBuilder.GenerateResponseDataset(GenerateTestDataSet()); @@ -67,7 +68,7 @@ public void GivenAllSeriesLevel_WithIncludeField_ValidReturned() { var includeField = QueryIncludeField.AllFields; var filters = new List(); - var query = new QueryExpression(QueryResource.AllSeries, includeField, false, 0, 0, filters); + var query = new QueryExpression(QueryResource.AllSeries, includeField, false, 0, 0, filters, Array.Empty()); var responseBuilder = new QueryResponseBuilder(query); DicomDataset responseDataset = responseBuilder.GenerateResponseDataset(GenerateTestDataSet()); @@ -85,7 +86,7 @@ public void GivenAllInstanceLevel_WithIncludeField_ValidReturned() { var includeField = QueryIncludeField.AllFields; var filters = new List(); - var query = new QueryExpression(QueryResource.AllInstances, includeField, false, 0, 0, filters); + var query = new QueryExpression(QueryResource.AllInstances, includeField, false, 0, 0, filters, Array.Empty()); var responseBuilder = new QueryResponseBuilder(query); DicomDataset responseDataset = responseBuilder.GenerateResponseDataset(GenerateTestDataSet()); @@ -106,7 +107,7 @@ public void GivenStudyInstanceLevel_WithIncludeField_ValidReturned() { new StringSingleValueMatchCondition(new QueryTag(DicomTag.StudyInstanceUID), "35"), }; - var query = new QueryExpression(QueryResource.StudyInstances, includeField, false, 0, 0, filters); + var query = new QueryExpression(QueryResource.StudyInstances, includeField, false, 0, 0, filters, Array.Empty()); var responseBuilder = new QueryResponseBuilder(query); DicomDataset responseDataset = responseBuilder.GenerateResponseDataset(GenerateTestDataSet()); @@ -129,7 +130,7 @@ public void GivenStudySeriesInstanceLevel_WithIncludeField_ValidReturned() new StringSingleValueMatchCondition(new QueryTag(DicomTag.StudyInstanceUID), "35"), new StringSingleValueMatchCondition(new QueryTag(DicomTag.SeriesInstanceUID), "351"), }; - var query = new QueryExpression(QueryResource.StudySeriesInstances, includeField, false, 0, 0, filters); + var query = new QueryExpression(QueryResource.StudySeriesInstances, includeField, false, 0, 0, filters, Array.Empty()); var responseBuilder = new QueryResponseBuilder(query); DicomDataset responseDataset = responseBuilder.GenerateResponseDataset(GenerateTestDataSet()); diff --git a/src/Microsoft.Health.Dicom.Core.UnitTests/Features/Query/DicomQueryServiceTests.cs b/src/Microsoft.Health.Dicom.Core.UnitTests/Features/Query/QueryServiceTests.cs similarity index 92% rename from src/Microsoft.Health.Dicom.Core.UnitTests/Features/Query/DicomQueryServiceTests.cs rename to src/Microsoft.Health.Dicom.Core.UnitTests/Features/Query/QueryServiceTests.cs index 63a356e735..d1ece23418 100644 --- a/src/Microsoft.Health.Dicom.Core.UnitTests/Features/Query/DicomQueryServiceTests.cs +++ b/src/Microsoft.Health.Dicom.Core.UnitTests/Features/Query/QueryServiceTests.cs @@ -3,6 +3,7 @@ // Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. // ------------------------------------------------------------------------------------------------- +using System; using System.Collections.Generic; using System.Linq; using System.Threading; @@ -19,14 +20,14 @@ namespace Microsoft.Health.Dicom.Core.UnitTests.Features.Query { - public class DicomQueryServiceTests + public class QueryServiceTests { private readonly QueryService _queryService; private readonly IQueryParser _queryParser; private readonly IQueryStore _queryStore; private readonly IQueryTagService _queryTagService; - public DicomQueryServiceTests() + public QueryServiceTests() { _queryParser = Substitute.For(); _queryStore = Substitute.For(); @@ -74,6 +75,7 @@ public void GivenQidoQuery_WithInvalidStudySeriesUid_ThrowsValidationException(Q [InlineData(QueryResource.StudySeriesInstances)] public async void GivenRequestForInstances_WhenRetrievingQueriableExtendedQueryTags_ReturnsAllTags(QueryResource resourceType) { + _queryParser.Parse(default, default).ReturnsForAnyArgs(new QueryExpression(default, default, default, default, default, Array.Empty(), Array.Empty())); var parameters = new QueryParameters { Filters = new Dictionary(), @@ -101,6 +103,7 @@ public async void GivenRequestForInstances_WhenRetrievingQueriableExtendedQueryT [InlineData(QueryResource.StudySeries)] public async void GivenRequestForSeries_WhenRetrievingQueriableExtendedQueryTags_ReturnsSeriesAndStudyTags(QueryResource resourceType) { + _queryParser.Parse(default, default).ReturnsForAnyArgs(new QueryExpression(default, default, default, default, default, Array.Empty(), Array.Empty())); var parameters = new QueryParameters { Filters = new Dictionary(), @@ -126,6 +129,7 @@ public async void GivenRequestForSeries_WhenRetrievingQueriableExtendedQueryTags [InlineData(QueryResource.AllStudies)] public async void GivenRequestForStudies_WhenRetrievingQueriableExtendedQueryTags_ReturnsStudyTags(QueryResource resourceType) { + _queryParser.Parse(default, default).ReturnsForAnyArgs(new QueryExpression(default, default, default, default, default, Array.Empty(), Array.Empty())); var parameters = new QueryParameters { Filters = new Dictionary(), diff --git a/src/Microsoft.Health.Dicom.Core/DicomCoreResource.Designer.cs b/src/Microsoft.Health.Dicom.Core/DicomCoreResource.Designer.cs index d02567ffb3..b0e39a7be0 100644 --- a/src/Microsoft.Health.Dicom.Core/DicomCoreResource.Designer.cs +++ b/src/Microsoft.Health.Dicom.Core/DicomCoreResource.Designer.cs @@ -674,6 +674,15 @@ internal static string QueryInvalidResourceLevel { } } + /// + /// Looks up a localized string similar to Query is disabled on specified attribute '{0}'.. + /// + internal static string QueryIsDisabledOnAttribute { + get { + return ResourceManager.GetString("QueryIsDisabledOnAttribute", resourceCulture); + } + } + /// /// Looks up a localized string similar to Invalid QIDO-RS query. Specified limit value {0} is outside the allowed range of {1}..{2}.. /// diff --git a/src/Microsoft.Health.Dicom.Core/DicomCoreResource.resx b/src/Microsoft.Health.Dicom.Core/DicomCoreResource.resx index 3f2d6647fe..f4a82ecdcb 100644 --- a/src/Microsoft.Health.Dicom.Core/DicomCoreResource.resx +++ b/src/Microsoft.Health.Dicom.Core/DicomCoreResource.resx @@ -414,4 +414,8 @@ The first part date {1} should be lesser than or equal to the second part date { Invalid QIDO-RS query. Cannot specify included fields in addition to 'all'. + + Query is disabled on specified attribute '{0}'. + '{0}' attribute in QIDO query. + \ No newline at end of file diff --git a/src/Microsoft.Health.Dicom.Core/Features/Query/Model/QueryExpression.cs b/src/Microsoft.Health.Dicom.Core/Features/Query/Model/QueryExpression.cs index a85a136f03..28b16b5029 100644 --- a/src/Microsoft.Health.Dicom.Core/Features/Query/Model/QueryExpression.cs +++ b/src/Microsoft.Health.Dicom.Core/Features/Query/Model/QueryExpression.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Linq; +using EnsureThat; using Microsoft.Health.Dicom.Core.Messages; namespace Microsoft.Health.Dicom.Core.Features.Query.Model @@ -20,14 +21,16 @@ public QueryExpression( bool fuzzyMatching, int limit, int offset, - IReadOnlyCollection filterConditions) + IReadOnlyCollection filterConditions, + IReadOnlyCollection erroneousTags) { QueryResource = resourceType; IncludeFields = includeFields; FuzzyMatching = fuzzyMatching; Limit = limit; Offset = offset; - FilterConditions = filterConditions; + FilterConditions = EnsureArg.IsNotNull(filterConditions, nameof(filterConditions)); + ErroneousTags = EnsureArg.IsNotNull(erroneousTags, nameof(erroneousTags)); SetIELevel(); } @@ -66,6 +69,11 @@ public QueryExpression( /// public IReadOnlyCollection FilterConditions { get; } + /// + /// List of erroneous tags. + /// + public IReadOnlyCollection ErroneousTags { get; } + /// /// Request query was empty /// diff --git a/src/Microsoft.Health.Dicom.Core/Features/Query/QueryParser.cs b/src/Microsoft.Health.Dicom.Core/Features/Query/QueryParser.cs index 1dc7c37ea4..ac01302f46 100644 --- a/src/Microsoft.Health.Dicom.Core/Features/Query/QueryParser.cs +++ b/src/Microsoft.Health.Dicom.Core/Features/Query/QueryParser.cs @@ -61,6 +61,8 @@ public QueryExpression Parse(QueryParameters parameters, IReadOnlyCollection erroneousTags = new List(); + var filterConditions = new Dictionary(); foreach (KeyValuePair filter in parameters.Filters) { @@ -70,6 +72,11 @@ public QueryExpression Parse(QueryParameters parameters, IReadOnlyCollection 0) + { + erroneousTags.Add(filter.Key); + } + if (!filterConditions.TryAdd(condition.QueryTag.Tag, condition)) { throw new QueryParseException(string.Format(DicomCoreResource.DuplicateAttribute, filter.Key)); @@ -101,7 +108,8 @@ public QueryExpression Parse(QueryParameters parameters, IReadOnlyCollection GetQualifiedQueryTags(IReadOnlyCollection queryTags, QueryResource queryResource) @@ -134,7 +142,14 @@ private bool ParseFilterCondition( return false; } - QueryTag queryTag = GetSupportedQueryTag(dicomTag, queryParameter.Key, queryTags); + // QueryTag could be either core or extended query tag. + QueryTag queryTag = GetMatchingQueryTag(dicomTag, queryParameter.Key, queryTags); + + // check if tag is disabled + if (queryTag.IsExtendedQueryTag && queryTag.ExtendedQueryTagStoreEntry.QueryStatus == QueryStatus.Disabled) + { + throw new QueryParseException(string.Format(DicomCoreResource.QueryIsDisabledOnAttribute, queryParameter.Key)); + } if (string.IsNullOrWhiteSpace(queryParameter.Value)) { @@ -168,7 +183,7 @@ private bool TryParseDicomAttributeId(string attributeId, out DicomTag dicomTag) return false; } - private static QueryTag GetSupportedQueryTag(DicomTag dicomTag, string attributeId, IEnumerable queryTags) + private static QueryTag GetMatchingQueryTag(DicomTag dicomTag, string attributeId, IEnumerable queryTags) { QueryTag queryTag = queryTags.FirstOrDefault(item => { diff --git a/src/Microsoft.Health.Dicom.Core/Features/Query/QueryService.cs b/src/Microsoft.Health.Dicom.Core/Features/Query/QueryService.cs index 68c1b0312c..ce8486efd1 100644 --- a/src/Microsoft.Health.Dicom.Core/Features/Query/QueryService.cs +++ b/src/Microsoft.Health.Dicom.Core/Features/Query/QueryService.cs @@ -3,6 +3,7 @@ // Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. // ------------------------------------------------------------------------------------------------- +using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; @@ -57,7 +58,7 @@ public async Task QueryAsync( if (!queryResult.DicomInstances.Any()) { - return new QueryResourceResponse(); + return new QueryResourceResponse(Array.Empty(), queryExpression.ErroneousTags); } IEnumerable instanceMetadata = await Task.WhenAll( @@ -66,7 +67,7 @@ public async Task QueryAsync( var responseBuilder = new QueryResponseBuilder(queryExpression); IEnumerable responseMetadata = instanceMetadata.Select(m => responseBuilder.GenerateResponseDataset(m)); - return new QueryResourceResponse(responseMetadata); + return new QueryResourceResponse(responseMetadata, queryExpression.ErroneousTags); } private static void ValidateRequestIdentifiers(QueryParameters parameters) diff --git a/src/Microsoft.Health.Dicom.Core/Messages/Query/QueryResourceResponse.cs b/src/Microsoft.Health.Dicom.Core/Messages/Query/QueryResourceResponse.cs index 7a306bbbb6..67f93549f5 100644 --- a/src/Microsoft.Health.Dicom.Core/Messages/Query/QueryResourceResponse.cs +++ b/src/Microsoft.Health.Dicom.Core/Messages/Query/QueryResourceResponse.cs @@ -4,18 +4,21 @@ // ------------------------------------------------------------------------------------------------- using System.Collections.Generic; -using System.Linq; using Dicom; +using EnsureThat; namespace Microsoft.Health.Dicom.Core.Messages.Query { public sealed class QueryResourceResponse { - public QueryResourceResponse(IEnumerable responseDataset = null) + public QueryResourceResponse(IEnumerable responseDataset, IReadOnlyCollection erroneousTags) { - ResponseDataset = responseDataset ?? Enumerable.Empty(); + ResponseDataset = EnsureArg.IsNotNull(responseDataset, nameof(responseDataset)); + ErroneousTags = EnsureArg.IsNotNull(erroneousTags, nameof(erroneousTags)); } public IEnumerable ResponseDataset { get; } + + public IReadOnlyCollection ErroneousTags { get; } } } diff --git a/src/Microsoft.Health.Dicom.SqlServer.UnitTests/Features/Query/SqlQueryGeneratorTests.cs b/src/Microsoft.Health.Dicom.SqlServer.UnitTests/Features/Query/SqlQueryGeneratorTests.cs index 8e28cb4443..16b0d5134d 100644 --- a/src/Microsoft.Health.Dicom.SqlServer.UnitTests/Features/Query/SqlQueryGeneratorTests.cs +++ b/src/Microsoft.Health.Dicom.SqlServer.UnitTests/Features/Query/SqlQueryGeneratorTests.cs @@ -33,7 +33,7 @@ public void GivenStudyDate_WhenIELevelStudy_ValidateDistinctStudyStudies() { new DateRangeValueMatchCondition(new QueryTag(DicomTag.StudyDate), minDate, maxDate), }; - var query = new QueryExpression(QueryResource.AllStudies, includeField, false, 0, 0, filters); + var query = new QueryExpression(QueryResource.AllStudies, includeField, false, 0, 0, filters, Array.Empty()); var parm = new SqlQueryParameterManager(CreateSqlParameterCollection()); new SqlQueryGenerator(stringBuilder, query, parm); @@ -69,7 +69,7 @@ public void GivenModality_WhenIELevelSeries_ValidateDistinctSeries() { new StringSingleValueMatchCondition(new QueryTag(DicomTag.Modality), "123"), }; - var query = new QueryExpression(QueryResource.AllSeries, includeField, false, 0, 0, filters); + var query = new QueryExpression(QueryResource.AllSeries, includeField, false, 0, 0, filters, Array.Empty()); var parm = new SqlQueryParameterManager(CreateSqlParameterCollection()); new SqlQueryGenerator(stringBuilder, query, parm); @@ -111,7 +111,7 @@ public void GivenNonUidFilter_WhenIELevelInstance_ValidateDistinctInstances() { new StringSingleValueMatchCondition(new QueryTag(DicomTag.Modality), "123"), }; - var query = new QueryExpression(QueryResource.AllInstances, includeField, false, 0, 0, filters); + var query = new QueryExpression(QueryResource.AllInstances, includeField, false, 0, 0, filters, Array.Empty()); var parm = new SqlQueryParameterManager(CreateSqlParameterCollection()); new SqlQueryGenerator(stringBuilder, query, parm); @@ -151,7 +151,7 @@ public void GivenStringExtendedQueryTagFilter_WhenIELevelStudy_ValidateExtendedQ { filter, }; - var query = new QueryExpression(QueryResource.AllStudies, includeField, false, 0, 0, filters); + var query = new QueryExpression(QueryResource.AllStudies, includeField, false, 0, 0, filters, Array.Empty()); SqlParameterCollection sqlParameterCollection = CreateSqlParameterCollection(); var parm = new SqlQueryParameterManager(sqlParameterCollection); @@ -183,7 +183,7 @@ public void GivenLongExtendedQueryTagFilter_WhenIELevelStudy_ValidateExtendedQue { filter, }; - var query = new QueryExpression(QueryResource.AllStudies, includeField, false, 0, 0, filters); + var query = new QueryExpression(QueryResource.AllStudies, includeField, false, 0, 0, filters, Array.Empty()); SqlParameterCollection sqlParameterCollection = CreateSqlParameterCollection(); var parm = new SqlQueryParameterManager(sqlParameterCollection); @@ -215,7 +215,7 @@ public void GivenDoubleExtendedQueryTagFilter_WhenIELevelStudy_ValidateExtendedQ { filter, }; - var query = new QueryExpression(QueryResource.AllStudies, includeField, false, 0, 0, filters); + var query = new QueryExpression(QueryResource.AllStudies, includeField, false, 0, 0, filters, Array.Empty()); SqlParameterCollection sqlParameterCollection = CreateSqlParameterCollection(); var parm = new SqlQueryParameterManager(sqlParameterCollection); @@ -248,7 +248,7 @@ public void GivenDateExtendedQueryTagFilter_WhenIELevelStudy_ValidateExtendedQue { filter, }; - var query = new QueryExpression(QueryResource.AllStudies, includeField, false, 0, 0, filters); + var query = new QueryExpression(QueryResource.AllStudies, includeField, false, 0, 0, filters, Array.Empty()); SqlParameterCollection sqlParameterCollection = CreateSqlParameterCollection(); var parm = new SqlQueryParameterManager(sqlParameterCollection); @@ -283,7 +283,7 @@ public void GivenExtendedQueryTagFilterWithNonUidFilter_WhenIELevelSeries_Valida filter, extendedQueryTagFilter, }; - var query = new QueryExpression(QueryResource.StudySeries, includeField, false, 0, 0, filters); + var query = new QueryExpression(QueryResource.StudySeries, includeField, false, 0, 0, filters, Array.Empty()); SqlParameterCollection sqlParameterCollection = CreateSqlParameterCollection(); var parm = new SqlQueryParameterManager(sqlParameterCollection); @@ -322,7 +322,7 @@ public void GivenMultipleExtendedQueryTagFiltersOnSameLevel_WhenIELevelInstance_ filter1, filter2, }; - var query = new QueryExpression(QueryResource.AllInstances, includeField, false, 0, 0, filters); + var query = new QueryExpression(QueryResource.AllInstances, includeField, false, 0, 0, filters, Array.Empty()); SqlParameterCollection sqlParameterCollection = CreateSqlParameterCollection(); var parm = new SqlQueryParameterManager(sqlParameterCollection); @@ -375,7 +375,7 @@ public void GivenMultipleExtendedQueryTagFiltersOnDifferentLevels_WhenIELevelIns filter2, filter3, }; - var query = new QueryExpression(QueryResource.AllInstances, includeField, false, 0, 0, filters); + var query = new QueryExpression(QueryResource.AllInstances, includeField, false, 0, 0, filters, Array.Empty()); SqlParameterCollection sqlParameterCollection = CreateSqlParameterCollection(); var parm = new SqlQueryParameterManager(sqlParameterCollection); @@ -422,7 +422,7 @@ public void GivenPatientNameFilter_WithFuzzyMatchMultiWord_ValidateContainsFilte { new PersonNameFuzzyMatchCondition(new QueryTag(DicomTag.PatientName), "Fall 6"), }; - var query = new QueryExpression(QueryResource.AllStudies, includeField, true, 10, 0, filters); + var query = new QueryExpression(QueryResource.AllStudies, includeField, true, 10, 0, filters, Array.Empty()); SqlParameterCollection sqlParameterCollection = CreateSqlParameterCollection(); var parm = new SqlQueryParameterManager(sqlParameterCollection); new SqlQueryGenerator(stringBuilder, query, parm); @@ -447,7 +447,7 @@ public void GivenPatientNameFilterForExtendedQueryTag_WithFuzzyMatchMultiWord_Va { filter, }; - var query = new QueryExpression(QueryResource.AllInstances, includeField, true, 10, 0, filters); + var query = new QueryExpression(QueryResource.AllInstances, includeField, true, 10, 0, filters, Array.Empty()); SqlParameterCollection sqlParameterCollection = CreateSqlParameterCollection(); var parm = new SqlQueryParameterManager(sqlParameterCollection); new SqlQueryGenerator(stringBuilder, query, parm);