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

  1. Read the wrapped message — SQLite names the specific error (e.g. `database is locked`, `UNIQUE constraint failed`, `no such table`).
  2. For SQLITE_BUSY, enable WAL mode and use a connection pool with a busy timeout (`PRAGMA busy_timeout`).
  3. For constraint violations, deduplicate before insert (e.g. INSERT OR IGNORE / OR REPLACE).
  4. 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

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


AI-assisted analysis of janhq/jan@fad3f12a14 (2026-08-12). Data as JSON: /api/errors/e59d2ed45083db3c. Report an issue: GitHub.