From cbed662c184c026a00b2f7142b5e0cb4157b7b23 Mon Sep 17 00:00:00 2001 From: Anders Olsson Date: Tue, 2 Jun 2026 10:16:30 +0200 Subject: [PATCH] feat(db): add field_definition tables Co-Authored-By: Claude Sonnet 4.6 --- .../db/migrations/0004_field_definition.sql | 23 +++++++++++++++++++ crates/db/tests/migrate.rs | 18 +++++++++++++++ 2 files changed, 41 insertions(+) create mode 100644 crates/db/migrations/0004_field_definition.sql diff --git a/crates/db/migrations/0004_field_definition.sql b/crates/db/migrations/0004_field_definition.sql new file mode 100644 index 0000000..eee46d2 --- /dev/null +++ b/crates/db/migrations/0004_field_definition.sql @@ -0,0 +1,23 @@ +-- Registry of flexible field definitions (the "schema of schemas"). +CREATE TABLE field_definition ( + id UUID PRIMARY KEY, + key TEXT NOT NULL UNIQUE CHECK (key <> ''), + data_type TEXT NOT NULL CHECK (data_type IN + ('text', 'localized_text', 'integer', 'date', 'boolean', 'term', 'authority')), + vocabulary_id UUID REFERENCES vocabulary (id) ON DELETE RESTRICT, + authority_kind TEXT CHECK (authority_kind IN ('person', 'organisation', 'place')), + required BOOLEAN NOT NULL DEFAULT false, + group_key TEXT CHECK (group_key <> ''), + -- A term field must name a vocabulary; any other type must not. + CONSTRAINT term_has_vocabulary CHECK ((data_type = 'term') = (vocabulary_id IS NOT NULL)), + -- authority_kind is only meaningful for authority fields. + CONSTRAINT authority_kind_only_for_authority + CHECK (authority_kind IS NULL OR data_type = 'authority') +); + +CREATE TABLE field_definition_label ( + field_definition_id UUID NOT NULL REFERENCES field_definition (id) ON DELETE CASCADE, + lang TEXT NOT NULL CHECK (lang <> ''), + label TEXT NOT NULL CHECK (label <> ''), + PRIMARY KEY (field_definition_id, lang) +); diff --git a/crates/db/tests/migrate.rs b/crates/db/tests/migrate.rs index 5588e37..680e992 100644 --- a/crates/db/tests/migrate.rs +++ b/crates/db/tests/migrate.rs @@ -53,3 +53,21 @@ async fn migrate_creates_vocabulary_and_authority_tables(pool: PgPool) { ); } } + +#[sqlx::test] +async fn migrate_creates_field_definition_tables(pool: PgPool) { + let db = Db::from_pool(pool); + + for table in ["field_definition", "field_definition_label"] { + let regclass: Option = + sqlx::query_scalar(&format!("SELECT to_regclass('public.{table}')::text")) + .fetch_one(db.pool()) + .await + .unwrap(); + assert_eq!( + regclass.as_deref(), + Some(table), + "table {table} should exist" + ); + } +}