FOSSredder 0.5.0
Loading...
Searching...
No Matches
SqliteAnalysisRepository.cpp
Go to the documentation of this file.
1
5
7
11
12#include <sqlite3.h>
13
14#include <stdexcept>
15#include <string>
16#include <unordered_map>
17#include <utility>
18#include <vector>
19
21 std::shared_ptr<SqliteDb> db;
22};
23
24namespace {
25
26std::unordered_map<std::string, double>
27loadAdjustments(sqlite3* db, const std::string& analysisId) {
28 std::unordered_map<std::string, double> out;
29 if (!db || analysisId.empty()) {
30 return out;
31 }
32
34 db, "SELECT adjustment_key, adjustment_value FROM analysis_adjustments "
35 "WHERE analysis_id = ? ORDER BY position, adjustment_key;");
36 if (!stmt) {
37 return out;
38 }
39
40 stmt.bindText(1, analysisId);
41 while (stmt.step() == SQLITE_ROW) {
42 out.emplace(stmt.columnText(0), stmt.columnDouble(1));
43 }
44
45 return out;
46}
47
48void saveAdjustments(sqlite3* db, const Analysis& analysis) {
49 if (!db || analysis.id().empty()) {
50 return;
51 }
52
53 persistence::StmtGuard deleteStmt(
54 db, "DELETE FROM analysis_adjustments WHERE analysis_id = ?;");
55 if (!deleteStmt) {
56 return;
57 }
58 deleteStmt.bindText(1, analysis.id());
59 deleteStmt.step();
60
61 persistence::StmtGuard insertStmt(
62 db, "INSERT OR REPLACE INTO analysis_adjustments (analysis_id, "
63 "adjustment_key, adjustment_value, position) VALUES (?, ?, ?, ?);");
64 if (!insertStmt) {
65 return;
66 }
67
68 std::size_t position = 0;
69 for (const auto& [key, value] : analysis.adjustments()) {
70 if (key.empty()) {
71 continue;
72 }
73
74 insertStmt.reset();
75 insertStmt.bindText(1, analysis.id());
76 insertStmt.bindText(2, key);
77 insertStmt.bindDouble(3, value);
78 insertStmt.bindInt(4, static_cast<int>(position++));
79 insertStmt.step();
80 }
81}
82
83std::shared_ptr<Analysis> readAnalysis(persistence::StmtGuard& stmt,
84 sqlite3* db) {
85 auto analysis = std::make_shared<Analysis>();
86 analysis->setId(stmt.columnText(0));
87 analysis->rename(stmt.columnText(1));
88 analysis->setType(stmt.columnText(2));
89 analysis->setConfigJson(stmt.columnText(3));
90 analysis->setFilterSpec(stmt.columnText(4));
91 analysis->setExportFormat(stmt.columnText(5));
92 analysis->setIncludeCalculationAdjustments(stmt.columnInt(6) != 0);
93 analysis->setExportStateJson(stmt.columnText(7));
94 analysis->setSnapshotTransactionsJson(stmt.columnText(8));
95 analysis->setCreatedAt(stmt.columnText(9));
96 analysis->setUpdatedAt(stmt.columnText(10));
97
98 for (const auto& [key, value] : loadAdjustments(db, analysis->id())) {
99 analysis->setAdjustment(key, value);
100 }
101
102 return analysis;
103}
104
105} // namespace
106
108 : SqliteAnalysisRepository(std::make_shared<SqliteDb>(dbPath)) {}
109
111 : pimpl_(std::make_unique<Impl>()) {
112 if (!db) {
113 throw std::runtime_error("db is null");
114 }
115
116 pimpl_->db = std::move(db);
117}
118
120
122 const std::shared_ptr<Analysis>& analysis) {
123 if (!analysis || analysis->id().empty()) {
124 return;
125 }
126
128 pimpl_->db->handle(),
129 "INSERT OR IGNORE INTO analyses (id, name, type, config_json, "
130 "filter_spec, export_format, include_calc_adjustments, "
131 "export_state_json, snapshot_transactions_json, created_at, updated_at)"
132 " VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);");
133 if (!stmt) {
134 return;
135 }
136
137 stmt.bindText(1, analysis->id());
138 stmt.bindText(2, analysis->name());
139 stmt.bindText(3, analysis->type());
140 stmt.bindText(4, analysis->configJson());
141 stmt.bindText(5, analysis->filterSpec());
142 stmt.bindText(6, analysis->exportFormat());
143 stmt.bindInt(7, analysis->includeCalculationAdjustments() ? 1 : 0);
144 stmt.bindText(8, analysis->exportStateJson());
145 stmt.bindText(9, analysis->snapshotTransactionsJson());
146 stmt.bindText(10, analysis->createdAt());
147 stmt.bindText(11, analysis->updatedAt());
148 if (stmt.step() == SQLITE_DONE) {
149 saveAdjustments(pimpl_->db->handle(), *analysis);
150 }
151}
152
153std::vector<std::shared_ptr<Analysis>>
155 std::vector<std::shared_ptr<Analysis>> out;
157 pimpl_->db->handle(),
158 "SELECT id, name, type, config_json, filter_spec, export_format, "
159 "include_calc_adjustments, export_state_json, "
160 "snapshot_transactions_json, created_at, updated_at FROM analyses ORDER "
161 "BY id;");
162 if (!stmt) {
163 return out;
164 }
165
166 while (stmt.step() == SQLITE_ROW) {
167 out.push_back(readAnalysis(stmt, pimpl_->db->handle()));
168 }
169 return out;
170}
171
172std::optional<std::shared_ptr<Analysis>>
173SqliteAnalysisRepository::getAnalysisById(const std::string& id) const {
175 pimpl_->db->handle(),
176 "SELECT id, name, type, config_json, filter_spec, export_format, "
177 "include_calc_adjustments, export_state_json, "
178 "snapshot_transactions_json, created_at, updated_at FROM analyses WHERE "
179 "id = ? LIMIT 1;");
180 if (!stmt) {
181 return std::nullopt;
182 }
183
184 stmt.bindText(1, id);
185 if (stmt.step() != SQLITE_ROW) {
186 return std::nullopt;
187 }
188
189 return readAnalysis(stmt, pimpl_->db->handle());
190}
191
192void SqliteAnalysisRepository::removeAnalysis(const std::string& id) {
193 persistence::StmtGuard stmt(pimpl_->db->handle(),
194 "DELETE FROM analyses WHERE id = ?;");
195 if (!stmt) {
196 return;
197 }
198
199 stmt.bindText(1, id);
200 stmt.step();
201}
202
204 const std::shared_ptr<Analysis>& analysis) {
205 if (!analysis || analysis->id().empty()) {
206 return;
207 }
208
210 pimpl_->db->handle(),
211 "UPDATE analyses SET name=?, type=?, config_json=?, filter_spec=?, "
212 "export_format=?, include_calc_adjustments=?, export_state_json=?, "
213 "snapshot_transactions_json=?, created_at=?, updated_at=?"
214 " WHERE id=?;");
215 if (!stmt) {
216 return;
217 }
218
219 stmt.bindText(1, analysis->name());
220 stmt.bindText(2, analysis->type());
221 stmt.bindText(3, analysis->configJson());
222 stmt.bindText(4, analysis->filterSpec());
223 stmt.bindText(5, analysis->exportFormat());
224 stmt.bindInt(6, analysis->includeCalculationAdjustments() ? 1 : 0);
225 stmt.bindText(7, analysis->exportStateJson());
226 stmt.bindText(8, analysis->snapshotTransactionsJson());
227 stmt.bindText(9, analysis->createdAt());
228 stmt.bindText(10, analysis->updatedAt());
229 stmt.bindText(11, analysis->id());
230 if (stmt.step() == SQLITE_DONE) {
231 saveAdjustments(pimpl_->db->handle(), *analysis);
232 }
233}
234
236 const std::shared_ptr<Analysis>& analysis) {
237 if (!analysis || analysis->id().empty()) {
238 return;
239 }
240
241 updateAnalysis(analysis);
242 if (sqlite3_changes(pimpl_->db->handle()) == 0) {
243 addAnalysis(analysis);
244 }
245}
246
248 char* err = nullptr;
249 sqlite3_exec(pimpl_->db->handle(), "DELETE FROM analyses;", nullptr, nullptr,
250 &err);
251 if (err) {
252 sqlite3_free(err);
253 }
254}
255
256sqlite3* SqliteAnalysisRepository::sqliteHandle() const noexcept {
257 return pimpl_ && pimpl_->db ? pimpl_->db->handle() : nullptr;
258}
Domain model for persisted analysis definitions.
Declares the SQLite-backed analysis repository.
Declares the lightweight SQLite connection wrapper.
RAII wrapper for sqlite3_stmt* that auto-finalizes on destruction.
void rename(EntityName value)
Renames the analysis with normalized entity text.
Definition Analysis.cpp:16
void setAdjustment(std::string key, double value)
Sets or updates a normalized adjustment entry.
Definition Analysis.cpp:111
void setCreatedAt(std::string value)
Definition Analysis.cpp:206
void setExportStateJson(std::string value)
Stores the export state JSON snapshot.
Definition Analysis.cpp:56
void setType(AnalysisType value)
Sets the analysis type from a value object.
Definition Analysis.cpp:24
const std::string & id() const noexcept
Definition Analysis.cpp:193
void setUpdatedAt(std::string value)
Definition Analysis.cpp:207
void setSnapshotTransactionsJson(std::string value)
Stores the snapshot transactions JSON.
Definition Analysis.cpp:60
void setId(std::string value)
Definition Analysis.cpp:205
void setExportFormat(ExportFormat value)
Sets the export format from a value object.
Definition Analysis.cpp:44
void setFilterSpec(FilterSpec value)
Sets the filter specification from a value object.
Definition Analysis.cpp:36
void setConfigJson(std::string value)
Stores the raw configuration JSON.
Definition Analysis.cpp:32
const std::unordered_map< std::string, double > & adjustments() const noexcept
Definition Analysis.cpp:202
void setIncludeCalculationAdjustments(bool value)
Enables or disables calculation adjustments.
Definition Analysis.cpp:52
void addAnalysis(const std::shared_ptr< core::domain::Analysis > &analysis) override
Insert a new analysis.
SqliteAnalysisRepository(const std::string &dbPath)
Create an analysis repository for the database at the given path.
std::vector< std::shared_ptr< core::domain::Analysis > > getAnalyses() const override
Retrieve all analyses.
std::optional< std::shared_ptr< core::domain::Analysis > > getAnalysisById(const std::string &id) const override
Retrieve an analysis by identifier.
void upsertAnalysis(const std::shared_ptr< core::domain::Analysis > &analysis) override
Insert or update an analysis.
void clearAnalyses() override
Remove all analyses.
~SqliteAnalysisRepository() override
Destroy the repository.
void removeAnalysis(const std::string &id) override
Remove an analysis by identifier.
sqlite3 * sqliteHandle() const noexcept override
Return the SQLite handle.
void updateAnalysis(const std::shared_ptr< core::domain::Analysis > &analysis) override
Update an existing analysis.
std::string columnText(int col) const noexcept
Definition StmtGuard.h:57
void bindInt(int col, int v) noexcept
Definition StmtGuard.h:47
double columnDouble(int col) const noexcept
Definition StmtGuard.h:67
int step() noexcept
Definition StmtGuard.h:37
void bindText(int col, const std::string &v) noexcept
Definition StmtGuard.h:44
int columnInt(int col) const noexcept
Definition StmtGuard.h:61