chore(storage): drop dictionary table (v7) + retire unused storage fns — profile_terms is sole surface
This commit is contained in:
@@ -256,62 +256,6 @@ pub async fn search_transcripts(
|
|||||||
Ok(rows.iter().map(transcript_row_from).collect())
|
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<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn list_dictionary(pool: &SqlitePool) -> Result<Vec<DictionaryEntry>> {
|
|
||||||
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<i64> {
|
|
||||||
// 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 ---
|
// --- Task CRUD ---
|
||||||
|
|
||||||
/// Insert a task. `list_id` and `effort` are nullable (schema predates their
|
/// 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())
|
Ok(rows.iter().map(profile_term_row_from).collect())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// UPSERT on UNIQUE(profile_id, term). Mirrors `add_dictionary_entry`'s
|
/// UPSERT on UNIQUE(profile_id, term). Uses ON CONFLICT … DO UPDATE so that
|
||||||
/// ON CONFLICT … DO UPDATE pattern so that re-adding an existing term
|
/// re-adding an existing term simply refreshes the note and returns the
|
||||||
/// simply refreshes the note and returns the existing row (not a fresh id).
|
/// existing row (not a fresh id).
|
||||||
pub async fn add_profile_term(
|
pub async fn add_profile_term(
|
||||||
pool: &SqlitePool,
|
pool: &SqlitePool,
|
||||||
profile_id: &str,
|
profile_id: &str,
|
||||||
|
|||||||
@@ -7,14 +7,14 @@ pub mod migrations;
|
|||||||
pub const DEFAULT_PROFILE_ID: &str = "00000000-0000-0000-0000-000000000001";
|
pub const DEFAULT_PROFILE_ID: &str = "00000000-0000-0000-0000-000000000001";
|
||||||
|
|
||||||
pub use database::{
|
pub use database::{
|
||||||
add_dictionary_entry, add_profile_term, complete_subtask_and_check_parent, complete_task,
|
add_profile_term, complete_subtask_and_check_parent, complete_task,
|
||||||
count_transcripts, create_profile, delete_dictionary_entry, delete_profile,
|
count_transcripts, create_profile, delete_profile,
|
||||||
delete_profile_term, delete_task, delete_transcript, get_profile, get_setting, get_task_by_id,
|
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_profile_terms, list_profiles, list_recent_errors, list_subtasks, list_tasks,
|
||||||
list_transcripts, list_transcripts_paged, log_error, search_transcripts, set_setting,
|
list_transcripts, list_transcripts_paged, log_error, search_transcripts, set_setting,
|
||||||
uncomplete_task, update_profile, update_task, update_transcript, update_transcript_meta,
|
uncomplete_task, update_profile, update_task, update_transcript, update_transcript_meta,
|
||||||
DictionaryEntry, ErrorLogRow, InsertTranscriptParams, ProfileRow, ProfileTermRow, TaskRow,
|
ErrorLogRow, InsertTranscriptParams, ProfileRow, ProfileTermRow, TaskRow,
|
||||||
TranscriptRow,
|
TranscriptRow,
|
||||||
};
|
};
|
||||||
pub use file_storage::{app_data_dir, crashes_dir, database_path, logs_dir, recordings_dir};
|
pub use file_storage::{app_data_dir, crashes_dir, database_path, logs_dir, recordings_dir};
|
||||||
|
|||||||
@@ -179,6 +179,14 @@ const MIGRATIONS: &[(i64, &str, &str)] = &[
|
|||||||
END;
|
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.
|
/// Split SQL into individual statements, respecting BEGIN...END trigger blocks.
|
||||||
@@ -279,7 +287,7 @@ mod tests {
|
|||||||
.fetch_one(&pool)
|
.fetch_one(&pool)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(count, 6);
|
assert_eq!(count, 7);
|
||||||
|
|
||||||
sqlx::query("INSERT INTO settings (key, value) VALUES ('test', 'value')")
|
sqlx::query("INSERT INTO settings (key, value) VALUES ('test', 'value')")
|
||||||
.execute(&pool)
|
.execute(&pool)
|
||||||
@@ -301,7 +309,7 @@ mod tests {
|
|||||||
.fetch_one(&pool)
|
.fetch_one(&pool)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(count, 6);
|
assert_eq!(count, 7);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
@@ -522,4 +530,23 @@ mod tests {
|
|||||||
|
|
||||||
assert!(result.is_err(), "trigger must block Default rename");
|
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<i64, sqlx::Error> =
|
||||||
|
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}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user