feat: add SQLite cache store for incremental session persistence
Implement the caching layer that enables fast subsequent runs by
persisting parsed session data in SQLite:
- store/schema.go: DDL for three tables — sessions (primary metrics
with file_mtime_ns/file_size for change detection), session_models
(per-model breakdown, FK cascade on delete), and file_tracker
(path -> mtime+size mapping for cache invalidation). Indexes on
start_time and project for efficient time-range and filter queries.
- store/cache.go: Cache struct wrapping database/sql with WAL mode
and synchronous=normal for concurrent read safety and write
performance. Key operations:
* Open: creates the cache directory, opens/creates the database,
and ensures the schema is applied (idempotent via IF NOT EXISTS).
* GetTrackedFiles: returns the mtime/size map used by the pipeline
to determine which files need reparsing.
* SaveSession: transactional upsert of session stats + model
breakdown + file tracker entry. Uses INSERT OR REPLACE to handle
both new files and files that changed since last parse.
* LoadAllSessions: batch-loads all cached sessions with a two-pass
strategy — first loads session rows, then batch-loads model data
with an index map for O(1) join, avoiding N+1 queries.
Uses modernc.org/sqlite (pure-Go, no CGO) for zero-dependency
cross-platform builds.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
49
internal/store/schema.go
Normal file
49
internal/store/schema.go
Normal file
@@ -0,0 +1,49 @@
|
||||
package store
|
||||
|
||||
const schemaSQL = `
|
||||
CREATE TABLE IF NOT EXISTS sessions (
|
||||
session_id TEXT PRIMARY KEY,
|
||||
project TEXT NOT NULL,
|
||||
project_path TEXT,
|
||||
file_path TEXT NOT NULL,
|
||||
is_subagent INTEGER NOT NULL DEFAULT 0,
|
||||
parent_session TEXT,
|
||||
start_time TEXT,
|
||||
end_time TEXT,
|
||||
duration_secs INTEGER,
|
||||
user_messages INTEGER,
|
||||
api_calls INTEGER,
|
||||
input_tokens INTEGER,
|
||||
output_tokens INTEGER,
|
||||
cache_creation_5m INTEGER,
|
||||
cache_creation_1h INTEGER,
|
||||
cache_read_tokens INTEGER,
|
||||
estimated_cost REAL,
|
||||
cache_hit_rate REAL,
|
||||
file_mtime_ns INTEGER NOT NULL,
|
||||
file_size INTEGER NOT NULL,
|
||||
parsed_at TEXT NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS session_models (
|
||||
session_id TEXT NOT NULL REFERENCES sessions(session_id) ON DELETE CASCADE,
|
||||
model TEXT NOT NULL,
|
||||
api_calls INTEGER,
|
||||
input_tokens INTEGER,
|
||||
output_tokens INTEGER,
|
||||
cache_creation_5m INTEGER,
|
||||
cache_creation_1h INTEGER,
|
||||
cache_read_tokens INTEGER,
|
||||
estimated_cost REAL,
|
||||
PRIMARY KEY (session_id, model)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS file_tracker (
|
||||
file_path TEXT PRIMARY KEY,
|
||||
mtime_ns INTEGER NOT NULL,
|
||||
size_bytes INTEGER NOT NULL
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_sessions_start ON sessions(start_time);
|
||||
CREATE INDEX IF NOT EXISTS idx_sessions_project ON sessions(project);
|
||||
`
|
||||
Reference in New Issue
Block a user