remem-ai 0.6.78

Local-first coding agent memory for Claude Code and OpenAI Codex
Documentation
use anyhow::Result;
use rusqlite::Connection;

use super::sql::{branch_filter_sql, project_filter_sql, status_filter_sql};

pub(super) fn search_by_query_words(
    conn: &Connection,
    query: &str,
    project: Option<&str>,
    memory_type: Option<&str>,
    branch: Option<&str>,
    limit: i64,
    include_inactive: bool,
) -> Result<Vec<i64>> {
    let mut ids = Vec::new();
    for word in query.split_whitespace() {
        if word.len() < 2 {
            continue;
        }
        let pattern = format!("%{}%", word);
        let matches = query_memory_ids(
            conn,
            "e.canonical_name LIKE ?1 COLLATE NOCASE".to_string(),
            Box::new(pattern),
            project,
            memory_type,
            branch,
            limit,
            include_inactive,
        )?;
        for id in matches {
            if !ids.contains(&id) {
                ids.push(id);
            }
        }
    }
    Ok(ids)
}

pub(super) fn query_memory_ids(
    conn: &Connection,
    entity_condition: String,
    first_param: Box<dyn rusqlite::types::ToSql>,
    project: Option<&str>,
    memory_type: Option<&str>,
    branch: Option<&str>,
    limit: i64,
    include_inactive: bool,
) -> Result<Vec<i64>> {
    let mut conditions = vec![
        entity_condition,
        status_filter_sql(include_inactive).to_string(),
    ];
    let mut params_vec: Vec<Box<dyn rusqlite::types::ToSql>> = vec![first_param];
    let mut idx = 2;
    if let Some(project) = project {
        conditions.push(project_filter_sql(idx));
        params_vec.push(Box::new(project.to_string()));
        idx += 1;
    }
    if let Some(memory_type) = memory_type {
        conditions.push(format!("m.memory_type = ?{idx}"));
        params_vec.push(Box::new(memory_type.to_string()));
        idx += 1;
    }
    if let Some(branch) = branch {
        conditions.push(branch_filter_sql(idx));
        params_vec.push(Box::new(branch.to_string()));
        idx += 1;
    }
    let order_by = if let Some(project) = project {
        let order_project_idx = idx;
        params_vec.push(Box::new(project.to_string()));
        idx += 1;
        format!(
            "CASE \
             WHEN COALESCE(m.scope, 'project') = 'global' THEN 1 \
             WHEN m.project = ?{order_project_idx} THEN 0 \
             ELSE 2 END, \
             m.updated_at_epoch DESC, me.memory_id DESC"
        )
    } else {
        "m.updated_at_epoch DESC, me.memory_id DESC".to_string()
    };
    let sql = format!(
        "SELECT DISTINCT me.memory_id FROM memory_entities me
         JOIN entities e ON e.id = me.entity_id
         JOIN memories m ON m.id = me.memory_id
         WHERE {}
         ORDER BY {}
         LIMIT ?{}",
        conditions.join(" AND "),
        order_by,
        idx
    );
    params_vec.push(Box::new(limit));
    let refs: Vec<&dyn rusqlite::types::ToSql> =
        params_vec.iter().map(|value| value.as_ref()).collect();
    let mut stmt = conn.prepare(&sql)?;
    let rows = stmt.query_map(refs.as_slice(), |row| row.get::<_, i64>(0))?;
    crate::db::query::collect_rows(rows)
}