FOSSredder 0.5.0
Loading...
Searching...
No Matches
SqlitePropertyRepository.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 std::shared_ptr<core::ports::diagnostics::IErrorReporter> errorReporter;
20};
21
22namespace {
23
24constexpr auto kSelectProperty = "SELECT id, name FROM properties";
25
26struct AliasStats {
27 int hitCount = 1;
28 std::string lastUsedAt;
29 std::string createdAt;
30 std::string updatedAt;
31};
32
33core::domain::Alias makeAlias(const std::string& alias, int hitCount,
34 const std::string& createdAt,
35 const std::string& updatedAt,
36 const std::string& lastUsedAt) {
38 value.setValue(alias);
39 value.setSource(alias);
40 value.setHitCount(hitCount);
41 value.setCreatedAt(createdAt);
42 value.setUpdatedAt(updatedAt);
43 value.setLastUsedAt(lastUsedAt);
44 return value;
45}
46
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())
52 continue;
53 auto value = alias;
54 if (value.hitCount() < 1)
55 value.setHitCount(1);
56 out.push_back(std::move(value));
57 }
58 return out;
59}
60
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 = ?;");
67 if (!stmt)
68 return out;
69
70 stmt.bindText(1, propertyId);
71 while (stmt.step() == SQLITE_ROW) {
72 AliasStats stats;
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));
78 }
79
80 return out;
81}
82
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;");
88 if (!stmt)
89 return;
90
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),
96 stmt.columnText(4)));
97 }
98 property.setAliases(std::move(aliases));
99}
100
101void deletePropertyAliases(sqlite3* db, const std::string& propertyId) {
103 db, "DELETE FROM property_aliases WHERE property_id = ?;");
104 if (!stmt)
105 return;
106
107 stmt.bindText(1, propertyId);
108 stmt.step();
109}
110
111void replacePropertyAliases(
112 sqlite3* db, const Property& property,
113 const std::unordered_map<std::string, AliasStats>& existing) {
114 if (property.id().empty())
115 return;
116
117 deletePropertyAliases(db, property.id());
118
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));");
124 if (!stmt)
125 return;
126
127 std::unordered_set<std::string> seen;
128 for (const auto& alias : collectAliases(property)) {
129 if (alias.value().empty() || !seen.insert(alias.value()).second)
130 continue;
131
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()
138 ? alias.createdAt()
139 : it->second.createdAt;
140 const std::string updatedAt =
141 it == existing.end() || it->second.updatedAt.empty()
142 ? alias.updatedAt()
143 : it->second.updatedAt;
144 const std::string lastUsedAt =
145 it == existing.end() || it->second.lastUsedAt.empty()
146 ? alias.lastUsedAt()
147 : it->second.lastUsedAt;
148 stmt.reset();
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);
155 stmt.step();
156 }
157}
158
159} // namespace
160
162 : SqlitePropertyRepository(std::make_shared<SqliteDb>(dbPath), nullptr) {}
163
165 const std::string& dbPath,
166 std::shared_ptr<core::ports::diagnostics::IErrorReporter> er)
167 : SqlitePropertyRepository(std::make_shared<SqliteDb>(dbPath),
168 std::move(er)) {}
169
171 : SqlitePropertyRepository(std::move(db), nullptr) {}
172
174 std::shared_ptr<SqliteDb> db,
175 std::shared_ptr<core::ports::diagnostics::IErrorReporter> er)
176 : pimpl_(std::make_unique<Impl>()) {
177 if (!db)
178 throw std::runtime_error("db is null");
179 pimpl_->db = std::move(db);
180 pimpl_->errorReporter = std::move(er);
181}
182
184
185namespace {
186
187std::shared_ptr<Property> readProperty(persistence::StmtGuard& s) {
188 auto p = std::make_shared<Property>();
189 p->setId(s.columnText(0));
190 p->rename(s.columnText(1));
191 return p;
192}
193
194} // namespace
195
197 const std::shared_ptr<Property>& property) {
198 if (!property || property->id().empty())
199 return;
200 persistence::StmtGuard stmt(pimpl_->db->handle(),
201 "INSERT OR IGNORE INTO properties (id, name, "
202 "created_at, updated_at) VALUES (?, ?, ?, ?);");
203 if (!stmt)
204 return;
205 stmt.bindText(1, property->id());
206 stmt.bindText(2, property->name());
207 stmt.bindText(3, property->createdAt());
208 stmt.bindText(4, property->updatedAt());
209 if (stmt.step() == SQLITE_DONE) {
210 replacePropertyAliases(pimpl_->db->handle(), *property, {});
211 }
212}
213
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;");
220 if (!stmt)
221 return out;
222 while (stmt.step() == SQLITE_ROW) {
223 auto property = readProperty(stmt);
224 property->setCreatedAt(stmt.columnText(2));
225 property->setUpdatedAt(stmt.columnText(3));
226 loadPropertyAliases(pimpl_->db->handle(), *property);
227 out.push_back(std::move(property));
228 }
229 return out;
230}
231
232std::optional<std::shared_ptr<Property>>
233SqlitePropertyRepository::getPropertyById(const std::string& id) const {
234 if (id.empty())
235 return std::nullopt;
237 pimpl_->db->handle(),
238 "SELECT id, name, created_at, updated_at FROM properties WHERE id = ?;");
239 if (!stmt)
240 return std::nullopt;
241 stmt.bindText(1, id);
242 if (stmt.step() != SQLITE_ROW)
243 return std::nullopt;
244 auto property = readProperty(stmt);
245 property->setCreatedAt(stmt.columnText(2));
246 property->setUpdatedAt(stmt.columnText(3));
247 loadPropertyAliases(pimpl_->db->handle(), *property);
248 return property;
249}
250
251void SqlitePropertyRepository::removeProperty(const std::string& id) {
252 if (id.empty())
253 return;
254 persistence::StmtGuard stmt(pimpl_->db->handle(),
255 "DELETE FROM properties WHERE id = ?;");
256 if (!stmt)
257 return;
258 stmt.bindText(1, id);
259 stmt.step();
260}
261
263 const std::shared_ptr<Property>& property) {
264 if (!property || property->id().empty())
265 return;
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 = ?;");
272 if (!stmt)
273 return;
274 stmt.bindText(1, property->name());
275 stmt.bindText(2, property->createdAt());
276 stmt.bindText(3, property->updatedAt());
277 stmt.bindText(4, property->id());
278 if (stmt.step() == SQLITE_DONE) {
279 replacePropertyAliases(pimpl_->db->handle(), *property, existing);
280 }
281}
282
284 const std::shared_ptr<Property>& property) {
285 if (!property || property->id().empty())
286 return;
287 updateProperty(property);
288 if (sqlite3_changes(pimpl_->db->handle()) == 0)
289 addProperty(property);
290}
291
293 char* err = nullptr;
294 sqlite3_exec(pimpl_->db->handle(),
295 "DELETE FROM property_aliases; DELETE FROM properties;", nullptr,
296 nullptr, &err);
297 if (err)
298 sqlite3_free(err);
299}
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
Definition Property.cpp:88
const std::vector< Alias > & aliases() const noexcept
Definition Property.cpp:90
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)
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< core::ports::diagnostics::IErrorReporter > errorReporter