91 lines
3.0 KiB
Rust
91 lines
3.0 KiB
Rust
//! SQL DDL for the HA-compatible recorder schema (ADR-132).
|
|
//!
|
|
//! Schema mirrors Home Assistant recorder schema v48 (HA 2025.1):
|
|
//! - `states` — one row per state write (entity_id, state, attrs)
|
|
//! - `state_attributes` — shared attribute blobs, deduped by fnv64a hash
|
|
//! - `events` — domain events fired by integrations
|
|
//! - `recorder_runs` — boot/shutdown bookends for gap detection
|
|
//!
|
|
//! All DDL strings use `CREATE TABLE IF NOT EXISTS` so `apply_schema` is
|
|
//! idempotent and safe to call on every startup.
|
|
|
|
/// Create `state_attributes` table.
|
|
///
|
|
/// `shared_attrs` is stored as TEXT (JSON blob). `hash` is the FNV-1a 64-bit
|
|
/// hash of `shared_attrs` encoded as a signed i64 — matches HA's dedup key.
|
|
pub const CREATE_STATE_ATTRIBUTES: &str = "
|
|
CREATE TABLE IF NOT EXISTS state_attributes (
|
|
attributes_id INTEGER PRIMARY KEY NOT NULL,
|
|
shared_attrs TEXT NOT NULL,
|
|
hash INTEGER NOT NULL
|
|
);
|
|
|
|
CREATE UNIQUE INDEX IF NOT EXISTS ix_state_attributes_hash
|
|
ON state_attributes (hash);
|
|
";
|
|
|
|
/// Create `states` table.
|
|
///
|
|
/// `state_id` — auto-increment primary key
|
|
/// `entity_id` — validated `domain.name` string
|
|
/// `state` — state value string (\"on\", \"off\", \"20.5\", …)
|
|
/// `attributes_id` — FK → state_attributes (nullable for HA compat)
|
|
/// `last_changed_ts` — Unix timestamp seconds (float, UTC)
|
|
/// `last_updated_ts` — Unix timestamp seconds (float, UTC)
|
|
/// `context_id` — UUID as TEXT; links to the causality chain
|
|
pub const CREATE_STATES: &str = "
|
|
CREATE TABLE IF NOT EXISTS states (
|
|
state_id INTEGER PRIMARY KEY NOT NULL,
|
|
entity_id TEXT NOT NULL,
|
|
state TEXT,
|
|
attributes_id INTEGER,
|
|
last_changed_ts REAL,
|
|
last_updated_ts REAL NOT NULL,
|
|
context_id TEXT
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS ix_states_entity_id_last_updated_ts
|
|
ON states (entity_id, last_updated_ts);
|
|
|
|
CREATE INDEX IF NOT EXISTS ix_states_last_updated_ts
|
|
ON states (last_updated_ts);
|
|
";
|
|
|
|
/// Create `events` table.
|
|
///
|
|
/// `event_type` — string key (e.g. \"state_changed\", \"call_service\")
|
|
/// `event_data` — JSON blob
|
|
/// `time_fired_ts` — Unix timestamp seconds (float, UTC)
|
|
/// `context_id` — UUID as TEXT
|
|
pub const CREATE_EVENTS: &str = "
|
|
CREATE TABLE IF NOT EXISTS events (
|
|
event_id INTEGER PRIMARY KEY NOT NULL,
|
|
event_type TEXT NOT NULL,
|
|
event_data TEXT,
|
|
time_fired_ts REAL NOT NULL,
|
|
context_id TEXT
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS ix_events_event_type_time_fired_ts
|
|
ON events (event_type, time_fired_ts);
|
|
";
|
|
|
|
/// Create `recorder_runs` table.
|
|
///
|
|
/// Records each start/stop pair so the history API can annotate gaps.
|
|
pub const CREATE_RECORDER_RUNS: &str = "
|
|
CREATE TABLE IF NOT EXISTS recorder_runs (
|
|
run_id INTEGER PRIMARY KEY NOT NULL,
|
|
start_ts REAL NOT NULL,
|
|
end_ts REAL
|
|
);
|
|
";
|
|
|
|
/// All DDL statements in dependency order.
|
|
pub const ALL_DDL: &[&str] = &[
|
|
CREATE_STATE_ATTRIBUTES,
|
|
CREATE_STATES,
|
|
CREATE_EVENTS,
|
|
CREATE_RECORDER_RUNS,
|
|
];
|