talon-core 0.4.2

Core retrieval engine for Talon: hybrid search (BM25 + semantic + reranker), indexing, and graph-aware ranking over markdown corpora.
Documentation
use rusqlite::{Connection, params};

use crate::TalonError;
use crate::indexing::migrations::bump_db_version;

use super::{NoteUpsertResult, UpsertNoteParams};

fn random_docid() -> String {
    use std::sync::atomic::{AtomicU64, Ordering};
    use std::time::{SystemTime, UNIX_EPOCH};
    static SEQ: AtomicU64 = AtomicU64::new(0);
    let seq = SEQ.fetch_add(1, Ordering::Relaxed);
    let nanos = SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .map_or(0, |d| d.as_nanos());
    format!("doc-{nanos:x}-{seq:x}")
}

/// Inserts a new note row or updates the existing row for `vault_path`.
///
/// `JSON` serialization and docid generation happen inside this function.
///
/// # Errors
///
/// Returns [`TalonError::Sqlite`] if any underlying statement fails or if
/// the post-insert `id` lookup fails.
pub fn upsert_note(
    conn: &Connection,
    params: &UpsertNoteParams<'_>,
) -> Result<NoteUpsertResult, TalonError> {
    let aliases_json =
        serde_json::to_string(params.aliases).map_err(|err| TalonError::Internal {
            message: format!(
                "serializing aliases for {} failed: {err}",
                params.vault_path
            ),
        })?;
    let tags_json = serde_json::to_string(params.tags).map_err(|err| TalonError::Internal {
        message: format!("serializing tags for {} failed: {err}", params.vault_path),
    })?;
    let frontmatter_json =
        serde_json::to_string(params.frontmatter).map_err(|err| TalonError::Internal {
            message: format!(
                "serializing frontmatter for {} failed: {err}",
                params.vault_path
            ),
        })?;
    let existing: Option<i64> = conn
        .query_row(
            "SELECT id FROM notes WHERE vault_path = ?",
            [params.vault_path],
            |row| row.get(0),
        )
        .ok();

    if let Some(note_id) = existing {
        conn.execute(
            "UPDATE notes SET
               title = ?, tags = ?, aliases = ?, content = ?, frontmatter = ?,
               mtime_ms = ?, size_bytes = ?, hash = ?, active = 1, scope = ?
             WHERE vault_path = ?",
            params![
                params.title,
                tags_json,
                aliases_json,
                params.content,
                frontmatter_json,
                params.mtime_ms,
                params.size_bytes,
                params.source_hash.as_str(),
                params.scope,
                params.vault_path,
            ],
        )
        .map_err(|source| TalonError::Sqlite {
            context: "update note",
            source,
        })?;
        bump_db_version(conn)?;
        return Ok(NoteUpsertResult {
            note_id,
            is_new: false,
        });
    }

    let docid = random_docid();
    conn.execute(
        "INSERT INTO notes
           (vault_path, title, tags, aliases, content, frontmatter,
            mtime_ms, size_bytes, hash, docid, active, scope)
         VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1, ?)",
        params![
            params.vault_path,
            params.title,
            tags_json,
            aliases_json,
            params.content,
            frontmatter_json,
            params.mtime_ms,
            params.size_bytes,
            params.source_hash.as_str(),
            docid,
            params.scope,
        ],
    )
    .map_err(|source| TalonError::Sqlite {
        context: "insert note",
        source,
    })?;
    let note_id: i64 = conn
        .query_row(
            "SELECT id FROM notes WHERE vault_path = ?",
            [params.vault_path],
            |row| row.get(0),
        )
        .map_err(|source| TalonError::Sqlite {
            context: "fetch newly inserted note id",
            source,
        })?;
    bump_db_version(conn)?;
    Ok(NoteUpsertResult {
        note_id,
        is_new: true,
    })
}