zeppelin-embed 0.6.0

In-process vector, lexical, and hybrid search for macOS and Apple silicon
Documentation
#![allow(clippy::expect_used, clippy::indexing_slicing)]

use tempfile::tempdir;

use crate::ingest::{DeleteBatch, DocId, DocumentVersion, IngestBatch, IngestDocument, Revision};
use crate::meta::{ColumnDefinition, ColumnId, ColumnType, PredicateValue, Schema};

use super::{DocumentFields, OpenOptions, Store};

#[test]
fn get_documents_returns_active_document() {
    let directory = tempdir().expect("directory");
    let store = Store::open(directory.path(), OpenOptions::default()).expect("open");
    let id = DocId::new(7);
    store
        .ingest(IngestBatch::new(vec![
            IngestDocument::new(DocumentVersion::new(id, Revision::new(3)), vec![1.0, 2.0])
                .with_timestamp(41)
                .with_text("active text")
                .with_metadata(vec![4, 5, 6]),
        ]))
        .expect("ingest");

    let documents = store
        .get_documents(&[id], DocumentFields::ALL)
        .expect("get");

    assert_eq!(documents.len(), 1);
    let document = documents[0].as_ref().expect("document");
    assert_eq!(document.doc_id, id);
    assert_eq!(document.revision, Revision::new(3));
    assert_eq!(document.timestamp, 41);
    assert_eq!(document.vector.as_deref(), Some([1.0, 2.0].as_slice()));
    assert_eq!(document.text.as_deref(), Some("active text"));
    assert_eq!(document.metadata.as_deref(), Some([4, 5, 6].as_slice()));
    assert_eq!(document.attributes.as_deref(), Some([].as_slice()));
}

#[test]
fn get_documents_returns_sealed_document() {
    let directory = tempdir().expect("directory");
    let attribute = ColumnId::new(9);
    let schema = Schema::new(vec![ColumnDefinition::new(
        attribute,
        "category",
        ColumnType::RawString,
        false,
    )])
    .expect("schema");
    let store =
        Store::open(directory.path(), OpenOptions::default().with_schema(schema)).expect("open");
    let id = DocId::new(11);
    store
        .ingest(IngestBatch::new(vec![
            IngestDocument::new(DocumentVersion::new(id, Revision::new(4)), vec![3.0, 5.0])
                .with_timestamp(73)
                .with_text("sealed text")
                .with_metadata(vec![8, 9])
                .with_columns(vec![(attribute, PredicateValue::String("blue".to_owned()))]),
        ]))
        .expect("ingest");
    store.seal().expect("seal");

    let documents = store
        .get_documents(&[id], DocumentFields::ALL)
        .expect("get");

    let document = documents[0].as_ref().expect("document");
    assert_eq!(document.doc_id, id);
    assert_eq!(document.revision, Revision::new(4));
    assert_eq!(document.timestamp, 73);
    assert_eq!(document.vector.as_deref(), Some([3.0, 5.0].as_slice()));
    assert_eq!(document.text.as_deref(), Some("sealed text"));
    assert_eq!(document.metadata.as_deref(), Some([8, 9].as_slice()));
    assert_eq!(
        document.attributes.as_deref(),
        Some([(attribute, PredicateValue::String("blue".to_owned()))].as_slice())
    );
}

#[test]
fn get_documents_reads_mixed_active_and_sealed_documents() {
    let directory = tempdir().expect("directory");
    let store = Store::open(directory.path(), OpenOptions::default()).expect("open");
    let sealed_id = DocId::new(1);
    let active_id = DocId::new(2);
    store
        .ingest(IngestBatch::new(vec![IngestDocument::new(
            DocumentVersion::new(sealed_id, Revision::new(1)),
            vec![1.0, 0.0],
        )]))
        .expect("sealed ingest");
    store.seal().expect("seal");
    store
        .ingest(IngestBatch::new(vec![IngestDocument::new(
            DocumentVersion::new(active_id, Revision::new(1)),
            vec![0.0, 1.0],
        )]))
        .expect("active ingest");

    let documents = store
        .get_documents(&[active_id, sealed_id], DocumentFields::VECTOR)
        .expect("get");

    assert_eq!(
        documents[0].as_ref().map(|document| document.doc_id),
        Some(active_id)
    );
    assert_eq!(
        documents[1].as_ref().map(|document| document.doc_id),
        Some(sealed_id)
    );
}

#[test]
fn get_documents_returns_none_for_active_and_sealed_tombstones() {
    let directory = tempdir().expect("directory");
    let store = Store::open(directory.path(), OpenOptions::default()).expect("open");
    let sealed_id = DocId::new(1);
    let active_id = DocId::new(2);
    store
        .ingest(IngestBatch::new(vec![IngestDocument::new(
            DocumentVersion::new(sealed_id, Revision::new(1)),
            vec![1.0],
        )]))
        .expect("sealed ingest");
    store.seal().expect("seal");
    store
        .ingest(IngestBatch::new(vec![IngestDocument::new(
            DocumentVersion::new(active_id, Revision::new(1)),
            vec![2.0],
        )]))
        .expect("active ingest");
    store
        .delete(DeleteBatch::new(vec![sealed_id, active_id]))
        .expect("delete");

    let documents = store
        .get_documents(&[sealed_id, active_id], DocumentFields::ALL)
        .expect("get");

    assert_eq!(documents, vec![None, None]);
}

