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 super::*;
use crate::store::open_database;
use rusqlite::{Connection, params};
use std::sync::atomic::{AtomicU64, Ordering};

fn unique_path() -> std::path::PathBuf {
    static C: AtomicU64 = AtomicU64::new(0);
    let n = C.fetch_add(1, Ordering::Relaxed);
    std::env::temp_dir().join(format!(
        "talon-search-query-syntax-test-{}-{n}.sqlite",
        std::process::id()
    ))
}

fn cleanup(path: &std::path::Path) {
    let _ = fs_err::remove_file(path);
    let _ = fs_err::remove_file(path.with_extension("sqlite-wal"));
    let _ = fs_err::remove_file(path.with_extension("sqlite-shm"));
}

fn insert_note(conn: &Connection, vault_path: &str, content: &str) -> i64 {
    assert!(
        conn.execute(
            "INSERT INTO notes
             (vault_path, title, tags, aliases, content, mtime_ms, size_bytes, hash, docid, active)
             VALUES (?, 'Title', '[]', '[]', ?, 0, 0, 'h', 'd', 1)",
            params![vault_path, content],
        )
        .is_ok(),
        "failed to insert test note"
    );
    conn.last_insert_rowid()
}

fn insert_tag(conn: &Connection, note_id: i64, tag: &str) {
    assert!(
        conn.execute(
            "INSERT INTO note_tags (note_id, tag, tag_norm) VALUES (?, ?, ?)",
            params![note_id, tag, crate::text::normalize_keyword(tag)],
        )
        .is_ok(),
        "failed to insert test tag"
    );
}

fn insert_heading_chunk(conn: &Connection, note_id: i64, heading: &str) {
    assert!(
        conn.execute(
            "INSERT INTO chunks
             (note_id, chunk_index, text, embedding_text, heading_path, char_start, char_end,
              chunk_hash, token_estimate, embedding_status)
             VALUES (?, 0, 'alpha chunk', 'alpha chunk', ?, 0, 11, 'h', 2, 'pending')",
            params![note_id, heading],
        )
        .is_ok(),
        "failed to insert test chunk"
    );
}

#[test]
fn search_query_tag_syntax_filters_candidates() {
    let path = unique_path();
    let conn = open_database(&path).unwrap_or_else(|err| panic!("failed to open temp db: {err}"));
    let included = insert_note(&conn, "notes/included.md", "alpha shared");
    insert_note(&conn, "notes/excluded.md", "alpha shared");
    insert_tag(&conn, included, "fermentation");

    let input = SearchInput {
        query: Some("alpha #fermentation".to_string()),
        mode: SearchMode::Fulltext,
        ..SearchInput::default()
    };
    let response = run_search(&conn, &input, None, None, None, None);

    assert_eq!(response.results.len(), 1);
    assert_eq!(response.results[0].vault_path.as_str(), "notes/included.md");
    drop(conn);
    cleanup(&path);
}

#[test]
fn search_query_heading_syntax_filters_candidates() {
    let path = unique_path();
    let conn = open_database(&path).unwrap_or_else(|err| panic!("failed to open temp db: {err}"));
    let included = insert_note(&conn, "notes/included.md", "alpha shared");
    let excluded = insert_note(&conn, "notes/excluded.md", "alpha shared");
    insert_heading_chunk(&conn, included, "Targets");
    insert_heading_chunk(&conn, excluded, "Other");

    let input = SearchInput {
        query: Some("alpha heading:Targets".to_string()),
        mode: SearchMode::Fulltext,
        ..SearchInput::default()
    };
    let response = run_search(&conn, &input, None, None, None, None);

    assert_eq!(response.results.len(), 1);
    assert_eq!(response.results[0].vault_path.as_str(), "notes/included.md");
    drop(conn);
    cleanup(&path);
}