16#include <unordered_map>
21 std::shared_ptr<SqliteDb>
db;
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()) {
34 db,
"SELECT adjustment_key, adjustment_value FROM analysis_adjustments "
35 "WHERE analysis_id = ? ORDER BY position, adjustment_key;");
41 while (stmt.
step() == SQLITE_ROW) {
48void saveAdjustments(sqlite3* db,
const Analysis& analysis) {
49 if (!db || analysis.
id().empty()) {
54 db,
"DELETE FROM analysis_adjustments WHERE analysis_id = ?;");
58 deleteStmt.bindText(1, analysis.
id());
62 db,
"INSERT OR REPLACE INTO analysis_adjustments (analysis_id, "
63 "adjustment_key, adjustment_value, position) VALUES (?, ?, ?, ?);");
68 std::size_t position = 0;
69 for (
const auto& [key, value] : analysis.
adjustments()) {
75 insertStmt.bindText(1, analysis.
id());
76 insertStmt.bindText(2, key);
77 insertStmt.bindDouble(3, value);
78 insertStmt.bindInt(4,
static_cast<int>(position++));
85 auto analysis = std::make_shared<Analysis>();
98 for (
const auto& [key, value] : loadAdjustments(db, analysis->
id())) {
111 : pimpl_(std::make_unique<
Impl>()) {
113 throw std::runtime_error(
"db is null");
116 pimpl_->db = std::move(db);
122 const std::shared_ptr<Analysis>& analysis) {
123 if (!analysis || analysis->id().empty()) {
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 (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);");
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);
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 "
166 while (stmt.
step() == SQLITE_ROW) {
167 out.push_back(readAnalysis(stmt, pimpl_->db->handle()));
172std::optional<std::shared_ptr<Analysis>>
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 "
185 if (stmt.
step() != SQLITE_ROW) {
189 return readAnalysis(stmt, pimpl_->db->handle());
194 "DELETE FROM analyses WHERE id = ?;");
204 const std::shared_ptr<Analysis>& analysis) {
205 if (!analysis || analysis->id().empty()) {
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=?"
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());
230 if (stmt.
step() == SQLITE_DONE) {
231 saveAdjustments(pimpl_->db->handle(), *analysis);
236 const std::shared_ptr<Analysis>& analysis) {
237 if (!analysis || analysis->id().empty()) {
242 if (sqlite3_changes(pimpl_->db->handle()) == 0) {
249 sqlite3_exec(pimpl_->db->handle(),
"DELETE FROM analyses;",
nullptr,
nullptr,
257 return pimpl_ && pimpl_->db ? pimpl_->db->handle() :
nullptr;
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.
void setAdjustment(std::string key, double value)
Sets or updates a normalized adjustment entry.
void setCreatedAt(std::string value)
void setExportStateJson(std::string value)
Stores the export state JSON snapshot.
void setType(AnalysisType value)
Sets the analysis type from a value object.
const std::string & id() const noexcept
void setUpdatedAt(std::string value)
void setSnapshotTransactionsJson(std::string value)
Stores the snapshot transactions JSON.
void setId(std::string value)
void setExportFormat(ExportFormat value)
Sets the export format from a value object.
void setFilterSpec(FilterSpec value)
Sets the filter specification from a value object.
void setConfigJson(std::string value)
Stores the raw configuration JSON.
const std::unordered_map< std::string, double > & adjustments() const noexcept
void setIncludeCalculationAdjustments(bool value)
Enables or disables calculation adjustments.
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
void bindInt(int col, int v) noexcept
double columnDouble(int col) const noexcept
void bindText(int col, const std::string &v) noexcept
int columnInt(int col) const noexcept
std::shared_ptr< SqliteDb > db