14#include <unordered_map>
15#include <unordered_set>
19 std::shared_ptr<SqliteDb>
db;
26 std::string lastUsedAt;
27 std::string createdAt;
28 std::string updatedAt;
32 const std::string& createdAt,
33 const std::string& updatedAt,
34 const std::string& lastUsedAt) {
45std::vector<core::domain::Alias>
47 std::vector<core::domain::Alias> out;
48 out.reserve(actor.
aliases().size());
49 for (
const auto& alias : actor.
aliases()) {
50 if (alias.value().empty())
55 out.push_back(std::move(value));
60std::unordered_map<std::string, AliasStats>
61loadActorAliasStats(sqlite3* db,
const std::string& actorId) {
62 std::unordered_map<std::string, AliasStats> out;
64 db,
"SELECT alias, hit_count, created_at, updated_at, last_used_at FROM "
65 "actor_aliases WHERE actor_id = ?;");
69 stmt.bindText(1, actorId);
70 while (stmt.step() == SQLITE_ROW) {
72 stats.hitCount = stmt.columnInt(1);
73 stats.createdAt = stmt.columnText(2);
74 stats.updatedAt = stmt.columnText(3);
75 stats.lastUsedAt = stmt.columnText(4);
76 out.emplace(stmt.columnText(0), std::move(stats));
84 db,
"SELECT alias, hit_count, created_at, updated_at, last_used_at FROM "
85 "actor_aliases WHERE actor_id = ? ORDER BY hit_count DESC, "
86 "last_used_at DESC, alias ASC;");
90 stmt.bindText(1, actor.
id());
91 std::vector<core::domain::Alias> aliases;
92 while (stmt.step() == SQLITE_ROW) {
93 aliases.push_back(makeAlias(stmt.columnText(0), stmt.columnInt(1),
94 stmt.columnText(2), stmt.columnText(3),
100void deleteActorAliases(sqlite3* db,
const std::string& actorId) {
102 "DELETE FROM actor_aliases WHERE actor_id = ?;");
106 stmt.bindText(1, actorId);
110void replaceActorAliases(
112 const std::unordered_map<std::string, AliasStats>& existing) {
113 if (actor.
id().empty())
116 deleteActorAliases(db, actor.
id());
119 db,
"INSERT INTO actor_aliases (actor_id, alias, hit_count, created_at, "
120 "updated_at, last_used_at) VALUES (?, ?, ?, COALESCE(NULLIF(?, ''), "
121 "CURRENT_TIMESTAMP), COALESCE(NULLIF(?, ''), CURRENT_TIMESTAMP), "
122 "COALESCE(NULLIF(?, ''), CURRENT_TIMESTAMP));");
126 std::unordered_set<std::string> seen;
127 for (
const auto& alias : collectAliases(actor)) {
128 if (alias.value().empty() || !seen.insert(alias.value()).second)
131 const auto it = existing.find(alias.value());
132 const int hitCount = it == existing.end()
133 ? std::max(1, alias.hitCount())
134 : std::max(1, it->second.hitCount);
135 const std::string createdAt =
136 it == existing.end() || it->second.createdAt.empty()
138 : it->second.createdAt;
139 const std::string updatedAt =
140 it == existing.end() || it->second.updatedAt.empty()
142 : it->second.updatedAt;
143 const std::string lastUsedAt =
144 it == existing.end() || it->second.lastUsedAt.empty()
146 : it->second.lastUsedAt;
148 stmt.bindText(1, actor.
id());
149 stmt.bindText(2, alias.value());
150 stmt.bindInt(3, hitCount);
151 stmt.bindText(4, createdAt);
152 stmt.bindText(5, updatedAt);
153 stmt.bindText(6, lastUsedAt);
164 : pimpl_(std::make_unique<
Impl>()) {
166 throw std::runtime_error(
"db is null");
169 pimpl_->db = std::move(db);
175 if (!actor || actor->id().empty()) {
180 "INSERT OR IGNORE INTO actors (id, name, "
181 "created_at, updated_at) VALUES (?, ?, ?, ?);");
188 stmt.
bindText(3, actor->createdAt());
189 stmt.
bindText(4, actor->updatedAt());
190 if (stmt.
step() == SQLITE_DONE) {
191 replaceActorAliases(pimpl_->db->handle(), *actor, {});
196 std::vector<std::shared_ptr<Actor>> out;
198 pimpl_->db->handle(),
199 "SELECT id, name, created_at, updated_at FROM actors ORDER BY id;");
204 while (stmt.
step() == SQLITE_ROW) {
205 auto a = std::make_shared<Actor>();
210 loadActorAliases(pimpl_->db->handle(), *a);
211 out.push_back(std::move(a));
216std::optional<std::shared_ptr<Actor>>
223 pimpl_->db->handle(),
224 "SELECT id, name, created_at, updated_at FROM actors WHERE id = ?;");
230 if (stmt.
step() != SQLITE_ROW) {
234 auto a = std::make_shared<Actor>();
239 loadActorAliases(pimpl_->db->handle(), *a);
249 "DELETE FROM actors WHERE id = ?;");
259 if (!actor || actor->id().empty()) {
263 const auto existing = loadActorAliasStats(pimpl_->db->handle(), actor->id());
265 pimpl_->db->handle(),
266 "UPDATE actors SET name = ?, created_at = COALESCE(NULLIF(created_at, "
267 "''), ?), updated_at = ? WHERE id = ?;");
273 stmt.
bindText(2, actor->createdAt());
274 stmt.
bindText(3, actor->updatedAt());
276 if (stmt.
step() == SQLITE_DONE) {
277 replaceActorAliases(pimpl_->db->handle(), *actor, existing);
282 if (!actor || actor->id().empty()) {
287 if (sqlite3_changes(pimpl_->db->handle()) == 0) {
294 sqlite3_exec(pimpl_->db->handle(),
295 "DELETE FROM actor_aliases; DELETE FROM actors;",
nullptr,
303 return pimpl_ && pimpl_->db ? pimpl_->db->handle() :
nullptr;
Declares the SQLite-backed actor repository.
Declares the lightweight SQLite connection wrapper.
RAII wrapper for sqlite3_stmt* that auto-finalizes on destruction.
void updateActor(const std::shared_ptr< core::domain::Actor > &actor) override
Update an existing actor.
void addActor(const std::shared_ptr< core::domain::Actor > &actor) override
Insert a new actor.
void upsertActor(const std::shared_ptr< core::domain::Actor > &actor) override
Insert or update an actor.
std::optional< std::shared_ptr< core::domain::Actor > > getActorById(const std::string &id) const override
Retrieve an actor by identifier.
~SqliteActorRepository() override
Destroy the repository.
void removeActor(const std::string &id) override
Remove an actor by identifier.
SqliteActorRepository(const std::string &dbPath)
Create an actor repository for the database at the given path.
void clearActors() override
Remove all actors.
std::vector< std::shared_ptr< core::domain::Actor > > getActors() const override
Retrieve all actors.
sqlite3 * sqliteHandle() const noexcept override
Return the SQLite handle.
void setAliases(std::vector< Alias > value)
const std::string & id() const noexcept
const std::vector< Alias > & aliases() const noexcept
void setUpdatedAt(std::string value)
void setLastUsedAt(std::string value)
void setCreatedAt(std::string value)
int hitCount() const noexcept
void setValue(std::string value)
void setHitCount(int value)
void setSource(std::string value)
std::string columnText(int col) const noexcept
void bindText(int col, const std::string &v) noexcept
std::shared_ptr< SqliteDb > db