All of the user data is stored in a single encrypted, compressed SQLite database with the following table schema:
CREATE TABLE currencies (
code TEXT NOT NULL PRIMARY KEY CHECK (code <> ''),
stored_decimals INTEGER NOT NULL CHECK (stored_decimals >= 0)
) STRICT;
CREATE TABLE accounts (
name TEXT NOT NULL COLLATE NOCASE PRIMARY KEY CHECK (name <> ''),
display_currency TEXT REFERENCES currencies(code),
type TEXT NOT NULL CHECK (type <> ''),
identifier TEXT CHECK (identifier <> '')
) STRICT;
CREATE TABLE sources (
hash BLOB NOT NULL PRIMARY KEY CHECK (length(hash) = 32),
remarks TEXT NOT NULL CHECK (remarks <> ''),
import_timestamp INTEGER NOT NULL DEFAULT (unixepoch())
) STRICT;
CREATE TABLE categories (
name TEXT NOT NULL COLLATE NOCASE PRIMARY KEY CHECK (name <> ''),
parent TEXT COLLATE NOCASE REFERENCES categories(name) ON UPDATE CASCADE ON DELETE SET NULL,
CONSTRAINT no_self_parent CHECK (name <> parent)
) STRICT;
CREATE TABLE transactions (
id TEXT NOT NULL PRIMARY KEY CHECK (id GLOB '[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]-[0-9a-f][0-9a-f][0-9a-f][0-9a-f]-4[0-9a-f][0-9a-f][0-9a-f]-[89ab][0-9a-f][0-9a-f][0-9a-f]-[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]'),
source BLOB NOT NULL REFERENCES sources(hash) ON UPDATE CASCADE ON DELETE CASCADE,
account_name TEXT NOT NULL COLLATE NOCASE REFERENCES accounts(name) ON UPDATE CASCADE ON DELETE CASCADE,
transfer_account TEXT COLLATE NOCASE REFERENCES accounts(name) ON UPDATE CASCADE ON DELETE SET NULL,
currency TEXT NOT NULL REFERENCES currencies(code),
amount INTEGER NOT NULL,
counterparty TEXT NOT NULL CHECK (counterparty <> ''),
category TEXT COLLATE NOCASE REFERENCES categories(name) ON UPDATE CASCADE ON DELETE SET NULL,
duplicate_of TEXT REFERENCES transactions(id) ON UPDATE CASCADE ON DELETE SET NULL,
timestamp INTEGER,
naive_date TEXT NOT NULL CHECK (naive_date IS date(naive_date)),
sort_key INTEGER NOT NULL,
remarks TEXT CHECK (remarks <> ''),
CONSTRAINT no_self_duplicate CHECK (id <> duplicate_of),
CONSTRAINT no_self_transfer CHECK (account_name <> transfer_account)
) STRICT;
CREATE TABLE conversions (
transaction_id TEXT NOT NULL REFERENCES transactions(id) ON UPDATE CASCADE ON DELETE CASCADE,
currency TEXT NOT NULL REFERENCES currencies(code),
amount INTEGER NOT NULL,
PRIMARY KEY (transaction_id, currency)
) STRICT;