FOSSredder 0.5.0
Loading...
Searching...
No Matches
SqliteContractRepository.cpp
Go to the documentation of this file.
1
5
10#include <algorithm>
11#include <sqlite3.h>
12#include <stdexcept>
13#include <unordered_map>
14#include <unordered_set>
15#include <utility>
16
18 std::shared_ptr<SqliteDb> db;
19};
20
22 : SqliteContractRepository(std::make_shared<SqliteDb>(dbPath)) {}
23
25 : pimpl_(std::make_unique<Impl>()) {
26 if (!db)
27 throw std::runtime_error("db is null");
28 pimpl_->db = std::move(db);
29}
30
32
33namespace {
34
35struct AliasStats {
36 int hitCount = 1;
37 std::string lastUsedAt;
38 std::string createdAt;
39 std::string updatedAt;
40};
41
42core::domain::Alias makeAlias(const std::string& alias, int hitCount,
43 const std::string& createdAt,
44 const std::string& updatedAt,
45 const std::string& lastUsedAt) {
47 value.setValue(alias);
48 value.setSource(alias);
49 value.setHitCount(hitCount);
50 value.setCreatedAt(createdAt);
51 value.setUpdatedAt(updatedAt);
52 value.setLastUsedAt(lastUsedAt);
53 return value;
54}
55
56std::vector<core::domain::Alias> collectAliases(const Contract& contract) {
57 std::vector<core::domain::Alias> out;
58 out.reserve(contract.aliases().size());
59 for (const auto& alias : contract.aliases()) {
60 if (alias.value().empty())
61 continue;
62 auto value = alias;
63 if (value.hitCount() < 1)
64 value.setHitCount(1);
65 out.push_back(std::move(value));
66 }
67 return out;
68}
69
70std::unordered_map<std::string, AliasStats>
71loadContractAliasStats(sqlite3* db, const std::string& contractId) {
72 std::unordered_map<std::string, AliasStats> out;
74 db, "SELECT alias, hit_count, created_at, updated_at, last_used_at FROM "
75 "contract_aliases WHERE contract_id = ?;");
76 if (!stmt)
77 return out;
78 stmt.bindText(1, contractId);
79 while (stmt.step() == SQLITE_ROW) {
80 AliasStats stats;
81 stats.hitCount = stmt.columnInt(1);
82 stats.createdAt = stmt.columnText(2);
83 stats.updatedAt = stmt.columnText(3);
84 stats.lastUsedAt = stmt.columnText(4);
85 out.emplace(stmt.columnText(0), std::move(stats));
86 }
87 return out;
88}
89
90void loadContractAliases(sqlite3* db, Contract& contract) {
92 db, "SELECT alias, hit_count, created_at, updated_at, last_used_at FROM "
93 "contract_aliases WHERE contract_id = ? ORDER BY hit_count DESC, "
94 "last_used_at DESC, alias ASC;");
95 if (!stmt)
96 return;
97 stmt.bindText(1, contract.id());
98 std::vector<core::domain::Alias> aliases;
99 while (stmt.step() == SQLITE_ROW) {
100 aliases.push_back(makeAlias(stmt.columnText(0), stmt.columnInt(1),
101 stmt.columnText(2), stmt.columnText(3),
102 stmt.columnText(4)));
103 }
104 contract.setAliases(std::move(aliases));
105}
106
107void deleteContractAliases(sqlite3* db, const std::string& contractId) {
109 db, "DELETE FROM contract_aliases WHERE contract_id = ?;");
110 if (!stmt)
111 return;
112 stmt.bindText(1, contractId);
113 stmt.step();
114}
115
116void replaceContractAliases(
117 sqlite3* db, const Contract& contract,
118 const std::unordered_map<std::string, AliasStats>& existing) {
119 if (contract.id().empty())
120 return;
121 deleteContractAliases(db, contract.id());
122
124 db, "INSERT INTO contract_aliases (contract_id, alias, hit_count, "
125 "created_at, updated_at, last_used_at) VALUES (?, ?, ?, "
126 "COALESCE(NULLIF(?, ''), CURRENT_TIMESTAMP), COALESCE(NULLIF(?, ''), "
127 "CURRENT_TIMESTAMP), COALESCE(NULLIF(?, ''), CURRENT_TIMESTAMP));");
128 if (!stmt)
129 return;
130
131 std::unordered_set<std::string> seen;
132 for (const auto& alias : collectAliases(contract)) {
133 if (alias.value().empty() || !seen.insert(alias.value()).second)
134 continue;
135 const auto it = existing.find(alias.value());
136 const int hitCount = it == existing.end()
137 ? std::max(1, alias.hitCount())
138 : std::max(1, it->second.hitCount);
139 const std::string createdAt =
140 it == existing.end() || it->second.createdAt.empty()
141 ? alias.createdAt()
142 : it->second.createdAt;
143 const std::string updatedAt =
144 it == existing.end() || it->second.updatedAt.empty()
145 ? alias.updatedAt()
146 : it->second.updatedAt;
147 const std::string lastUsedAt =
148 it == existing.end() || it->second.lastUsedAt.empty()
149 ? alias.lastUsedAt()
150 : it->second.lastUsedAt;
151 stmt.reset();
152 stmt.bindText(1, contract.id());
153 stmt.bindText(2, alias.value());
154 stmt.bindInt(3, hitCount);
155 stmt.bindText(4, createdAt);
156 stmt.bindText(5, updatedAt);
157 stmt.bindText(6, lastUsedAt);
158 stmt.step();
159 }
160}
161
162void insertContractRelations(sqlite3* db, const std::string& contractId,
163 const Contract& c) {
164 {
165 persistence::StmtGuard s(db, "INSERT OR IGNORE INTO contract_actors "
166 "(contract_id, actor_id) VALUES (?, ?);");
167 if (s)
168 for (const auto& id : c.actorIds()) {
169 if (id.empty())
170 continue;
171 s.reset();
172 s.bindText(1, contractId);
173 s.bindText(2, id);
174 s.step();
175 }
176 }
177 {
178 persistence::StmtGuard s(db, "INSERT OR IGNORE INTO contract_properties "
179 "(contract_id, property_id) VALUES (?, ?);");
180 if (s)
181 for (const auto& id : c.propertyIds()) {
182 if (id.empty())
183 continue;
184 s.reset();
185 s.bindText(1, contractId);
186 s.bindText(2, id);
187 s.step();
188 }
189 }
190}
191
192void deleteContractRelations(sqlite3* db, const std::string& contractId) {
194 db, "DELETE FROM contract_actors WHERE contract_id = ?;");
195 if (a) {
196 a.bindText(1, contractId);
197 a.step();
198 }
200 db, "DELETE FROM contract_properties WHERE contract_id = ?;");
201 if (p) {
202 p.bindText(1, contractId);
203 p.step();
204 }
205 deleteContractAliases(db, contractId);
206}
207
208constexpr auto kSelectContract =
209 "SELECT id, name, type, allocatable_mode FROM contracts";
210
211std::shared_ptr<Contract> readContractRow(persistence::StmtGuard& s) {
212 auto c = std::make_shared<Contract>();
213 c->setId(s.columnText(0));
214 c->rename(s.columnText(1));
215 c->setType(s.columnText(2));
217 return c;
218}
219
220void loadContractRelations(sqlite3* db, Contract& c) {
221 {
223 db, "SELECT actor_id FROM contract_actors WHERE contract_id = ?;");
224 if (s) {
225 s.bindText(1, c.id());
226 while (s.step() == SQLITE_ROW)
227 c.addActorId(s.columnText(0));
228 }
229 }
230 {
232 db,
233 "SELECT property_id FROM contract_properties WHERE contract_id = ?;");
234 if (s) {
235 s.bindText(1, c.id());
236 while (s.step() == SQLITE_ROW)
238 }
239 }
240}
241
242} // namespace
243
245 const std::shared_ptr<Contract>& contract) {
246 if (!contract || contract->id().empty())
247 return;
249 pimpl_->db->handle(),
250 "INSERT OR IGNORE INTO contracts (id, name, type, allocatable_mode, "
251 "created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?);");
252 if (!stmt)
253 return;
254 stmt.bindText(1, contract->id());
255 stmt.bindText(2, contract->name());
256 stmt.bindText(3, contract->type());
257 stmt.bindText(4, contract->allocatableMode());
258 stmt.bindText(5, contract->createdAt());
259 stmt.bindText(6, contract->updatedAt());
260 if (stmt.step() == SQLITE_DONE) {
261 replaceContractAliases(pimpl_->db->handle(), *contract, {});
262 insertContractRelations(pimpl_->db->handle(), contract->id(), *contract);
263 }
264}
265
266std::vector<std::shared_ptr<Contract>>
268 std::vector<std::shared_ptr<Contract>> out;
270 pimpl_->db->handle(),
271 "SELECT id, name, type, allocatable_mode, created_at, updated_at FROM "
272 "contracts ORDER BY id;");
273 if (!stmt)
274 return out;
275 while (stmt.step() == SQLITE_ROW) {
276 auto c = readContractRow(stmt);
277 c->setCreatedAt(stmt.columnText(4));
278 c->setUpdatedAt(stmt.columnText(5));
279 loadContractAliases(pimpl_->db->handle(), *c);
280 loadContractRelations(pimpl_->db->handle(), *c);
281 out.push_back(std::move(c));
282 }
283 return out;
284}
285
286std::optional<std::shared_ptr<Contract>>
287SqliteContractRepository::getContractById(const std::string& id) const {
289 pimpl_->db->handle(),
290 "SELECT id, name, type, allocatable_mode, created_at, updated_at FROM "
291 "contracts WHERE id = ? LIMIT 1;");
292 if (!stmt)
293 return std::nullopt;
294 stmt.bindText(1, id);
295 if (stmt.step() != SQLITE_ROW)
296 return std::nullopt;
297 auto c = readContractRow(stmt);
298 c->setCreatedAt(stmt.columnText(4));
299 c->setUpdatedAt(stmt.columnText(5));
300 loadContractAliases(pimpl_->db->handle(), *c);
301 loadContractRelations(pimpl_->db->handle(), *c);
302 return c;
303}
304
305void SqliteContractRepository::removeContract(const std::string& id) {
306 deleteContractRelations(pimpl_->db->handle(), id);
307 persistence::StmtGuard stmt(pimpl_->db->handle(),
308 "DELETE FROM contracts WHERE id = ?;");
309 if (!stmt)
310 return;
311 stmt.bindText(1, id);
312 stmt.step();
313}
314
316 const std::shared_ptr<Contract>& contract) {
317 if (!contract || contract->id().empty())
318 return;
319 const auto existing =
320 loadContractAliasStats(pimpl_->db->handle(), contract->id());
322 pimpl_->db->handle(),
323 "UPDATE contracts SET name = ?, type = ?, allocatable_mode = ?, "
324 "created_at = COALESCE(NULLIF(created_at, ''), ?), updated_at = ? WHERE "
325 "id = ?;");
326 if (!stmt)
327 return;
328 stmt.bindText(1, contract->name());
329 stmt.bindText(2, contract->type());
330 stmt.bindText(3, contract->allocatableMode());
331 stmt.bindText(4, contract->createdAt());
332 stmt.bindText(5, contract->updatedAt());
333 stmt.bindText(6, contract->id());
334 if (stmt.step() != SQLITE_DONE)
335 return;
336 deleteContractRelations(pimpl_->db->handle(), contract->id());
337 insertContractRelations(pimpl_->db->handle(), contract->id(), *contract);
338 replaceContractAliases(pimpl_->db->handle(), *contract, existing);
339}
340
342 const std::shared_ptr<Contract>& contract) {
343 if (!contract || contract->id().empty())
344 return;
345 updateContract(contract);
346 if (sqlite3_changes(pimpl_->db->handle()) == 0)
347 addContract(contract);
348}
349
351 char* err = nullptr;
352 sqlite3_exec(pimpl_->db->handle(),
353 "DELETE FROM contract_aliases; DELETE FROM contract_actors; "
354 "DELETE FROM contract_properties; DELETE FROM contracts;",
355 nullptr, nullptr, &err);
356 if (err)
357 sqlite3_free(err);
358}
359
360std::vector<std::shared_ptr<Contract>>
362 const std::string& actorId) const {
363 std::vector<std::shared_ptr<Contract>> out;
365 pimpl_->db->handle(),
366 "SELECT contract_id FROM contract_actors WHERE actor_id = ?;");
367 if (!ids)
368 return out;
369 ids.bindText(1, actorId);
370 while (ids.step() == SQLITE_ROW) {
371 auto c = getContractById(ids.columnText(0));
372 if (c)
373 out.push_back(*c);
374 }
375 return out;
376}
377
378std::vector<std::shared_ptr<Contract>>
380 const std::string& propertyId) const {
381 std::vector<std::shared_ptr<Contract>> out;
383 pimpl_->db->handle(),
384 "SELECT contract_id FROM contract_properties WHERE property_id = ?;");
385 if (!ids)
386 return out;
387 ids.bindText(1, propertyId);
388 while (ids.step() == SQLITE_ROW) {
389 auto c = getContractById(ids.columnText(0));
390 if (c)
391 out.push_back(*c);
392 }
393 return out;
394}
395
397 const std::string& contractId) const {
398 std::vector<std::string> out;
400 pimpl_->db->handle(),
401 "SELECT actor_id FROM contract_actors WHERE contract_id = ?;");
402 if (!stmt)
403 return out;
404 stmt.bindText(1, contractId);
405 while (stmt.step() == SQLITE_ROW)
406 out.push_back(stmt.columnText(0));
407 return out;
408}
409
411 const std::string& contractId) const {
412 std::vector<std::string> out;
414 pimpl_->db->handle(),
415 "SELECT property_id FROM contract_properties WHERE contract_id = ?;");
416 if (!stmt)
417 return out;
418 stmt.bindText(1, contractId);
419 while (stmt.step() == SQLITE_ROW)
420 out.push_back(stmt.columnText(0));
421 return out;
422}
Domain model for contracts.
Declares the SQLite-backed contract repository.
Declares the lightweight SQLite connection wrapper.
RAII wrapper for sqlite3_stmt* that auto-finalizes on destruction.
void setAliases(std::vector< Alias > value)
Definition Contract.cpp:88
const std::vector< Alias > & aliases() const noexcept
Definition Contract.cpp:190
void setType(ContractType value)
Sets the contract type from a normalized value object.
Definition Contract.cpp:24
void setId(std::string value)
Definition Contract.cpp:193
void addActorId(std::string value)
Adds an actor id when it is not already present.
Definition Contract.cpp:49
void setCreatedAt(std::string value)
Definition Contract.cpp:194
const std::string & id() const noexcept
Definition Contract.cpp:184
void setUpdatedAt(std::string value)
Definition Contract.cpp:195
void addPropertyId(std::string value)
Adds a property id when it is not already present.
Definition Contract.cpp:71
void rename(EntityName value)
Renames the contract with normalized entity text.
Definition Contract.cpp:16
void setAllocatableMode(std::string value)
Definition Contract.cpp:32
const std::vector< std::string > & actorIds() const noexcept
Definition Contract.cpp:188
const std::vector< std::string > & propertyIds() const noexcept
Definition Contract.cpp:189
std::vector< std::string > getPropertyIdsForContract(const std::string &contractId) const override
Retrieve property identifiers linked to a contract.
std::vector< std::string > getActorIdsForContract(const std::string &contractId) const override
Retrieve actor identifiers linked to a contract.
std::vector< std::shared_ptr< core::domain::Contract > > getContractsForActor(const std::string &actorId) const override
Retrieve contracts associated with a given actor.
void removeContract(const std::string &id) override
Remove a contract by identifier.
void updateContract(const std::shared_ptr< core::domain::Contract > &contract) override
Update an existing contract.
std::vector< std::shared_ptr< core::domain::Contract > > getContracts() const override
Retrieve all contracts.
void upsertContract(const std::shared_ptr< core::domain::Contract > &contract) override
Insert or update a contract.
std::vector< std::shared_ptr< core::domain::Contract > > getContractsForProperty(const std::string &propertyId) const override
Retrieve contracts associated with a given property.
void addContract(const std::shared_ptr< core::domain::Contract > &contract) override
Insert a new contract.
std::optional< std::shared_ptr< core::domain::Contract > > getContractById(const std::string &id) const override
Retrieve a contract by identifier.
~SqliteContractRepository() override
Destroy the repository.
SqliteContractRepository(const std::string &dbPath)
Create a contract repository for the database at the given path.
void clearContracts() override
Remove all contracts.
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