13#include <unordered_map>
14#include <unordered_set>
18 std::shared_ptr<SqliteDb>
db;
19 std::shared_ptr<core::ports::diagnostics::IErrorReporter>
errorReporter;
24constexpr auto kSelectProperty =
"SELECT id, name FROM properties";
28 std::string lastUsedAt;
29 std::string createdAt;
30 std::string updatedAt;
34 const std::string& createdAt,
35 const std::string& updatedAt,
36 const std::string& lastUsedAt) {
47std::vector<core::domain::Alias> collectAliases(
const Property& property) {
48 std::vector<core::domain::Alias> out;
49 out.reserve(property.
aliases().size());
50 for (
const auto& alias : property.
aliases()) {
51 if (alias.value().empty())
56 out.push_back(std::move(value));
61std::unordered_map<std::string, AliasStats>
62loadPropertyAliasStats(sqlite3* db,
const std::string& propertyId) {
63 std::unordered_map<std::string, AliasStats> out;
65 db,
"SELECT alias, hit_count, created_at, updated_at, last_used_at FROM "
66 "property_aliases WHERE property_id = ?;");
70 stmt.bindText(1, propertyId);
71 while (stmt.step() == SQLITE_ROW) {
73 stats.hitCount = stmt.columnInt(1);
74 stats.createdAt = stmt.columnText(2);
75 stats.updatedAt = stmt.columnText(3);
76 stats.lastUsedAt = stmt.columnText(4);
77 out.emplace(stmt.columnText(0), std::move(stats));
83void loadPropertyAliases(sqlite3* db,
Property& property) {
85 db,
"SELECT alias, hit_count, created_at, updated_at, last_used_at FROM "
86 "property_aliases WHERE property_id = ? ORDER BY hit_count DESC, "
87 "last_used_at DESC, alias ASC;");
91 stmt.bindText(1, property.
id());
92 std::vector<core::domain::Alias> aliases;
93 while (stmt.step() == SQLITE_ROW) {
94 aliases.push_back(makeAlias(stmt.columnText(0), stmt.columnInt(1),
95 stmt.columnText(2), stmt.columnText(3),
98 property.setAliases(std::move(aliases));
101void deletePropertyAliases(sqlite3* db,
const std::string& propertyId) {
103 db,
"DELETE FROM property_aliases WHERE property_id = ?;");
107 stmt.bindText(1, propertyId);
111void replacePropertyAliases(
112 sqlite3* db,
const Property& property,
113 const std::unordered_map<std::string, AliasStats>& existing) {
114 if (property.
id().empty())
117 deletePropertyAliases(db, property.
id());
120 db,
"INSERT INTO property_aliases (property_id, alias, hit_count, "
121 "created_at, updated_at, last_used_at) VALUES (?, ?, ?, "
122 "COALESCE(NULLIF(?, ''), CURRENT_TIMESTAMP), COALESCE(NULLIF(?, ''), "
123 "CURRENT_TIMESTAMP), COALESCE(NULLIF(?, ''), CURRENT_TIMESTAMP));");
127 std::unordered_set<std::string> seen;
128 for (
const auto& alias : collectAliases(property)) {
129 if (alias.value().empty() || !seen.insert(alias.value()).second)
132 const auto it = existing.find(alias.value());
133 const int hitCount = it == existing.end()
134 ? std::max(1, alias.hitCount())
135 : std::max(1, it->second.hitCount);
136 const std::string createdAt =
137 it == existing.end() || it->second.createdAt.empty()
139 : it->second.createdAt;
140 const std::string updatedAt =
141 it == existing.end() || it->second.updatedAt.empty()
143 : it->second.updatedAt;
144 const std::string lastUsedAt =
145 it == existing.end() || it->second.lastUsedAt.empty()
147 : it->second.lastUsedAt;
149 stmt.bindText(1, property.
id());
150 stmt.bindText(2, alias.value());
151 stmt.bindInt(3, hitCount);
152 stmt.bindText(4, createdAt);
153 stmt.bindText(5, updatedAt);
154 stmt.bindText(6, lastUsedAt);
165 const std::string& dbPath,
166 std::shared_ptr<core::ports::diagnostics::IErrorReporter> er)
174 std::shared_ptr<SqliteDb> db,
175 std::shared_ptr<core::ports::diagnostics::IErrorReporter> er)
176 : pimpl_(std::make_unique<
Impl>()) {
178 throw std::runtime_error(
"db is null");
179 pimpl_->db = std::move(db);
180 pimpl_->errorReporter = std::move(er);
188 auto p = std::make_shared<Property>();
197 const std::shared_ptr<Property>& property) {
198 if (!property || property->id().empty())
201 "INSERT OR IGNORE INTO properties (id, name, "
202 "created_at, updated_at) VALUES (?, ?, ?, ?);");
207 stmt.
bindText(3, property->createdAt());
208 stmt.
bindText(4, property->updatedAt());
209 if (stmt.
step() == SQLITE_DONE) {
210 replacePropertyAliases(pimpl_->db->handle(), *property, {});
214std::vector<std::shared_ptr<Property>>
216 std::vector<std::shared_ptr<Property>> out;
218 pimpl_->db->handle(),
219 "SELECT id, name, created_at, updated_at FROM properties ORDER BY id;");
222 while (stmt.
step() == SQLITE_ROW) {
223 auto property = readProperty(stmt);
226 loadPropertyAliases(pimpl_->db->handle(), *property);
227 out.push_back(std::move(property));
232std::optional<std::shared_ptr<Property>>
237 pimpl_->db->handle(),
238 "SELECT id, name, created_at, updated_at FROM properties WHERE id = ?;");
242 if (stmt.
step() != SQLITE_ROW)
244 auto property = readProperty(stmt);
247 loadPropertyAliases(pimpl_->db->handle(), *property);
255 "DELETE FROM properties WHERE id = ?;");
263 const std::shared_ptr<Property>& property) {
264 if (!property || property->id().empty())
266 const auto existing =
267 loadPropertyAliasStats(pimpl_->db->handle(), property->id());
269 pimpl_->db->handle(),
270 "UPDATE properties SET name = ?, created_at = "
271 "COALESCE(NULLIF(created_at, ''), ?), updated_at = ? WHERE id = ?;");
275 stmt.
bindText(2, property->createdAt());
276 stmt.
bindText(3, property->updatedAt());
278 if (stmt.
step() == SQLITE_DONE) {
279 replacePropertyAliases(pimpl_->db->handle(), *property, existing);
284 const std::shared_ptr<Property>& property) {
285 if (!property || property->id().empty())
288 if (sqlite3_changes(pimpl_->db->handle()) == 0)
294 sqlite3_exec(pimpl_->db->handle(),
295 "DELETE FROM property_aliases; DELETE FROM properties;",
nullptr,
Domain model for properties.
Declares the lightweight SQLite connection wrapper.
Declares the SQLite-backed property repository.
RAII wrapper for sqlite3_stmt* that auto-finalizes on destruction.
const std::string & id() const noexcept
const std::vector< Alias > & aliases() const noexcept
SqlitePropertyRepository(const std::string &dbPath)
Create a property repository for the database at the given path.
void upsertProperty(const std::shared_ptr< core::domain::Property > &property) override
Insert or update a property.
~SqlitePropertyRepository() override
Destroy the repository.
void clearProperties() override
Remove all properties.
void addProperty(const std::shared_ptr< core::domain::Property > &property) override
Insert a new property.
std::optional< std::shared_ptr< core::domain::Property > > getPropertyById(const std::string &id) const override
Retrieve a property by identifier.
void updateProperty(const std::shared_ptr< core::domain::Property > &property) override
Update an existing property.
void removeProperty(const std::string &id) override
Remove a property by identifier.
std::vector< std::shared_ptr< core::domain::Property > > getProperties() const override
Retrieve all properties.
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< core::ports::diagnostics::IErrorReporter > errorReporter
std::shared_ptr< SqliteDb > db