text-document-search 1.6.3

Find and replace use cases for text-document
Documentation
// Generated by Qleany v1.4.8 from feature_use_case.tera
use super::search_helpers::{build_full_text_via_store, find_all_matches};
use crate::FindAllDto;
use crate::FindAllResultDto;
use anyhow::{Result, anyhow};
use common::database::QueryUnitOfWork;
use common::database::rope_helpers::rope_full_text_if_flow_matches;
use common::direct_access::document::document_repository::DocumentRelationshipField;
use common::direct_access::frame::frame_repository::FrameRelationshipField;
use common::direct_access::root::root_repository::RootRelationshipField;
#[allow(unused_imports)]
use common::entities::{Block, Document, Frame, Root};
use common::types::{EntityId, ROOT_ENTITY_ID};

pub trait FindAllUnitOfWorkFactoryTrait: Send + Sync {
    fn create(&self) -> Box<dyn FindAllUnitOfWorkTrait>;
}

#[macros::uow_action(entity = "Root", action = "GetRO")]
#[macros::uow_action(entity = "Root", action = "GetRelationshipRO")]
#[macros::uow_action(entity = "Document", action = "GetRelationshipRO")]
#[macros::uow_action(entity = "Frame", action = "GetRelationshipRO")]
#[macros::uow_action(entity = "Block", action = "GetMultiRO")]
pub trait FindAllUnitOfWorkTrait: QueryUnitOfWork {}

/// Fetch all blocks from the document via the UoW, sort them, and build the full text.
fn build_full_text(uow: &dyn FindAllUnitOfWorkTrait) -> Result<String> {
    let root = uow
        .get_root(&ROOT_ENTITY_ID)?
        .ok_or_else(|| anyhow!("Root entity not found"))?;

    let doc_ids = uow.get_root_relationship(&root.id, &RootRelationshipField::Document)?;
    let doc_id = *doc_ids
        .first()
        .ok_or_else(|| anyhow!("Root has no document"))?;

    let frame_ids = uow.get_document_relationship(&doc_id, &DocumentRelationshipField::Frames)?;

    // Fast path: whenever the rope's char space matches the document
    // flow (the common case, including documents with tables — cell
    // content is mirrored inline), the entire searchable text is the
    // rope itself. This is also what makes table-cell content reachable
    // by search.
    if let Some(text) = rope_full_text_if_flow_matches(&uow.store()) {
        return Ok(text);
    }

    let mut all_block_ids: Vec<EntityId> = Vec::new();
    for frame_id in &frame_ids {
        let block_ids = uow.get_frame_relationship(frame_id, &FrameRelationshipField::Blocks)?;
        all_block_ids.extend(block_ids);
    }

    let blocks_opt = uow.get_block_multi(&all_block_ids)?;
    let mut blocks: Vec<Block> = blocks_opt.into_iter().flatten().collect();
    blocks.sort_by_key(|b| b.document_position);

    Ok(build_full_text_via_store(&blocks, &uow.store()))
}

pub struct FindAllUseCase {
    uow_factory: Box<dyn FindAllUnitOfWorkFactoryTrait>,
}

impl FindAllUseCase {
    pub fn new(uow_factory: Box<dyn FindAllUnitOfWorkFactoryTrait>) -> Self {
        FindAllUseCase { uow_factory }
    }

    pub fn execute(&mut self, dto: &FindAllDto) -> Result<FindAllResultDto> {
        let uow = self.uow_factory.create();
        uow.begin_transaction()?;

        let full_text = build_full_text(uow.as_ref())?;

        let all_matches = find_all_matches(
            &full_text,
            &dto.query,
            dto.case_sensitive,
            dto.whole_word,
            dto.use_regex,
        )?;

        uow.end_transaction()?;

        let positions: Vec<i64> = all_matches.iter().map(|(pos, _)| *pos as i64).collect();
        let lengths: Vec<i64> = all_matches.iter().map(|(_, len)| *len as i64).collect();
        let count = all_matches.len() as i64;

        Ok(FindAllResultDto {
            positions,
            lengths,
            count,
        })
    }
}