diff --git a/crates/storage/src/database.rs b/crates/storage/src/database.rs index f8ee851..258eb47 100644 --- a/crates/storage/src/database.rs +++ b/crates/storage/src/database.rs @@ -256,62 +256,6 @@ pub async fn search_transcripts( Ok(rows.iter().map(transcript_row_from).collect()) } -// --- Dictionary (custom vocabulary injected into LLM cleanup prompts) --- - -#[derive(Debug, Clone)] -pub struct DictionaryEntry { - pub id: i64, - pub term: String, - pub note: Option, -} - -pub async fn list_dictionary(pool: &SqlitePool) -> Result> { - let rows = sqlx::query("SELECT id, term, note FROM dictionary ORDER BY term ASC") - .fetch_all(pool) - .await - .map_err(|e| KonError::StorageError(format!("List dictionary failed: {e}")))?; - Ok(rows - .into_iter() - .map(|r| DictionaryEntry { - id: r.get("id"), - term: r.get("term"), - note: r.get("note"), - }) - .collect()) -} - -pub async fn add_dictionary_entry( - pool: &SqlitePool, - term: &str, - note: Option<&str>, -) -> Result { - // ON CONFLICT … DO UPDATE so we always RETURN the actual row id. - // The previous `INSERT OR IGNORE` returned 0 (or a stale auto-increment - // counter) for duplicate terms, which lied to the frontend about which - // row it had just upserted. Bumping `note` to `excluded.note` also lets - // the user edit a term's note by re-adding it. - let row = sqlx::query( - "INSERT INTO dictionary (term, note) VALUES (?, ?) \ - ON CONFLICT(term) DO UPDATE SET note = excluded.note \ - RETURNING id", - ) - .bind(term) - .bind(note) - .fetch_one(pool) - .await - .map_err(|e| KonError::StorageError(format!("Insert dictionary entry failed: {e}")))?; - Ok(row.get("id")) -} - -pub async fn delete_dictionary_entry(pool: &SqlitePool, id: i64) -> Result<()> { - sqlx::query("DELETE FROM dictionary WHERE id = ?") - .bind(id) - .execute(pool) - .await - .map_err(|e| KonError::StorageError(format!("Delete dictionary entry failed: {e}")))?; - Ok(()) -} - // --- Task CRUD --- /// Insert a task. `list_id` and `effort` are nullable (schema predates their @@ -780,9 +724,9 @@ pub async fn list_profile_terms( Ok(rows.iter().map(profile_term_row_from).collect()) } -/// UPSERT on UNIQUE(profile_id, term). Mirrors `add_dictionary_entry`'s -/// ON CONFLICT … DO UPDATE pattern so that re-adding an existing term -/// simply refreshes the note and returns the existing row (not a fresh id). +/// UPSERT on UNIQUE(profile_id, term). Uses ON CONFLICT … DO UPDATE so that +/// re-adding an existing term simply refreshes the note and returns the +/// existing row (not a fresh id). pub async fn add_profile_term( pool: &SqlitePool, profile_id: &str, diff --git a/crates/storage/src/lib.rs b/crates/storage/src/lib.rs index 480a8db..90b11ca 100644 --- a/crates/storage/src/lib.rs +++ b/crates/storage/src/lib.rs @@ -7,14 +7,14 @@ pub mod migrations; pub const DEFAULT_PROFILE_ID: &str = "00000000-0000-0000-0000-000000000001"; pub use database::{ - add_dictionary_entry, add_profile_term, complete_subtask_and_check_parent, complete_task, - count_transcripts, create_profile, delete_dictionary_entry, delete_profile, + add_profile_term, complete_subtask_and_check_parent, complete_task, + count_transcripts, create_profile, delete_profile, delete_profile_term, delete_task, delete_transcript, get_profile, get_setting, get_task_by_id, - get_transcript, init, insert_subtask, insert_task, insert_transcript, list_dictionary, + get_transcript, init, insert_subtask, insert_task, insert_transcript, list_profile_terms, list_profiles, list_recent_errors, list_subtasks, list_tasks, list_transcripts, list_transcripts_paged, log_error, search_transcripts, set_setting, uncomplete_task, update_profile, update_task, update_transcript, update_transcript_meta, - DictionaryEntry, ErrorLogRow, InsertTranscriptParams, ProfileRow, ProfileTermRow, TaskRow, + ErrorLogRow, InsertTranscriptParams, ProfileRow, ProfileTermRow, TaskRow, TranscriptRow, }; pub use file_storage::{app_data_dir, crashes_dir, database_path, logs_dir, recordings_dir}; diff --git a/crates/storage/src/migrations.rs b/crates/storage/src/migrations.rs index 1f3f992..12c9d48 100644 --- a/crates/storage/src/migrations.rs +++ b/crates/storage/src/migrations.rs @@ -179,6 +179,14 @@ const MIGRATIONS: &[(i64, &str, &str)] = &[ END; "#, ), + ( + 7, + "drop_dictionary", + r#" + DROP INDEX IF EXISTS idx_dictionary_term; + DROP TABLE IF EXISTS dictionary; + "#, + ), ]; /// Split SQL into individual statements, respecting BEGIN...END trigger blocks. @@ -279,7 +287,7 @@ mod tests { .fetch_one(&pool) .await .unwrap(); - assert_eq!(count, 6); + assert_eq!(count, 7); sqlx::query("INSERT INTO settings (key, value) VALUES ('test', 'value')") .execute(&pool) @@ -301,7 +309,7 @@ mod tests { .fetch_one(&pool) .await .unwrap(); - assert_eq!(count, 6); + assert_eq!(count, 7); } #[tokio::test] @@ -522,4 +530,23 @@ mod tests { assert!(result.is_err(), "trigger must block Default rename"); } + + #[tokio::test] + async fn migration_v7_drops_dictionary_table() { + let pool = SqlitePoolOptions::new() + .max_connections(1) + .connect("sqlite::memory:") + .await + .expect("pool"); + run_migrations(&pool).await.expect("migrate"); + + // dictionary table should not exist after v7. + let result: std::result::Result = + sqlx::query_scalar("SELECT COUNT(*) FROM dictionary") + .fetch_one(&pool) + .await; + assert!(result.is_err(), "dictionary table must be gone after v7"); + let err = result.unwrap_err().to_string().to_lowercase(); + assert!(err.contains("no such table"), "got: {err}"); + } }