Database setup
Connection and initialization
{storage_dir}/hoot.db where storage_dir is platform-specific:
- Linux:
~/.local/share/hoot/ - macOS:
~/Library/Application Support/hoot/ - Windows:
%APPDATA%/hoot/
SQLCipher encryption
From src/db/mod.rs:46:- Requires a password to unlock using SQLCipher’s
keypragma - Runs migrations automatically after successful unlock
- Returns
NotADatabaseerror for incorrect passwords - Remains locked until password is provided
Migration system
Migrations are managed usingrusqlite_migration:
- Embedded in the binary at compile time from
migrations/directory - Automatically applied when database is unlocked
- Versioned sequentially (001, 002, 003, etc.)
- Each migration has an
up.sqlfile in its directory
Schema design
Events table
The core table stores Nostr events as JSON with generated virtual columns:- Raw JSON storage: Complete event stored in
rawcolumn as JSON - Virtual columns: Common fields extracted using
jsonb_extract()for efficient querying - Automatic extraction: SQLite generates column values automatically from JSON
- No data duplication: Virtual columns don’t take storage space
Profile metadata table
Caches Nostr metadata events (kind 0) for quick profile lookups:HashMap<String, ProfileOption> cache for fast access:
ProfileOption::Some(metadata): Metadata loadedProfileOption::Waiting: Request sent to relays, awaiting responseProfileOption::None: Not yet requested
Pubkeys table
Stores account public keys for the user’s identities:Additional tables
- contacts: User’s contact list with optional petnames
- drafts: Unsent message drafts with metadata
- deletions: Tracks deleted events to prevent re-insertion
- trash: Soft-deleted messages
- sender_status: Tracks trusted/blocked senders
- nip05: Caches NIP-05 verification status
Event storage
From src/db/events.rs:33:- Gift wrap unwrapping: Gift-wrapped events (kind 1059) are automatically unwrapped and stored as their inner rumor
- Duplicate prevention:
INSERT OR IGNOREprevents duplicate events - Deletion check: Events marked as deleted are not stored
- Raw JSON: Complete event serialized to JSON for storage
Query patterns
The database supports efficient queries using virtual columns:- Filtering by event fields without parsing JSON
- Efficient indexing on commonly queried fields
- Complex queries with joins and aggregations
Thread reconstruction
The database uses recursive CTEs to reconstruct message threads:Performance considerations
- Indexes: Created on
pubkeyandcreated_atfor common queries - Virtual columns: Avoid JSON parsing overhead for frequent operations
- Transaction batching: Multiple operations grouped in transactions
- Lazy loading: Profile metadata fetched on-demand
- Caching: In-memory HashMap cache for profile metadata