13#include <unordered_map>
14#include <unordered_set>
18 std::shared_ptr<SqliteDb>
db;
25 : pimpl_(std::make_unique<
Impl>()) {
27 throw std::runtime_error(
"db is null");
28 pimpl_->db = std::move(db);
37 std::string lastUsedAt;
38 std::string createdAt;
39 std::string updatedAt;
43 const std::string& createdAt,
44 const std::string& updatedAt,
45 const std::string& lastUsedAt) {
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())
65 out.push_back(std::move(value));
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 = ?;");
78 stmt.bindText(1, contractId);
79 while (stmt.step() == SQLITE_ROW) {
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));
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;");
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)));
107void deleteContractAliases(sqlite3* db,
const std::string& contractId) {
109 db,
"DELETE FROM contract_aliases WHERE contract_id = ?;");
112 stmt.bindText(1, contractId);
116void replaceContractAliases(
117 sqlite3* db,
const Contract& contract,
118 const std::unordered_map<std::string, AliasStats>& existing) {
119 if (contract.
id().empty())
121 deleteContractAliases(db, contract.
id());
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));");
131 std::unordered_set<std::string> seen;
132 for (
const auto& alias : collectAliases(contract)) {
133 if (alias.value().empty() || !seen.insert(alias.value()).second)
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()
142 : it->second.createdAt;
143 const std::string updatedAt =
144 it == existing.end() || it->second.updatedAt.empty()
146 : it->second.updatedAt;
147 const std::string lastUsedAt =
148 it == existing.end() || it->second.lastUsedAt.empty()
150 : it->second.lastUsedAt;
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);
162void insertContractRelations(sqlite3* db,
const std::string& contractId,
166 "(contract_id, actor_id) VALUES (?, ?);");
168 for (
const auto&
id : c.
actorIds()) {
172 s.bindText(1, contractId);
179 "(contract_id, property_id) VALUES (?, ?);");
185 s.bindText(1, contractId);
192void deleteContractRelations(sqlite3* db,
const std::string& contractId) {
194 db,
"DELETE FROM contract_actors WHERE contract_id = ?;");
196 a.bindText(1, contractId);
200 db,
"DELETE FROM contract_properties WHERE contract_id = ?;");
202 p.bindText(1, contractId);
205 deleteContractAliases(db, contractId);
208constexpr auto kSelectContract =
209 "SELECT id, name, type, allocatable_mode FROM contracts";
212 auto c = std::make_shared<Contract>();
220void loadContractRelations(sqlite3* db,
Contract& c) {
223 db,
"SELECT actor_id FROM contract_actors WHERE contract_id = ?;");
226 while (s.
step() == SQLITE_ROW)
233 "SELECT property_id FROM contract_properties WHERE contract_id = ?;");
236 while (s.
step() == SQLITE_ROW)
245 const std::shared_ptr<Contract>& contract) {
246 if (!contract || contract->id().empty())
249 pimpl_->db->handle(),
250 "INSERT OR IGNORE INTO contracts (id, name, type, allocatable_mode, "
251 "created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?);");
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);
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;");
275 while (stmt.
step() == SQLITE_ROW) {
276 auto c = readContractRow(stmt);
279 loadContractAliases(pimpl_->db->handle(), *c);
280 loadContractRelations(pimpl_->db->handle(), *c);
281 out.push_back(std::move(c));
286std::optional<std::shared_ptr<Contract>>
289 pimpl_->db->handle(),
290 "SELECT id, name, type, allocatable_mode, created_at, updated_at FROM "
291 "contracts WHERE id = ? LIMIT 1;");
295 if (stmt.
step() != SQLITE_ROW)
297 auto c = readContractRow(stmt);
300 loadContractAliases(pimpl_->db->handle(), *c);
301 loadContractRelations(pimpl_->db->handle(), *c);
306 deleteContractRelations(pimpl_->db->handle(),
id);
308 "DELETE FROM contracts WHERE id = ?;");
316 const std::shared_ptr<Contract>& contract) {
317 if (!contract || contract->id().empty())
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 "
330 stmt.
bindText(3, contract->allocatableMode());
331 stmt.
bindText(4, contract->createdAt());
332 stmt.
bindText(5, contract->updatedAt());
334 if (stmt.
step() != SQLITE_DONE)
336 deleteContractRelations(pimpl_->db->handle(), contract->id());
337 insertContractRelations(pimpl_->db->handle(), contract->id(), *contract);
338 replaceContractAliases(pimpl_->db->handle(), *contract, existing);
342 const std::shared_ptr<Contract>& contract) {
343 if (!contract || contract->id().empty())
346 if (sqlite3_changes(pimpl_->db->handle()) == 0)
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);
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 = ?;");
370 while (ids.
step() == SQLITE_ROW) {
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 = ?;");
388 while (ids.
step() == SQLITE_ROW) {
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 = ?;");
405 while (stmt.
step() == SQLITE_ROW)
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 = ?;");
419 while (stmt.
step() == SQLITE_ROW)
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)
const std::vector< Alias > & aliases() const noexcept
void setType(ContractType value)
Sets the contract type from a normalized value object.
void setId(std::string value)
void addActorId(std::string value)
Adds an actor id when it is not already present.
void setCreatedAt(std::string value)
const std::string & id() const noexcept
void setUpdatedAt(std::string value)
void addPropertyId(std::string value)
Adds a property id when it is not already present.
void rename(EntityName value)
Renames the contract with normalized entity text.
void setAllocatableMode(std::string value)
const std::vector< std::string > & actorIds() const noexcept
const std::vector< std::string > & propertyIds() const noexcept
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)
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