use chrono::{DateTime, SecondsFormat, Utc};
use sqlmodel_core::Value;
use crate::db::{DbConnection, DbError, DbOperation, Result};
const READ_EVENT_AT: &str = "SELECT CASE WHEN length(CAST(last_event_at AS BLOB)) <= 128 THEN last_event_at ELSE NULL END FROM retrieval_affinity_accumulation WHERE workspace_id = ?1 AND memory_a = ?2 AND memory_b = ?3 LIMIT 2";
const WRITE_EVENT_AT: &str = "UPDATE retrieval_affinity_accumulation SET last_event_at = ?4 WHERE workspace_id = ?1 AND memory_a = ?2 AND memory_b = ?3";
fn invalid_event_time() -> DbError {
DbError::MalformedRow {
operation: DbOperation::Query,
message: "Could not validate retrieval-affinity edge evidence time".to_owned(),
}
}
impl DbConnection {
pub(crate) fn apply_retrieval_affinity_timed_deltas(
&self,
workspace_id: &str,
deltas: &[(String, String, f64, DateTime<Utc>)],
) -> Result<()> {
for (memory_a, memory_b, weight, event_at) in deltas {
if !weight.is_finite() || *weight <= 0.0 {
return Err(invalid_event_time());
}
let mut params = vec![
Value::Text(workspace_id.to_owned()),
Value::Text(memory_a.clone()),
Value::Text(memory_b.clone()),
];
let rows = self.query(READ_EVENT_AT, ¶ms)?;
let latest = match rows.as_slice() {
[] => *event_at,
[row] => {
let previous = row
.get(0)
.and_then(Value::as_str)
.and_then(|value| DateTime::parse_from_rfc3339(value).ok())
.ok_or_else(invalid_event_time)?
.with_timezone(&Utc);
previous.max(*event_at)
}
_ => return Err(invalid_event_time()),
};
let canonical = latest.to_rfc3339_opts(SecondsFormat::Nanos, true);
self.apply_retrieval_affinity_deltas(
workspace_id,
&[(memory_a.clone(), memory_b.clone(), *weight)],
&canonical,
)?;
params.push(Value::Text(canonical));
let changed = self.execute_for(DbOperation::Execute, WRITE_EVENT_AT, ¶ms)?;
if changed != 1 {
return Err(invalid_event_time());
}
}
Ok(())
}
}