See More

/** * 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