From 9ee8cb18b1017dce11e7c8c9b7fafad301cb766e Mon Sep 17 00:00:00 2001 From: Anders Betts Date: Sun, 20 Sep 2026 10:36:09 +0200 Subject: db: make attachments append-only (schema v7) --- src/db.c | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) (limited to 'src/db.c') diff --git a/src/db.c b/src/db.c index 627efbc..c809bea 100644 --- a/src/db.c +++ b/src/db.c @@ -206,6 +206,12 @@ static const char SCHEMA_V1[] = " UNIQUE (org_id, id)," " UNIQUE (org_id, sha256, filename)" ") STRICT;\n" + "CREATE TRIGGER attachments_no_update BEFORE UPDATE ON attachments BEGIN" + " SELECT RAISE(ABORT, 'attachments are append-only');" + "END;\n" + "CREATE TRIGGER attachments_no_delete BEFORE DELETE ON attachments BEGIN" + " SELECT RAISE(ABORT, 'attachments are append-only');" + "END;\n" "CREATE TABLE voucher_attachments (" " org_id INTEGER NOT NULL," @@ -433,6 +439,25 @@ static int db_upgrade_v6(sqlite3 *db, char **err) " DEFAULT ''", err); } +/* v7: attachments are immutable too — the content-addressed underlag may + never be updated or deleted, only linked/unlinked. */ +static int db_upgrade_v7(sqlite3 *db, char **err) +{ + if (db_exec(db, + "CREATE TRIGGER IF NOT EXISTS attachments_no_update" + " BEFORE UPDATE ON attachments BEGIN" + " SELECT RAISE(ABORT, 'attachments are append-only');" + "END", + err) != 0) + return -1; + return db_exec(db, + "CREATE TRIGGER IF NOT EXISTS attachments_no_delete" + " BEFORE DELETE ON attachments BEGIN" + " SELECT RAISE(ABORT, 'attachments are append-only');" + "END", + err); +} + static int db_upgrade(sqlite3 *db, int from, char **err) { if (db_exec(db, "BEGIN IMMEDIATE", err) != 0) @@ -457,6 +482,10 @@ static int db_upgrade(sqlite3 *db, int from, char **err) db_exec(db, "ROLLBACK", NULL); return -1; } + if (from < 7 && db_upgrade_v7(db, err) != 0) { + db_exec(db, "ROLLBACK", NULL); + return -1; + } char *sql = sqlite3_mprintf( "UPDATE meta SET value='%d' WHERE key='schema_version'", BOKF_SCHEMA_VERSION); -- cgit v1.3