Skip to main content

Connecting to PostgreSQL

The PostgresSource adapter lets you evaluate clinical logic against FHIR data stored in a PostgreSQL database — without copying data out of Postgres.

:::caution Scope Boundary This adapter requires that your Postgres tables already contain FHIR resource JSON in a column (JSON or JSONB). It does not construct FHIR JSON from arbitrary relational column schemas. If your data is in a normalized relational schema, you will need to build the FHIR JSON before using this adapter. :::

Prerequisites

Your Postgres tables must conform to the Standard FHIR4DS Schema Contract, specifically:

  • A column containing the FHIR resource ID (castable to VARCHAR).
  • A column containing the complete FHIR resource as JSON/JSONB.
  • A column containing the raw Patient ID (castable to VARCHAR, no prefixes).

Step-by-Step

1. Define Table Mappings

Each Postgres table needs a PostgresTableMapping that tells fhir4ds which columns correspond to the schema contract:

from fhir4ds.sources import PostgresTableMapping

patient_mapping = PostgresTableMapping(
table_name='fhir_patients',
id_column='patient_id',
resource_type='Patient',
resource_column='fhir_json',
patient_ref_column='patient_id',
)

obs_mapping = PostgresTableMapping(
table_name='fhir_observations',
id_column='obs_id',
resource_type='Observation',
resource_column='fhir_json',
patient_ref_column='patient_id',
)

2. Create the Source and Attach

import fhir4ds
from fhir4ds.sources import PostgresSource

source = PostgresSource(
connection_string='postgresql://user:pass@localhost:5432/clinical',
table_mappings=[patient_mapping, obs_mapping],
)

con = fhir4ds.create_connection()
fhir4ds.attach(con, source)

3. Run a Measure

result = fhir4ds.evaluate_measure(
library_path="./CMS165.cql",
conn=con,
output_columns={"ip": "Initial Population"},
)
print(result.df())

How It Works

When you attach a PostgresSource:

  1. DuckDB's postgres extension is installed and loaded.
  2. The Postgres database is attached as a read-only connection.
  3. A unified resources view is created as a UNION ALL of all mapped tables, with columns cast to the required types.
  4. Schema validation runs immediately.

Queries against resources are pushed down to Postgres — DuckDB fetches only the data needed.

Idempotency

register() is fully idempotent:

  • Uses ATTACH IF NOT EXISTS for the Postgres connection.
  • Uses CREATE OR REPLACE VIEW for the resources view.

Cleanup

unregister() drops the resources view and detaches the Postgres connection.

Troubleshooting

IssueSolution
Connection refusedVerify connection string, ensure Postgres is running, check firewall rules.
SchemaValidationError: column 'resource' has type 'VARCHAR'Ensure the Postgres column stores JSON/JSONB.
ValueError: requires at least one PostgresTableMappingProvide at least one mapping.
Duplicate attachmentregister() handles this via ATTACH IF NOT EXISTS.