Skip to main content

Connecting to HAPI FHIR PostgreSQL

HapiPostgresSource lets FHIR4DS query a HAPI FHIR JPA Server PostgreSQL backend in place through DuckDB's PostgreSQL extension.

This adapter is read-only. It creates the standard resources view from HAPI's current-resource tables without copying FHIR data into DuckDB.

Supported Scope

The integration currently supports:

  • Current, non-deleted HAPI resources.
  • PostgreSQL-backed HAPI JPA Server.
  • Inline JSON resource bodies in hfj_res_ver.res_text_vc.
  • Uncompressed JSON resource bodies stored as PostgreSQL large objects in hfj_res_ver.res_text, when using the decoded view installed by fhir4ds dqm hapi install.
  • res_encoding = 'JSON'.

The adapter deliberately rejects compressed resource storage such as JSONC by default. This prevents partial analysis where compressed resources are silently omitted.

Example

import duckdb
import fhir4ds
from fhir4ds.sources import HapiPostgresSource

con = duckdb.connect(":memory:")

source = HapiPostgresSource(
"postgresql://hapi:hapi@localhost:15432/hapi"
)

fhir4ds.attach(con, source)

rows = con.execute("""
SELECT id, resourceType, patient_ref
FROM resources
WHERE resourceType = 'Patient'
""").fetchall()

HAPI Schema Defaults

The default schema mapping targets the current HAPI 8.8 PostgreSQL layout:

PurposeDefault
Resource tablepublic.hfj_resource
Version tablepublic.hfj_res_ver
Resource primary keyhfj_resource.res_id
Version resource FKhfj_res_ver.res_id
Current version joinhfj_resource.res_ver = hfj_res_ver.res_ver
FHIR resource IDhfj_resource.fhir_id
Resource typehfj_resource.res_type
Updated timestamphfj_resource.res_updated
Deleted timestamphfj_resource.res_deleted_at
Resource JSON texthfj_res_ver.res_text_vc
Resource JSON large objecthfj_res_ver.res_text
Resource encodinghfj_res_ver.res_encoding
Decoded current-resource viewunset

Override these defaults with HapiPostgresSchema for older HAPI versions or custom table names:

from fhir4ds.sources import HapiPostgresSchema, HapiPostgresSource

schema = HapiPostgresSchema(
resource_pk_column="pid",
version_resource_fk_column="res_id",
)

source = HapiPostgresSource(
"postgresql://readonly:[email protected]:5432/hapi",
schema=schema,
)

Set decoded_view when the FHIR4DS HAPI materialization schema has been installed and you want the adapter to read the decoded PostgreSQL view:

schema = HapiPostgresSchema(
decoded_view="fhir4ds_hapi_current_resources",
)

Patient Attribution

The adapter maps patient_ref as a raw Patient ID:

  • Patient resources use their own fhir_id.
  • Other resources check common FHIR reference paths:
    • subject.reference
    • patient.reference
    • beneficiary.reference

Only local references of the form Patient/<id> are converted to raw IDs. Add paths with patient_reference_paths if your target resources use different patient-bearing fields.

Local Docker Stack

Use the disposable compose stack for development:

cd docker/hapi-postgres
docker compose up -d

It exposes:

  • HAPI FHIR at http://localhost:18080/fhir
  • PostgreSQL at postgresql://hapi:hapi@localhost:15432/hapi
  • Optional FHIR4DS worker profile with docker compose --profile worker up worker

Operational Notes

Use a read replica or a read-only database user for production analytics. Large CQL or DQM queries can scan many resources and should not compete with a busy transactional HAPI server.

HapiPostgresSource.supports_incremental() supports insert/update delta checks against current resources that the adapter can decode. Deletes and compressed historical bodies are outside the v1 incremental scope.

For event-driven DQM materialization with PostgreSQL triggers and LISTEN/NOTIFY, see HAPI FHIR Server Integration.