KeFHIR can handle different search implementations, but only one search implementation can be used in the one installation, since resources may reference between each other.
The search implementation is independent from the storage implementation.
Search module stores only data that should be indexed based on the information about search parameters in Conformance.
KeFHIR detects changes of the search parameters and starts (re)indexing changed search parameters in the background.
kefhir-core
manages search parameters, it parses Conformance and provides composed SearchCriterion model to search implementing interface ResourceSearchHandler.
To persist resource implement ResourceAfterSaveInterceptor and ResourceAfterDeleteInterceptor see Extensibility of service interceptors.
Search implementation may return only the list of resource-id. Resource-id may be used to load resource content using Storage implementation.
KeFHIR provides default ResourceSearchHandler implementation using PostgreSQL.
implementation "com.kodality.kefhir:pg-core:${kefhirVersion}"
implementation "com.kodality.kefhir:pg-search:${kefhirVersion}"
datasources:
default:
url: jdbc:postgresql://localhost:5151/kefhirdb
username: kefhir_app
password: test
maximum.pool.size: 10
driver.class.name: org.postgresql.Driver
admin:
url: jdbc:postgresql://localhost:5151/kefhirdb
username: kefhir_admin
password: test
maximum.pool.size: 1
driver.class.name: org.postgresql.Driver
liquibase:
datasources:
admin:
enabled: true
change-log: 'classpath:changelog.xml'
default-schema: 'public'
parameters:
app-username: ${datasources.default.username}
default
- datasource is used for data save/load.admin
- datasource is used for liquibase database migrations and for adding/removing resource types, search indexes, configurations.blindex
- managed by KeFHIR. Stores information about indexes.resource_structure
- managed by KeFHIR. Autogeneration based on StructureDefinition resources. Stores full recursive information about resource structure - fields, types.search_configuration
- configurable. Tells KeFHIR how to take actual data from complex objects.resource
- fact of a resource existence and overall index fields. Partitioned by resource type. It contains next columns:
sid
- generated resource idresource_type
, resource_id
- resource referencelast_updated
active
- logical deletesid
- reference to resourceblindex_id
- reference to blindex (type of index)active
- logical deletebase_index_date
range
- date rangebase_index_number
range
- number rangebase_index_quantity
range
- number rangesystem
code
unit
base_index_reference
type
- type of referenced resourceid
- id of referenced resourcebase_index_string
string
base_index_token
system
value
base_index_uri
uri
For every defined SearchParameter a partition of corresponding index table is created and information about this index is stored in blindex
.
Every search run generates two database queries, one for total rows, another for actual data.
For every query parameter an exising construction is build, selecting from a specific index partition. Correct partition is found using SearchParameter definitions and blindex
.
Chaining parameters are build on top of each other using INNER JOIN
.
Search returns a set of resource ids, which are then being loaded using kefhir-store
.
/Encounter?episode-of-care.patient:Patient.name=Alex&identifier=somesystem|123&practitioner=333&date=gt2022-01-01
SELECT base.resource_id, rt.type resource_type FROM search.resource base
INNER JOIN search.resource_type rt on rt.id = base.resource_type
--chain episode-of-care.patient
INNER JOIN search.resource episodeofcare ON episodeofcare.resource_type = 53 AND episodeofcare.active = true
AND EXISTS (SELECT 1 FROM search.encounter_reference_episodeofcare i WHERE i.active = true and i.sid = base.sid
AND i.id = episodeofcare.resource_id AND i.type_id = episodeofcare.resource_type)
INNER JOIN search.resource patient ON patient.resource_type = 107 AND patient.active = true
AND EXISTS (SELECT 1 FROM search.episodeofcare_reference_patient i WHERE i.active = true and i.sid = episodeofcare.sid
AND i.id = patient.resource_id AND i.type_id = patient.resource_type)
-- patient.name = Alex
AND EXISTS (SELECT 1 FROM search.patient_string_name i WHERE i.active = true and i.sid = patient.sid AND i.string ilike 'Alex%')
WHERE base.resource_type = 49 and base.active = true
-- date = gt2022-01-01
AND EXISTS (SELECT 1 FROM search.encounter_date_period i WHERE i.active = true and i.sid = base.sid AND i.range >> search.range('2022-01-01T00:00:00+02:00', '1 day'))
-- identifier = somesystem|123
AND EXISTS (SELECT 1 FROM search.encounter_token_identifier i WHERE i.active = true and i.sid = base.sid AND (i.value = '123' and i.system_id = search.sys_id('somesystem')))
-- practitioner = 333
AND EXISTS (SELECT 1 FROM search.encounter_reference_participant_individual i WHERE i.active = true and i.sid = base.sid AND (i.id = '333' and i.type_id = search.rt_id('Practitioner')))
Comming soon
Search federation allows interaction between different FHIR servers or applications with FHIR interfaces to create global search database over several FHIR instances.