fix: SQL trigger-aware migration splitter, SpeechModel re-export, i64 type alignment

crates/storage/src/migrations.rs:
  Replace naive sql.split(';') with split_statements() that tracks
  BEGIN...END depth, so migrations containing trigger definitions
  execute correctly instead of being split mid-block.

crates/transcription/src/lib.rs:
  Re-export transcribe_rs::SpeechModel so callers in src-tauri can
  reference it without adding transcribe-rs as a direct dependency.

src-tauri/src/commands/models.rs:
  Use Box<dyn SpeechModel + Send> as the load_model_from_disk return
  type, matching the trait object that transcription crates produce.

src-tauri/src/commands/transcripts.rs:
  Remove the stale .map(|v| v as i32) casts on sample_rate and
  audio_channels. InsertTranscriptParams now stores these as i64
  (ebf449b), matching the i64 fields on CreateTranscriptRequest;
  casting to i32 first would silently truncate large sample rates.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-18 09:03:01 +01:00
parent ebf449b47b
commit cca79dec4c
3 changed files with 38 additions and 6 deletions

View File

@@ -110,6 +110,40 @@ const MIGRATIONS: &[(i64, &str, &str)] = &[
"#),
];
/// Split SQL into individual statements, respecting BEGIN...END trigger blocks.
fn split_statements(sql: &str) -> Vec<String> {
let mut result = Vec::new();
let mut current = String::new();
let mut depth: i32 = 0;
let upper = sql.to_ascii_uppercase();
let ub = upper.as_bytes();
let ob = sql.as_bytes();
let n = ob.len();
let mut i = 0;
while i < n {
let prev_alpha = i > 0 && ob[i - 1].is_ascii_alphanumeric();
if !prev_alpha && ub[i..].starts_with(b"BEGIN") {
let next_alpha = i + 5 < n && ob[i + 5].is_ascii_alphanumeric();
if !next_alpha { depth += 1; }
}
if !prev_alpha && ub[i..].starts_with(b"END") {
let next_alpha = i + 3 < n && ob[i + 3].is_ascii_alphanumeric();
if !next_alpha && depth > 0 { depth -= 1; }
}
if ob[i] == b';' && depth == 0 {
let stmt = current.trim().to_string();
if !stmt.is_empty() { result.push(stmt); }
current = String::new();
} else {
current.push(ob[i] as char);
}
i += 1;
}
let stmt = current.trim().to_string();
if !stmt.is_empty() { result.push(stmt); }
result
}
/// Ensure the schema_version table exists and run any pending migrations.
pub async fn run_migrations(pool: &SqlitePool) -> Result<()> {
sqlx::query(
@@ -132,12 +166,9 @@ pub async fn run_migrations(pool: &SqlitePool) -> Result<()> {
if *version > current {
log::info!("Running migration {}: {}", version, description);
let statements: Vec<&str> = sql.split(';')
.map(|s| s.trim())
.filter(|s| !s.is_empty())
.collect();
let statements = split_statements(sql);
for statement in statements {
for statement in &statements {
sqlx::query(statement)
.execute(pool)
.await

View File

@@ -6,6 +6,7 @@ pub use concurrency::run_inference;
pub use local_engine::{
load_parakeet, load_whisper, LocalEngine, TimedTranscript,
};
pub use transcribe_rs::SpeechModel;
pub use model_manager::{
download, is_downloaded, list_downloaded, model_dir, models_dir,
};