use anyhow::{Context, Result};
use rusqlite::{params, OptionalExtension, Transaction};
use super::connection::with_connection;
use crate::memory::config::MemoryConfig;
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
pub struct RawRef {
pub path: String,
#[serde(default)]
pub start: usize,
#[serde(default)]
pub end: Option<usize>,
}
pub fn set_chunk_raw_refs(config: &MemoryConfig, chunk_id: &str, refs: &[RawRef]) -> Result<()> {
let json = serde_json::to_string(refs).context("serialize raw_refs")?;
with_connection(config, |conn| {
conn.execute(
"UPDATE mem_tree_chunks SET raw_refs_json = ?1 WHERE id = ?2",
params![json, chunk_id],
)?;
Ok(())
})
}
pub fn set_chunk_raw_refs_tx(tx: &Transaction<'_>, chunk_id: &str, refs: &[RawRef]) -> Result<()> {
let json = serde_json::to_string(refs).context("serialize raw_refs")?;
tx.execute(
"UPDATE mem_tree_chunks SET raw_refs_json = ?1 WHERE id = ?2",
params![json, chunk_id],
)?;
Ok(())
}
pub fn get_chunk_raw_refs(config: &MemoryConfig, chunk_id: &str) -> Result<Option<Vec<RawRef>>> {
with_connection(config, |conn| {
let row = conn
.query_row(
"SELECT raw_refs_json FROM mem_tree_chunks WHERE id = ?1",
params![chunk_id],
|r| r.get::<_, Option<String>>(0),
)
.optional()?
.flatten();
match row {
Some(json) if !json.is_empty() => {
let refs: Vec<RawRef> =
serde_json::from_str(&json).context("deserialize raw_refs_json")?;
Ok(Some(refs))
}
_ => Ok(None),
}
})
}
pub fn list_chunk_raw_ref_paths_with_prefix(
config: &MemoryConfig,
rel_prefix: &str,
) -> Result<std::collections::HashSet<String>> {
with_connection(config, |conn| {
let mut stmt = conn.prepare(
"SELECT raw_refs_json FROM mem_tree_chunks \
WHERE raw_refs_json IS NOT NULL AND raw_refs_json != ''",
)?;
let rows = stmt.query_map([], |r| r.get::<_, String>(0))?;
let mut out: std::collections::HashSet<String> = std::collections::HashSet::new();
for row in rows {
let json = row?;
match serde_json::from_str::<Vec<RawRef>>(&json) {
Ok(refs) => {
for raw_ref in refs {
if raw_ref.path.starts_with(rel_prefix) {
out.insert(raw_ref.path);
}
}
}
Err(_) => {}
}
}
Ok(out)
})
}
pub fn get_chunk_content_pointers(
config: &MemoryConfig,
chunk_id: &str,
) -> Result<Option<(String, String)>> {
with_connection(config, |conn| {
let row = conn
.query_row(
"SELECT content_path, content_sha256 FROM mem_tree_chunks WHERE id = ?1",
params![chunk_id],
|r| {
let path: Option<String> = r.get(0)?;
let sha: Option<String> = r.get(1)?;
Ok((path, sha))
},
)
.optional()?;
Ok(row.and_then(|(p, s)| p.zip(s)))
})
}
pub fn get_chunk_content_path(config: &MemoryConfig, chunk_id: &str) -> Result<Option<String>> {
with_connection(config, |conn| {
let row = conn
.query_row(
"SELECT content_path FROM mem_tree_chunks WHERE id = ?1",
params![chunk_id],
|r| r.get::<_, Option<String>>(0),
)
.optional()?
.flatten();
Ok(row)
})
}
pub fn get_summary_content_pointers(
config: &MemoryConfig,
summary_id: &str,
) -> Result<Option<(String, String)>> {
with_connection(config, |conn| {
let row = conn
.query_row(
"SELECT content_path, content_sha256 FROM mem_tree_summaries WHERE id = ?1",
params![summary_id],
|r| {
let path: Option<String> = r.get(0)?;
let sha: Option<String> = r.get(1)?;
Ok((path, sha))
},
)
.optional()?;
Ok(row.and_then(|(p, s)| p.zip(s)))
})
}
pub fn list_summaries_with_content_path(
config: &MemoryConfig,
) -> Result<Vec<(String, String, String)>> {
with_connection(config, |conn| {
let mut stmt = conn.prepare(
"SELECT id, content_path, content_sha256
FROM mem_tree_summaries
WHERE content_path IS NOT NULL AND content_sha256 IS NOT NULL
AND deleted = 0",
)?;
let rows = stmt
.query_map([], |r| {
let id: String = r.get(0)?;
let path: String = r.get(1)?;
let sha: String = r.get(2)?;
Ok((id, path, sha))
})?
.collect::<rusqlite::Result<Vec<_>>>()
.context("Failed to list summaries with content_path")?;
Ok(rows)
})
}