diff --git a/src/Microsoft.Health.Fhir.Core/Features/Search/Expressions/BinaryExpression.cs b/src/Microsoft.Health.Fhir.Core/Features/Search/Expressions/BinaryExpression.cs index 8ccc362bd0..84edbf9479 100644 --- a/src/Microsoft.Health.Fhir.Core/Features/Search/Expressions/BinaryExpression.cs +++ b/src/Microsoft.Health.Fhir.Core/Features/Search/Expressions/BinaryExpression.cs @@ -76,6 +76,11 @@ string ValueToString() return $"(Field{BinaryOperator} {(ComponentIndex == null ? null : $"[{ComponentIndex}].")}{FieldName} {ValueToString()})"; } + public override string ToValueInsensitiveString() + { + return $"(Field{BinaryOperator} {(ComponentIndex == null ? null : $"[{ComponentIndex}].")}{FieldName})"; + } + public override void AddValueInsensitiveHashCode(ref HashCode hashCode) { hashCode.Add(typeof(BinaryExpression)); diff --git a/src/Microsoft.Health.Fhir.Core/Features/Search/Expressions/ChainedExpression.cs b/src/Microsoft.Health.Fhir.Core/Features/Search/Expressions/ChainedExpression.cs index e129fd0192..ba3d65f85a 100644 --- a/src/Microsoft.Health.Fhir.Core/Features/Search/Expressions/ChainedExpression.cs +++ b/src/Microsoft.Health.Fhir.Core/Features/Search/Expressions/ChainedExpression.cs @@ -83,6 +83,11 @@ public override string ToString() return $"({(Reversed ? "Reverse " : string.Empty)}Chain {ReferenceSearchParameter.Code}:{string.Join(", ", TargetResourceTypes)} {Expression})"; } + public override string ToValueInsensitiveString() + { + return $"({(Reversed ? "Reverse " : string.Empty)}Chain {ReferenceSearchParameter.Code}:{string.Join(", ", TargetResourceTypes)} {Expression.ToValueInsensitiveString()})"; + } + public override void AddValueInsensitiveHashCode(ref HashCode hashCode) { hashCode.Add(typeof(ChainedExpression)); diff --git a/src/Microsoft.Health.Fhir.Core/Features/Search/Expressions/CompartmentSearchExpression.cs b/src/Microsoft.Health.Fhir.Core/Features/Search/Expressions/CompartmentSearchExpression.cs index cabba1245b..7edb31aa5b 100644 --- a/src/Microsoft.Health.Fhir.Core/Features/Search/Expressions/CompartmentSearchExpression.cs +++ b/src/Microsoft.Health.Fhir.Core/Features/Search/Expressions/CompartmentSearchExpression.cs @@ -57,6 +57,11 @@ public override string ToString() return $"(Compartment {CompartmentType} '{CompartmentId}')"; } + public override string ToValueInsensitiveString() + { + return $"(Compartment {CompartmentType} '{CompartmentId}')"; + } + public override void AddValueInsensitiveHashCode(ref HashCode hashCode) { hashCode.Add(typeof(CompartmentSearchExpression)); diff --git a/src/Microsoft.Health.Fhir.Core/Features/Search/Expressions/Expression.cs b/src/Microsoft.Health.Fhir.Core/Features/Search/Expressions/Expression.cs index 2c0bc29e7c..93b2e1842d 100644 --- a/src/Microsoft.Health.Fhir.Core/Features/Search/Expressions/Expression.cs +++ b/src/Microsoft.Health.Fhir.Core/Features/Search/Expressions/Expression.cs @@ -316,9 +316,12 @@ public static SmartCompartmentSearchExpression SmartCompartmentSearch(string com /// public abstract override string ToString(); + public abstract string ToValueInsensitiveString(); + /// /// Accumulates a "value-insensitive" hash code of this instance, meaning it ignores parameterizable values. /// For example, date=2013&name=Smith and date=2014&name=Trudeau would have the same hash code. + /// HashCodes change after a restart, don't use this for something that needs to be consistent between restarts. /// /// The HashCode instance to accumulate into public abstract void AddValueInsensitiveHashCode(ref HashCode hashCode); diff --git a/src/Microsoft.Health.Fhir.Core/Features/Search/Expressions/InExpression.cs b/src/Microsoft.Health.Fhir.Core/Features/Search/Expressions/InExpression.cs index a2f3e50e4e..cf4a52c416 100644 --- a/src/Microsoft.Health.Fhir.Core/Features/Search/Expressions/InExpression.cs +++ b/src/Microsoft.Health.Fhir.Core/Features/Search/Expressions/InExpression.cs @@ -50,6 +50,11 @@ public override string ToString() return $"({(ComponentIndex == null ? null : $"[{ComponentIndex}].")}{FieldName} IN ({string.Join(", ", Values)}))"; } + public override string ToValueInsensitiveString() + { + return $"({(ComponentIndex == null ? null : $"[{ComponentIndex}].")}{FieldName} IN )"; + } + public override void AddValueInsensitiveHashCode(ref HashCode hashCode) { hashCode.Add(typeof(InExpression)); diff --git a/src/Microsoft.Health.Fhir.Core/Features/Search/Expressions/IncludeExpression.cs b/src/Microsoft.Health.Fhir.Core/Features/Search/Expressions/IncludeExpression.cs index a16ce4d960..8fce46cc19 100644 --- a/src/Microsoft.Health.Fhir.Core/Features/Search/Expressions/IncludeExpression.cs +++ b/src/Microsoft.Health.Fhir.Core/Features/Search/Expressions/IncludeExpression.cs @@ -144,6 +144,18 @@ public override string ToString() return $"({reversed}Include{iterate}{wildcard}{paramName}{targetType})"; } + + // Check this one, I don't think the toString is correct + public override string ToValueInsensitiveString() + { + var targetType = TargetResourceType != null ? $":{TargetResourceType}" : string.Empty; + var iterate = Iterate ? " Iterate" : string.Empty; + var reversed = Reversed ? "Reversed " : string.Empty; + var wildcard = WildCard ? " Wildcard" : string.Empty; + var paramName = ReferenceSearchParameter != null ? $" {ReferenceSearchParameter.Code}" : string.Empty; + return $"({reversed}Include{iterate}{wildcard}{paramName}{targetType})"; + } + private IReadOnlyCollection GetRequiredResources() { if (Reversed) diff --git a/src/Microsoft.Health.Fhir.Core/Features/Search/Expressions/MissingFieldExpression.cs b/src/Microsoft.Health.Fhir.Core/Features/Search/Expressions/MissingFieldExpression.cs index 4304541c01..ef394b267f 100644 --- a/src/Microsoft.Health.Fhir.Core/Features/Search/Expressions/MissingFieldExpression.cs +++ b/src/Microsoft.Health.Fhir.Core/Features/Search/Expressions/MissingFieldExpression.cs @@ -42,6 +42,11 @@ public override string ToString() return $"(MissingField {(ComponentIndex == null ? null : $"[{ComponentIndex}].")}{FieldName})"; } + public override string ToValueInsensitiveString() + { + return $"(MissingField {(ComponentIndex == null ? null : $"[{ComponentIndex}].")}{FieldName})"; + } + public override void AddValueInsensitiveHashCode(ref HashCode hashCode) { hashCode.Add(typeof(MissingFieldException)); diff --git a/src/Microsoft.Health.Fhir.Core/Features/Search/Expressions/MissingSearchParameterExpression.cs b/src/Microsoft.Health.Fhir.Core/Features/Search/Expressions/MissingSearchParameterExpression.cs index 6eae0c3194..9e52bfe5d8 100644 --- a/src/Microsoft.Health.Fhir.Core/Features/Search/Expressions/MissingSearchParameterExpression.cs +++ b/src/Microsoft.Health.Fhir.Core/Features/Search/Expressions/MissingSearchParameterExpression.cs @@ -42,6 +42,11 @@ public override string ToString() return $"({(!IsMissing ? "Not" : null)}MissingParam {Parameter.Name})"; } + public override string ToValueInsensitiveString() + { + return $"({(!IsMissing ? "Not" : null)}MissingParam {Parameter.Name})"; + } + public override void AddValueInsensitiveHashCode(ref HashCode hashCode) { hashCode.Add(typeof(MissingSearchParameterExpression)); diff --git a/src/Microsoft.Health.Fhir.Core/Features/Search/Expressions/MultiaryExpression.cs b/src/Microsoft.Health.Fhir.Core/Features/Search/Expressions/MultiaryExpression.cs index dbb6adf9a2..a823d6ccaf 100644 --- a/src/Microsoft.Health.Fhir.Core/Features/Search/Expressions/MultiaryExpression.cs +++ b/src/Microsoft.Health.Fhir.Core/Features/Search/Expressions/MultiaryExpression.cs @@ -52,6 +52,11 @@ public override string ToString() return $"({MultiaryOperation} {string.Join(' ', Expressions)})"; } + public override string ToValueInsensitiveString() + { + return $"({MultiaryOperation} {string.Join(' ', Expressions.Select(x => x.ToValueInsensitiveString()))})"; + } + public override void AddValueInsensitiveHashCode(ref HashCode hashCode) { hashCode.Add(typeof(MultiaryExpression)); diff --git a/src/Microsoft.Health.Fhir.Core/Features/Search/Expressions/NotExpression.cs b/src/Microsoft.Health.Fhir.Core/Features/Search/Expressions/NotExpression.cs index 3148d3ae9e..fed7b7a41c 100644 --- a/src/Microsoft.Health.Fhir.Core/Features/Search/Expressions/NotExpression.cs +++ b/src/Microsoft.Health.Fhir.Core/Features/Search/Expressions/NotExpression.cs @@ -40,6 +40,11 @@ public override string ToString() return $"(Not {Expression})"; } + public override string ToValueInsensitiveString() + { + return $"(Not {Expression.ToValueInsensitiveString()})"; + } + public override void AddValueInsensitiveHashCode(ref HashCode hashCode) { hashCode.Add(typeof(NotExpression)); diff --git a/src/Microsoft.Health.Fhir.Core/Features/Search/Expressions/SearchParameterExpression.cs b/src/Microsoft.Health.Fhir.Core/Features/Search/Expressions/SearchParameterExpression.cs index 4427f804fd..724dd21916 100644 --- a/src/Microsoft.Health.Fhir.Core/Features/Search/Expressions/SearchParameterExpression.cs +++ b/src/Microsoft.Health.Fhir.Core/Features/Search/Expressions/SearchParameterExpression.cs @@ -35,6 +35,11 @@ public override string ToString() return $"(Param {Parameter.Code} {Expression})"; } + public override string ToValueInsensitiveString() + { + return $"(Param {Parameter.Code} {Expression.ToValueInsensitiveString()})"; + } + public override void AddValueInsensitiveHashCode(ref HashCode hashCode) { hashCode.Add(typeof(SearchParameterExpression)); diff --git a/src/Microsoft.Health.Fhir.Core/Features/Search/Expressions/SortExpression.cs b/src/Microsoft.Health.Fhir.Core/Features/Search/Expressions/SortExpression.cs index ee0371be6e..fb3f7ad305 100644 --- a/src/Microsoft.Health.Fhir.Core/Features/Search/Expressions/SortExpression.cs +++ b/src/Microsoft.Health.Fhir.Core/Features/Search/Expressions/SortExpression.cs @@ -30,6 +30,11 @@ public override string ToString() return $"(Sort Param: {Parameter.Code})"; } + public override string ToValueInsensitiveString() + { + return $"(Sort Param: {Parameter.Code})"; + } + public override void AddValueInsensitiveHashCode(ref HashCode hashCode) { hashCode.Add(typeof(SortExpression)); diff --git a/src/Microsoft.Health.Fhir.Core/Features/Search/Expressions/StringExpression.cs b/src/Microsoft.Health.Fhir.Core/Features/Search/Expressions/StringExpression.cs index 44e533c319..22771554a6 100644 --- a/src/Microsoft.Health.Fhir.Core/Features/Search/Expressions/StringExpression.cs +++ b/src/Microsoft.Health.Fhir.Core/Features/Search/Expressions/StringExpression.cs @@ -63,6 +63,11 @@ public override string ToString() return $"(String{StringOperator}{(IgnoreCase ? "IgnoreCase" : null)} {(ComponentIndex == null ? null : $"[{ComponentIndex}].")}{FieldName} '{Value}')"; } + public override string ToValueInsensitiveString() + { + return $"(String{StringOperator}{(IgnoreCase ? "IgnoreCase" : null)} {(ComponentIndex == null ? null : $"[{ComponentIndex}].")}{FieldName})"; + } + public override void AddValueInsensitiveHashCode(ref HashCode hashCode) { hashCode.Add(typeof(StringExpression)); diff --git a/src/Microsoft.Health.Fhir.Core/Features/Search/Expressions/UnionExpression.cs b/src/Microsoft.Health.Fhir.Core/Features/Search/Expressions/UnionExpression.cs index 71dce1b83d..022a0d0e8b 100644 --- a/src/Microsoft.Health.Fhir.Core/Features/Search/Expressions/UnionExpression.cs +++ b/src/Microsoft.Health.Fhir.Core/Features/Search/Expressions/UnionExpression.cs @@ -43,6 +43,11 @@ public override string ToString() return $"(Union ({Operator}) {Expressions} {string.Join(' ', Expressions)})"; } + public override string ToValueInsensitiveString() + { + return $"(Union ({Operator}) {string.Join(' ', Expressions.Select(x => x.ToValueInsensitiveString()))})"; + } + public override void AddValueInsensitiveHashCode(ref HashCode hashCode) { hashCode.Add(typeof(UnionExpression)); diff --git a/src/Microsoft.Health.Fhir.SqlServer/Features/Search/ISqlQueryHashCalculator.cs b/src/Microsoft.Health.Fhir.SqlServer/Features/Search/ISqlQueryHashCalculator.cs deleted file mode 100644 index 34ddc55067..0000000000 --- a/src/Microsoft.Health.Fhir.SqlServer/Features/Search/ISqlQueryHashCalculator.cs +++ /dev/null @@ -1,17 +0,0 @@ -// ------------------------------------------------------------------------------------------------- -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. -// ------------------------------------------------------------------------------------------------- - -namespace Microsoft.Health.Fhir.SqlServer.Features.Search -{ - public interface ISqlQueryHashCalculator - { - /// - /// Given a string that represents a SQL query, this returns the calculated hash of that query. - /// - /// The SQL query as text - /// A string hash value. - string CalculateHash(string query); - } -} diff --git a/src/Microsoft.Health.Fhir.SqlServer/Features/Search/SqlQueryHashCalculator.cs b/src/Microsoft.Health.Fhir.SqlServer/Features/Search/SqlQueryHashCalculator.cs deleted file mode 100644 index 76725c7e90..0000000000 --- a/src/Microsoft.Health.Fhir.SqlServer/Features/Search/SqlQueryHashCalculator.cs +++ /dev/null @@ -1,17 +0,0 @@ -// ------------------------------------------------------------------------------------------------- -// 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.Core.Extensions; - -namespace Microsoft.Health.Fhir.SqlServer.Features.Search -{ - internal class SqlQueryHashCalculator : ISqlQueryHashCalculator - { - public string CalculateHash(string query) - { - return query.ComputeHash(); - } - } -} diff --git a/src/Microsoft.Health.Fhir.SqlServer/Features/Search/SqlSearchOptions.cs b/src/Microsoft.Health.Fhir.SqlServer/Features/Search/SqlSearchOptions.cs index a1a6566a21..890ca122a5 100644 --- a/src/Microsoft.Health.Fhir.SqlServer/Features/Search/SqlSearchOptions.cs +++ b/src/Microsoft.Health.Fhir.SqlServer/Features/Search/SqlSearchOptions.cs @@ -3,7 +3,12 @@ // Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. // ------------------------------------------------------------------------------------------------- +using System; +using System.Linq; +using System.Security.Cryptography; +using Microsoft.Health.Core.Extensions; using Microsoft.Health.Fhir.Core.Features.Search; +using Microsoft.Health.Fhir.Core.Models; namespace Microsoft.Health.Fhir.SqlServer.Features.Search { @@ -39,5 +44,30 @@ public SqlSearchOptions(SearchOptions searchOptions) /// Performs a shallow clone of this instance /// public SqlSearchOptions CloneSqlSearchOptions() => (SqlSearchOptions)MemberwiseClone(); + + /// + /// Hashes the search option to indicate if two search options will return the same results. + /// UnsupportedSearchParams isn't inlcuded in the has because it isn't used in the actual search + /// + /// A hash of the search options + public string GetHash() + { + var expressionHash = default(HashCode); + Expression?.AddValueInsensitiveHashCode(ref expressionHash); + + var sort = Sort?.Aggregate(string.Empty, (string result, (SearchParameterInfo param, SortOrder order) input) => + { + return result + $"{input.param.Url}_{input.order}_"; + }); + + var queryHints = QueryHints?.Aggregate(string.Empty, (string result, (string param, string value) input) => + { + return result + $"{input.param}_{input.value}_"; + }); + + var hashString = $"{ContinuationToken}_{FeedRange}_{CountOnly}_{IgnoreSearchParamHash}_{IncludeTotal}_{MaxItemCount}_{MaxItemCountSpecifiedByClient}_{IncludeCount}_{ResourceVersionTypes}_{OnlyIds}_{IsLargeAsyncOperation}_{SortQuerySecondPhase}_{IsSortWithFilter}_{DidWeSearchForSortValue}_{SortHasMissingModifier}_{expressionHash.ToHashCode()}_{sort}_{queryHints}"; + + return hashString.ComputeHash(); + } } } diff --git a/src/Microsoft.Health.Fhir.SqlServer/Features/Search/SqlServerSearchService.cs b/src/Microsoft.Health.Fhir.SqlServer/Features/Search/SqlServerSearchService.cs index 25d9eade9c..c3deb5e0d3 100644 --- a/src/Microsoft.Health.Fhir.SqlServer/Features/Search/SqlServerSearchService.cs +++ b/src/Microsoft.Health.Fhir.SqlServer/Features/Search/SqlServerSearchService.cs @@ -69,7 +69,6 @@ internal class SqlServerSearchService : SearchService private readonly ICompressedRawResourceConverter _compressedRawResourceConverter; private readonly RequestContextAccessor _requestContextAccessor; private readonly SearchParameterInfo _fakeLastUpdate = new SearchParameterInfo(SearchParameterNames.LastUpdated, SearchParameterNames.LastUpdated); - private readonly ISqlQueryHashCalculator _queryHashCalculator; private readonly IParameterStore _parameterStore; private static ResourceSearchParamStats _resourceSearchParamStats; private static object _locker = new object(); @@ -91,7 +90,6 @@ public SqlServerSearchService( SchemaInformation schemaInformation, RequestContextAccessor requestContextAccessor, ICompressedRawResourceConverter compressedRawResourceConverter, - ISqlQueryHashCalculator queryHashCalculator, IParameterStore parameterStore, ILogger logger) : base(searchOptionsFactory, fhirDataStore, logger) @@ -116,7 +114,6 @@ public SqlServerSearchService( _smartCompartmentSearchRewriter = smartCompartmentSearchRewriter; _chainFlatteningRewriter = chainFlatteningRewriter; _sqlRetryService = sqlRetryService; - _queryHashCalculator = queryHashCalculator; _parameterStore = parameterStore; _logger = logger; @@ -366,7 +363,7 @@ await _sqlRetryService.ExecuteSql( SqlCommandSimplifier.RemoveRedundantParameters(stringBuilder, sqlCommand.Parameters, _logger); var queryText = stringBuilder.ToString(); - var queryHash = _queryHashCalculator.CalculateHash(queryText); + var queryHash = clonedSearchOptions.GetHash(); _logger.LogInformation("SQL Search Service query hash: {QueryHash}", queryHash); var customQuery = CustomQueries.CheckQueryHash(connection, queryHash, _logger); diff --git a/src/Microsoft.Health.Fhir.SqlServer/Registration/FhirServerBuilderSqlServerRegistrationExtensions.cs b/src/Microsoft.Health.Fhir.SqlServer/Registration/FhirServerBuilderSqlServerRegistrationExtensions.cs index 22dbea0a7b..2d648bff18 100644 --- a/src/Microsoft.Health.Fhir.SqlServer/Registration/FhirServerBuilderSqlServerRegistrationExtensions.cs +++ b/src/Microsoft.Health.Fhir.SqlServer/Registration/FhirServerBuilderSqlServerRegistrationExtensions.cs @@ -73,10 +73,6 @@ public static IFhirServerBuilder AddSqlServer(this IFhirServerBuilder fhirServer .AsSelf() .AsImplementedInterfaces(); - services.Add() - .Singleton() - .AsImplementedInterfaces(); - services.Add() .Scoped() .AsSelf() diff --git a/test/Microsoft.Health.Fhir.Shared.Tests.Integration/Helpers/ReadableLogger.cs b/test/Microsoft.Health.Fhir.Shared.Tests.Integration/Helpers/ReadableLogger.cs new file mode 100644 index 0000000000..6bc03072eb --- /dev/null +++ b/test/Microsoft.Health.Fhir.Shared.Tests.Integration/Helpers/ReadableLogger.cs @@ -0,0 +1,59 @@ +// ------------------------------------------------------------------------------------------------- +// 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 System.Collections.Generic; +using System.Linq; +using Microsoft.Extensions.Logging; + +namespace Microsoft.Health.Fhir.Tests.Integration +{ + public class ReadableLogger : ILogger + { + private List _logs = new List(); + + public IDisposable BeginScope(TState state) + { + throw new NotImplementedException(); + } + + public bool IsEnabled(LogLevel logLevel) + { + throw new NotImplementedException(); + } + + public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter) + { + _logs.Add(formatter(state, exception)); + } + + public void LogError(string message) + { + _logs.Add(message); + } + + public void LogInformation(string message) + { + _logs.Add(message); + } + + public void LogWarning(string message) + { + _logs.Add(message); + } + + public bool TryGetLatestLog(string content, out string match) + { + if (_logs.Any(l => l.Contains(content))) + { + match = _logs.FindLast(l => l.Contains(content)); + return true; + } + + match = null; + return false; + } + } +} diff --git a/test/Microsoft.Health.Fhir.Shared.Tests.Integration/Microsoft.Health.Fhir.Shared.Tests.Integration.projitems b/test/Microsoft.Health.Fhir.Shared.Tests.Integration/Microsoft.Health.Fhir.Shared.Tests.Integration.projitems index 6dae7f7621..52dbbe5401 100644 --- a/test/Microsoft.Health.Fhir.Shared.Tests.Integration/Microsoft.Health.Fhir.Shared.Tests.Integration.projitems +++ b/test/Microsoft.Health.Fhir.Shared.Tests.Integration/Microsoft.Health.Fhir.Shared.Tests.Integration.projitems @@ -23,6 +23,7 @@ + @@ -49,6 +50,5 @@ - \ No newline at end of file diff --git a/test/Microsoft.Health.Fhir.Shared.Tests.Integration/Persistence/FhirStorageTestsFixture.cs b/test/Microsoft.Health.Fhir.Shared.Tests.Integration/Persistence/FhirStorageTestsFixture.cs index bbef6e8fe3..4522299557 100644 --- a/test/Microsoft.Health.Fhir.Shared.Tests.Integration/Persistence/FhirStorageTestsFixture.cs +++ b/test/Microsoft.Health.Fhir.Shared.Tests.Integration/Persistence/FhirStorageTestsFixture.cs @@ -48,6 +48,7 @@ using Microsoft.Health.Fhir.Core.Models; using Microsoft.Health.Fhir.Core.UnitTests; using Microsoft.Health.Fhir.Core.UnitTests.Extensions; +using Microsoft.Health.Fhir.SqlServer.Features.Search; using Microsoft.Health.Fhir.Tests.Common; using Microsoft.Health.Fhir.Tests.Common.FixtureParameters; using Microsoft.Health.Fhir.Tests.Common.Mocks; @@ -125,12 +126,12 @@ internal FhirStorageTestsFixture(IServiceProvider fixture) public RequestContextAccessor FhirRequestContextAccessor => _fixture.GetRequiredService>(); - public TestSqlHashCalculator SqlQueryHashCalculator => _fixture.GetRequiredService(); - public GetResourceHandler GetResourceHandler { get; set; } public IQueueClient QueueClient => _fixture.GetRequiredService(); + internal ReadableLogger ReadableLogger => _fixture.GetRequiredService>(); + public void Dispose() { (_fixture as IDisposable)?.Dispose(); diff --git a/test/Microsoft.Health.Fhir.Shared.Tests.Integration/Persistence/SqlComplexQueryTests.cs b/test/Microsoft.Health.Fhir.Shared.Tests.Integration/Persistence/SqlComplexQueryTests.cs index 6eee24e19e..2b34337f11 100644 --- a/test/Microsoft.Health.Fhir.Shared.Tests.Integration/Persistence/SqlComplexQueryTests.cs +++ b/test/Microsoft.Health.Fhir.Shared.Tests.Integration/Persistence/SqlComplexQueryTests.cs @@ -187,7 +187,8 @@ public async Task GivenASqlQuery_IfAStoredProcExistsWithMatchingHash_ThenStoredP // Query before adding an sproc to the database await _fixture.SearchService.SearchAsync(KnownResourceTypes.Patient, query, CancellationToken.None); - var hash = _fixture.SqlQueryHashCalculator.MostRecentSqlHash; + var hash = _fixture.ReadableLogger.TryGetLatestLog("SQL Search Service query hash:", out var hashValue) ? hashValue.Substring(31) : null; + Assert.NotNull(hash); // assert an sproc was not used Assert.False(await CheckIfSprocUsed(hash)); @@ -199,11 +200,16 @@ public async Task GivenASqlQuery_IfAStoredProcExistsWithMatchingHash_ThenStoredP // Query after adding an sproc to the database var sw = Stopwatch.StartNew(); var sprocWasUsed = false; + + // Change parameter values to test that hash is independent of parameter values + query = new[] { Tuple.Create("birthdate", "gt1900-01-01"), Tuple.Create("birthdate", "lt2000-01-01"), Tuple.Create("address-city", "Town"), Tuple.Create("address-state", "State") }; + while (sw.Elapsed.TotalSeconds < 100) // previous single try after 1.1 sec delay was not reliable. { await Task.Delay(300); await _fixture.SearchService.SearchAsync(KnownResourceTypes.Patient, query, CancellationToken.None); - Assert.Equal(hash, _fixture.SqlQueryHashCalculator.MostRecentSqlHash); + var newHash = _fixture.ReadableLogger.TryGetLatestLog("SQL Search Service query hash:", out var newHashValue) ? newHashValue.Substring(31) : null; + Assert.Equal(hash, newHash); if (await CheckIfSprocUsed(hash)) { sprocWasUsed = true; diff --git a/test/Microsoft.Health.Fhir.Shared.Tests.Integration/Persistence/SqlServerFhirStorageTestsFixture.cs b/test/Microsoft.Health.Fhir.Shared.Tests.Integration/Persistence/SqlServerFhirStorageTestsFixture.cs index 4be4aa54a3..9355c4fb35 100644 --- a/test/Microsoft.Health.Fhir.Shared.Tests.Integration/Persistence/SqlServerFhirStorageTestsFixture.cs +++ b/test/Microsoft.Health.Fhir.Shared.Tests.Integration/Persistence/SqlServerFhirStorageTestsFixture.cs @@ -77,6 +77,7 @@ public class SqlServerFhirStorageTestsFixture : IServiceProvider, IAsyncLifetime private SupportedSearchParameterDefinitionManager _supportedSearchParameterDefinitionManager; private SearchParameterStatusManager _searchParameterStatusManager; private SqlQueueClient _sqlQueueClient; + private ReadableLogger _readableLogger; public SqlServerFhirStorageTestsFixture() : this(SchemaVersionConstants.Max, GetDatabaseName()) @@ -125,8 +126,6 @@ internal SqlServerFhirStorageTestsFixture(int maximumSupportedSchemaVersion, str internal SchemaInformation SchemaInformation { get; private set; } - internal ISqlQueryHashCalculator SqlQueryHashCalculator { get; private set; } - internal static string GetDatabaseName(string test = null) { return $"{ModelInfoProvider.Version}{(test == null ? string.Empty : $"_{test}")}_{DateTimeOffset.UtcNow.ToString("s").Replace("-", string.Empty).Replace(":", string.Empty)}_{Guid.NewGuid().ToString().Replace("-", string.Empty)}"; @@ -278,7 +277,7 @@ public async Task InitializeAsync() var compartmentSearchRewriter = new CompartmentSearchRewriter(new Lazy(() => compartmentDefinitionManager), new Lazy(() => _searchParameterDefinitionManager)); var smartCompartmentSearchRewriter = new SmartCompartmentSearchRewriter(compartmentSearchRewriter, new Lazy(() => _searchParameterDefinitionManager)); - SqlQueryHashCalculator = new TestSqlHashCalculator(); + _readableLogger = new ReadableLogger(); _searchService = new SqlServerSearchService( searchOptionsFactory, @@ -295,9 +294,8 @@ public async Task InitializeAsync() SchemaInformation, _fhirRequestContextAccessor, new CompressedRawResourceConverter(), - SqlQueryHashCalculator, new SqlServerParameterStore(SqlConnectionBuilder, NullLogger.Instance), - NullLogger.Instance); + _readableLogger); ISearchParameterSupportResolver searchParameterSupportResolver = Substitute.For(); searchParameterSupportResolver.IsSearchParameterSupported(Arg.Any()).Returns((true, false)); @@ -413,9 +411,9 @@ object IServiceProvider.GetService(Type serviceType) return _sqlQueueClient; } - if (serviceType == typeof(TestSqlHashCalculator)) + if (serviceType == typeof(ReadableLogger)) { - return SqlQueryHashCalculator as TestSqlHashCalculator; + return _readableLogger; } return null; diff --git a/test/Microsoft.Health.Fhir.Shared.Tests.Integration/Persistence/TestSqlHashCalculator.cs b/test/Microsoft.Health.Fhir.Shared.Tests.Integration/Persistence/TestSqlHashCalculator.cs deleted file mode 100644 index 9e7922ecf0..0000000000 --- a/test/Microsoft.Health.Fhir.Shared.Tests.Integration/Persistence/TestSqlHashCalculator.cs +++ /dev/null @@ -1,25 +0,0 @@ -// ------------------------------------------------------------------------------------------------- -// 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.Core.Extensions; -using Microsoft.Health.Fhir.SqlServer.Features.Search; - -namespace Microsoft.Health.Fhir.Tests.Integration.Persistence -{ - public class TestSqlHashCalculator : ISqlQueryHashCalculator - { - public string MostRecentSqlQuery { get; set; } - - public string MostRecentSqlHash { get; set; } - - public string CalculateHash(string query) - { - MostRecentSqlQuery = query; - MostRecentSqlHash = query.ComputeHash(); - - return MostRecentSqlHash; - } - } -}