openehr-mssql

openEHR persistence for Microsoft SQL Server 2022 — the schema dialect.

Conformance level: Dialect#

This crate emits DDL for the shared openEHR schema. It does not contain a store. There is no driver dependency, no connection handling, no implementation of Store, and no statement in this crate has ever been submitted to a Microsoft SQL Server 2022 server.

See openehr-store/spec/conformance.md for what each level means and why they are stated this bluntly.

use openehr_mssql::MssqlDialect;
use openehr_store::ddl_script;

println!("{}", ddl_script(&MssqlDialect));

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 five 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 five to make sure.

Microsoft SQL Server 2022 specifics#

Decision Why
nvarchar, never varchar openEHR content is Unicode by construction — DV_TEXT carries an encoding attribute and clinical names are not ASCII. A varchar column silently substitutes ? for anything outside the collation's code page.
nvarchar(max) for JSON SQL Server has no JSON column type; JSON_VALUE and friends operate on nvarchar.
datetimeoffset(7), not datetime2 openEHR instants carry a UTC offset, and datetime2 would drop it — making two records from different zones compare as the same moment.
No IF NOT EXISTS SQL Server has no such clause for tables or indexes. Emitting it anyway would produce a script that fails on the one engine it targets, which is the shape of the sibling monorepo's F-25 and F-26.
No append-only trigger An INSTEAD OF trigger is the right mechanism, and its exact form has not been run against a server. Left undone and stated.

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#

cargo test

The tests are golden: they assert the SQL this crate emits, including assertions that it is not another engine's SQL.

Licence#

MIT OR Apache-2.0.

View this page's source on GitHub — the crates are the source of truth; this site renders them.