daemon: store/ — SQLite WAL schema + forward-only migrator (build step 3)
The daemon's persistent state. SQLite in WAL mode, foreign keys on,
5 s busy timeout so a writer waits rather than SQLITE_BUSY under the
RPC loop.
- store/sqlite — RAII Db/Stmt over the C API; errors returned as
DbResult<T> (std::expected), never thrown — the RPC loop must not
unwind. transaction() helper: BEGIN / fn / COMMIT, ROLLBACK on error.
- store/migrations/0001_initial.sql — the eight tables from the brief:
settings, categories, queues, tasks, segments, rules, history,
pairings. Notable choices:
* tasks columns project onto proto TaskSummary with no computation;
requested vs effective segments/buffer split per ADR 0010/0012;
pause_reason column per ADR 0013.
* segments end_byte is NOT constrained >= 0 so a whole-file
zero-length download is one row with end_byte = -1 (ADR 0010 B3a).
* pairings stores only token_sha256 — the plaintext token is
returned once from session.pair and never persisted (CLAUDE.md §4).
* indices on tasks(state), (category_id), (queue_id, queue_position),
(created_at), (completed_at) for the "1000 tasks, download.list
under 50 ms" DoD.
* six built-in categories + a Main queue seeded.
- store/migrations — runs every embedded migration past PRAGMA
user_version, each in its own transaction, forward-only. SQL files
are embedded at build time by cmake/embed_migrations.cmake.
Test veloxd.store_migrations (ASan+UBSan and TSan clean): fresh DB ->
head, all tables present, seed rows, FK cascade (segment orphan
rejected, task delete cascades), the end_byte=-1 zero-length case,
idempotent re-run, and forward-only from every released user_version.
Also: daemon/docs/proto-requests-m1.md — P1 marked landed on lane/proto
as 1.4.0 (HandlerError/HandlerResult), to be adopted in rpc/ once that
merges to main; P2 resolved.
Not linked into the running daemon yet — the store is wired to the
dispatcher when download.add/list/get get real bodies, next.
Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Upd9WhG9oppieig5nRDLig
This commit is contained in:
@@ -0,0 +1,44 @@
|
||||
#include "store/migrations.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
// Generated at build time from store/migrations/*.sql by embed_migrations.cmake.
|
||||
#include "migrations_embedded.hpp"
|
||||
|
||||
namespace velox::daemon::store {
|
||||
|
||||
std::span<const Migration> embedded_migrations() {
|
||||
return {kEmbeddedMigrations.data(), kEmbeddedMigrations.size()};
|
||||
}
|
||||
|
||||
DbResult<MigrationOutcome> migrate_to_head(Db& db) {
|
||||
const auto all = embedded_migrations();
|
||||
|
||||
MigrationOutcome out;
|
||||
out.from_version = db.user_version();
|
||||
out.to_version = out.from_version;
|
||||
if (out.from_version < 0) {
|
||||
return std::unexpected(DbError{0, "could not read PRAGMA user_version"});
|
||||
}
|
||||
|
||||
for (const auto& m : all) {
|
||||
if (m.version <= out.from_version) continue;
|
||||
|
||||
// Each migration is one transaction: a failure half-way leaves user_version and
|
||||
// the schema exactly where they were.
|
||||
auto r = db.transaction([&]() -> DbResult<void> {
|
||||
if (auto e = db.exec(m.sql); !e) return e;
|
||||
return db.set_user_version(m.version);
|
||||
});
|
||||
if (!r) {
|
||||
return std::unexpected(DbError{r.error().code, "migration " + std::string(m.name) +
|
||||
" failed: " + r.error().message});
|
||||
}
|
||||
out.to_version = m.version;
|
||||
++out.applied;
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
} // namespace velox::daemon::store
|
||||
Reference in New Issue
Block a user