FOSSredder 0.5.0
Loading...
Searching...
No Matches
SqliteActorRepository.cpp
Go to the documentation of this file.
1
5
10#include <algorithm>
11#include <sqlite3.h>
12#include <stdexcept>
13#include <string>
14#include <unordered_map>
15#include <unordered_set>
16#include <utility>
17
19 std::shared_ptr<SqliteDb> db;
20};
21
22namespace {
23
24struct AliasStats {
25 int hitCount = 1;
26 std::string lastUsedAt;
27 std::string createdAt;
28 std::string updatedAt;
29};
30
31core::domain::Alias makeAlias(const std::string& alias, int hitCount,
32 const std::string& createdAt,
33 const std::string& updatedAt,
34 const std::string& lastUsedAt) {
36 value.setValue(alias);
37 value.setSource(alias);
38 value.setHitCount(hitCount);
39 value.setCreatedAt(createdAt);
40 value.setUpdatedAt(updatedAt);
41 value.setLastUsedAt(lastUsedAt);
42 return value;
43}
44
45std::vector<core::domain::Alias>
46collectAliases(const core::domain::Actor& actor) {
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())
51 continue;
52 auto value = alias;
53 if (value.hitCount() < 1)
54 value.setHitCount(1);
55 out.push_back(std::move(value));
56 }
57 return out;
58}
59
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 = ?;");
66 if (!stmt)
67 return out;
68
69 stmt.bindText(1, actorId);
70 while (stmt.step() == SQLITE_ROW) {
71 AliasStats stats;
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));
77 }
78
79 return out;
80}
81
82void loadActorAliases(sqlite3* db, core::domain::Actor& actor) {
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;");
87 if (!stmt)
88 return;
89
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),
95 stmt.columnText(4)));
96 }
97 actor.setAliases(std::move(aliases));
98}
99
100void deleteActorAliases(sqlite3* db, const std::string& actorId) {
102 "DELETE FROM actor_aliases WHERE actor_id = ?;");
103 if (!stmt)
104 return;
105
106 stmt.bindText(1, actorId);
107 stmt.step();
108}
109
110void replaceActorAliases(
111 sqlite3* db, const core::domain::Actor& actor,
112 const std::unordered_map<std::string, AliasStats>& existing) {
113 if (actor.id().empty())
114 return;
115
116 deleteActorAliases(db, actor.id());
117
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));");
123 if (!stmt)
124 return;
125
126 std::unordered_set<std::string> seen;
127 for (const auto& alias : collectAliases(actor)) {
128 if (alias.value().empty() || !seen.insert(alias.value()).second)
129 continue;
130
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()
137 ? alias.createdAt()
138 : it->second.createdAt;
139 const std::string updatedAt =
140 it == existing.end() || it->second.updatedAt.empty()
141 ? alias.updatedAt()
142 : it->second.updatedAt;
143 const std::string lastUsedAt =
144 it == existing.end() || it->second.lastUsedAt.empty()
145 ? alias.lastUsedAt()
146 : it->second.lastUsedAt;
147 stmt.reset();
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);
154 stmt.step();
155 }
156}
157
158} // namespace
159
161 : SqliteActorRepository(std::make_shared<SqliteDb>(dbPath)) {}
162
164 : pimpl_(std::make_unique<Impl>()) {
165 if (!db) {
166 throw std::runtime_error("db is null");
167 }
168
169 pimpl_->db = std::move(db);
170}
171
173
174void SqliteActorRepository::addActor(const std::shared_ptr<Actor>& actor) {
175 if (!actor || actor->id().empty()) {
176 return;
177 }
178
179 persistence::StmtGuard stmt(pimpl_->db->handle(),
180 "INSERT OR IGNORE INTO actors (id, name, "
181 "created_at, updated_at) VALUES (?, ?, ?, ?);");
182 if (!stmt) {
183 return;
184 }
185
186 stmt.bindText(1, actor->id());
187 stmt.bindText(2, actor->name());
188 stmt.bindText(3, actor->createdAt());
189 stmt.bindText(4, actor->updatedAt());
190 if (stmt.step() == SQLITE_DONE) {
191 replaceActorAliases(pimpl_->db->handle(), *actor, {});
192 }
193}
194
195std::vector<std::shared_ptr<Actor>> SqliteActorRepository::getActors() const {
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;");
200 if (!stmt) {
201 return out;
202 }
203
204 while (stmt.step() == SQLITE_ROW) {
205 auto a = std::make_shared<Actor>();
206 a->setId(stmt.columnText(0));
207 a->rename(stmt.columnText(1));
208 a->setCreatedAt(stmt.columnText(2));
209 a->setUpdatedAt(stmt.columnText(3));
210 loadActorAliases(pimpl_->db->handle(), *a);
211 out.push_back(std::move(a));
212 }
213 return out;
214}
215
216std::optional<std::shared_ptr<Actor>>
217SqliteActorRepository::getActorById(const std::string& id) const {
218 if (id.empty()) {
219 return std::nullopt;
220 }
221
223 pimpl_->db->handle(),
224 "SELECT id, name, created_at, updated_at FROM actors WHERE id = ?;");
225 if (!stmt) {
226 return std::nullopt;
227 }
228
229 stmt.bindText(1, id);
230 if (stmt.step() != SQLITE_ROW) {
231 return std::nullopt;
232 }
233
234 auto a = std::make_shared<Actor>();
235 a->setId(stmt.columnText(0));
236 a->rename(stmt.columnText(1));
237 a->setCreatedAt(stmt.columnText(2));
238 a->setUpdatedAt(stmt.columnText(3));
239 loadActorAliases(pimpl_->db->handle(), *a);
240 return a;
241}
242
243void SqliteActorRepository::removeActor(const std::string& id) {
244 if (id.empty()) {
245 return;
246 }
247
248 persistence::StmtGuard stmt(pimpl_->db->handle(),
249 "DELETE FROM actors WHERE id = ?;");
250 if (!stmt) {
251 return;
252 }
253
254 stmt.bindText(1, id);
255 stmt.step();
256}
257
258void SqliteActorRepository::updateActor(const std::shared_ptr<Actor>& actor) {
259 if (!actor || actor->id().empty()) {
260 return;
261 }
262
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 = ?;");
268 if (!stmt) {
269 return;
270 }
271
272 stmt.bindText(1, actor->name());
273 stmt.bindText(2, actor->createdAt());
274 stmt.bindText(3, actor->updatedAt());
275 stmt.bindText(4, actor->id());
276 if (stmt.step() == SQLITE_DONE) {
277 replaceActorAliases(pimpl_->db->handle(), *actor, existing);
278 }
279}
280
281void SqliteActorRepository::upsertActor(const std::shared_ptr<Actor>& actor) {
282 if (!actor || actor->id().empty()) {
283 return;
284 }
285
286 updateActor(actor);
287 if (sqlite3_changes(pimpl_->db->handle()) == 0) {
288 addActor(actor);
289 }
290}
291
293 char* err = nullptr;
294 sqlite3_exec(pimpl_->db->handle(),
295 "DELETE FROM actor_aliases; DELETE FROM actors;", nullptr,
296 nullptr, &err);
297 if (err) {
298 sqlite3_free(err);
299 }
300}
301
302sqlite3* SqliteActorRepository::sqliteHandle() const noexcept {
303 return pimpl_ && pimpl_->db ? pimpl_->db->handle() : nullptr;
304}
Domain model for actors.
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)
Definition Actor.cpp:23
const std::string & id() const noexcept
Definition Actor.cpp:88
const std::vector< Alias > & aliases() const noexcept
Definition Actor.cpp:90
void setUpdatedAt(std::string value)
Definition Alias.cpp:35
void setLastUsedAt(std::string value)
Definition Alias.cpp:40
void setCreatedAt(std::string value)
Definition Alias.cpp:33
int hitCount() const noexcept
Definition Alias.cpp:37
void setValue(std::string value)
Definition Alias.cpp:28
void setHitCount(int value)
Definition Alias.cpp:39
void setSource(std::string value)
Definition Alias.cpp:31
std::string columnText(int col) const noexcept
Definition StmtGuard.h:57
int step() noexcept
Definition StmtGuard.h:37
void bindText(int col, const std::string &v) noexcept
Definition StmtGuard.h:44
std::shared_ptr< SqliteDb > db