Pagination
FHIR Gateway performs pagination using default pagination size, or the _count
parameter if it is provided.
For the purposes of pagination, uncategorized resources are considered as a match and sorted among them.
When no sort parameter is present in request, to return a bundle to a FHIR Gateway client user, the Gateway will first evaluate the first target, and collect matched resources until it fulfills its desired _count
. If this value is reached before the downstream bundles have been exhausted of entries, the remainder of that bundle will be used for the subsequent page.
If, however, the first downstream target becomes exhausted before the requested _count
is reached, FHIR Gateway will query the second target,
and so on and so forth, until all targets become exhausted. It is important to note that regardless of whether the search routes are defined to execute in parallel: true
mode, the evaluation of pagination will occur in a sequential manner.
Result pages will contain _count
number of match and uncategorized resources, except last page which could have fewer, when exhausting the results of all target servers. Intermediate pages will include a next
and a previous
link to provide access to following or previous pages.
Gateway pagination requires target servers replying next
and previous
links in responses when extra resources are available in the corresponding pagination direction.
It is important to note that regardless of whether the search routes are defined to execute in parallel: true
mode, the evaluation of pagination will occur in a sequential manner.
Gateway sorts by merging sorted target resources, which requires all target servers to support sorting.
Custom parameter sorting is supported and requires involved target servers to provide those parameters.
In case any custom search parameter is unknown by any active target server, the result bundle won't be sorted by the related field, and will have an OperationOutcome
resource detailing which target servers failed to retrieve which custom parameters.
Properties of type token
are sorted by the token system
, then the token value
with null sorting last.
Properties of type quantity
are sorted considering their un-normalized value, to match HAPI-FHIR target servers.
When sorting, the Gateway requires resources from all targets which have resources matching the request. For this reason, performance will be improved by configuring parallel: true
in the GatewaySearchRoute model, unless a compelling reason to change it exists (e.g. target requests depending on a previous target responses).
For an explanation on match and uncategorized search modes, see (Search Modes)[#search-modes] below.
When a downstream target returns a bundle, it contains various pieces of data. The ones that FHIR Gateway critically relies on are the total
element, as well as the entries
section. The entries
section contains all the resources that the downstream target returned.
The following is an example bundle we will use to explain the various search modes in the entries.
{
"resourceType": "Bundle",
"id": "461666c1-2a1b-453a-9765-be272bdb48e9",
"type": "searchset",
"total": 1,
"entry": [
{
"fullUrl": "http://localhost:8000/Patient/1",
"resource": {
"resourceType": "Patient",
"id": "1",
"identifier": [
{
"system": "urn:hapitest:mrns",
"value": "00002"
}
]
},
"search": {
"mode": "match"
}
},
{
"fullUrl": "http://localhost:8000/Observation/2",
"resource": {
"resourceType": "Observation",
"id": "2",
"subject": {
"reference": "Patient/1"
}
},
"search": {
"mode": "include"
}
},
{
"fullUrl": "http://localhost:8000/OperationOutcome/3",
"resource": {
"resourceType": "OperationOutcome",
"id": "3",
"issue": [
{
"code": "unknown"
}
]
},
"search": {
"mode": "outcome"
}
},
{
"fullUrl": "http://localhost:8000/Patient/4",
"resource": {
"resourceType": "Patient",
"id": "4"
}
}
]
}
This bundle contains 4 entries, each bearing a unique Search Mode. Here's what they mean:
/Patient?_revinclude=Observation:subject
any Observation resources would have search mode include
.Finally, it is permitted by the FHIR Specification to omit the search
section entirely from a searchset bundle, we call this internally an uncategorized
entry, and this is the fourth and final possible value of search.mode
.
The total field represents the total number of resources that have search mode of match across all pages of a query, not how many resources are returned in a particular response.
The _count
parameter determines the number of match and uncategorized resources that should be returned per page, from downstream targets. However, what happens to downstream entries that are not of match type?
Search Mode | Logic for inclusion in Gateway Bundle |
---|---|
match | Includes up to _count entries from target servers, returning remainders in subsequent page requests. |
include | Includes a resource if its related target resources are in the included match entries. e.g. only include an observation if its related patient was included in the gateway bundle. |
outcome | Included in pages containing match resources retrieved from target in same response. Can be returned multiple times, when resources in same target response are split in different pages. |
uncategorized | Treated as match for pagination purposes. |
Let's take the following example bundle from a single downstream server. For brevity, the resource bodies have been truncated.
{
"resourceType": "Bundle",
"id": "461666c1-2a1b-453a-9765-be272bdb48e9",
"type": "searchset",
"total": 2,
"entry": [
{
"fullUrl": "http://localhost:8000/Patient/1",
"resource": {...},
"search": {
"mode": "match"
}
},
{
"fullUrl": "http://localhost:8000/Patient/2",
"resource": {...},
"search": {
"mode": "match"
}
},
{
"fullUrl": "http://localhost:8000/Observation/3",
"resource": {
"subject": {
"reference": "Patient/1"
}
},
"search": {
"mode": "include"
}
},
{
"fullUrl": "http://localhost:8000/Observation/4",
"resource": {
"subject": {
"reference": "Patient/2"
}
},
"search": {
"mode": "include"
}
},
{
"fullUrl": "http://localhost:8000/OperationOutcome/3",
"resource": {...},
"search": {
"mode": "outcome"
}
},
{
"fullUrl": "http://localhost:8000/Patient/4",
"resource": {...}
}
]
}
You'll note that there are two resources that match, two which are included, one outcome and one uncategorized.
Consider that the query which was executed against the gateway was the following:
GET localhost:8008/Patient?_revinclude=Observation:subject&_count=1
The important part of this is that the client has requested _count=1
, meaning there should be no more than one match entry. Let's have a look at what the Gateway returns here, remembering what our one downstream server returned.
{
"resourceType": "Bundle",
"id": "461666c1-2a1b-453a-9765-be272bdb48e9",
"type": "searchset",
"total": 2,
"link":{
"relation": "next",
"url": "..."
}
"entry": [
{
"fullUrl": "http://localhost:8000/Patient/1",
"resource": {...},
"search": {
"mode": "match"
}
},
{
"fullUrl": "http://localhost:8000/Observation/3",
"resource": {
"subject": {
"reference": "Patient/1"
}
},
"search": {
"mode": "include"
}
},
{
"fullUrl": "http://localhost:8000/OperationOutcome/3",
"resource": {...},
"search": {
"mode": "outcome"
}
}
]
}
You will see that since _count=1
and no _sort
was requested, the gateway only took the first-in-id-order match or uncategorized result from the downstream target. Furthermore, it included only the observation which was related to the chosen patient, and omitted the Observation which refers to the patient which was not included for this page.
You will also note that the outcome results were included. Here is what the second page looks like, when the next
url is visited:
{
"resourceType": "Bundle",
"id": "461666c1-2a1b-453a-9765-be272bdb48e9",
"type": "searchset",
"total": 2,
"entry": [
{
"fullUrl": "http://localhost:8000/Patient/2",
"resource": {...},
"search": {
"mode": "match"
}
},
{
"fullUrl": "http://localhost:8000/Observation/4",
"resource": {
"subject": {
"reference": "Patient/2"
}
},
"search": {
"mode": "include"
}
}
]
}
In this second page, the second match patient is returned, along with its included Observation. Finally, let's see what the final page of data would look like:
"resourceType": "Bundle",
"id": "461666c1-2a1b-453a-9765-be272bdb48e9",
"type": "searchset",
"total": 1,
"entry": [
{
"fullUrl": "http://localhost:8000/Patient/4",
"resource": {
"resourceType": "Patient",
"id": "4"
},
}
]
}
In this final page, we found the uncategorized entry with the last-in-id-order.