cratestack_sqlx/audit/
schema.rs1use std::sync::atomic::Ordering;
4
5use cratestack_core::CoolError;
6
7use crate::SqlxRuntime;
8use crate::sqlx;
9
10pub const AUDIT_TABLE_DDL: &str = r#"
15CREATE TABLE IF NOT EXISTS cratestack_audit (
16 event_id UUID PRIMARY KEY,
17 schema_name TEXT NOT NULL,
18 model TEXT NOT NULL,
19 operation TEXT NOT NULL,
20 primary_key JSONB NOT NULL,
21 actor JSONB NOT NULL,
22 tenant TEXT,
23 before JSONB,
24 after JSONB,
25 request_id TEXT,
26 occurred_at TIMESTAMPTZ NOT NULL,
27 delivered_at TIMESTAMPTZ,
28 attempts BIGINT NOT NULL DEFAULT 0,
29 last_error TEXT
30);
31
32CREATE INDEX IF NOT EXISTS cratestack_audit_model_idx
33 ON cratestack_audit (schema_name, model, occurred_at DESC);
34
35CREATE INDEX IF NOT EXISTS cratestack_audit_tenant_idx
36 ON cratestack_audit (tenant, occurred_at DESC)
37 WHERE tenant IS NOT NULL;
38
39CREATE INDEX IF NOT EXISTS cratestack_audit_undelivered_idx
40 ON cratestack_audit (occurred_at)
41 WHERE delivered_at IS NULL;
42"#;
43
44pub(crate) async fn ensure_audit_table(runtime: &SqlxRuntime) -> Result<(), CoolError> {
54 if runtime.audit_table_ensured().load(Ordering::Acquire) {
55 return Ok(());
56 }
57
58 sqlx::raw_sql(AUDIT_TABLE_DDL)
64 .execute(runtime.pool())
65 .await
66 .map_err(|error| CoolError::Database(error.to_string()))?;
67
68 runtime.audit_table_ensured().store(true, Ordering::Release);
69 Ok(())
70}