Implements SQL search parameter status data layer. (#1430)
Adds new schema version and refactors SQL initialization logic.
This commit is contained in:
Родитель
5d8f5d601d
Коммит
40cda490a2
|
@ -31,7 +31,7 @@ namespace Microsoft.Health.Fhir.Core.UnitTests.Features.Search.Registry
|
|||
private static readonly string ResourceQuery = "http://hl7.org/fhir/SearchParameter/Resource-query";
|
||||
|
||||
private readonly SearchParameterStatusManager _manager;
|
||||
private readonly ISearchParameterRegistry _searchParameterRegistry;
|
||||
private readonly ISearchParameterStatusDataStore _searchParameterStatusDataStore;
|
||||
private readonly ISearchParameterDefinitionManager _searchParameterDefinitionManager;
|
||||
private readonly IMediator _mediator;
|
||||
private readonly SearchParameterInfo[] _searchParameterInfos;
|
||||
|
@ -41,13 +41,13 @@ namespace Microsoft.Health.Fhir.Core.UnitTests.Features.Search.Registry
|
|||
|
||||
public SearchParameterStatusManagerTests()
|
||||
{
|
||||
_searchParameterRegistry = Substitute.For<ISearchParameterRegistry>();
|
||||
_searchParameterStatusDataStore = Substitute.For<ISearchParameterStatusDataStore>();
|
||||
_searchParameterDefinitionManager = Substitute.For<ISearchParameterDefinitionManager>();
|
||||
_searchParameterSupportResolver = Substitute.For<ISearchParameterSupportResolver>();
|
||||
_mediator = Substitute.For<IMediator>();
|
||||
|
||||
_manager = new SearchParameterStatusManager(
|
||||
_searchParameterRegistry,
|
||||
_searchParameterStatusDataStore,
|
||||
_searchParameterDefinitionManager,
|
||||
_searchParameterSupportResolver,
|
||||
_mediator);
|
||||
|
@ -81,7 +81,7 @@ namespace Microsoft.Health.Fhir.Core.UnitTests.Features.Search.Registry
|
|||
},
|
||||
};
|
||||
|
||||
_searchParameterRegistry.GetSearchParameterStatuses().Returns(_resourceSearchParameterStatuses);
|
||||
_searchParameterStatusDataStore.GetSearchParameterStatuses().Returns(_resourceSearchParameterStatuses);
|
||||
|
||||
List<string> baseResourceTypes = new List<string>() { "Resource" };
|
||||
List<string> targetResourceTypes = new List<string>() { "Patient" };
|
||||
|
@ -161,9 +161,9 @@ namespace Microsoft.Health.Fhir.Core.UnitTests.Features.Search.Registry
|
|||
{
|
||||
await _manager.EnsureInitialized();
|
||||
|
||||
await _searchParameterRegistry
|
||||
await _searchParameterStatusDataStore
|
||||
.DidNotReceive()
|
||||
.UpdateStatuses(Arg.Any<IEnumerable<ResourceSearchParameterStatus>>());
|
||||
.UpsertStatuses(Arg.Any<List<ResourceSearchParameterStatus>>());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
|
@ -25,7 +25,6 @@ namespace Microsoft.Health.Fhir.Core.Features.Definition
|
|||
private readonly IModelInfoProvider _modelInfoProvider;
|
||||
|
||||
private IDictionary<string, IDictionary<string, SearchParameterInfo>> _typeLookup;
|
||||
private bool _started;
|
||||
private ConcurrentDictionary<string, string> _resourceTypeSearchParameterHashMap;
|
||||
|
||||
public SearchParameterDefinitionManager(IModelInfoProvider modelInfoProvider)
|
||||
|
@ -49,22 +48,14 @@ namespace Microsoft.Health.Fhir.Core.Features.Definition
|
|||
|
||||
public Task StartAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
// This method is idempotent because dependent Start methods are not guaranteed to be executed in order.
|
||||
if (!_started)
|
||||
{
|
||||
var builder = new SearchParameterDefinitionBuilder(
|
||||
_modelInfoProvider,
|
||||
"search-parameters.json");
|
||||
var builder = new SearchParameterDefinitionBuilder(
|
||||
_modelInfoProvider,
|
||||
"search-parameters.json");
|
||||
|
||||
builder.Build();
|
||||
builder.Build();
|
||||
|
||||
_typeLookup = builder.ResourceTypeDictionary;
|
||||
UrlLookup = builder.UriDictionary;
|
||||
|
||||
List<string> list = UrlLookup.Values.Where(p => p.Type == ValueSets.SearchParamType.Composite).Select(p => string.Join("|", p.Component.Select(c => UrlLookup[c.DefinitionUrl].Type))).Distinct().ToList();
|
||||
|
||||
_started = true;
|
||||
}
|
||||
_typeLookup = builder.ResourceTypeDictionary;
|
||||
UrlLookup = builder.UriDictionary;
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
|
|
@ -16,13 +16,13 @@ using Newtonsoft.Json;
|
|||
|
||||
namespace Microsoft.Health.Fhir.Core.Features.Search.Registry
|
||||
{
|
||||
public class FilebasedSearchParameterRegistry : ISearchParameterRegistry
|
||||
public class FilebasedSearchParameterStatusDataStore : ISearchParameterStatusDataStore
|
||||
{
|
||||
private readonly ISearchParameterDefinitionManager _searchParameterDefinitionManager;
|
||||
private readonly IModelInfoProvider _modelInfoProvider;
|
||||
private ResourceSearchParameterStatus[] _statusResults;
|
||||
|
||||
public FilebasedSearchParameterRegistry(
|
||||
public FilebasedSearchParameterStatusDataStore(
|
||||
ISearchParameterDefinitionManager searchParameterDefinitionManager,
|
||||
IModelInfoProvider modelInfoProvider)
|
||||
{
|
||||
|
@ -32,7 +32,7 @@ namespace Microsoft.Health.Fhir.Core.Features.Search.Registry
|
|||
_modelInfoProvider = modelInfoProvider;
|
||||
}
|
||||
|
||||
public delegate ISearchParameterRegistry Resolver();
|
||||
public delegate ISearchParameterStatusDataStore Resolver();
|
||||
|
||||
public Task<IReadOnlyCollection<ResourceSearchParameterStatus>> GetSearchParameterStatuses()
|
||||
{
|
||||
|
@ -76,7 +76,7 @@ namespace Microsoft.Health.Fhir.Core.Features.Search.Registry
|
|||
return Task.FromResult<IReadOnlyCollection<ResourceSearchParameterStatus>>(_statusResults);
|
||||
}
|
||||
|
||||
public Task UpdateStatuses(IEnumerable<ResourceSearchParameterStatus> statuses)
|
||||
public Task UpsertStatuses(List<ResourceSearchParameterStatus> statuses)
|
||||
{
|
||||
// File based registry does not persist runtime updates
|
||||
return Task.CompletedTask;
|
|
@ -8,10 +8,10 @@ using System.Threading.Tasks;
|
|||
|
||||
namespace Microsoft.Health.Fhir.Core.Features.Search.Registry
|
||||
{
|
||||
public interface ISearchParameterRegistry
|
||||
public interface ISearchParameterStatusDataStore
|
||||
{
|
||||
Task<IReadOnlyCollection<ResourceSearchParameterStatus>> GetSearchParameterStatuses();
|
||||
|
||||
Task UpdateStatuses(IEnumerable<ResourceSearchParameterStatus> statuses);
|
||||
Task UpsertStatuses(List<ResourceSearchParameterStatus> statuses);
|
||||
}
|
||||
}
|
|
@ -19,23 +19,23 @@ namespace Microsoft.Health.Fhir.Core.Features.Search.Registry
|
|||
{
|
||||
public class SearchParameterStatusManager : IRequireInitializationOnFirstRequest
|
||||
{
|
||||
private readonly ISearchParameterRegistry _searchParameterRegistry;
|
||||
private readonly ISearchParameterStatusDataStore _searchParameterStatusDataStore;
|
||||
private readonly ISearchParameterDefinitionManager _searchParameterDefinitionManager;
|
||||
private readonly ISearchParameterSupportResolver _searchParameterSupportResolver;
|
||||
private readonly IMediator _mediator;
|
||||
|
||||
public SearchParameterStatusManager(
|
||||
ISearchParameterRegistry searchParameterRegistry,
|
||||
ISearchParameterStatusDataStore searchParameterStatusDataStore,
|
||||
ISearchParameterDefinitionManager searchParameterDefinitionManager,
|
||||
ISearchParameterSupportResolver searchParameterSupportResolver,
|
||||
IMediator mediator)
|
||||
{
|
||||
EnsureArg.IsNotNull(searchParameterRegistry, nameof(searchParameterRegistry));
|
||||
EnsureArg.IsNotNull(searchParameterStatusDataStore, nameof(searchParameterStatusDataStore));
|
||||
EnsureArg.IsNotNull(searchParameterDefinitionManager, nameof(searchParameterDefinitionManager));
|
||||
EnsureArg.IsNotNull(searchParameterSupportResolver, nameof(searchParameterSupportResolver));
|
||||
EnsureArg.IsNotNull(mediator, nameof(mediator));
|
||||
|
||||
_searchParameterRegistry = searchParameterRegistry;
|
||||
_searchParameterStatusDataStore = searchParameterStatusDataStore;
|
||||
_searchParameterDefinitionManager = searchParameterDefinitionManager;
|
||||
_searchParameterSupportResolver = searchParameterSupportResolver;
|
||||
_mediator = mediator;
|
||||
|
@ -46,7 +46,7 @@ namespace Microsoft.Health.Fhir.Core.Features.Search.Registry
|
|||
var updated = new List<SearchParameterInfo>();
|
||||
var resourceTypeSearchParamStatusMap = new Dictionary<string, List<ResourceSearchParameterStatus>>();
|
||||
|
||||
var parameters = (await _searchParameterRegistry.GetSearchParameterStatuses())
|
||||
var parameters = (await _searchParameterStatusDataStore.GetSearchParameterStatuses())
|
||||
.ToDictionary(x => x.Uri);
|
||||
|
||||
// Set states of known parameters
|
||||
|
|
|
@ -18,23 +18,23 @@ using Xunit;
|
|||
|
||||
namespace Microsoft.Health.Fhir.CosmosDb.UnitTests.Features.Storage.Registry
|
||||
{
|
||||
public class CosmosDbStatusRegistryInitializerTests
|
||||
public class CosmosDbSearchParameterStatusInitializerTests
|
||||
{
|
||||
private readonly CosmosDbStatusRegistryInitializer _initializer;
|
||||
private readonly CosmosDbSearchParameterStatusInitializer _initializer;
|
||||
private readonly ICosmosQueryFactory _cosmosDocumentQueryFactory;
|
||||
private readonly Uri _testParameterUri;
|
||||
|
||||
public CosmosDbStatusRegistryInitializerTests()
|
||||
public CosmosDbSearchParameterStatusInitializerTests()
|
||||
{
|
||||
ISearchParameterRegistry searchParameterRegistry = Substitute.For<ISearchParameterRegistry>();
|
||||
ISearchParameterStatusDataStore searchParameterStatusDataStore = Substitute.For<ISearchParameterStatusDataStore>();
|
||||
_cosmosDocumentQueryFactory = Substitute.For<ICosmosQueryFactory>();
|
||||
|
||||
_initializer = new CosmosDbStatusRegistryInitializer(
|
||||
() => searchParameterRegistry,
|
||||
_initializer = new CosmosDbSearchParameterStatusInitializer(
|
||||
() => searchParameterStatusDataStore,
|
||||
_cosmosDocumentQueryFactory);
|
||||
|
||||
_testParameterUri = new Uri("/test", UriKind.Relative);
|
||||
searchParameterRegistry
|
||||
searchParameterStatusDataStore
|
||||
.GetSearchParameterStatuses()
|
||||
.Returns(new[]
|
||||
{
|
|
@ -17,12 +17,12 @@ using Microsoft.Health.Fhir.CosmosDb.Configs;
|
|||
|
||||
namespace Microsoft.Health.Fhir.CosmosDb.Features.Storage.Registry
|
||||
{
|
||||
public class CosmosDbStatusRegistry : ISearchParameterRegistry
|
||||
public class CosmosDbSearchParameterStatusDataStore : ISearchParameterStatusDataStore
|
||||
{
|
||||
private readonly Func<IScoped<Container>> _containerScopeFactory;
|
||||
private readonly ICosmosQueryFactory _queryFactory;
|
||||
|
||||
public CosmosDbStatusRegistry(
|
||||
public CosmosDbSearchParameterStatusDataStore(
|
||||
Func<IScoped<Container>> containerScopeFactory,
|
||||
CosmosDataStoreConfiguration cosmosDataStoreConfiguration,
|
||||
ICosmosQueryFactory queryFactory)
|
||||
|
@ -35,8 +35,6 @@ namespace Microsoft.Health.Fhir.CosmosDb.Features.Storage.Registry
|
|||
_queryFactory = queryFactory;
|
||||
}
|
||||
|
||||
public Uri CollectionUri { get; set; }
|
||||
|
||||
public async Task<IReadOnlyCollection<ResourceSearchParameterStatus>> GetSearchParameterStatuses()
|
||||
{
|
||||
using var cancellationSource = new CancellationTokenSource(TimeSpan.FromMinutes(1));
|
||||
|
@ -72,10 +70,15 @@ namespace Microsoft.Health.Fhir.CosmosDb.Features.Storage.Registry
|
|||
return parameterStatus;
|
||||
}
|
||||
|
||||
public async Task UpdateStatuses(IEnumerable<ResourceSearchParameterStatus> statuses)
|
||||
public async Task UpsertStatuses(List<ResourceSearchParameterStatus> statuses)
|
||||
{
|
||||
EnsureArg.IsNotNull(statuses, nameof(statuses));
|
||||
|
||||
if (statuses.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
using var clientScope = _containerScopeFactory.Invoke();
|
||||
var batch = clientScope.Value.CreateTransactionalBatch(new PartitionKey(SearchParameterStatusWrapper.SearchParameterStatusPartitionKey));
|
||||
|
|
@ -14,19 +14,19 @@ using Microsoft.Health.Fhir.CosmosDb.Features.Storage.Versioning;
|
|||
|
||||
namespace Microsoft.Health.Fhir.CosmosDb.Features.Storage.Registry
|
||||
{
|
||||
public class CosmosDbStatusRegistryInitializer : ICollectionUpdater
|
||||
public class CosmosDbSearchParameterStatusInitializer : ICollectionUpdater
|
||||
{
|
||||
private readonly ISearchParameterRegistry _filebasedRegistry;
|
||||
private readonly ISearchParameterStatusDataStore _filebasedSearchParameterStatusDataStore;
|
||||
private readonly ICosmosQueryFactory _queryFactory;
|
||||
|
||||
public CosmosDbStatusRegistryInitializer(
|
||||
FilebasedSearchParameterRegistry.Resolver filebasedRegistry,
|
||||
public CosmosDbSearchParameterStatusInitializer(
|
||||
FilebasedSearchParameterStatusDataStore.Resolver filebasedSearchParameterStatusDataStoreResolver,
|
||||
ICosmosQueryFactory queryFactory)
|
||||
{
|
||||
EnsureArg.IsNotNull(filebasedRegistry, nameof(filebasedRegistry));
|
||||
EnsureArg.IsNotNull(filebasedSearchParameterStatusDataStoreResolver, nameof(filebasedSearchParameterStatusDataStoreResolver));
|
||||
EnsureArg.IsNotNull(queryFactory, nameof(queryFactory));
|
||||
|
||||
_filebasedRegistry = filebasedRegistry.Invoke();
|
||||
_filebasedSearchParameterStatusDataStore = filebasedSearchParameterStatusDataStoreResolver.Invoke();
|
||||
_queryFactory = queryFactory;
|
||||
}
|
||||
|
||||
|
@ -46,7 +46,7 @@ namespace Microsoft.Health.Fhir.CosmosDb.Features.Storage.Registry
|
|||
|
||||
if (!results.Any())
|
||||
{
|
||||
var statuses = await _filebasedRegistry.GetSearchParameterStatuses();
|
||||
var statuses = await _filebasedSearchParameterStatusDataStore.GetSearchParameterStatuses();
|
||||
|
||||
foreach (var batch in statuses.TakeBatch(100))
|
||||
{
|
|
@ -161,7 +161,7 @@ namespace Microsoft.Extensions.DependencyInjection
|
|||
.Transient()
|
||||
.AsService<ICollectionUpdater>();
|
||||
|
||||
services.Add<CosmosDbStatusRegistryInitializer>()
|
||||
services.Add<CosmosDbSearchParameterStatusInitializer>()
|
||||
.Transient()
|
||||
.AsService<ICollectionUpdater>();
|
||||
|
||||
|
@ -185,10 +185,10 @@ namespace Microsoft.Extensions.DependencyInjection
|
|||
.AsSelf()
|
||||
.AsImplementedInterfaces();
|
||||
|
||||
services.Add<CosmosDbStatusRegistry>()
|
||||
services.Add<CosmosDbSearchParameterStatusDataStore>()
|
||||
.Singleton()
|
||||
.AsSelf()
|
||||
.ReplaceService<ISearchParameterRegistry>();
|
||||
.ReplaceService<ISearchParameterStatusDataStore>();
|
||||
|
||||
// Each CosmosClient needs new instances of a RequestHandler
|
||||
services.TypesInSameAssemblyAs<FhirCosmosClientInitializer>()
|
||||
|
|
|
@ -66,11 +66,11 @@ namespace Microsoft.Health.Fhir.Api.Modules
|
|||
.AsSelf()
|
||||
.AsImplementedInterfaces();
|
||||
|
||||
services.Add<FilebasedSearchParameterRegistry>()
|
||||
services.Add<FilebasedSearchParameterStatusDataStore>()
|
||||
.Transient()
|
||||
.AsSelf()
|
||||
.AsService<ISearchParameterRegistry>()
|
||||
.AsDelegate<FilebasedSearchParameterRegistry.Resolver>();
|
||||
.AsService<ISearchParameterStatusDataStore>()
|
||||
.AsDelegate<FilebasedSearchParameterStatusDataStore.Resolver>();
|
||||
|
||||
services.Add<SearchParameterSupportResolver>()
|
||||
.Singleton()
|
||||
|
|
|
@ -29,7 +29,7 @@ namespace Microsoft.Health.Fhir.Core.UnitTests.Features.Operations.Reindex
|
|||
private readonly ISearchIndexer _searchIndexer = Substitute.For<ISearchIndexer>();
|
||||
private readonly ResourceDeserializer _resourceDeserializer = Deserializers.ResourceDeserializer;
|
||||
private readonly ISupportedSearchParameterDefinitionManager _searchParameterDefinitionManager = Substitute.For<ISupportedSearchParameterDefinitionManager>();
|
||||
private readonly ISearchParameterRegistry _searchParameterRegistry = Substitute.For<ISearchParameterRegistry>();
|
||||
private readonly ISearchParameterStatusDataStore _searchParameterStatusDataStore = Substitute.For<ISearchParameterStatusDataStore>();
|
||||
private readonly ITestOutputHelper _output;
|
||||
private IReadOnlyDictionary<string, string> _searchParameterHashMap;
|
||||
private readonly ReindexUtilities _reindexUtilities;
|
||||
|
@ -39,7 +39,7 @@ namespace Microsoft.Health.Fhir.Core.UnitTests.Features.Operations.Reindex
|
|||
_output = output;
|
||||
_searchParameterHashMap = new Dictionary<string, string>() { { "Patient", "hash1" } };
|
||||
Func<Health.Extensions.DependencyInjection.IScoped<IFhirDataStore>> fhirDataStoreScope = () => _fhirDataStore.CreateMockScope();
|
||||
_reindexUtilities = new ReindexUtilities(fhirDataStoreScope, _searchIndexer, _resourceDeserializer, _searchParameterDefinitionManager, _searchParameterRegistry);
|
||||
_reindexUtilities = new ReindexUtilities(fhirDataStoreScope, _searchIndexer, _resourceDeserializer, _searchParameterDefinitionManager, _searchParameterStatusDataStore);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
|
@ -31,7 +31,7 @@ namespace Microsoft.Health.Fhir.Core.UnitTests.Features.Search
|
|||
private static readonly string ResourceTest = "http://hl7.org/fhir/SearchParameter/Resource-test";
|
||||
|
||||
private readonly SearchParameterStatusManager _manager;
|
||||
private readonly ISearchParameterRegistry _searchParameterRegistry;
|
||||
private readonly ISearchParameterStatusDataStore _searchParameterStatusDataStore;
|
||||
private readonly SearchParameterDefinitionManager _searchParameterDefinitionManager;
|
||||
private readonly IMediator _mediator;
|
||||
private readonly SearchParameterInfo[] _searchParameterInfos;
|
||||
|
@ -45,18 +45,18 @@ namespace Microsoft.Health.Fhir.Core.UnitTests.Features.Search
|
|||
{
|
||||
_searchParameterSupportResolver = Substitute.For<ISearchParameterSupportResolver>();
|
||||
_mediator = Substitute.For<IMediator>();
|
||||
_searchParameterRegistry = Substitute.For<ISearchParameterRegistry>();
|
||||
_searchParameterStatusDataStore = Substitute.For<ISearchParameterStatusDataStore>();
|
||||
_searchParameterDefinitionManager = new SearchParameterDefinitionManager(ModelInfoProvider.Instance);
|
||||
_fhirRequestContextAccessor = Substitute.For<IFhirRequestContextAccessor>();
|
||||
_fhirRequestContextAccessor.FhirRequestContext.Returns(_fhirRequestContext);
|
||||
|
||||
_manager = new SearchParameterStatusManager(
|
||||
_searchParameterRegistry,
|
||||
_searchParameterStatusDataStore,
|
||||
_searchParameterDefinitionManager,
|
||||
_searchParameterSupportResolver,
|
||||
_mediator);
|
||||
|
||||
_searchParameterRegistry.GetSearchParameterStatuses()
|
||||
_searchParameterStatusDataStore.GetSearchParameterStatuses()
|
||||
.Returns(new[]
|
||||
{
|
||||
new ResourceSearchParameterStatus
|
||||
|
|
|
@ -74,7 +74,7 @@ namespace Microsoft.Health.Fhir.Core.UnitTests.Features.Search
|
|||
var definitionManager = new SearchParameterDefinitionManager(modelInfoProvider);
|
||||
await definitionManager.StartAsync(CancellationToken.None);
|
||||
|
||||
var statusRegistry = new FilebasedSearchParameterRegistry(
|
||||
var statusRegistry = new FilebasedSearchParameterStatusDataStore(
|
||||
definitionManager,
|
||||
modelInfoProvider);
|
||||
var statusManager = new SearchParameterStatusManager(
|
||||
|
|
|
@ -22,26 +22,26 @@ namespace Microsoft.Health.Fhir.Core.Features.Operations.Reindex
|
|||
private ISearchIndexer _searchIndexer;
|
||||
private ResourceDeserializer _deserializer;
|
||||
private readonly ISupportedSearchParameterDefinitionManager _searchParameterDefinitionManager;
|
||||
private readonly ISearchParameterRegistry _searchParameterRegistry;
|
||||
private readonly ISearchParameterStatusDataStore _searchParameterStatusDataStore;
|
||||
|
||||
public ReindexUtilities(
|
||||
Func<IScoped<IFhirDataStore>> fhirDataStoreFactory,
|
||||
ISearchIndexer searchIndexer,
|
||||
ResourceDeserializer deserializer,
|
||||
ISupportedSearchParameterDefinitionManager searchParameterDefinitionManager,
|
||||
ISearchParameterRegistry searchParameterRegistry)
|
||||
ISearchParameterStatusDataStore searchParameterStatusDataStore)
|
||||
{
|
||||
EnsureArg.IsNotNull(fhirDataStoreFactory, nameof(fhirDataStoreFactory));
|
||||
EnsureArg.IsNotNull(searchIndexer, nameof(searchIndexer));
|
||||
EnsureArg.IsNotNull(deserializer, nameof(deserializer));
|
||||
EnsureArg.IsNotNull(searchParameterDefinitionManager, nameof(searchParameterDefinitionManager));
|
||||
EnsureArg.IsNotNull(searchParameterRegistry, nameof(searchParameterRegistry));
|
||||
EnsureArg.IsNotNull(searchParameterStatusDataStore, nameof(searchParameterStatusDataStore));
|
||||
|
||||
_fhirDataStoreFactory = fhirDataStoreFactory;
|
||||
_searchIndexer = searchIndexer;
|
||||
_deserializer = deserializer;
|
||||
_searchParameterDefinitionManager = searchParameterDefinitionManager;
|
||||
_searchParameterRegistry = searchParameterRegistry;
|
||||
_searchParameterStatusDataStore = searchParameterStatusDataStore;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -114,7 +114,7 @@ namespace Microsoft.Health.Fhir.Core.Features.Operations.Reindex
|
|||
});
|
||||
}
|
||||
|
||||
await _searchParameterRegistry.UpdateStatuses(searchParameterStatusList);
|
||||
await _searchParameterStatusDataStore.UpsertStatuses(searchParameterStatusList);
|
||||
|
||||
return (true, null);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,76 @@
|
|||
/*************************************************************
|
||||
Search Parameter Status Information
|
||||
**************************************************************/
|
||||
|
||||
ALTER TABLE dbo.SearchParam
|
||||
ADD
|
||||
Status varchar(10) NULL,
|
||||
LastUpdated datetimeoffset(7) NULL,
|
||||
IsPartiallySupported bit NULL
|
||||
|
||||
-- We adopted this naming convention for table-valued parameters because they are immutable.
|
||||
CREATE TYPE dbo.SearchParamTableType_1 AS TABLE
|
||||
(
|
||||
Uri varchar(128) COLLATE Latin1_General_100_CS_AS NOT NULL,
|
||||
Status varchar(10) NOT NULL,
|
||||
IsPartiallySupported bit NOT NULL
|
||||
)
|
||||
|
||||
GO
|
||||
|
||||
/*************************************************************
|
||||
Stored procedures for search parameter information
|
||||
**************************************************************/
|
||||
--
|
||||
-- STORED PROCEDURE
|
||||
-- GetSearchParamStatuses
|
||||
--
|
||||
-- DESCRIPTION
|
||||
-- Gets all the search parameters and their statuses.
|
||||
--
|
||||
-- RETURN VALUE
|
||||
-- The search parameters and their statuses.
|
||||
--
|
||||
CREATE PROCEDURE dbo.GetSearchParamStatuses
|
||||
AS
|
||||
SET NOCOUNT ON
|
||||
|
||||
SELECT Uri, Status, LastUpdated, IsPartiallySupported FROM dbo.SearchParam
|
||||
GO
|
||||
|
||||
--
|
||||
-- STORED PROCEDURE
|
||||
-- UpsertSearchParams
|
||||
--
|
||||
-- DESCRIPTION
|
||||
-- Given a set of search parameters, creates or updates the parameters.
|
||||
--
|
||||
-- PARAMETERS
|
||||
-- @searchParams
|
||||
-- * The updated existing search parameters or the new search parameters
|
||||
--
|
||||
CREATE PROCEDURE dbo.UpsertSearchParams
|
||||
@searchParams dbo.SearchParamTableType_1 READONLY
|
||||
AS
|
||||
SET NOCOUNT ON
|
||||
SET XACT_ABORT ON
|
||||
|
||||
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
|
||||
BEGIN TRANSACTION
|
||||
|
||||
DECLARE @lastUpdated datetimeoffset(7) = SYSDATETIMEOFFSET()
|
||||
|
||||
-- Acquire and hold an exclusive table lock for the entire transaction to prevent parameters from being added or modified during upsert.
|
||||
MERGE INTO dbo.SearchParam WITH (TABLOCKX) AS target
|
||||
USING @searchParams AS source
|
||||
ON target.Uri = source.Uri
|
||||
WHEN MATCHED THEN
|
||||
UPDATE
|
||||
SET Status = source.Status, LastUpdated = @lastUpdated, IsPartiallySupported = source.IsPartiallySupported
|
||||
WHEN NOT MATCHED BY target THEN
|
||||
INSERT
|
||||
(Uri, Status, LastUpdated, IsPartiallySupported)
|
||||
VALUES (source.Uri, source.Status, @lastUpdated, source.IsPartiallySupported);
|
||||
|
||||
COMMIT TRANSACTION
|
||||
GO
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -41,10 +41,12 @@ namespace Microsoft.Health.Fhir.SqlServer.Features.Schema.Model
|
|||
internal readonly static CreateExportJobProcedure CreateExportJob = new CreateExportJobProcedure();
|
||||
internal readonly static GetExportJobByHashProcedure GetExportJobByHash = new GetExportJobByHashProcedure();
|
||||
internal readonly static GetExportJobByIdProcedure GetExportJobById = new GetExportJobByIdProcedure();
|
||||
internal readonly static GetSearchParamStatusesProcedure GetSearchParamStatuses = new GetSearchParamStatusesProcedure();
|
||||
internal readonly static HardDeleteResourceProcedure HardDeleteResource = new HardDeleteResourceProcedure();
|
||||
internal readonly static ReadResourceProcedure ReadResource = new ReadResourceProcedure();
|
||||
internal readonly static UpdateExportJobProcedure UpdateExportJob = new UpdateExportJobProcedure();
|
||||
internal readonly static UpsertResourceProcedure UpsertResource = new UpsertResourceProcedure();
|
||||
internal readonly static UpsertSearchParamsProcedure UpsertSearchParams = new UpsertSearchParamsProcedure();
|
||||
internal class ClaimTypeTable : Table
|
||||
{
|
||||
internal ClaimTypeTable(): base("dbo.ClaimType")
|
||||
|
@ -229,6 +231,9 @@ namespace Microsoft.Health.Fhir.SqlServer.Features.Schema.Model
|
|||
|
||||
internal readonly SmallIntColumn SearchParamId = new SmallIntColumn("SearchParamId");
|
||||
internal readonly VarCharColumn Uri = new VarCharColumn("Uri", 128, "Latin1_General_100_CS_AS");
|
||||
internal readonly NullableVarCharColumn Status = new NullableVarCharColumn("Status", 10);
|
||||
internal readonly NullableDateTimeOffsetColumn LastUpdated = new NullableDateTimeOffsetColumn("LastUpdated", 7);
|
||||
internal readonly NullableBitColumn IsPartiallySupported = new NullableBitColumn("IsPartiallySupported");
|
||||
}
|
||||
|
||||
internal class StringSearchParamTable : Table
|
||||
|
@ -452,6 +457,19 @@ namespace Microsoft.Health.Fhir.SqlServer.Features.Schema.Model
|
|||
}
|
||||
}
|
||||
|
||||
internal class GetSearchParamStatusesProcedure : StoredProcedure
|
||||
{
|
||||
internal GetSearchParamStatusesProcedure(): base("dbo.GetSearchParamStatuses")
|
||||
{
|
||||
}
|
||||
|
||||
public void PopulateCommand(SqlCommandWrapper command)
|
||||
{
|
||||
command.CommandType = global::System.Data.CommandType.StoredProcedure;
|
||||
command.CommandText = "dbo.GetSearchParamStatuses";
|
||||
}
|
||||
}
|
||||
|
||||
internal class HardDeleteResourceProcedure : StoredProcedure
|
||||
{
|
||||
internal HardDeleteResourceProcedure(): base("dbo.HardDeleteResource")
|
||||
|
@ -724,6 +742,53 @@ namespace Microsoft.Health.Fhir.SqlServer.Features.Schema.Model
|
|||
}
|
||||
}
|
||||
|
||||
internal class UpsertSearchParamsProcedure : StoredProcedure
|
||||
{
|
||||
internal UpsertSearchParamsProcedure(): base("dbo.UpsertSearchParams")
|
||||
{
|
||||
}
|
||||
|
||||
private readonly SearchParamTableTypeTableValuedParameterDefinition _searchParams = new SearchParamTableTypeTableValuedParameterDefinition("@searchParams");
|
||||
public void PopulateCommand(SqlCommandWrapper command, global::System.Collections.Generic.IEnumerable<SearchParamTableTypeRow> searchParams)
|
||||
{
|
||||
command.CommandType = global::System.Data.CommandType.StoredProcedure;
|
||||
command.CommandText = "dbo.UpsertSearchParams";
|
||||
_searchParams.AddParameter(command.Parameters, searchParams);
|
||||
}
|
||||
|
||||
public void PopulateCommand(SqlCommandWrapper command, UpsertSearchParamsTableValuedParameters tableValuedParameters)
|
||||
{
|
||||
PopulateCommand(command, searchParams: tableValuedParameters.SearchParams);
|
||||
}
|
||||
}
|
||||
|
||||
internal class UpsertSearchParamsTvpGenerator<TInput> : IStoredProcedureTableValuedParametersGenerator<TInput, UpsertSearchParamsTableValuedParameters>
|
||||
{
|
||||
public UpsertSearchParamsTvpGenerator(ITableValuedParameterRowGenerator<TInput, SearchParamTableTypeRow> SearchParamTableTypeRowGenerator)
|
||||
{
|
||||
this.SearchParamTableTypeRowGenerator = SearchParamTableTypeRowGenerator;
|
||||
}
|
||||
|
||||
private readonly ITableValuedParameterRowGenerator<TInput, SearchParamTableTypeRow> SearchParamTableTypeRowGenerator;
|
||||
public UpsertSearchParamsTableValuedParameters Generate(TInput input)
|
||||
{
|
||||
return new UpsertSearchParamsTableValuedParameters(SearchParamTableTypeRowGenerator.GenerateRows(input));
|
||||
}
|
||||
}
|
||||
|
||||
internal struct UpsertSearchParamsTableValuedParameters
|
||||
{
|
||||
internal UpsertSearchParamsTableValuedParameters(global::System.Collections.Generic.IEnumerable<SearchParamTableTypeRow> SearchParams)
|
||||
{
|
||||
this.SearchParams = SearchParams;
|
||||
}
|
||||
|
||||
internal global::System.Collections.Generic.IEnumerable<SearchParamTableTypeRow> SearchParams
|
||||
{
|
||||
get;
|
||||
}
|
||||
}
|
||||
|
||||
private class CompartmentAssignmentTableTypeTableValuedParameterDefinition : TableValuedParameterDefinition<CompartmentAssignmentTableTypeRow>
|
||||
{
|
||||
internal CompartmentAssignmentTableTypeTableValuedParameterDefinition(System.String parameterName): base(parameterName, "dbo.CompartmentAssignmentTableType_1")
|
||||
|
@ -1097,6 +1162,49 @@ namespace Microsoft.Health.Fhir.SqlServer.Features.Schema.Model
|
|||
}
|
||||
}
|
||||
|
||||
private class SearchParamTableTypeTableValuedParameterDefinition : TableValuedParameterDefinition<SearchParamTableTypeRow>
|
||||
{
|
||||
internal SearchParamTableTypeTableValuedParameterDefinition(System.String parameterName): base(parameterName, "dbo.SearchParamTableType_1")
|
||||
{
|
||||
}
|
||||
|
||||
internal readonly VarCharColumn Uri = new VarCharColumn("Uri", 128, "Latin1_General_100_CS_AS");
|
||||
internal readonly VarCharColumn Status = new VarCharColumn("Status", 10);
|
||||
internal readonly BitColumn IsPartiallySupported = new BitColumn("IsPartiallySupported");
|
||||
protected override global::System.Collections.Generic.IEnumerable<Column> Columns => new Column[]{Uri, Status, IsPartiallySupported};
|
||||
protected override void FillSqlDataRecord(global::Microsoft.Data.SqlClient.Server.SqlDataRecord record, SearchParamTableTypeRow rowData)
|
||||
{
|
||||
Uri.Set(record, 0, rowData.Uri);
|
||||
Status.Set(record, 1, rowData.Status);
|
||||
IsPartiallySupported.Set(record, 2, rowData.IsPartiallySupported);
|
||||
}
|
||||
}
|
||||
|
||||
internal struct SearchParamTableTypeRow
|
||||
{
|
||||
internal SearchParamTableTypeRow(System.String Uri, System.String Status, System.Boolean IsPartiallySupported)
|
||||
{
|
||||
this.Uri = Uri;
|
||||
this.Status = Status;
|
||||
this.IsPartiallySupported = IsPartiallySupported;
|
||||
}
|
||||
|
||||
internal System.String Uri
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
internal System.String Status
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
internal System.Boolean IsPartiallySupported
|
||||
{
|
||||
get;
|
||||
}
|
||||
}
|
||||
|
||||
private class StringSearchParamTableTypeTableValuedParameterDefinition : TableValuedParameterDefinition<StringSearchParamTableTypeRow>
|
||||
{
|
||||
internal StringSearchParamTableTypeTableValuedParameterDefinition(System.String parameterName): base(parameterName, "dbo.StringSearchParamTableType_1")
|
||||
|
@ -1620,4 +1728,4 @@ namespace Microsoft.Health.Fhir.SqlServer.Features.Schema.Model
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -15,5 +15,6 @@ namespace Microsoft.Health.Fhir.SqlServer.Features.Schema
|
|||
V3 = 3,
|
||||
V4 = 4,
|
||||
V5 = 5,
|
||||
V6 = 6,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,7 +7,8 @@ namespace Microsoft.Health.Fhir.SqlServer.Features.Schema
|
|||
{
|
||||
public static class SchemaVersionConstants
|
||||
{
|
||||
public const int Max = (int)SchemaVersion.V5;
|
||||
public const int Min = (int)SchemaVersion.V4;
|
||||
public const int Max = (int)SchemaVersion.V6;
|
||||
public const int SearchParameterStatusSchemaVersion = (int)SchemaVersion.V6;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
// -------------------------------------------------------------------------------------------------
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information.
|
||||
// -------------------------------------------------------------------------------------------------
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using Microsoft.Data.SqlClient.Server;
|
||||
using Microsoft.Health.Fhir.Core.Features.Search.Registry;
|
||||
|
||||
namespace Microsoft.Health.Fhir.SqlServer.Features.Storage.Registry
|
||||
{
|
||||
public class SearchParameterStatusCollection : List<ResourceSearchParameterStatus>, IEnumerable<SqlDataRecord>
|
||||
{
|
||||
IEnumerator<SqlDataRecord> IEnumerable<SqlDataRecord>.GetEnumerator()
|
||||
{
|
||||
var sqlRow = new SqlDataRecord(
|
||||
new SqlMetaData("Uri", SqlDbType.VarChar, 128),
|
||||
new SqlMetaData("Status", SqlDbType.VarChar, 10),
|
||||
new SqlMetaData("IsPartiallySupported", SqlDbType.Bit));
|
||||
|
||||
foreach (ResourceSearchParameterStatus status in this)
|
||||
{
|
||||
sqlRow.SetString(0, status.Uri.ToString());
|
||||
sqlRow.SetString(1, status.Status.ToString());
|
||||
sqlRow.SetSqlBoolean(2, status.IsPartiallySupported);
|
||||
|
||||
yield return sqlRow;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,127 @@
|
|||
// -------------------------------------------------------------------------------------------------
|
||||
// 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.Data;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using EnsureThat;
|
||||
using Microsoft.Health.Extensions.DependencyInjection;
|
||||
using Microsoft.Health.Fhir.Core.Features.Persistence;
|
||||
using Microsoft.Health.Fhir.Core.Features.Search.Registry;
|
||||
using Microsoft.Health.Fhir.SqlServer.Features.Schema;
|
||||
using Microsoft.Health.Fhir.SqlServer.Features.Schema.Model;
|
||||
using Microsoft.Health.SqlServer.Features.Client;
|
||||
using Microsoft.Health.SqlServer.Features.Schema;
|
||||
using Microsoft.Health.SqlServer.Features.Storage;
|
||||
using SqlDataReader = Microsoft.Data.SqlClient.SqlDataReader;
|
||||
|
||||
namespace Microsoft.Health.Fhir.SqlServer.Features.Storage.Registry
|
||||
{
|
||||
internal class SqlServerSearchParameterStatusDataStore : ISearchParameterStatusDataStore
|
||||
{
|
||||
private readonly Func<IScoped<SqlConnectionWrapperFactory>> _scopedSqlConnectionWrapperFactory;
|
||||
private readonly VLatest.UpsertSearchParamsTvpGenerator<List<ResourceSearchParameterStatus>> _updateSearchParamsTvpGenerator;
|
||||
private readonly ISearchParameterStatusDataStore _filebasedSearchParameterStatusDataStore;
|
||||
private readonly SchemaInformation _schemaInformation;
|
||||
|
||||
public SqlServerSearchParameterStatusDataStore(
|
||||
Func<IScoped<SqlConnectionWrapperFactory>> scopedSqlConnectionWrapperFactory,
|
||||
VLatest.UpsertSearchParamsTvpGenerator<List<ResourceSearchParameterStatus>> updateSearchParamsTvpGenerator,
|
||||
FilebasedSearchParameterStatusDataStore.Resolver filebasedRegistry,
|
||||
SchemaInformation schemaInformation)
|
||||
{
|
||||
EnsureArg.IsNotNull(scopedSqlConnectionWrapperFactory, nameof(scopedSqlConnectionWrapperFactory));
|
||||
EnsureArg.IsNotNull(updateSearchParamsTvpGenerator, nameof(updateSearchParamsTvpGenerator));
|
||||
EnsureArg.IsNotNull(filebasedRegistry, nameof(filebasedRegistry));
|
||||
EnsureArg.IsNotNull(schemaInformation, nameof(schemaInformation));
|
||||
|
||||
_scopedSqlConnectionWrapperFactory = scopedSqlConnectionWrapperFactory;
|
||||
_updateSearchParamsTvpGenerator = updateSearchParamsTvpGenerator;
|
||||
_filebasedSearchParameterStatusDataStore = filebasedRegistry.Invoke();
|
||||
_schemaInformation = schemaInformation;
|
||||
}
|
||||
|
||||
// TODO: Make cancellation token an input.
|
||||
public async Task<IReadOnlyCollection<ResourceSearchParameterStatus>> GetSearchParameterStatuses()
|
||||
{
|
||||
// If the search parameter table in SQL does not yet contain status columns
|
||||
if (_schemaInformation.Current < SchemaVersionConstants.SearchParameterStatusSchemaVersion)
|
||||
{
|
||||
// Get status information from file.
|
||||
return await _filebasedSearchParameterStatusDataStore.GetSearchParameterStatuses();
|
||||
}
|
||||
|
||||
using (IScoped<SqlConnectionWrapperFactory> scopedSqlConnectionWrapperFactory = _scopedSqlConnectionWrapperFactory())
|
||||
using (SqlConnectionWrapper sqlConnectionWrapper = await scopedSqlConnectionWrapperFactory.Value.ObtainSqlConnectionWrapperAsync(CancellationToken.None, true))
|
||||
using (SqlCommandWrapper sqlCommandWrapper = sqlConnectionWrapper.CreateSqlCommand())
|
||||
{
|
||||
VLatest.GetSearchParamStatuses.PopulateCommand(sqlCommandWrapper);
|
||||
|
||||
var parameterStatuses = new List<ResourceSearchParameterStatus>();
|
||||
|
||||
using (SqlDataReader sqlDataReader = await sqlCommandWrapper.ExecuteReaderAsync(CommandBehavior.SequentialAccess, CancellationToken.None))
|
||||
{
|
||||
while (await sqlDataReader.ReadAsync())
|
||||
{
|
||||
(string uri, string stringStatus, DateTimeOffset? lastUpdated, bool? isPartiallySupported) = sqlDataReader.ReadRow(
|
||||
VLatest.SearchParam.Uri,
|
||||
VLatest.SearchParam.Status,
|
||||
VLatest.SearchParam.LastUpdated,
|
||||
VLatest.SearchParam.IsPartiallySupported);
|
||||
|
||||
if (string.IsNullOrEmpty(stringStatus) || lastUpdated == null || isPartiallySupported == null)
|
||||
{
|
||||
// These columns are nullable because they are added to dbo.SearchParam in a later schema version.
|
||||
// They should be populated as soon as they are added to the table and should never be null.
|
||||
throw new NullReferenceException(Resources.SearchParameterStatusShouldNotBeNull);
|
||||
}
|
||||
|
||||
var status = Enum.Parse<SearchParameterStatus>(stringStatus, true);
|
||||
|
||||
var resourceSearchParameterStatus = new ResourceSearchParameterStatus()
|
||||
{
|
||||
Uri = new Uri(uri),
|
||||
Status = status,
|
||||
IsPartiallySupported = (bool)isPartiallySupported,
|
||||
LastUpdated = (DateTimeOffset)lastUpdated,
|
||||
};
|
||||
|
||||
parameterStatuses.Add(resourceSearchParameterStatus);
|
||||
}
|
||||
}
|
||||
|
||||
return parameterStatuses;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Make cancellation token an input.
|
||||
public async Task UpsertStatuses(List<ResourceSearchParameterStatus> statuses)
|
||||
{
|
||||
EnsureArg.IsNotNull(statuses, nameof(statuses));
|
||||
|
||||
if (!statuses.Any())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_schemaInformation.Current < SchemaVersionConstants.SearchParameterStatusSchemaVersion)
|
||||
{
|
||||
throw new BadRequestException(Resources.SchemaVersionNeedsToBeUpgraded);
|
||||
}
|
||||
|
||||
using (IScoped<SqlConnectionWrapperFactory> scopedSqlConnectionWrapperFactory = _scopedSqlConnectionWrapperFactory())
|
||||
using (SqlConnectionWrapper sqlConnectionWrapper = await scopedSqlConnectionWrapperFactory.Value.ObtainSqlConnectionWrapperAsync(CancellationToken.None, true))
|
||||
using (SqlCommandWrapper sqlCommandWrapper = sqlConnectionWrapper.CreateSqlCommand())
|
||||
{
|
||||
VLatest.UpsertSearchParams.PopulateCommand(sqlCommandWrapper, _updateSearchParamsTvpGenerator.Generate(statuses));
|
||||
|
||||
await sqlCommandWrapper.ExecuteNonQueryAsync(CancellationToken.None);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
// -------------------------------------------------------------------------------------------------
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information.
|
||||
// -------------------------------------------------------------------------------------------------
|
||||
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using EnsureThat;
|
||||
using MediatR;
|
||||
using Microsoft.Health.SqlServer.Features.Schema.Messages.Notifications;
|
||||
|
||||
namespace Microsoft.Health.Fhir.SqlServer.Features.Storage
|
||||
{
|
||||
public class SchemaUpgradedHandler : INotificationHandler<SchemaUpgradedNotification>
|
||||
{
|
||||
private SqlServerFhirModel _sqlServerFhirModel;
|
||||
|
||||
public SchemaUpgradedHandler(SqlServerFhirModel sqlServerFhirModel)
|
||||
{
|
||||
EnsureArg.IsNotNull(sqlServerFhirModel, nameof(sqlServerFhirModel));
|
||||
|
||||
_sqlServerFhirModel = sqlServerFhirModel;
|
||||
}
|
||||
|
||||
public Task Handle(SchemaUpgradedNotification notification, CancellationToken cancellationToken)
|
||||
{
|
||||
EnsureArg.IsNotNull(notification, nameof(notification));
|
||||
|
||||
// If it is a snapshot upgrade, then we need to run initialization for all schema versions up to the current version.
|
||||
_sqlServerFhirModel.Initialize(notification.Version, notification.IsFullSchemaSnapshot);
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -8,18 +8,20 @@ using System.Collections.Concurrent;
|
|||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using EnsureThat;
|
||||
using Microsoft.Data.SqlClient;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Microsoft.Health.Abstractions.Exceptions;
|
||||
using Microsoft.Health.Extensions.DependencyInjection;
|
||||
using Microsoft.Health.Fhir.Core.Configs;
|
||||
using Microsoft.Health.Fhir.Core.Features.Definition;
|
||||
using Microsoft.Health.Fhir.Core.Features.Search.Registry;
|
||||
using Microsoft.Health.Fhir.Core.Models;
|
||||
using Microsoft.Health.Fhir.SqlServer.Features.Schema;
|
||||
using Microsoft.Health.Fhir.SqlServer.Features.Schema.Model;
|
||||
using Microsoft.Health.Fhir.SqlServer.Features.Storage.Registry;
|
||||
using Microsoft.Health.SqlServer.Configs;
|
||||
using Microsoft.Health.SqlServer.Features.Schema;
|
||||
using Microsoft.Health.SqlServer.Features.Schema.Model;
|
||||
|
@ -34,11 +36,12 @@ namespace Microsoft.Health.Fhir.SqlServer.Features.Storage
|
|||
/// many many times in the database. For more compact storage, we use IDs instead of the strings when referencing these.
|
||||
/// Also, because the number of distinct values is small, we can maintain all values in memory and avoid joins when querying.
|
||||
/// </summary>
|
||||
public sealed class SqlServerFhirModel : IHostedService
|
||||
public sealed class SqlServerFhirModel : IRequireInitializationOnFirstRequest
|
||||
{
|
||||
private readonly SqlServerDataStoreConfiguration _configuration;
|
||||
private readonly SchemaInitializer _schemaInitializer;
|
||||
private readonly SchemaInformation _schemaInformation;
|
||||
private readonly ISearchParameterDefinitionManager _searchParameterDefinitionManager;
|
||||
private readonly ISearchParameterStatusDataStore _filebasedSearchParameterStatusDataStore;
|
||||
private readonly SecurityConfiguration _securityConfiguration;
|
||||
private readonly ILogger<SqlServerFhirModel> _logger;
|
||||
private Dictionary<string, short> _resourceTypeToId;
|
||||
|
@ -48,73 +51,76 @@ namespace Microsoft.Health.Fhir.SqlServer.Features.Storage
|
|||
private ConcurrentDictionary<string, int> _quantityCodeToId;
|
||||
private Dictionary<string, byte> _claimNameToId;
|
||||
private Dictionary<string, byte> _compartmentTypeToId;
|
||||
private bool _started;
|
||||
private int _highestInitializedVersion;
|
||||
|
||||
public SqlServerFhirModel(
|
||||
SqlServerDataStoreConfiguration configuration,
|
||||
SchemaInitializer schemaInitializer,
|
||||
SchemaInformation schemaInformation,
|
||||
ISearchParameterDefinitionManager searchParameterDefinitionManager,
|
||||
FilebasedSearchParameterStatusDataStore.Resolver filebasedRegistry,
|
||||
IOptions<SecurityConfiguration> securityConfiguration,
|
||||
ILogger<SqlServerFhirModel> logger)
|
||||
{
|
||||
EnsureArg.IsNotNull(configuration, nameof(configuration));
|
||||
EnsureArg.IsNotNull(schemaInitializer, nameof(schemaInitializer));
|
||||
EnsureArg.IsNotNull(schemaInformation, nameof(schemaInformation));
|
||||
EnsureArg.IsNotNull(searchParameterDefinitionManager, nameof(searchParameterDefinitionManager));
|
||||
EnsureArg.IsNotNull(filebasedRegistry, nameof(filebasedRegistry));
|
||||
EnsureArg.IsNotNull(securityConfiguration?.Value, nameof(securityConfiguration));
|
||||
EnsureArg.IsNotNull(logger, nameof(logger));
|
||||
|
||||
_configuration = configuration;
|
||||
_schemaInitializer = schemaInitializer;
|
||||
_schemaInformation = schemaInformation;
|
||||
_searchParameterDefinitionManager = searchParameterDefinitionManager;
|
||||
_filebasedSearchParameterStatusDataStore = filebasedRegistry.Invoke();
|
||||
_securityConfiguration = securityConfiguration.Value;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public short GetResourceTypeId(string resourceTypeName)
|
||||
{
|
||||
ThrowIfNotStarted();
|
||||
ThrowIfNotInitialized();
|
||||
return _resourceTypeToId[resourceTypeName];
|
||||
}
|
||||
|
||||
public bool TryGetResourceTypeId(string resourceTypeName, out short id)
|
||||
{
|
||||
ThrowIfNotStarted();
|
||||
ThrowIfNotInitialized();
|
||||
return _resourceTypeToId.TryGetValue(resourceTypeName, out id);
|
||||
}
|
||||
|
||||
public string GetResourceTypeName(short resourceTypeId)
|
||||
{
|
||||
ThrowIfNotStarted();
|
||||
ThrowIfNotInitialized();
|
||||
return _resourceTypeIdToTypeName[resourceTypeId];
|
||||
}
|
||||
|
||||
public byte GetClaimTypeId(string claimTypeName)
|
||||
{
|
||||
ThrowIfNotStarted();
|
||||
ThrowIfNotInitialized();
|
||||
return _claimNameToId[claimTypeName];
|
||||
}
|
||||
|
||||
public short GetSearchParamId(Uri searchParamUri)
|
||||
{
|
||||
ThrowIfNotStarted();
|
||||
ThrowIfNotInitialized();
|
||||
return _searchParamUriToId[searchParamUri];
|
||||
}
|
||||
|
||||
public byte GetCompartmentTypeId(string compartmentType)
|
||||
{
|
||||
ThrowIfNotStarted();
|
||||
ThrowIfNotInitialized();
|
||||
return _compartmentTypeToId[compartmentType];
|
||||
}
|
||||
|
||||
public bool TryGetSystemId(string system, out int systemId)
|
||||
{
|
||||
ThrowIfNotStarted();
|
||||
ThrowIfNotInitialized();
|
||||
return _systemToId.TryGetValue(system, out systemId);
|
||||
}
|
||||
|
||||
public int GetSystemId(string system)
|
||||
{
|
||||
ThrowIfNotStarted();
|
||||
ThrowIfNotInitialized();
|
||||
|
||||
VLatest.SystemTable systemTable = VLatest.System;
|
||||
return GetStringId(_systemToId, system, systemTable, systemTable.SystemId, systemTable.Value);
|
||||
|
@ -122,7 +128,7 @@ namespace Microsoft.Health.Fhir.SqlServer.Features.Storage
|
|||
|
||||
public int GetQuantityCodeId(string code)
|
||||
{
|
||||
ThrowIfNotStarted();
|
||||
ThrowIfNotInitialized();
|
||||
|
||||
VLatest.QuantityCodeTable quantityCodeTable = VLatest.QuantityCode;
|
||||
return GetStringId(_quantityCodeToId, code, quantityCodeTable, quantityCodeTable.QuantityCodeId, quantityCodeTable.Value);
|
||||
|
@ -130,156 +136,229 @@ namespace Microsoft.Health.Fhir.SqlServer.Features.Storage
|
|||
|
||||
public bool TryGetQuantityCodeId(string code, out int quantityCodeId)
|
||||
{
|
||||
ThrowIfNotStarted();
|
||||
ThrowIfNotInitialized();
|
||||
return _quantityCodeToId.TryGetValue(code, out quantityCodeId);
|
||||
}
|
||||
|
||||
public async Task StartAsync(CancellationToken cancellationToken)
|
||||
public Task EnsureInitialized()
|
||||
{
|
||||
await _schemaInitializer.StartAsync(cancellationToken);
|
||||
ThrowIfCurrentSchemaVersionIsNull();
|
||||
|
||||
if (_searchParameterDefinitionManager is IHostedService hostedService)
|
||||
// If the fhir-server is just starting up, synchronize the fhir-server dictionaries with the SQL database
|
||||
Initialize((int)_schemaInformation.Current, true);
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public void Initialize(int version, bool runAllInitialization)
|
||||
{
|
||||
if (_highestInitializedVersion == version)
|
||||
{
|
||||
await hostedService.StartAsync(cancellationToken);
|
||||
return;
|
||||
}
|
||||
|
||||
var connectionStringBuilder = new SqlConnectionStringBuilder(_configuration.ConnectionString);
|
||||
_logger.LogInformation("Initializing {Server} {Database} to version {Version}", connectionStringBuilder.DataSource, connectionStringBuilder.InitialCatalog, version);
|
||||
|
||||
_logger.LogInformation("Initializing {Server} {Database}", connectionStringBuilder.DataSource, connectionStringBuilder.InitialCatalog);
|
||||
|
||||
await using var connection = new SqlConnection(_configuration.ConnectionString);
|
||||
await connection.OpenAsync(cancellationToken);
|
||||
|
||||
// Synchronous calls are used because this code is executed on startup and doesn't need to be async.
|
||||
// Additionally, XUnit task scheduler constraints prevent async calls from being easily tested.
|
||||
await using SqlCommand sqlCommand = connection.CreateCommand();
|
||||
sqlCommand.CommandText = @"
|
||||
SET XACT_ABORT ON
|
||||
BEGIN TRANSACTION
|
||||
|
||||
INSERT INTO dbo.ResourceType (Name)
|
||||
SELECT value FROM string_split(@resourceTypes, ',')
|
||||
EXCEPT SELECT Name FROM dbo.ResourceType WITH (TABLOCKX);
|
||||
|
||||
-- result set 1
|
||||
SELECT ResourceTypeId, Name FROM dbo.ResourceType;
|
||||
|
||||
INSERT INTO dbo.SearchParam (Uri)
|
||||
SELECT * FROM OPENJSON (@searchParams)
|
||||
WITH (Uri varchar(128) '$.Uri')
|
||||
EXCEPT SELECT Uri FROM dbo.SearchParam;
|
||||
|
||||
-- result set 2
|
||||
SELECT Uri, SearchParamId FROM dbo.SearchParam;
|
||||
|
||||
INSERT INTO dbo.ClaimType (Name)
|
||||
SELECT value FROM string_split(@claimTypes, ',')
|
||||
EXCEPT SELECT Name FROM dbo.ClaimType;
|
||||
|
||||
-- result set 3
|
||||
SELECT ClaimTypeId, Name FROM dbo.ClaimType;
|
||||
|
||||
INSERT INTO dbo.CompartmentType (Name)
|
||||
SELECT value FROM string_split(@compartmentTypes, ',')
|
||||
EXCEPT SELECT Name FROM dbo.CompartmentType;
|
||||
|
||||
-- result set 4
|
||||
SELECT CompartmentTypeId, Name FROM dbo.CompartmentType;
|
||||
|
||||
COMMIT TRANSACTION
|
||||
|
||||
-- result set 5
|
||||
SELECT Value, SystemId from dbo.System;
|
||||
|
||||
-- result set 6
|
||||
SELECT Value, QuantityCodeId FROM dbo.QuantityCode";
|
||||
|
||||
string commaSeparatedResourceTypes = string.Join(",", ModelInfoProvider.GetResourceTypeNames());
|
||||
string searchParametersJson = JsonConvert.SerializeObject(_searchParameterDefinitionManager.AllSearchParameters.Select(p => new { Name = p.Name, Uri = p.Url }));
|
||||
string commaSeparatedClaimTypes = string.Join(',', _securityConfiguration.PrincipalClaims);
|
||||
string commaSeparatedCompartmentTypes = string.Join(',', ModelInfoProvider.GetCompartmentTypeNames());
|
||||
|
||||
sqlCommand.Parameters.AddWithValue("@resourceTypes", commaSeparatedResourceTypes);
|
||||
sqlCommand.Parameters.AddWithValue("@searchParams", searchParametersJson);
|
||||
sqlCommand.Parameters.AddWithValue("@claimTypes", commaSeparatedClaimTypes);
|
||||
sqlCommand.Parameters.AddWithValue("@compartmentTypes", commaSeparatedCompartmentTypes);
|
||||
|
||||
await using SqlDataReader reader = await sqlCommand.ExecuteReaderAsync(CommandBehavior.SequentialAccess, cancellationToken);
|
||||
|
||||
var resourceTypeToId = new Dictionary<string, short>(StringComparer.Ordinal);
|
||||
var resourceTypeIdToTypeName = new Dictionary<short, string>();
|
||||
var searchParamUriToId = new Dictionary<Uri, short>();
|
||||
var systemToId = new ConcurrentDictionary<string, int>(StringComparer.OrdinalIgnoreCase);
|
||||
var quantityCodeToId = new ConcurrentDictionary<string, int>(StringComparer.OrdinalIgnoreCase);
|
||||
var claimNameToId = new Dictionary<string, byte>(StringComparer.Ordinal);
|
||||
var compartmentTypeToId = new Dictionary<string, byte>();
|
||||
|
||||
// result set 1
|
||||
while (await reader.ReadAsync(cancellationToken))
|
||||
// If we are applying a full snap shot schema file, or if the server is just starting up
|
||||
if (runAllInitialization || _highestInitializedVersion == 0)
|
||||
{
|
||||
(short id, string resourceTypeName) = reader.ReadRow(VLatest.ResourceType.ResourceTypeId, VLatest.ResourceType.Name);
|
||||
// Run the schema initialization required for all schema versions, from the minimum version to the current version.
|
||||
InitializeBase();
|
||||
|
||||
resourceTypeToId.Add(resourceTypeName, id);
|
||||
resourceTypeIdToTypeName.Add(id, resourceTypeName);
|
||||
if (version >= SchemaVersionConstants.SearchParameterStatusSchemaVersion)
|
||||
{
|
||||
InitializeSearchParameterStatuses();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Only run the schema initialization required for the current version
|
||||
if (version == SchemaVersionConstants.SearchParameterStatusSchemaVersion)
|
||||
{
|
||||
InitializeSearchParameterStatuses();
|
||||
}
|
||||
}
|
||||
|
||||
// result set 2
|
||||
await reader.NextResultAsync(cancellationToken);
|
||||
|
||||
while (await reader.ReadAsync(cancellationToken))
|
||||
{
|
||||
(string uri, short searchParamId) = reader.ReadRow(VLatest.SearchParam.Uri, VLatest.SearchParam.SearchParamId);
|
||||
searchParamUriToId.Add(new Uri(uri), searchParamId);
|
||||
}
|
||||
|
||||
// result set 3
|
||||
await reader.NextResultAsync(cancellationToken);
|
||||
|
||||
while (await reader.ReadAsync(cancellationToken))
|
||||
{
|
||||
(byte id, string claimTypeName) = reader.ReadRow(VLatest.ClaimType.ClaimTypeId, VLatest.ClaimType.Name);
|
||||
claimNameToId.Add(claimTypeName, id);
|
||||
}
|
||||
|
||||
// result set 4
|
||||
await reader.NextResultAsync(cancellationToken);
|
||||
|
||||
while (await reader.ReadAsync(cancellationToken))
|
||||
{
|
||||
(byte id, string compartmentName) = reader.ReadRow(VLatest.CompartmentType.CompartmentTypeId, VLatest.CompartmentType.Name);
|
||||
compartmentTypeToId.Add(compartmentName, id);
|
||||
}
|
||||
|
||||
// result set 5
|
||||
await reader.NextResultAsync(cancellationToken);
|
||||
|
||||
while (await reader.ReadAsync(cancellationToken))
|
||||
{
|
||||
var (value, systemId) = reader.ReadRow(VLatest.System.Value, VLatest.System.SystemId);
|
||||
systemToId.TryAdd(value, systemId);
|
||||
}
|
||||
|
||||
// result set 6
|
||||
await reader.NextResultAsync(cancellationToken);
|
||||
|
||||
while (await reader.ReadAsync(cancellationToken))
|
||||
{
|
||||
(string value, int quantityCodeId) = reader.ReadRow(VLatest.QuantityCode.Value, VLatest.QuantityCode.QuantityCodeId);
|
||||
quantityCodeToId.TryAdd(value, quantityCodeId);
|
||||
}
|
||||
|
||||
_resourceTypeToId = resourceTypeToId;
|
||||
_resourceTypeIdToTypeName = resourceTypeIdToTypeName;
|
||||
_searchParamUriToId = searchParamUriToId;
|
||||
_systemToId = systemToId;
|
||||
_quantityCodeToId = quantityCodeToId;
|
||||
_claimNameToId = claimNameToId;
|
||||
_compartmentTypeToId = compartmentTypeToId;
|
||||
|
||||
_started = true;
|
||||
_highestInitializedVersion = version;
|
||||
}
|
||||
|
||||
public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
|
||||
private void InitializeBase()
|
||||
{
|
||||
using (var connection = new SqlConnection(_configuration.ConnectionString))
|
||||
{
|
||||
connection.Open();
|
||||
|
||||
// Synchronous calls are used because this code is executed on startup and doesn't need to be async.
|
||||
// Additionally, XUnit task scheduler constraints prevent async calls from being easily tested.
|
||||
using (SqlCommand sqlCommand = connection.CreateCommand())
|
||||
{
|
||||
sqlCommand.CommandText = @"
|
||||
SET XACT_ABORT ON
|
||||
BEGIN TRANSACTION
|
||||
|
||||
INSERT INTO dbo.ResourceType (Name)
|
||||
SELECT value FROM string_split(@resourceTypes, ',')
|
||||
EXCEPT SELECT Name FROM dbo.ResourceType WITH (TABLOCKX);
|
||||
|
||||
-- result set 1
|
||||
SELECT ResourceTypeId, Name FROM dbo.ResourceType;
|
||||
|
||||
INSERT INTO dbo.SearchParam (Uri)
|
||||
SELECT * FROM OPENJSON (@searchParams)
|
||||
WITH (Uri varchar(128) '$.Uri')
|
||||
EXCEPT SELECT Uri FROM dbo.SearchParam;
|
||||
|
||||
-- result set 2
|
||||
SELECT Uri, SearchParamId FROM dbo.SearchParam;
|
||||
|
||||
INSERT INTO dbo.ClaimType (Name)
|
||||
SELECT value FROM string_split(@claimTypes, ',')
|
||||
EXCEPT SELECT Name FROM dbo.ClaimType;
|
||||
|
||||
-- result set 3
|
||||
SELECT ClaimTypeId, Name FROM dbo.ClaimType;
|
||||
|
||||
INSERT INTO dbo.CompartmentType (Name)
|
||||
SELECT value FROM string_split(@compartmentTypes, ',')
|
||||
EXCEPT SELECT Name FROM dbo.CompartmentType;
|
||||
|
||||
-- result set 4
|
||||
SELECT CompartmentTypeId, Name FROM dbo.CompartmentType;
|
||||
|
||||
COMMIT TRANSACTION
|
||||
|
||||
-- result set 5
|
||||
SELECT Value, SystemId from dbo.System;
|
||||
|
||||
-- result set 6
|
||||
SELECT Value, QuantityCodeId FROM dbo.QuantityCode";
|
||||
|
||||
string searchParametersJson = JsonConvert.SerializeObject(_searchParameterDefinitionManager.AllSearchParameters.Select(p => new { Uri = p.Url }));
|
||||
string commaSeparatedResourceTypes = string.Join(",", ModelInfoProvider.GetResourceTypeNames());
|
||||
string commaSeparatedClaimTypes = string.Join(',', _securityConfiguration.PrincipalClaims);
|
||||
string commaSeparatedCompartmentTypes = string.Join(',', ModelInfoProvider.GetCompartmentTypeNames());
|
||||
|
||||
sqlCommand.Parameters.AddWithValue("@searchParams", searchParametersJson);
|
||||
sqlCommand.Parameters.AddWithValue("@resourceTypes", commaSeparatedResourceTypes);
|
||||
sqlCommand.Parameters.AddWithValue("@claimTypes", commaSeparatedClaimTypes);
|
||||
sqlCommand.Parameters.AddWithValue("@compartmentTypes", commaSeparatedCompartmentTypes);
|
||||
|
||||
using (SqlDataReader reader = sqlCommand.ExecuteReader(CommandBehavior.SequentialAccess))
|
||||
{
|
||||
var resourceTypeToId = new Dictionary<string, short>(StringComparer.Ordinal);
|
||||
var resourceTypeIdToTypeName = new Dictionary<short, string>();
|
||||
var searchParamUriToId = new Dictionary<Uri, short>();
|
||||
var systemToId = new ConcurrentDictionary<string, int>(StringComparer.OrdinalIgnoreCase);
|
||||
var quantityCodeToId = new ConcurrentDictionary<string, int>(StringComparer.OrdinalIgnoreCase);
|
||||
var claimNameToId = new Dictionary<string, byte>(StringComparer.Ordinal);
|
||||
var compartmentTypeToId = new Dictionary<string, byte>();
|
||||
|
||||
// result set 1
|
||||
while (reader.Read())
|
||||
{
|
||||
(short id, string resourceTypeName) = reader.ReadRow(VLatest.ResourceType.ResourceTypeId, VLatest.ResourceType.Name);
|
||||
|
||||
resourceTypeToId.Add(resourceTypeName, id);
|
||||
resourceTypeIdToTypeName.Add(id, resourceTypeName);
|
||||
}
|
||||
|
||||
// result set 2
|
||||
reader.NextResult();
|
||||
|
||||
while (reader.Read())
|
||||
{
|
||||
(string uri, short searchParamId) = reader.ReadRow(VLatest.SearchParam.Uri, VLatest.SearchParam.SearchParamId);
|
||||
searchParamUriToId.Add(new Uri(uri), searchParamId);
|
||||
}
|
||||
|
||||
// result set 3
|
||||
reader.NextResult();
|
||||
|
||||
while (reader.Read())
|
||||
{
|
||||
(byte id, string claimTypeName) = reader.ReadRow(VLatest.ClaimType.ClaimTypeId, VLatest.ClaimType.Name);
|
||||
claimNameToId.Add(claimTypeName, id);
|
||||
}
|
||||
|
||||
// result set 4
|
||||
reader.NextResult();
|
||||
|
||||
while (reader.Read())
|
||||
{
|
||||
(byte id, string compartmentName) = reader.ReadRow(VLatest.CompartmentType.CompartmentTypeId, VLatest.CompartmentType.Name);
|
||||
compartmentTypeToId.Add(compartmentName, id);
|
||||
}
|
||||
|
||||
// result set 5
|
||||
reader.NextResult();
|
||||
|
||||
while (reader.Read())
|
||||
{
|
||||
var (value, systemId) = reader.ReadRow(VLatest.System.Value, VLatest.System.SystemId);
|
||||
systemToId.TryAdd(value, systemId);
|
||||
}
|
||||
|
||||
// result set 6
|
||||
reader.NextResult();
|
||||
|
||||
while (reader.Read())
|
||||
{
|
||||
(string value, int quantityCodeId) = reader.ReadRow(VLatest.QuantityCode.Value, VLatest.QuantityCode.QuantityCodeId);
|
||||
quantityCodeToId.TryAdd(value, quantityCodeId);
|
||||
}
|
||||
|
||||
_resourceTypeToId = resourceTypeToId;
|
||||
_resourceTypeIdToTypeName = resourceTypeIdToTypeName;
|
||||
_searchParamUriToId = searchParamUriToId;
|
||||
_systemToId = systemToId;
|
||||
_quantityCodeToId = quantityCodeToId;
|
||||
_claimNameToId = claimNameToId;
|
||||
_compartmentTypeToId = compartmentTypeToId;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void InitializeSearchParameterStatuses()
|
||||
{
|
||||
using (var connection = new SqlConnection(_configuration.ConnectionString))
|
||||
{
|
||||
connection.Open();
|
||||
|
||||
using (SqlCommand sqlCommand = connection.CreateCommand())
|
||||
{
|
||||
sqlCommand.CommandText = @"
|
||||
SET XACT_ABORT ON
|
||||
BEGIN TRANSACTION
|
||||
DECLARE @lastUpdated datetimeoffset(7) = SYSDATETIMEOFFSET()
|
||||
|
||||
UPDATE dbo.SearchParam
|
||||
SET Status = sps.Status, LastUpdated = @lastUpdated, IsPartiallySupported = sps.IsPartiallySupported
|
||||
FROM dbo.SearchParam INNER JOIN @searchParamStatuses as sps
|
||||
ON dbo.SearchParam.Uri = sps.Uri
|
||||
COMMIT TRANSACTION";
|
||||
|
||||
IEnumerable<ResourceSearchParameterStatus> statuses = _filebasedSearchParameterStatusDataStore
|
||||
.GetSearchParameterStatuses().GetAwaiter().GetResult();
|
||||
|
||||
var collection = new SearchParameterStatusCollection();
|
||||
collection.AddRange(statuses);
|
||||
|
||||
var tableValuedParameter = new SqlParameter
|
||||
{
|
||||
ParameterName = "searchParamStatuses",
|
||||
SqlDbType = SqlDbType.Structured,
|
||||
Value = collection,
|
||||
Direction = ParameterDirection.Input,
|
||||
TypeName = "dbo.SearchParamTableType_1",
|
||||
};
|
||||
|
||||
sqlCommand.Parameters.Add(tableValuedParameter);
|
||||
sqlCommand.ExecuteNonQuery();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int GetStringId(ConcurrentDictionary<string, int> cache, string stringValue, Table table, Column<int> idColumn, Column<string> stringColumn)
|
||||
{
|
||||
|
@ -324,13 +403,23 @@ namespace Microsoft.Health.Fhir.SqlServer.Features.Storage
|
|||
}
|
||||
}
|
||||
|
||||
private void ThrowIfNotStarted()
|
||||
private void ThrowIfNotInitialized()
|
||||
{
|
||||
if (!_started)
|
||||
ThrowIfCurrentSchemaVersionIsNull();
|
||||
|
||||
if (_highestInitializedVersion < _schemaInformation.Current)
|
||||
{
|
||||
_logger.LogError($"The {nameof(SqlServerFhirModel)} instance has not been initialized.");
|
||||
_logger.LogError($"The {nameof(SqlServerFhirModel)} instance has not run the initialization required for the current schema version");
|
||||
throw new ServiceUnavailableException();
|
||||
}
|
||||
}
|
||||
|
||||
private void ThrowIfCurrentSchemaVersionIsNull()
|
||||
{
|
||||
if (_schemaInformation.Current == null)
|
||||
{
|
||||
throw new InvalidOperationException(Resources.SchemaVersionShouldNotBeNull);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
// -------------------------------------------------------------------------------------------------
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information.
|
||||
// -------------------------------------------------------------------------------------------------
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.Health.Fhir.Core.Features.Search.Registry;
|
||||
using Microsoft.Health.Fhir.SqlServer.Features.Schema.Model;
|
||||
using Microsoft.Health.SqlServer.Features.Schema.Model;
|
||||
|
||||
namespace Microsoft.Health.Fhir.SqlServer.Features.Storage.TvpRowGeneration
|
||||
{
|
||||
internal class SearchParameterStatusRowGenerator : ITableValuedParameterRowGenerator<List<ResourceSearchParameterStatus>, VLatest.SearchParamTableTypeRow>
|
||||
{
|
||||
public IEnumerable<VLatest.SearchParamTableTypeRow> GenerateRows(List<ResourceSearchParameterStatus> searchParameterStatuses)
|
||||
{
|
||||
return searchParameterStatuses.Select(searchParameterStatus => new VLatest.SearchParamTableTypeRow(
|
||||
searchParameterStatus.Uri.ToString(),
|
||||
searchParameterStatus.Status.ToString(),
|
||||
searchParameterStatus.IsPartiallySupported))
|
||||
.ToList();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -7,17 +7,23 @@
|
|||
<None Remove="Features\Schema\Migrations\3.diff.sql" />
|
||||
<None Remove="Features\Schema\Migrations\4.diff.sql" />
|
||||
<None Remove="Features\Schema\Migrations\4.sql" />
|
||||
<None Remove="Features\Schema\Migrations\5.diff.sql" />
|
||||
<None Remove="Features\Schema\Migrations\5.sql" />
|
||||
<None Remove="Features\Schema\Migrations\6.diff.sql" />
|
||||
<None Remove="Features\Schema\Migrations\6.sql" />
|
||||
<EmbeddedResource Include="Features\Schema\Migrations\2.diff.sql" />
|
||||
<EmbeddedResource Include="Features\Schema\Migrations\3.diff.sql" />
|
||||
<EmbeddedResource Include="Features\Schema\Migrations\4.diff.sql" />
|
||||
<EmbeddedResource Include="Features\Schema\Migrations\4.sql" />
|
||||
<EmbeddedResource Include="Features\Schema\Migrations\5.diff.sql" />
|
||||
<EmbeddedResource Include="Features\Schema\Migrations\5.sql" />
|
||||
<GenerateFilesInputs Include="Features\Schema\Migrations\5.sql" />
|
||||
<EmbeddedResource Include="Features\Schema\Migrations\6.diff.sql" />
|
||||
<EmbeddedResource Include="Features\Schema\Migrations\6.sql" />
|
||||
<GenerateFilesInputs Include="Features\Schema\Migrations\6.sql" />
|
||||
<Generated Include="Features\Schema\Model\VLatest.Generated.cs">
|
||||
<Generator>SqlModelGenerator</Generator>
|
||||
<Namespace>Microsoft.Health.Fhir.SqlServer.Features.Schema.Model</Namespace>
|
||||
<Args>$([System.IO.Path]::GetFullPath('$(MSBuildThisFileDirectory)\Features\Schema\Migrations\5.sql'))</Args>
|
||||
<Args>$([System.IO.Path]::GetFullPath('$(MSBuildThisFileDirectory)\Features\Schema\Migrations\6.sql'))</Args>
|
||||
</Generated>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
|
|
|
@ -8,13 +8,16 @@ using System.Linq;
|
|||
using EnsureThat;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Health.Extensions.DependencyInjection;
|
||||
using Microsoft.Health.Fhir.Core.Features.Search.Registry;
|
||||
using Microsoft.Health.Fhir.Core.Registration;
|
||||
using Microsoft.Health.Fhir.SqlServer.Features.Schema;
|
||||
using Microsoft.Health.Fhir.SqlServer.Features.Search;
|
||||
using Microsoft.Health.Fhir.SqlServer.Features.Search.Expressions.Visitors;
|
||||
using Microsoft.Health.Fhir.SqlServer.Features.Storage;
|
||||
using Microsoft.Health.Fhir.SqlServer.Features.Storage.Registry;
|
||||
using Microsoft.Health.SqlServer.Api.Registration;
|
||||
using Microsoft.Health.SqlServer.Configs;
|
||||
using Microsoft.Health.SqlServer.Features.Client;
|
||||
using Microsoft.Health.SqlServer.Features.Schema;
|
||||
using Microsoft.Health.SqlServer.Features.Schema.Model;
|
||||
using Microsoft.Health.SqlServer.Registration;
|
||||
|
@ -36,6 +39,11 @@ namespace Microsoft.Extensions.DependencyInjection
|
|||
.AsSelf()
|
||||
.AsImplementedInterfaces();
|
||||
|
||||
services.Add<SqlServerSearchParameterStatusDataStore>()
|
||||
.Singleton()
|
||||
.AsSelf()
|
||||
.ReplaceService<ISearchParameterStatusDataStore>();
|
||||
|
||||
services.Add<SqlServerFhirModel>()
|
||||
.Singleton()
|
||||
.AsSelf()
|
||||
|
@ -87,6 +95,12 @@ namespace Microsoft.Extensions.DependencyInjection
|
|||
.AsSelf()
|
||||
.AsImplementedInterfaces();
|
||||
|
||||
services.AddFactory<IScoped<SqlConnectionWrapperFactory>>();
|
||||
|
||||
services.Add<SchemaUpgradedHandler>()
|
||||
.Transient()
|
||||
.AsImplementedInterfaces();
|
||||
|
||||
return fhirServerBuilder;
|
||||
}
|
||||
|
||||
|
|
|
@ -19,7 +19,7 @@ namespace Microsoft.Health.Fhir.SqlServer {
|
|||
// class via a tool like ResGen or Visual Studio.
|
||||
// To add or remove a member, edit your .ResX file then rerun ResGen
|
||||
// with the /str option, or rebuild your VS project.
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")]
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||
internal class Resources {
|
||||
|
@ -60,6 +60,15 @@ namespace Microsoft.Health.Fhir.SqlServer {
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Cyclic include iterate queries are not supported..
|
||||
/// </summary>
|
||||
internal static string CyclicIncludeIterateNotSupported {
|
||||
get {
|
||||
return ResourceManager.GetString("CyclicIncludeIterateNotSupported", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to The provided continuation token is not valid..
|
||||
/// </summary>
|
||||
|
@ -69,6 +78,33 @@ namespace Microsoft.Health.Fhir.SqlServer {
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Cannot carry out the SQL datastore operation because the SQL schema needs to be upgraded..
|
||||
/// </summary>
|
||||
internal static string SchemaVersionNeedsToBeUpgraded {
|
||||
get {
|
||||
return ResourceManager.GetString("SchemaVersionNeedsToBeUpgraded", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to The current schema version should not be null..
|
||||
/// </summary>
|
||||
internal static string SchemaVersionShouldNotBeNull {
|
||||
get {
|
||||
return ResourceManager.GetString("SchemaVersionShouldNotBeNull", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Search parameter status information should not be null..
|
||||
/// </summary>
|
||||
internal static string SearchParameterStatusShouldNotBeNull {
|
||||
get {
|
||||
return ResourceManager.GetString("SearchParameterStatusShouldNotBeNull", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to There was an internal server error while processing the transaction..
|
||||
/// </summary>
|
||||
|
@ -77,14 +113,5 @@ namespace Microsoft.Health.Fhir.SqlServer {
|
|||
return ResourceManager.GetString("TransactionProcessingException", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Cyclic Include Iterate Not Supported..
|
||||
/// </summary>
|
||||
internal static string CyclicIncludeIterateNotSupported {
|
||||
get {
|
||||
return ResourceManager.GetString("CyclicIncludeIterateNotSupported", resourceCulture);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
|
||||
Example:
|
||||
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
|
@ -26,36 +26,36 @@
|
|||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
|
@ -126,4 +126,13 @@
|
|||
<data name="CyclicIncludeIterateNotSupported" xml:space="preserve">
|
||||
<value>Cyclic include iterate queries are not supported.</value>
|
||||
</data>
|
||||
</root>
|
||||
<data name="SchemaVersionNeedsToBeUpgraded" xml:space="preserve">
|
||||
<value>Cannot carry out the SQL datastore operation because the SQL schema needs to be upgraded.</value>
|
||||
</data>
|
||||
<data name="SchemaVersionShouldNotBeNull" xml:space="preserve">
|
||||
<value>The current schema version should not be null.</value>
|
||||
</data>
|
||||
<data name="SearchParameterStatusShouldNotBeNull" xml:space="preserve">
|
||||
<value>Search parameter status information should not be null.</value>
|
||||
</data>
|
||||
</root>
|
||||
|
|
|
@ -52,7 +52,7 @@ namespace Microsoft.Health.Fhir.Tests.Integration.Features.Operations.Reindex
|
|||
private readonly ISearchIndexer _searchIndexer = Substitute.For<ISearchIndexer>();
|
||||
private ISupportedSearchParameterDefinitionManager _supportedSearchParameterDefinitionManager;
|
||||
private SearchableSearchParameterDefinitionManager _searchableSearchParameterDefinitionManager;
|
||||
private readonly ISearchParameterRegistry _searchParameterRegistry = Substitute.For<ISearchParameterRegistry>();
|
||||
private readonly ISearchParameterStatusDataStore _searchParameterStatusDataStore = Substitute.For<ISearchParameterStatusDataStore>();
|
||||
private readonly IOptions<CoreFeatureConfiguration> coreOptions = Substitute.For<IOptions<CoreFeatureConfiguration>>();
|
||||
|
||||
private ReindexJobWorker _reindexJobWorker;
|
||||
|
@ -91,7 +91,7 @@ namespace Microsoft.Health.Fhir.Tests.Integration.Features.Operations.Reindex
|
|||
_searchIndexer,
|
||||
Deserializers.ResourceDeserializer,
|
||||
_supportedSearchParameterDefinitionManager,
|
||||
_searchParameterRegistry);
|
||||
_searchParameterStatusDataStore);
|
||||
|
||||
coreOptions.Value.Returns(new CoreFeatureConfiguration());
|
||||
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
<Compile Include="$(MSBuildThisFileDirectory)Features\Operations\Export\CreateExportRequestHandlerTests.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Features\Operations\FhirOperationTestConstants.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Features\Operations\Reindex\ReindexJobTests.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Persistence\SearchParameterStatusDataStoreTests.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Persistence\SqlServerSchemaUpgradeTests.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Persistence\CosmosDbFhirStorageTestHelper.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Persistence\CosmosDbFhirStorageTestsFixture.cs" />
|
||||
|
|
|
@ -7,6 +7,8 @@ using System;
|
|||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Azure.Cosmos;
|
||||
using Microsoft.Health.Core.Extensions;
|
||||
using Microsoft.Health.Fhir.CosmosDb.Features.Storage.Registry;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Xunit;
|
||||
|
||||
|
@ -18,17 +20,10 @@ namespace Microsoft.Health.Fhir.Tests.Integration.Persistence
|
|||
private const string ReindexJobPartitionKey = "ReindexJob";
|
||||
|
||||
private readonly Container _documentClient;
|
||||
private readonly string _databaseId;
|
||||
private readonly string _collectionId;
|
||||
|
||||
public CosmosDbFhirStorageTestHelper(
|
||||
Container documentClient,
|
||||
string databaseId,
|
||||
string collectionId)
|
||||
public CosmosDbFhirStorageTestHelper(Container documentClient)
|
||||
{
|
||||
_documentClient = documentClient;
|
||||
_databaseId = databaseId;
|
||||
_collectionId = collectionId;
|
||||
}
|
||||
|
||||
public async Task DeleteAllExportJobRecordsAsync(CancellationToken cancellationToken = default)
|
||||
|
@ -41,6 +36,11 @@ namespace Microsoft.Health.Fhir.Tests.Integration.Persistence
|
|||
await _documentClient.DeleteItemStreamAsync(id, new PartitionKey(ExportJobPartitionKey), cancellationToken: cancellationToken);
|
||||
}
|
||||
|
||||
public async Task DeleteSearchParameterStatusAsync(string uri, CancellationToken cancellationToken = default)
|
||||
{
|
||||
await _documentClient.DeleteItemStreamAsync(uri.ComputeHash(), new PartitionKey(SearchParameterStatusWrapper.SearchParameterStatusPartitionKey), cancellationToken: cancellationToken);
|
||||
}
|
||||
|
||||
public async Task DeleteAllReindexJobRecordsAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
await DeleteAllRecordsAsync(ReindexJobPartitionKey, cancellationToken);
|
||||
|
|
|
@ -44,7 +44,8 @@ namespace Microsoft.Health.Fhir.Tests.Integration.Persistence
|
|||
private IFhirDataStore _fhirDataStore;
|
||||
private IFhirOperationDataStore _fhirOperationDataStore;
|
||||
private IFhirStorageTestHelper _fhirStorageTestHelper;
|
||||
private FilebasedSearchParameterRegistry _filebasedSearchParameterRegistry;
|
||||
private FilebasedSearchParameterStatusDataStore _filebasedSearchParameterStatusDataStore;
|
||||
private ISearchParameterStatusDataStore _searchParameterStatusDataStore;
|
||||
private CosmosClient _cosmosClient;
|
||||
|
||||
public CosmosDbFhirStorageTestsFixture()
|
||||
|
@ -79,14 +80,14 @@ namespace Microsoft.Health.Fhir.Tests.Integration.Persistence
|
|||
var searchParameterDefinitionManager = new SearchParameterDefinitionManager(ModelInfoProvider.Instance);
|
||||
await searchParameterDefinitionManager.StartAsync(CancellationToken.None);
|
||||
|
||||
_filebasedSearchParameterRegistry = new FilebasedSearchParameterRegistry(searchParameterDefinitionManager, ModelInfoProvider.Instance);
|
||||
_filebasedSearchParameterStatusDataStore = new FilebasedSearchParameterStatusDataStore(searchParameterDefinitionManager, ModelInfoProvider.Instance);
|
||||
|
||||
var updaters = new ICollectionUpdater[]
|
||||
{
|
||||
new FhirCollectionSettingsUpdater(_cosmosDataStoreConfiguration, optionsMonitor, NullLogger<FhirCollectionSettingsUpdater>.Instance),
|
||||
new StoredProcedureInstaller(fhirStoredProcs),
|
||||
new CosmosDbStatusRegistryInitializer(
|
||||
() => _filebasedSearchParameterRegistry,
|
||||
new CosmosDbSearchParameterStatusInitializer(
|
||||
() => _filebasedSearchParameterStatusDataStore,
|
||||
new CosmosQueryFactory(
|
||||
new CosmosResponseProcessor(Substitute.For<IFhirRequestContextAccessor>(), Substitute.For<IMediator>(), NullLogger<CosmosResponseProcessor>.Instance),
|
||||
NullFhirCosmosQueryLogger.Instance)),
|
||||
|
@ -124,6 +125,11 @@ namespace Microsoft.Health.Fhir.Tests.Integration.Persistence
|
|||
|
||||
var documentClient = new NonDisposingScope(_container);
|
||||
|
||||
_searchParameterStatusDataStore = new CosmosDbSearchParameterStatusDataStore(
|
||||
() => documentClient,
|
||||
_cosmosDataStoreConfiguration,
|
||||
cosmosDocumentQueryFactory);
|
||||
|
||||
_fhirDataStore = new CosmosFhirDataStore(
|
||||
documentClient,
|
||||
_cosmosDataStoreConfiguration,
|
||||
|
@ -142,10 +148,7 @@ namespace Microsoft.Health.Fhir.Tests.Integration.Persistence
|
|||
new CosmosQueryFactory(responseProcessor, new NullFhirCosmosQueryLogger()),
|
||||
NullLogger<CosmosFhirOperationDataStore>.Instance);
|
||||
|
||||
_fhirStorageTestHelper = new CosmosDbFhirStorageTestHelper(
|
||||
_container,
|
||||
_cosmosDataStoreConfiguration.DatabaseId,
|
||||
_cosmosCollectionConfiguration.CollectionId);
|
||||
_fhirStorageTestHelper = new CosmosDbFhirStorageTestHelper(_container);
|
||||
}
|
||||
|
||||
public async Task DisposeAsync()
|
||||
|
@ -180,6 +183,16 @@ namespace Microsoft.Health.Fhir.Tests.Integration.Persistence
|
|||
return this;
|
||||
}
|
||||
|
||||
if (serviceType == typeof(ISearchParameterStatusDataStore))
|
||||
{
|
||||
return _searchParameterStatusDataStore;
|
||||
}
|
||||
|
||||
if (serviceType == typeof(FilebasedSearchParameterStatusDataStore))
|
||||
{
|
||||
return _filebasedSearchParameterStatusDataStore;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ using Microsoft.Extensions.DependencyInjection;
|
|||
using Microsoft.Health.Abstractions.Features.Transactions;
|
||||
using Microsoft.Health.Fhir.Core.Features.Operations;
|
||||
using Microsoft.Health.Fhir.Core.Features.Persistence;
|
||||
using Microsoft.Health.Fhir.Core.Features.Search.Registry;
|
||||
using Microsoft.Health.Fhir.Tests.Common.FixtureParameters;
|
||||
using Xunit;
|
||||
|
||||
|
@ -41,6 +42,10 @@ namespace Microsoft.Health.Fhir.Tests.Integration.Persistence
|
|||
|
||||
public ITransactionHandler TransactionHandler => _fixture.GetRequiredService<ITransactionHandler>();
|
||||
|
||||
public ISearchParameterStatusDataStore SearchParameterStatusDataStore => _fixture.GetRequiredService<ISearchParameterStatusDataStore>();
|
||||
|
||||
public FilebasedSearchParameterStatusDataStore FilebasedSearchParameterStatusDataStore => _fixture.GetRequiredService<FilebasedSearchParameterStatusDataStore>();
|
||||
|
||||
void IDisposable.Dispose()
|
||||
{
|
||||
(_fixture as IDisposable)?.Dispose();
|
||||
|
|
|
@ -25,6 +25,14 @@ namespace Microsoft.Health.Fhir.Tests.Integration.Persistence
|
|||
/// <returns>A task.</returns>
|
||||
Task DeleteExportJobRecordAsync(string id, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Deletes specified search parameter statuses from the database.
|
||||
/// </summary>
|
||||
/// <param name="uri">The string URI of the status to be deleted.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>A task.</returns>
|
||||
Task DeleteSearchParameterStatusAsync(string uri, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Deletes all reindex job records from the database.
|
||||
/// </summary>
|
||||
|
|
|
@ -0,0 +1,141 @@
|
|||
// -------------------------------------------------------------------------------------------------
|
||||
// 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 System.Threading.Tasks;
|
||||
using Microsoft.Health.Fhir.Core.Features.Search.Registry;
|
||||
using Microsoft.Health.Fhir.Tests.Common.FixtureParameters;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.Health.Fhir.Tests.Integration.Persistence
|
||||
{
|
||||
[FhirStorageTestsFixtureArgumentSets(DataStore.All)]
|
||||
public class SearchParameterStatusDataStoreTests : IClassFixture<FhirStorageTestsFixture>
|
||||
{
|
||||
private readonly FhirStorageTestsFixture _fixture;
|
||||
private readonly IFhirStorageTestHelper _testHelper;
|
||||
|
||||
public SearchParameterStatusDataStoreTests(FhirStorageTestsFixture fixture)
|
||||
{
|
||||
_fixture = fixture;
|
||||
_testHelper = fixture.TestHelper;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GivenAStatusRegistry_WhenGettingStatuses_ThenTheStatusesAreRetrieved()
|
||||
{
|
||||
IReadOnlyCollection<ResourceSearchParameterStatus> expectedStatuses = await _fixture.FilebasedSearchParameterStatusDataStore.GetSearchParameterStatuses();
|
||||
IReadOnlyCollection<ResourceSearchParameterStatus> actualStatuses = await _fixture.SearchParameterStatusDataStore.GetSearchParameterStatuses();
|
||||
|
||||
ValidateSearchParameterStatuses(expectedStatuses, actualStatuses);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GivenAStatusRegistry_WhenUpsertingNewStatuses_ThenTheStatusesAreAdded()
|
||||
{
|
||||
string statusName1 = "http://hl7.org/fhir/SearchParameter/Test-1";
|
||||
string statusName2 = "http://hl7.org/fhir/SearchParameter/Test-2";
|
||||
|
||||
var status1 = new ResourceSearchParameterStatus
|
||||
{
|
||||
Uri = new Uri(statusName1), Status = SearchParameterStatus.Disabled, IsPartiallySupported = false,
|
||||
};
|
||||
|
||||
var status2 = new ResourceSearchParameterStatus
|
||||
{
|
||||
Uri = new Uri(statusName2), Status = SearchParameterStatus.Disabled, IsPartiallySupported = false,
|
||||
};
|
||||
|
||||
IReadOnlyCollection<ResourceSearchParameterStatus> readonlyStatusesBeforeUpsert = await _fixture.SearchParameterStatusDataStore.GetSearchParameterStatuses();
|
||||
var expectedStatuses = readonlyStatusesBeforeUpsert.ToList();
|
||||
expectedStatuses.Add(status1);
|
||||
expectedStatuses.Add(status2);
|
||||
|
||||
var statusesToUpsert = new List<ResourceSearchParameterStatus> { status1, status2 };
|
||||
|
||||
try
|
||||
{
|
||||
await _fixture.SearchParameterStatusDataStore.UpsertStatuses(statusesToUpsert);
|
||||
|
||||
IReadOnlyCollection<ResourceSearchParameterStatus> actualStatuses = await _fixture.SearchParameterStatusDataStore.GetSearchParameterStatuses();
|
||||
|
||||
ValidateSearchParameterStatuses(expectedStatuses, actualStatuses);
|
||||
}
|
||||
finally
|
||||
{
|
||||
await _testHelper.DeleteSearchParameterStatusAsync(statusName1);
|
||||
await _testHelper.DeleteSearchParameterStatusAsync(statusName2);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GivenAStatusRegistry_WhenUpsertingExistingStatuses_ThenTheExistingStatusesAreUpdated()
|
||||
{
|
||||
IReadOnlyCollection<ResourceSearchParameterStatus> statusesBeforeUpdate = await _fixture.SearchParameterStatusDataStore.GetSearchParameterStatuses();
|
||||
|
||||
// Get two existing statuses.
|
||||
ResourceSearchParameterStatus expectedStatus1 = statusesBeforeUpdate.First();
|
||||
ResourceSearchParameterStatus expectedStatus2 = statusesBeforeUpdate.Last();
|
||||
|
||||
// Modify them in some way.
|
||||
expectedStatus1.IsPartiallySupported = !expectedStatus1.IsPartiallySupported;
|
||||
expectedStatus2.IsPartiallySupported = !expectedStatus2.IsPartiallySupported;
|
||||
|
||||
var statusesToUpsert = new List<ResourceSearchParameterStatus> { expectedStatus1, expectedStatus2 };
|
||||
|
||||
try
|
||||
{
|
||||
// Upsert the two existing, modified statuses.
|
||||
await _fixture.SearchParameterStatusDataStore.UpsertStatuses(statusesToUpsert);
|
||||
|
||||
IReadOnlyCollection<ResourceSearchParameterStatus> statusesAfterUpdate =
|
||||
await _fixture.SearchParameterStatusDataStore.GetSearchParameterStatuses();
|
||||
|
||||
Assert.Equal(statusesBeforeUpdate.Count, statusesAfterUpdate.Count);
|
||||
|
||||
ResourceSearchParameterStatus actualStatus1 = statusesAfterUpdate.FirstOrDefault(s => s.Uri.Equals(expectedStatus1.Uri));
|
||||
ResourceSearchParameterStatus actualStatus2 = statusesAfterUpdate.FirstOrDefault(s => s.Uri.Equals(expectedStatus2.Uri));
|
||||
|
||||
Assert.NotNull(actualStatus1);
|
||||
Assert.NotNull(actualStatus2);
|
||||
|
||||
Assert.Equal(expectedStatus1.Status, actualStatus1.Status);
|
||||
Assert.Equal(expectedStatus1.IsPartiallySupported, actualStatus1.IsPartiallySupported);
|
||||
|
||||
Assert.Equal(expectedStatus2.Status, actualStatus2.Status);
|
||||
Assert.Equal(expectedStatus2.IsPartiallySupported, actualStatus2.IsPartiallySupported);
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Reset changes made.
|
||||
expectedStatus1.IsPartiallySupported = !expectedStatus1.IsPartiallySupported;
|
||||
expectedStatus2.IsPartiallySupported = !expectedStatus2.IsPartiallySupported;
|
||||
|
||||
statusesToUpsert = new List<ResourceSearchParameterStatus> { expectedStatus1, expectedStatus2 };
|
||||
|
||||
await _fixture.SearchParameterStatusDataStore.UpsertStatuses(statusesToUpsert);
|
||||
}
|
||||
}
|
||||
|
||||
private static void ValidateSearchParameterStatuses(IReadOnlyCollection<ResourceSearchParameterStatus> expectedStatuses, IReadOnlyCollection<ResourceSearchParameterStatus> actualStatuses)
|
||||
{
|
||||
Assert.NotEmpty(expectedStatuses);
|
||||
|
||||
var sortedExpected = expectedStatuses.OrderBy(status => status.Uri.ToString()).ToList();
|
||||
var sortedActual = actualStatuses.OrderBy(status => status.Uri.ToString()).ToList();
|
||||
|
||||
Assert.Equal(sortedExpected.Count, sortedActual.Count);
|
||||
|
||||
for (int i = 0; i < sortedExpected.Count; i++)
|
||||
{
|
||||
Assert.Equal(sortedExpected[i].Uri, sortedActual[i].Uri);
|
||||
Assert.Equal(sortedExpected[i].Status, sortedActual[i].Status);
|
||||
Assert.Equal(sortedExpected[i].IsPartiallySupported, sortedActual[i].IsPartiallySupported);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -11,6 +11,7 @@ using EnsureThat;
|
|||
using MediatR;
|
||||
using Microsoft.Data.SqlClient;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Microsoft.Health.Fhir.Core.Features.Definition;
|
||||
using Microsoft.Health.Fhir.SqlServer.Features.Schema;
|
||||
using Microsoft.Health.Fhir.SqlServer.Features.Storage;
|
||||
using Microsoft.Health.SqlServer;
|
||||
|
@ -29,15 +30,24 @@ namespace Microsoft.Health.Fhir.Tests.Integration.Persistence
|
|||
{
|
||||
private readonly string _masterDatabaseName;
|
||||
private readonly string _initialConnectionString;
|
||||
private readonly SearchParameterDefinitionManager _searchParameterDefinitionManager;
|
||||
private readonly SqlServerFhirModel _sqlServerFhirModel;
|
||||
private readonly ISqlConnectionFactory _sqlConnectionFactory;
|
||||
|
||||
public SqlServerFhirStorageTestHelper(string initialConnectionString, string masterDatabaseName, SqlServerFhirModel sqlServerFhirModel, ISqlConnectionFactory sqlConnectionFactory)
|
||||
public SqlServerFhirStorageTestHelper(
|
||||
string initialConnectionString,
|
||||
string masterDatabaseName,
|
||||
SearchParameterDefinitionManager searchParameterDefinitionManager,
|
||||
SqlServerFhirModel sqlServerFhirModel,
|
||||
ISqlConnectionFactory sqlConnectionFactory)
|
||||
{
|
||||
EnsureArg.IsNotNull(searchParameterDefinitionManager, nameof(searchParameterDefinitionManager));
|
||||
EnsureArg.IsNotNull(sqlServerFhirModel, nameof(sqlServerFhirModel));
|
||||
EnsureArg.IsNotNull(sqlConnectionFactory, nameof(sqlConnectionFactory));
|
||||
|
||||
_masterDatabaseName = masterDatabaseName;
|
||||
_initialConnectionString = initialConnectionString;
|
||||
_searchParameterDefinitionManager = searchParameterDefinitionManager;
|
||||
_sqlServerFhirModel = sqlServerFhirModel;
|
||||
_sqlConnectionFactory = sqlConnectionFactory;
|
||||
}
|
||||
|
@ -80,7 +90,8 @@ namespace Microsoft.Health.Fhir.Tests.Integration.Persistence
|
|||
});
|
||||
|
||||
await schemaInitializer.InitializeAsync(forceIncrementalSchemaUpgrade, cancellationToken);
|
||||
await _sqlServerFhirModel.StartAsync(cancellationToken);
|
||||
await _searchParameterDefinitionManager.StartAsync(CancellationToken.None);
|
||||
_sqlServerFhirModel.Initialize(SchemaVersionConstants.Max, true);
|
||||
}
|
||||
|
||||
public async Task DeleteDatabase(string databaseName, CancellationToken cancellationToken = default)
|
||||
|
@ -137,6 +148,18 @@ namespace Microsoft.Health.Fhir.Tests.Integration.Persistence
|
|||
}
|
||||
}
|
||||
|
||||
public async Task DeleteSearchParameterStatusAsync(string uri, CancellationToken cancellationToken = default)
|
||||
{
|
||||
using (var connection = await _sqlConnectionFactory.GetSqlConnectionAsync())
|
||||
{
|
||||
var command = new SqlCommand("DELETE FROM dbo.SearchParam WHERE Uri = @uri", connection);
|
||||
command.Parameters.AddWithValue("@uri", uri);
|
||||
|
||||
await command.Connection.OpenAsync(cancellationToken);
|
||||
await command.ExecuteNonQueryAsync(cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
public Task DeleteAllReindexJobRecordsAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
|
|
|
@ -4,9 +4,9 @@
|
|||
// -------------------------------------------------------------------------------------------------
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
using System.Threading;
|
||||
using Hl7.Fhir.Model;
|
||||
using MediatR;
|
||||
using Microsoft.Data.SqlClient;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
@ -14,14 +14,16 @@ using Microsoft.Extensions.Logging.Abstractions;
|
|||
using Microsoft.Extensions.Options;
|
||||
using Microsoft.Health.Abstractions.Features.Transactions;
|
||||
using Microsoft.Health.Fhir.Core.Configs;
|
||||
using Microsoft.Health.Fhir.Core.Extensions;
|
||||
using Microsoft.Health.Fhir.Core.Features.Definition;
|
||||
using Microsoft.Health.Fhir.Core.Features.Operations;
|
||||
using Microsoft.Health.Fhir.Core.Features.Persistence;
|
||||
using Microsoft.Health.Fhir.Core.Features.Search;
|
||||
using Microsoft.Health.Fhir.Core.Features.Search.Registry;
|
||||
using Microsoft.Health.Fhir.Core.Models;
|
||||
using Microsoft.Health.Fhir.Core.UnitTests.Extensions;
|
||||
using Microsoft.Health.Fhir.SqlServer.Features.Schema;
|
||||
using Microsoft.Health.Fhir.SqlServer.Features.Schema.Model;
|
||||
using Microsoft.Health.Fhir.SqlServer.Features.Storage;
|
||||
using Microsoft.Health.Fhir.SqlServer.Features.Storage.Registry;
|
||||
using Microsoft.Health.SqlServer;
|
||||
using Microsoft.Health.SqlServer.Configs;
|
||||
using Microsoft.Health.SqlServer.Features.Client;
|
||||
|
@ -43,6 +45,7 @@ namespace Microsoft.Health.Fhir.Tests.Integration.Persistence
|
|||
private readonly IFhirOperationDataStore _fhirOperationDataStore;
|
||||
private readonly SqlServerFhirStorageTestHelper _testHelper;
|
||||
private readonly SchemaInitializer _schemaInitializer;
|
||||
private readonly FilebasedSearchParameterStatusDataStore _filebasedSearchParameterStatusDataStore;
|
||||
|
||||
public SqlServerFhirStorageTestsFixture()
|
||||
{
|
||||
|
@ -63,16 +66,19 @@ namespace Microsoft.Health.Fhir.Tests.Integration.Persistence
|
|||
var schemaUpgradeRunner = new SchemaUpgradeRunner(scriptProvider, baseScriptProvider, mediator, NullLogger<SchemaUpgradeRunner>.Instance, sqlConnectionFactory);
|
||||
_schemaInitializer = new SchemaInitializer(config, schemaUpgradeRunner, schemaInformation, sqlConnectionFactory, NullLogger<SchemaInitializer>.Instance);
|
||||
|
||||
var searchParameterDefinitionManager = Substitute.For<ISearchParameterDefinitionManager>();
|
||||
searchParameterDefinitionManager.AllSearchParameters.Returns(new[]
|
||||
{
|
||||
new SearchParameter { Name = SearchParameterNames.Id, Type = SearchParamType.Token, Url = SearchParameterNames.IdUri.ToString() }.ToInfo(),
|
||||
new SearchParameter { Name = SearchParameterNames.LastUpdated, Type = SearchParamType.Date, Url = SearchParameterNames.LastUpdatedUri.ToString() }.ToInfo(),
|
||||
});
|
||||
var searchParameterDefinitionManager = new SearchParameterDefinitionManager(ModelInfoProvider.Instance);
|
||||
|
||||
_filebasedSearchParameterStatusDataStore = new FilebasedSearchParameterStatusDataStore(searchParameterDefinitionManager, ModelInfoProvider.Instance);
|
||||
|
||||
var securityConfiguration = new SecurityConfiguration { PrincipalClaims = { "oid" } };
|
||||
|
||||
var sqlServerFhirModel = new SqlServerFhirModel(config, _schemaInitializer, searchParameterDefinitionManager, Options.Create(securityConfiguration), NullLogger<SqlServerFhirModel>.Instance);
|
||||
var sqlServerFhirModel = new SqlServerFhirModel(
|
||||
config,
|
||||
schemaInformation,
|
||||
searchParameterDefinitionManager,
|
||||
() => _filebasedSearchParameterStatusDataStore,
|
||||
Options.Create(securityConfiguration),
|
||||
NullLogger<SqlServerFhirModel>.Instance);
|
||||
|
||||
var serviceCollection = new ServiceCollection();
|
||||
serviceCollection.AddSqlServerTableRowParameterGenerators();
|
||||
|
@ -81,17 +87,24 @@ namespace Microsoft.Health.Fhir.Tests.Integration.Persistence
|
|||
ServiceProvider serviceProvider = serviceCollection.BuildServiceProvider();
|
||||
|
||||
var upsertResourceTvpGenerator = serviceProvider.GetRequiredService<VLatest.UpsertResourceTvpGenerator<ResourceMetadata>>();
|
||||
var upsertSearchParamsTvpGenerator = serviceProvider.GetRequiredService<VLatest.UpsertSearchParamsTvpGenerator<List<ResourceSearchParameterStatus>>>();
|
||||
|
||||
var searchParameterToSearchValueTypeMap = new SearchParameterToSearchValueTypeMap(new SupportedSearchParameterDefinitionManager(searchParameterDefinitionManager));
|
||||
|
||||
SqlTransactionHandler = new SqlTransactionHandler();
|
||||
SqlConnectionWrapperFactory = new SqlConnectionWrapperFactory(SqlTransactionHandler, new SqlCommandWrapperFactory(), sqlConnectionFactory);
|
||||
|
||||
SqlServerSearchParameterStatusDataStore = new SqlServerSearchParameterStatusDataStore(
|
||||
() => SqlConnectionWrapperFactory.CreateMockScope(),
|
||||
upsertSearchParamsTvpGenerator,
|
||||
() => _filebasedSearchParameterStatusDataStore,
|
||||
schemaInformation);
|
||||
|
||||
_fhirDataStore = new SqlServerFhirDataStore(config, sqlServerFhirModel, searchParameterToSearchValueTypeMap, upsertResourceTvpGenerator, Options.Create(new CoreFeatureConfiguration()), SqlConnectionWrapperFactory, NullLogger<SqlServerFhirDataStore>.Instance, schemaInformation);
|
||||
|
||||
_fhirOperationDataStore = new SqlServerFhirOperationDataStore(SqlConnectionWrapperFactory, NullLogger<SqlServerFhirOperationDataStore>.Instance);
|
||||
|
||||
_testHelper = new SqlServerFhirStorageTestHelper(initialConnectionString, MasterDatabaseName, sqlServerFhirModel, sqlConnectionFactory);
|
||||
_testHelper = new SqlServerFhirStorageTestHelper(initialConnectionString, MasterDatabaseName, searchParameterDefinitionManager, sqlServerFhirModel, sqlConnectionFactory);
|
||||
}
|
||||
|
||||
public string TestConnectionString { get; }
|
||||
|
@ -100,6 +113,8 @@ namespace Microsoft.Health.Fhir.Tests.Integration.Persistence
|
|||
|
||||
internal SqlConnectionWrapperFactory SqlConnectionWrapperFactory { get; }
|
||||
|
||||
internal SqlServerSearchParameterStatusDataStore SqlServerSearchParameterStatusDataStore { get; }
|
||||
|
||||
public async Task InitializeAsync()
|
||||
{
|
||||
await _testHelper.CreateAndInitializeDatabase(_databaseName, forceIncrementalSchemaUpgrade: false, _schemaInitializer, CancellationToken.None);
|
||||
|
@ -137,6 +152,16 @@ namespace Microsoft.Health.Fhir.Tests.Integration.Persistence
|
|||
return SqlTransactionHandler;
|
||||
}
|
||||
|
||||
if (serviceType == typeof(ISearchParameterStatusDataStore))
|
||||
{
|
||||
return SqlServerSearchParameterStatusDataStore;
|
||||
}
|
||||
|
||||
if (serviceType == typeof(FilebasedSearchParameterStatusDataStore))
|
||||
{
|
||||
return _filebasedSearchParameterStatusDataStore;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче