Pagination
By default, FHIR Gateway performs pagination dependent on the _count
parameter of the query, or the default pagination size, if the _count
parameter is unset on a given query 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.
Since Pagination counts are derived from match results, it is important to define what this means.
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 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 resource if it is 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 | Always included, regardless of pagination. This means that it can be returned multiple times, once per page. |
uncategorized | Always included, regardless of pagination. This means that it can be returned multiple times, once per page. |
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. For the purposes of pagination, uncategorized resources are considered as matchecs. 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
was requested, the gateway only took the first match 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"
}
},
{
"fullUrl": "http://localhost:8000/OperationOutcome/3",
"resource": {...},
"search": {
"mode": "outcome"
}
}
]
}
In this second page, the second match patient is returned, along with its included Observation. You will note that the outcome results are included again. This is because there is no way to determine which page they should be included with. As a result, they are included in each page derived from that downstream target server. 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"
},
},
{
"fullUrl": "http://localhost:8000/OperationOutcome/3",
"resource": {...}
"search": {
"mode": "outcome"
}
},
]
}
In this final page, since we have exhausted explicit match entries, the pagination continues onto uncategorized entries. As before, the outcome is included as well.