#[test]
fn get_documents_returns_none_for_unknown_id() {
    let directory = tempdir().expect("directory");
    let store = Store::open(directory.path(), OpenOptions::default()).expect("open");

    assert_eq!(
        store
            .get_documents(&[DocId::new(404)], DocumentFields::ALL)
            .expect("get"),
        vec![None]
    );
}

#[test]
fn get_documents_returns_only_live_superseding_revision() {
    let directory = tempdir().expect("directory");
    let store = Store::open(directory.path(), OpenOptions::default()).expect("open");
    let id = DocId::new(5);
    store
        .ingest(IngestBatch::new(vec![IngestDocument::new(
            DocumentVersion::new(id, Revision::new(1)),
            vec![1.0],
        )]))
        .expect("first ingest");
    store.seal().expect("seal");
    store
        .ingest(IngestBatch::new(vec![IngestDocument::new(
            DocumentVersion::new(id, Revision::new(2)),
            vec![2.0],
        )]))
        .expect("second ingest");

    let documents = store
        .get_documents(&[id], DocumentFields::VECTOR)
        .expect("get");
    let document = documents[0].as_ref().expect("document");

    assert_eq!(document.revision, Revision::new(2));
    assert_eq!(document.vector.as_deref(), Some([2.0].as_slice()));
}

#[test]
fn get_documents_preserves_request_order_with_duplicates_and_misses() {
    let directory = tempdir().expect("directory");
    let store = Store::open(directory.path(), OpenOptions::default()).expect("open");
    let first = DocId::new(10);
    let second = DocId::new(20);
    let missing = DocId::new(30);
    store
        .ingest(IngestBatch::new(vec![
            IngestDocument::new(DocumentVersion::new(first, Revision::new(1)), vec![1.0]),
            IngestDocument::new(DocumentVersion::new(second, Revision::new(1)), vec![2.0]),
        ]))
        .expect("ingest");

    let documents = store
        .get_documents(
            &[second, missing, first, second, missing],
            DocumentFields::NONE,
        )
        .expect("get");

    assert_eq!(
        documents
            .iter()
            .map(|document| document.as_ref().map(|document| document.doc_id))
            .collect::<Vec<_>>(),
        vec![Some(second), None, Some(first), Some(second), None]
    );
}

#[test]
fn get_documents_selects_exactly_requested_fields() {
    let directory = tempdir().expect("directory");
    let attribute = ColumnId::new(8);
    let schema = Schema::new(vec![ColumnDefinition::new(
        attribute,
        "rank",
        ColumnType::U64,
        false,
    )])
    .expect("schema");
    let store =
        Store::open(directory.path(), OpenOptions::default().with_schema(schema)).expect("open");
    let id = DocId::new(8);
    store
        .ingest(IngestBatch::new(vec![
            IngestDocument::new(DocumentVersion::new(id, Revision::new(2)), vec![1.0, 2.0])
                .with_timestamp(9)
                .with_text("fields")
                .with_metadata(vec![3, 4])
                .with_columns(vec![(attribute, PredicateValue::U64(12))]),
        ]))
        .expect("ingest");

    let cases = [
        (DocumentFields::NONE, [false, false, false, false]),
        (DocumentFields::VECTOR, [true, false, false, false]),
        (DocumentFields::TEXT, [false, true, false, false]),
        (DocumentFields::METADATA, [false, false, true, false]),
        (DocumentFields::ATTRIBUTES, [false, false, false, true]),
        (
            DocumentFields::VECTOR | DocumentFields::METADATA,
            [true, false, true, false],
        ),
        (DocumentFields::ALL, [true, true, true, true]),
    ];
    for (fields, expected) in cases {
        let documents = store.get_documents(&[id], fields).expect("get");
        let document = documents[0].as_ref().expect("document");
        assert_eq!(
            [
                document.vector.is_some(),
                document.text.is_some(),
                document.metadata.is_some(),
                document.attributes.is_some(),
            ],
            expected
        );
        assert_eq!(document.doc_id, id);
        assert_eq!(document.revision, Revision::new(2));
        assert_eq!(document.timestamp, 9);
    }
}

#[test]
fn get_documents_reports_absent_sealed_text_and_metadata_regions() {
    let directory = tempdir().expect("directory");
    let store = Store::open(directory.path(), OpenOptions::default()).expect("open");
    let id = DocId::new(6);
    store
        .ingest(IngestBatch::new(vec![IngestDocument::new(
            DocumentVersion::new(id, Revision::new(1)),
            vec![1.0],
        )]))
        .expect("ingest");
    store.seal().expect("seal");

    let documents = store
        .get_documents(&[id], DocumentFields::TEXT | DocumentFields::METADATA)
        .expect("get");
    let document = documents[0].as_ref().expect("document");

    assert_eq!(document.text, None);
    assert_eq!(document.metadata, None);
}