openehr-postgresql
openEHR persistence for PostgreSQL 18 — the schema dialect.
Conformance level: Schema
This crate emits DDL for the shared openEHR schema, and a PostgreSQL 18 server
has executed it: five tables, seven indexes, idempotent on re-application,
foreign keys enforced, and both append-only tables refusing UPDATE and
DELETE with a row present and intact afterwards.
It does not contain a store. There is no driver dependency, no connection
handling, and no implementation of Store. Schema level means the database
accepts the schema, not that this crate can talk to it.
See openehr-store/spec/conformance.md
for what each level means and why they are stated this bluntly.
use PostgresqlDialect;
use ddl_script;
println!;
What this crate owns
Four things: type spellings, identifier quoting, placeholder style, and how the
engine enforces append-only. Everything else — which tables exist, which
columns, which indexes, the projection from openEHR objects onto rows, the
commit rules, the conformance suite — lives in
openehr-store and is shared by all six engines.
That boundary is deliberate. The sibling FHIR monorepo in this repository gave
each of six ports a full copy of the DDL generator, and one of the copies spent
the fork's whole life emitting another engine's types (F-08). A dialect that
owns only spellings cannot do that, and
openehr-sqlite/tests/dialects.rs compares all six to make sure.
PostgreSQL-specific choices
| Decision | Why |
|---|---|
text, not varchar(n) |
PostgreSQL stores both identically; the length would only add a check that rejects a long-but-legal ARCHETYPE_ID. |
jsonb, not json |
The canonical byte form is regenerated from the parsed object, never read back from the column, so preserving whitespace buys nothing and containment indexes buy a lot. |
timestamptz for derived instants |
The authoritative instant is stored as text alongside it — see below. |
| An append-only trigger | The guarantee lives in the database, not in application code, where it would end the first time somebody opened psql. |
Every instant is stored twice, and that is the point
openEHR times are ISO 8601 strings with deliberate partial precision:
2024-05 is a date known to the month, and it is not 2024-05-01. A native
timestamp column silently completes it — fabricating a clinical fact — and
normalises the lexical form, breaking round-trip fidelity.
So each time occupies …_text (authoritative, exact) and …_utc (derived,
nullable, for ordering). The derived column is NULL whenever the instant is
not established, which is the same answer the library gives, so SQL and Rust
cannot disagree about one record.
Testing
The tests are golden: they assert the SQL this crate emits, including assertions that it is not another engine's SQL.
Licence
Any of these, at your option — MIT, Apache-2.0, BSD-3-Clause, GPL-2.0-only, or
GPL-3.0-only. See LICENSE.md.