/**
* SQLite backend for Ragger Memory (C++ port)
*/
#include "sqlite/backend.h"
#include "sqlite/backend_impl.h"
#include "config.h"
#include "util/time.h"
#include "nlohmann_json.hpp"
#include
#include
#include
#include
#include
#include
namespace ragger::sqlite {
using json = nlohmann::json;
// -----------------------------------------------------------------------
// Public API (delegates to Impl)
// -----------------------------------------------------------------------
Backend::Backend(Embedder& embedder, const std::string& db_path)
: pImpl(std::make_unique(embedder, db_path)) {}
Backend::Backend(const std::string& db_path, bool readonly)
: pImpl(std::make_unique(db_path, readonly)) {}
Backend::~Backend() = default;
std::string Backend::db_path() const { return pImpl->db_path; }
std::string Backend::store(const std::string& text, json metadata, bool defer_embedding) {
std::lock_guard<:mutex> lk(pImpl->mu);
return pImpl->store(text, std::move(metadata), defer_embedding);
}
int Backend::store_document(const DocumentChunk& chunk, bool defer_embedding) {
std::lock_guard<:mutex> lk(pImpl->mu);
return pImpl->store_document(chunk, defer_embedding);
}
int Backend::store_turn(const std::string& user_text,
const std::string& assistant_text,
const std::string& model_name, bool defer_embedding,
const std::string& session_guid,
const std::string& source_timestamp,
const std::string& session_name,
const std::string& name_source) {
std::lock_guard<:mutex> lk(pImpl->mu);
return pImpl->store_turn(user_text, assistant_text, model_name,
defer_embedding, session_guid, source_timestamp,
session_name, name_source);
}
std::vector Backend::turns_by_session(
const std::string& session_guid) {
std::lock_guard<:mutex> lk(pImpl->mu);
return pImpl->turns_by_session(session_guid);
}
bool Backend::finalize_turn(int turn_id, const std::string& assistant_text,
const std::string& model_name) {
std::lock_guard<:mutex> lk(pImpl->mu);
return pImpl->finalize_turn(turn_id, assistant_text, model_name);
}
// PHASE8: the old summary-store forwarder is gone with its table.
bool Backend::note_exists_exact(const std::string& text, const std::string& created_at) {
std::lock_guard<:mutex> lk(pImpl->mu);
return pImpl->note_exists_exact(text, created_at);
}
bool Backend::decision_exists_exact(const std::string& text, const std::string& created_at) {
std::lock_guard<:mutex> lk(pImpl->mu);
return pImpl->decision_exists_exact(text, created_at);
}
bool Backend::turn_exists_fuzzy(const std::string& user_text, const std::string& ts,
int window_seconds) {
std::lock_guard<:mutex> lk(pImpl->mu);
return pImpl->turn_exists_fuzzy(user_text, ts, window_seconds);
}
std::optional Backend::find_turn_by_text(const std::string& user_text) {
std::lock_guard<:mutex> lk(pImpl->mu);
return pImpl->find_turn_by_text(user_text);
}
bool Backend::update_turn_meta(int turn_id, const std::string& timestamp,
const std::string& session_guid) {
std::lock_guard<:mutex> lk(pImpl->mu);
return pImpl->update_turn_meta(turn_id, timestamp, session_guid);
}
// PHASE8: the old summary text-update forwarder is gone.
bool Backend::turn_summary_exists(int turn_id) {
std::lock_guard<:mutex> lk(pImpl->mu);
return pImpl->turn_summary_exists(turn_id);
}
bool Backend::finalize_turn_summary(int turn_id, const std::string& text,
const std::string& summary_model_name) {
std::lock_guard<:mutex> lk(pImpl->mu);
return pImpl->finalize_turn_summary(turn_id, text, summary_model_name);
}
bool Backend::mark_turn_summarized(int turn_id, const std::string& model_name) {
std::lock_guard<:mutex> lk(pImpl->mu);
return pImpl->mark_turn_summarized(turn_id, model_name);
}
int Backend::reset_abandoned_turn_summaries(int limit) {
std::lock_guard<:mutex> lk(pImpl->mu);
return pImpl->reset_abandoned_turn_summaries(limit);
}
// PHASE8: the old summary tag-setter / recent-list forwarders are gone.
std::vector<:string> Backend::current_decisions(int limit) {
std::lock_guard<:mutex> lk(pImpl->mu);
return pImpl->current_decisions(limit);
}
bool Backend::set_decision_status(int decision_id, const std::string& status) {
std::lock_guard<:mutex> lk(pImpl->mu);
return pImpl->set_decision_status(decision_id, status);
}
std::vector<:string> Backend::decisions_by_status(const std::string& status, int limit) {
std::lock_guard<:mutex> lk(pImpl->mu);
return pImpl->decisions_by_status(status, limit);
}
std::vector Backend::unsummarized_turns(int limit) {
std::lock_guard<:mutex> lk(pImpl->mu);
return pImpl->unsummarized_turns(limit);
}
std::string Backend::last_episode_end(const std::string& session_guid) {
std::lock_guard<:mutex> lk(pImpl->mu);
return pImpl->last_episode_end(session_guid);
}
std::vector Backend::l2_summaries_since(
const std::string& session_guid, const std::string& since_ts) {
std::lock_guard<:mutex> lk(pImpl->mu);
return pImpl->l2_summaries_since(session_guid, since_ts);
}
std::vector Backend::episode_candidate_turns(
const std::string& session_guid, const std::string& since_ts) {
std::lock_guard<:mutex> lk(pImpl->mu);
return pImpl->episode_candidate_turns(session_guid, since_ts);
}
int Backend::store_episode_close(const EpisodeClose& close) {
std::lock_guard<:mutex> lk(pImpl->mu);
return pImpl->store_episode_close(close);
}
std::vector<:string> Backend::episodes_needing_close(int idle_minutes) {
std::lock_guard<:mutex> lk(pImpl->mu);
return pImpl->episodes_needing_close(idle_minutes);
}
std::vector Backend::turns_by_session_desc(
const std::string& session_guid, int limit) {
std::lock_guard<:mutex> lk(pImpl->mu);
return pImpl->turns_by_session_desc(session_guid, limit);
}
std::vector
Backend::turn_summaries_by_session_desc(
const std::string& session_guid, int limit) {
std::lock_guard<:mutex> lk(pImpl->mu);
return pImpl->turn_summaries_by_session_desc(session_guid, limit);
}
std::vector
Backend::episodes_by_session_desc(
const std::string& session_guid, int limit) {
std::lock_guard<:mutex> lk(pImpl->mu);
return pImpl->episodes_by_session_desc(session_guid, limit);
}
// PHASE8: update_text forwarder is gone (text is never rewritten in place).
SearchResponse Backend::search(const std::string& query, int limit,
float min_score,
std::vector<:string> collections) {
std::lock_guard<:mutex> lk(pImpl->mu);
return pImpl->search(query, limit, min_score, std::move(collections));
}
SearchResponse Backend::search_text_only(const std::string& query,
int limit) {
std::lock_guard<:mutex> lk(pImpl->mu);
return pImpl->search_text_only(query, limit);
}
int Backend::count() const { std::lock_guard<:mutex> lk(pImpl->mu); return pImpl->count(); }
std::vector<:pair int64_t>> Backend::table_row_counts() const { std::lock_guard<:mutex> lk(pImpl->mu); return pImpl->table_row_counts(); }
bool Backend::has_embeddings() const { std::lock_guard<:mutex> lk(pImpl->mu); return pImpl->has_embeddings(); }
int Backend::count_embeddable_rows(const std::string& table) const { std::lock_guard<:mutex> lk(pImpl->mu); return pImpl->count_embeddable_rows(table); }
std::vector Backend::load_all(const std::string& collection) {
std::lock_guard<:mutex> lk(pImpl->mu);
return pImpl->load_all(collection);
}
int Backend::rebuild_embeddings(Embedder& embedder, bool progress, const std::string& table) {
std::lock_guard<:mutex> lk(pImpl->mu);
return pImpl->rebuild_embeddings(embedder, progress, table);
}
// RaggerMemory swaps its Embedder object on recovery/update; the backend must
// be repointed immediately or the next store/search is a use-after-free.
void Backend::set_embedder(Embedder& e) {
std::lock_guard<:mutex> lk(pImpl->mu);
pImpl->embedder = &e;
}
void Backend::set_vector_type(vector_codec::VectorType t) {
std::lock_guard<:mutex> lk(pImpl->mu);
pImpl->vtype_ = t;
}
int Backend::backfill_embeddings(Embedder& embedder, const std::string& table) {
std::lock_guard<:mutex> lk(pImpl->mu);
return pImpl->backfill_embeddings(embedder, table);
}
uint8_t Backend::embedding_version() const {
std::lock_guard<:mutex> lk(pImpl->mu);
return pImpl->get_embedding_version();
}
uint8_t Backend::increment_embedding_version() {
std::lock_guard<:mutex> lk(pImpl->mu);
return pImpl->increment_embedding_version();
}
int Backend::reindex_table(const std::string& table, bool progress) {
std::lock_guard<:mutex> lk(pImpl->mu);
return pImpl->reindex_table(table, progress);
}
void Backend::reset_terms_table() {
std::lock_guard<:mutex> lk(pImpl->mu);
pImpl->reset_terms_table();
}
bool Backend::update_document_embedding(int document_id,
const std::vector& emb) {
std::lock_guard<:mutex> lk(pImpl->mu);
return pImpl->update_document_embedding(document_id, emb);
}
int Backend::store_decision(const std::string& text,
const std::string& status,
const std::string& tags,
const std::string& source_timestamp,
bool defer_embedding) {
std::lock_guard<:mutex> lk(pImpl->mu);
return pImpl->store_decision(text, status, tags, source_timestamp, defer_embedding);
}
KnowledgeResult Backend::store_knowledge(const KnowledgeInput& in) {
std::lock_guard<:mutex> lk(pImpl->mu);
return pImpl->store_knowledge(in);
}
void Backend::invalidate_all_caches() {
std::lock_guard<:mutex> lk(pImpl->mu);
pImpl->invalidate_all_caches();
}
json Backend::list_document_sources() {
std::lock_guard<:mutex> lk(pImpl->mu);
return pImpl->list_document_sources();
}
json Backend::delete_document_source(int document_source_id) {
std::lock_guard<:mutex> lk(pImpl->mu);
return pImpl->delete_document_source(document_source_id);
}
json Backend::find_document_source(const std::string& path) {
std::lock_guard<:mutex> lk(pImpl->mu);
return pImpl->find_document_source(path);
}
std::vector Backend::agent_search(const AgentSearchQuery& q) {
std::lock_guard<:mutex> lk(pImpl->mu);
return pImpl->agent_search(q);
}
json Backend::agent_get(const AgentGetQuery& q) {
std::lock_guard<:mutex> lk(pImpl->mu);
return pImpl->agent_get(q);
}
json Backend::agent_timeline(const TimelineQuery& q) {
std::lock_guard<:mutex> lk(pImpl->mu);
return pImpl->agent_timeline(q);
}
json Backend::agent_update(const UpdateRequest& req) {
std::lock_guard<:mutex> lk(pImpl->mu);
return pImpl->agent_update(req);
}
json Backend::agent_review(const std::string& action,
const std::vector<:string>& ids) {
std::lock_guard<:mutex> lk(pImpl->mu);
return pImpl->agent_review(action, ids);
}
std::vector<:pair>> Backend::possible_supersedes_for_text(
const std::string& text) {
std::lock_guard<:mutex> lk(pImpl->mu);
return pImpl->possible_supersedes_for_text(text);
}
bool Backend::update_decision_embedding(int decision_id,
const std::vector& emb) {
std::lock_guard<:mutex> lk(pImpl->mu);
return pImpl->update_decision_embedding(decision_id, emb);
}
// PHASE8: the old summary embedding write-back forwarder is gone.
bool Backend::update_turn_embedding(int turn_id,
const std::vector& emb) {
std::lock_guard<:mutex> lk(pImpl->mu);
return pImpl->update_turn_embedding(turn_id, emb);
}
std::vector<:string> Backend::collections() const {
std::lock_guard<:mutex> lk(pImpl->mu);
return pImpl->collections();
}
void Backend::close() {
std::lock_guard<:mutex> lk(pImpl->mu);
pImpl->close();
}
bool Backend::delete_memory(int memory_id) {
std::lock_guard<:mutex> lk(pImpl->mu);
return pImpl->delete_memory(memory_id);
}
int Backend::delete_batch(const std::vector& memory_ids) {
std::lock_guard<:mutex> lk(pImpl->mu);
return pImpl->delete_batch(memory_ids);
}
std::vector Backend::search_by_metadata(const json& metadata_filter, int limit,
const std::string& after,
const std::string& before) {
std::lock_guard<:mutex> lk(pImpl->mu);
return pImpl->search_by_metadata(metadata_filter, limit, after, before);
}
int Backend::cleanup_old_conversations(float max_age_hours) {
std::lock_guard<:mutex> lk(pImpl->mu);
auto cutoff = std::chrono::system_clock::now() -
std::chrono::duration_cast<:chrono::system_clock::duration>(
std::chrono::duration>(max_age_hours));
int64_t cutoff_epoch = db_epoch(std::chrono::system_clock::to_time_t(cutoff));
// v2: raw verbatim exchanges (L1 turns) are what age out by retention;
// their gist is preserved in the L2/L3 summaries. Purge old turns by
// timestamp. (Pre-v2 this deleted summaries tagged collection='conversation';
// that column is gone in the lean schema.)
Stmt stmt(pImpl->db,
"DELETE FROM turns WHERE created_at < ?");
stmt.bind(1, cutoff_epoch);
int deleted = 0;
if (stmt.exec()) {
deleted = static_cast(sqlite3_changes(pImpl->db));
}
return deleted;
}
// ---- users / settings CRUD (delegate through mutex) -----------------------
std::optional Backend::get_user_by_username(const std::string& username) {
std::lock_guard<:mutex> lk(pImpl->mu);
return pImpl->get_user_by_username(username);
}
std::optional<:string> Backend::get_user_password(const std::string& username) {
std::lock_guard<:mutex> lk(pImpl->mu);
return pImpl->get_user_password(username);
}
void Backend::update_user_token(const std::string& username, const std::string& new_hash) {
std::lock_guard<:mutex> lk(pImpl->mu);
pImpl->update_user_token(username, new_hash);
}
int Backend::create_user(const std::string& username, const std::string& token_hash) {
std::lock_guard<:mutex> lk(pImpl->mu);
return pImpl->create_user(username, token_hash);
}
bool Backend::delete_user(const std::string& username) {
std::lock_guard<:mutex> lk(pImpl->mu);
return pImpl->delete_user(username);
}
void Backend::set_user_password(const std::string& username,
const std::string& password_hash) {
std::lock_guard<:mutex> lk(pImpl->mu);
pImpl->set_user_password(username, password_hash);
}
std::optional Backend::get_user_by_token_hash(const std::string& token_hash) {
std::lock_guard<:mutex> lk(pImpl->mu);
return pImpl->get_user_by_token_hash(token_hash);
}
std::optional<:string> Backend::get_setting(const std::string& key) {
std::lock_guard<:mutex> lk(pImpl->mu);
return pImpl->get_setting(key);
}
void Backend::set_setting(const std::string& key, const std::string& value) {
std::lock_guard<:mutex> lk(pImpl->mu);
pImpl->set_setting(key, value);
}
// ---- schema introspection (delegate through mutex) ------------------------
std::vector Backend::list_schema_objects() {
std::lock_guard<:mutex> lk(pImpl->mu);
return pImpl->list_schema_objects();
}
std::vector<:string> Backend::table_column_names(const std::string& table) {
std::lock_guard<:mutex> lk(pImpl->mu);
return pImpl->table_column_names(table);
}
void Backend::iterate_table_rows(const std::string& table,
const std::function& cb) {
std::lock_guard<:mutex> lk(pImpl->mu);
pImpl->iterate_table_rows(table, cb);
}
} // namespace ragger::sqlite