janhq/jan · error · VectorDBError
Database error: {0}
Error message
Database error: {0} What it means
`VectorDBError::DatabaseError(String)` wraps a `rusqlite::Error` via the `From` impl — the wrapped string is `err.to_string()`. It fires for any SQLite-level failure: SQL syntax error, constraint violation, busy/locked database, corrupt database file, or a type coercion failure. The string carries the SQLite error code and message.
Source
Thrown at src-tauri/plugins/tauri-plugin-vector-db/src/error.rs:5
use serde::{Deserialize, Serialize};
#[derive(Debug, thiserror::Error, Serialize, Deserialize)]
pub enum VectorDBError {
#[error("Database error: {0}")]
DatabaseError(String),
#[error("Invalid input: {0}")]
InvalidInput(String),
}
impl From<rusqlite::Error> for VectorDBError {
fn from(err: rusqlite::Error) -> Self {
VectorDBError::DatabaseError(err.to_string())
}
}
impl From<serde_json::Error> for VectorDBError {
fn from(err: serde_json::Error) -> Self {
VectorDBError::DatabaseError(err.to_string())
}
}
View on GitHub (pinned to fad3f12a14)
Solutions
- Read the wrapped message — SQLite names the specific error (e.g. `database is locked`, `UNIQUE constraint failed`, `no such table`).
- For SQLITE_BUSY, enable WAL mode and use a connection pool with a busy timeout (`PRAGMA busy_timeout`).
- For constraint violations, deduplicate before insert (e.g. INSERT OR IGNORE / OR REPLACE).
- For schema drift, run migrations on startup and confirm the expected schema version.
Example fix
// before
conn.execute("INSERT INTO embeddings (...) VALUES (...)", params![...])?;
// after - WAL + busy timeout + idempotent insert
conn.execute_batch("PRAGMA journal_mode=WAL; PRAGMA busy_timeout=5000;")?;
conn.execute("INSERT OR IGNORE INTO embeddings (...) VALUES (...)", params![...])?; Defensive patterns
Strategy: try-catch
Validate before calling
fn db_wal_busy(path: &str) -> Result<Connection, rusqlite::Error> {
let conn = Connection::open(path)?;
conn.execute_batch("PRAGMA journal_mode=WAL; PRAGMA busy_timeout=5000;")?;
Ok(conn)
} Type guard
null
Try / catch
match conn.execute(sql, params) {
Ok(n) => Ok(n),
Err(rusqlite::Error::SqliteFailure(err, msg)) if err.code == rusqlite::ErrorCode::DatabaseBusy => {
tracing::warn!("db busy, retrying once");
// single bounded retry, not a poll loop
conn.execute(sql, params).map_err(VectorDBError::from)
}
Err(e) => Err(e.into()),
} Prevention
- Enable WAL mode and set a busy_timeout on every connection.
- Use a single-writer connection pool to avoid SQLITE_BUSY under concurrency.
- Run migrations on startup and assert the expected schema version.
- Use INSERT OR IGNORE / OR REPLACE for idempotent embedding inserts.
When it happens
Trigger: Executing malformed SQL; inserting a row that violates a UNIQUE constraint; concurrent writes causing SQLITE_BUSY; a schema migration that fails halfway; a database file that is corrupt or locked by another process; binding a parameter of the wrong type.
Common situations: Two threads writing without a connection pool/WAL causing SQLITE_BUSY; a UNIQUE collision when re-inserting embeddings; a schema drift after a plugin upgrade; antivirus or backup tools locking the `.db` file on Windows; running off a network filesystem that does not support SQLite locking.
Related errors
- ${String(e)}
- ${e}
- LlamacppError {{ code: {code:?}, message: "{message}" }}
- IO error: {0}
- Tauri error: {0}
AI-assisted analysis of janhq/jan@fad3f12a14 (2026-08-12).
Data as JSON: /api/errors/e59d2ed45083db3c.
Report an issue: GitHub.