feat(db): add object.fields jsonb column, read it into CatalogueObject

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-02 10:54:49 +02:00
parent 7b0f804461
commit 2aaf98794f
6 changed files with 33 additions and 1 deletions
+1
View File
@@ -15,3 +15,4 @@ serde_json.workspace = true
[dev-dependencies]
tokio.workspace = true
time.workspace = true
serde_json.workspace = true
@@ -0,0 +1,2 @@
-- Flexible field values for a catalogue object, keyed by field-definition key.
ALTER TABLE object ADD COLUMN fields JSONB NOT NULL DEFAULT '{}'::jsonb;
+2 -1
View File
@@ -15,7 +15,7 @@ const ENTITY_TYPE: &str = "object";
const OBJECT_COLUMNS: &str = "id, object_number, object_name, number_of_objects, \
brief_description, current_location, current_owner, recorder, recording_date, \
visibility, created_at, updated_at";
visibility, fields, created_at, updated_at";
/// Create an object and record a `created` audit entry, both on `conn`
/// (pass a transaction connection `&mut *tx` so they commit atomically).
@@ -110,6 +110,7 @@ fn map_object(row: sqlx::postgres::PgRow) -> Result<CatalogueObject, sqlx::Error
recorder: row.try_get("recorder")?,
recording_date: row.try_get("recording_date")?,
visibility,
fields: row.try_get("fields")?,
created_at: row.try_get("created_at")?,
updated_at: row.try_get("updated_at")?,
})
+13
View File
@@ -116,3 +116,16 @@ async fn object_with_date_and_all_none_optionals_round_trips(pool: PgPool) {
.any(|c| c.field == "recording_date")
);
}
#[sqlx::test]
async fn new_object_has_empty_fields(pool: PgPool) {
let db = Db::from_pool(pool);
let mut tx = db.pool().begin().await.unwrap();
let id = catalog::create_object(&mut tx, AuditActor::System, &sample_input("LM-9"))
.await
.unwrap();
tx.commit().await.unwrap();
let obj = catalog::object_by_id(db.pool(), id).await.unwrap().unwrap();
assert_eq!(obj.fields, serde_json::json!({}));
}
+13
View File
@@ -71,3 +71,16 @@ async fn migrate_creates_field_definition_tables(pool: PgPool) {
);
}
}
#[sqlx::test]
async fn migrate_adds_object_fields_column(pool: PgPool) {
let db = Db::from_pool(pool);
let exists: Option<bool> = sqlx::query_scalar(
"SELECT true FROM information_schema.columns \
WHERE table_name = 'object' AND column_name = 'fields'",
)
.fetch_optional(db.pool())
.await
.unwrap();
assert_eq!(exists, Some(true));
}
+2
View File
@@ -62,6 +62,8 @@ pub struct CatalogueObject {
pub recorder: Option<String>,
pub recording_date: Option<Date>,
pub visibility: Visibility,
/// Flexible field values (field key -> value), validated against the registry.
pub fields: serde_json::Value,
pub created_at: OffsetDateTime,
pub updated_at: OffsetDateTime,
}