kima2/src/core/database.cpp

747 lines
26 KiB
C++

#include "database.h"
#include <filesystem>
#include <iostream>
#include <stdexcept>
#include <vector>
#include "boost/date_time/posix_time/posix_time.hpp"
Database::Database(const std::string& dbname)
{
dbname_ = dbname;
init();
}
Database::Database()
{
namespace fs = std::filesystem;
#if defined(__linux__) || defined(__APPLE__)
fs::path dbpath = fs::path(std::getenv("HOME")) / ".local/share/kima2";
#elif defined(_WIN64) || defined(_WIN32)
fs::path dbpath = fs::path(std::getenv("LOCALAPPDATA")) / "kima2";
#else
throw std::runtime_error("Platform not supported.");
#endif
if (!fs::exists(dbpath)) {
try {
fs::create_directories(dbpath);
} catch (fs::filesystem_error& err) {
throw err;
}
}
dbpath /= "kima2.db";
dbname_ = dbpath.string();
init();
}
void Database::newDb()
{
namespace fs = std::filesystem;
sqlite3_close(db_);
fs::path sourcePath = dbname_;
fs::path destPath = sourcePath.parent_path() / sourcePath.stem();
destPath += std::string("_") +=
boost::posix_time::to_iso_string(boost::posix_time::second_clock::local_time()) += ".db";
fs::copy_file(sourcePath, destPath, fs::copy_options::overwrite_existing);
fs::remove(sourcePath);
init();
}
Database::~Database() { sqlite3_close(db_); }
void Database::exec(const std::string& sql)
{
char* errMsg;
const int errCode = sqlite3_exec(db_, sql.c_str(), nullptr, nullptr, &errMsg);
if (errCode) {
std::string errMsgString(errMsg); // Make a C++ string of the errMsg, so that we can call
// sqlite3_free() before throwing the exception
sqlite3_free(errMsg);
throw std::runtime_error("Error in SQL execution: " + errMsgString);
}
}
void Database::createNew()
{
std::vector<std::string> sqlStrings{};
std::string sqlCreateKima2{"CREATE TABLE IF NOT EXISTS kima2 ("
"version INTEGER NOT NULL);"
"INSERT INTO kima2 (version) VALUES (3);"};
sqlStrings.push_back(sqlCreateKima2);
std::string sqlCreateSellers{"CREATE TABLE IF NOT EXISTS sellers ("
"seller_no INTEGER PRIMARY KEY NOT NULL, "
"first_name TEXT, "
"last_name TEXT, "
"num_offered_articles INTEGER, "
"UNIQUE (seller_no)"
");"};
sqlStrings.push_back(sqlCreateSellers);
std::string sqlCreateArticles{
"CREATE TABLE IF NOT EXISTS articles ("
"id TEXT PRIMARY KEY NOT NULL, "
"seller_no TEXT NOT NULL, "
"source_no INTEGER NOT NULL, "
"article_no INTEGER NOT NULL, "
"description TEXT, "
"price INTEGER NOT NULL, "
"UNIQUE (source_no, article_no), "
"FOREIGN KEY (seller_no) REFERENCES sellers(seller_no) ON DELETE CASCADE, "
"CHECK (article_no BETWEEN 0 AND 99999)"
");"};
sqlStrings.push_back(sqlCreateArticles);
std::string sqlCreateSales{"CREATE TABLE IF NOT EXISTS sales ("
" id TEXT PRIMARY KEY NOT NULL,"
" source_no INTEGER NOT NULL,"
" sold_at TEXT"
");"};
sqlStrings.push_back(sqlCreateSales);
std::string sqlCreateSalesItems{
"CREATE TABLE IF NOT EXISTS sales_items("
" sale_id TEXT NOT NULL,"
" article_id TEXT NOT NULL,"
" FOREIGN KEY (sale_id) REFERENCES sales(id) ON DELETE CASCADE,"
" FOREIGN KEY (article_id) REFERENCES articles(id) ON DELETE CASCADE"
");"};
sqlStrings.push_back(sqlCreateSalesItems);
std::string sqlInitialEntries{
"INSERT OR IGNORE INTO sellers (seller_no, first_name, last_name, "
"num_offered_articles) VALUES "
"(0, 'Sonderkonto', 'Sonderkonto', 0)"};
sqlStrings.push_back(sqlInitialEntries);
beginTransaction();
for (const auto& sql : sqlStrings) {
exec(sql);
}
endTransaction();
}
void Database::updateDbToVer2()
{
beginTransaction();
exec("INSERT OR IGNORE INTO sellers (seller_no, first_name, last_name, "
"num_offered_articles) VALUES "
"(0, 'Sonderkonto', 'Sonderkonto', 0)");
exec("UPDATE kima2 SET version = 3");
endTransaction();
}
void Database::updateDbToVer3() { newDb(); }
void Database::init()
{
const int errCode = sqlite3_open(dbname_.c_str(), &db_);
if (errCode) {
throw std::runtime_error("Could not open database file.");
}
// sqlite3_db_config(db_, SQLITE_DBCONFIG_ENABLE_FKEY);
exec("PRAGMA foreign_keys = ON;");
int version = getVersion();
switch (version) {
case 0:
createNew();
initResult_ = InitResult::OK;
break;
case 1:
updateDbToVer3();
initResult_ = InitResult::OUTDATED_REPLACED;
break;
case 2:
updateDbToVer3();
initResult_ = InitResult::OUTDATED_REPLACED;
break;
default:
// Do nothing because we are up-to-date.
initResult_ = InitResult::OK;
break;
}
}
int Database::getVersion()
{
int retCode{};
sqlite3_stmt* stmt;
// Check if there's already a kima2 table available.
// If not, return version == 0.
retCode = sqlite3_prepare_v2(
db_, "SELECT count(*) FROM sqlite_master WHERE type='table' AND name='kima2';", -1, &stmt,
nullptr);
if (retCode != SQLITE_OK)
throw std::string(sqlite3_errmsg(db_));
retCode = sqlite3_step(stmt);
if (retCode != SQLITE_ROW && retCode != SQLITE_DONE) {
std::string errMsg(sqlite3_errmsg(db_));
sqlite3_finalize(stmt);
throw errMsg;
} else if (retCode != SQLITE_DONE) {
int count = sqlite3_column_int(stmt, 0);
sqlite3_finalize(stmt);
if (count == 0)
return 0; // no kima2 table, so version is 0
}
// Now that we know that the kima2 table is present, read and return the schema version.
retCode = sqlite3_prepare_v2(db_, "SELECT version FROM kima2", -1, &stmt, nullptr);
if (retCode != SQLITE_OK)
throw std::string(sqlite3_errmsg(db_));
retCode = sqlite3_step(stmt);
if (retCode != SQLITE_ROW && retCode != SQLITE_DONE) {
std::string errMsg(sqlite3_errmsg(db_));
sqlite3_finalize(stmt);
throw errMsg;
} else if (retCode == SQLITE_DONE) {
sqlite3_finalize(stmt);
return 0; // no version entry, so version is 0
}
int version = sqlite3_column_int(stmt, 0);
sqlite3_finalize(stmt);
return version;
}
void Database::beginTransaction() { exec("BEGIN TRANSACTION"); }
void Database::endTransaction() { exec("END TRANSACTION"); }
unsigned int Database::storeSellers(std::vector<std::unique_ptr<Seller>>& sellers, bool onlyDelete)
{
int retCode{};
int count{};
sqlite3_stmt* stmt;
beginTransaction();
for (auto& seller : sellers) {
if (seller->getState() == Seller::State::NEW && !onlyDelete) {
retCode = sqlite3_prepare_v2(
db_,
"INSERT INTO sellers"
" (seller_no, first_name, last_name, num_offered_articles)"
" VALUES (:seller_no, :first_name, :last_name, :num_offered_articles)",
-1, &stmt, nullptr);
if (retCode != SQLITE_OK)
throw std::runtime_error(sqlite3_errmsg(db_));
sqlite3_bind_int(stmt, sqlite3_bind_parameter_index(stmt, ":seller_no"),
seller->getSellerNo());
sqlite3_bind_text(stmt, sqlite3_bind_parameter_index(stmt, ":first_name"),
seller->getFirstName().c_str(), -1, SQLITE_TRANSIENT);
sqlite3_bind_text(stmt, sqlite3_bind_parameter_index(stmt, ":last_name"),
seller->getLastName().c_str(), -1, SQLITE_TRANSIENT);
sqlite3_bind_int(stmt, sqlite3_bind_parameter_index(stmt, ":num_offered_articles"),
seller->numArticlesOffered());
retCode = sqlite3_step(stmt);
if (retCode != SQLITE_DONE) {
std::string errMsg(sqlite3_errmsg(db_));
sqlite3_finalize(stmt);
throw std::runtime_error(errMsg);
}
++count;
sqlite3_finalize(stmt);
} else if (seller->getState() == Seller::State::UPDATE && !onlyDelete) {
retCode = sqlite3_prepare_v2(
db_,
"UPDATE sellers SET"
" seller_no = :seller_no, first_name = :first_name,"
" last_name = :last_name, num_offered_articles = :num_offered_articles"
" WHERE seller_no = :id",
-1, &stmt, nullptr);
if (retCode != SQLITE_OK)
throw std::runtime_error(sqlite3_errmsg(db_));
sqlite3_bind_int(stmt, sqlite3_bind_parameter_index(stmt, ":id"), seller->getId());
sqlite3_bind_int(stmt, sqlite3_bind_parameter_index(stmt, ":seller_no"),
seller->getSellerNo());
sqlite3_bind_text(stmt, sqlite3_bind_parameter_index(stmt, ":first_name"),
seller->getFirstName().c_str(), -1, SQLITE_TRANSIENT);
sqlite3_bind_text(stmt, sqlite3_bind_parameter_index(stmt, ":last_name"),
seller->getLastName().c_str(), -1, SQLITE_TRANSIENT);
sqlite3_bind_int(stmt, sqlite3_bind_parameter_index(stmt, ":num_offered_articles"),
seller->numArticlesOffered());
retCode = sqlite3_step(stmt);
if (retCode != SQLITE_DONE) {
std::string errMsg(sqlite3_errmsg(db_));
sqlite3_finalize(stmt);
throw std::runtime_error(errMsg);
}
++count;
sqlite3_finalize(stmt);
} else if (seller->getState() == Seller::State::DELETE) {
count += static_cast<int>(seller->getArticles(false).size());
retCode = sqlite3_prepare_v2(db_, "DELETE FROM sellers WHERE seller_no = :id", -1,
&stmt, nullptr);
if (retCode != SQLITE_OK)
throw std::runtime_error(sqlite3_errmsg(db_));
sqlite3_bind_int(stmt, sqlite3_bind_parameter_index(stmt, ":id"), seller->getId());
retCode = sqlite3_step(stmt);
if (retCode != SQLITE_DONE) {
std::string errMsg(sqlite3_errmsg(db_));
sqlite3_finalize(stmt);
throw std::runtime_error(errMsg);
}
++count;
sqlite3_finalize(stmt);
}
if (seller->getState() != Seller::State::DELETE) {
count += storeArticles(seller->getArticles(false));
}
}
endTransaction();
// Everything went fine, so we can now update our objects
sellers.erase(std::remove_if(sellers.begin(), sellers.end(),
[](const std::unique_ptr<Seller>& seller) {
return (seller->getState() == Seller::State::DELETE);
}),
sellers.end());
for (auto& seller : sellers) {
seller->cleanupArticles();
seller->setState(Seller::State::OK);
}
return count;
}
unsigned int Database::storeArticles(std::vector<Article*> articles)
{
int retCode{};
int count{};
sqlite3_stmt* stmt;
for (auto& article : articles) {
if (article->getState() == Article::State::NEW) {
retCode = sqlite3_prepare_v2(
db_,
"INSERT INTO articles"
" (id, seller_no, source_no, article_no, description, price)"
" VALUES (:uuid, :seller_id, :source_no, :article_no, :desc, :price)",
-1, &stmt, nullptr);
if (retCode != SQLITE_OK)
throw std::runtime_error(sqlite3_errmsg(db_));
sqlite3_bind_text(stmt, sqlite3_bind_parameter_index(stmt, ":uuid"),
boost::uuids::to_string(article->getUuid()).c_str(), -1,
SQLITE_TRANSIENT);
sqlite3_bind_int(stmt, sqlite3_bind_parameter_index(stmt, ":seller_id"),
article->getSeller()->getId());
sqlite3_bind_int(stmt, sqlite3_bind_parameter_index(stmt, ":source_no"),
article->getSourceNo());
sqlite3_bind_int(stmt, sqlite3_bind_parameter_index(stmt, ":article_no"),
article->getArticleNo());
sqlite3_bind_text(stmt, sqlite3_bind_parameter_index(stmt, ":desc"),
article->getDescription().c_str(), -1, SQLITE_TRANSIENT);
sqlite3_bind_int(stmt, sqlite3_bind_parameter_index(stmt, ":price"),
article->getPrice());
retCode = sqlite3_step(stmt);
if (retCode != SQLITE_DONE) {
std::string errMsg(sqlite3_errmsg(db_));
sqlite3_finalize(stmt);
throw std::runtime_error(errMsg);
}
++count;
sqlite3_finalize(stmt);
} else if (article->getState() == Article::State::UPDATE) {
retCode = sqlite3_prepare_v2(
db_,
"UPDATE articles SET"
" seller_no = :seller_id, source_no = :source_no, article_no = :article_no,"
" description = :desc, price = :price"
" WHERE id = :uuid",
-1, &stmt, nullptr);
if (retCode != SQLITE_OK)
throw std::runtime_error(sqlite3_errmsg(db_));
sqlite3_bind_text(stmt, sqlite3_bind_parameter_index(stmt, ":uuid"),
boost::uuids::to_string(article->getUuid()).c_str(), -1,
SQLITE_TRANSIENT);
sqlite3_bind_int(stmt, sqlite3_bind_parameter_index(stmt, ":seller_id"),
article->getSeller()->getId());
sqlite3_bind_int(stmt, sqlite3_bind_parameter_index(stmt, ":source_no"),
article->getSourceNo());
sqlite3_bind_int(stmt, sqlite3_bind_parameter_index(stmt, ":article_no"),
article->getArticleNo());
sqlite3_bind_text(stmt, sqlite3_bind_parameter_index(stmt, ":desc"),
article->getDescription().c_str(), -1, SQLITE_TRANSIENT);
sqlite3_bind_int(stmt, sqlite3_bind_parameter_index(stmt, ":price"),
article->getPrice());
retCode = sqlite3_step(stmt);
if (retCode != SQLITE_DONE) {
std::string errMsg(sqlite3_errmsg(db_));
sqlite3_finalize(stmt);
throw std::runtime_error(errMsg);
}
++count;
sqlite3_finalize(stmt);
} else if (article->getState() == Article::State::DELETE) {
retCode = sqlite3_prepare_v2(db_, "DELETE FROM articles WHERE id = :uuid", -1, &stmt,
nullptr);
if (retCode != SQLITE_OK)
throw std::runtime_error(sqlite3_errmsg(db_));
sqlite3_bind_text(stmt, sqlite3_bind_parameter_index(stmt, ":uuid"),
boost::uuids::to_string(article->getUuid()).c_str(), -1,
SQLITE_TRANSIENT);
retCode = sqlite3_step(stmt);
if (retCode != SQLITE_DONE) {
std::string errMsg(sqlite3_errmsg(db_));
sqlite3_finalize(stmt);
throw std::runtime_error(errMsg);
}
++count;
sqlite3_finalize(stmt);
}
}
return count;
}
unsigned int Database::storeSales(std::vector<std::unique_ptr<Sale>>& sales)
{
int retCode{};
int count{};
sqlite3_stmt* stmt;
if (sales.size() == 0)
return 0;
beginTransaction();
for (auto& sale : sales) {
if (sale->getState() == Sale::State::NEW) {
retCode = sqlite3_prepare_v2(db_,
"INSERT INTO sales"
" (id, source_no, sold_at)"
" VALUES (:uuid, :source_no, :sold_at)",
-1, &stmt, nullptr);
if (retCode != SQLITE_OK)
throw std::runtime_error(sqlite3_errmsg(db_));
sqlite3_bind_text(stmt, sqlite3_bind_parameter_index(stmt, ":uuid"),
boost::uuids::to_string(sale->getUuid()).c_str(), -1,
SQLITE_TRANSIENT);
sqlite3_bind_int(stmt, sqlite3_bind_parameter_index(stmt, ":source_no"),
sale->getSourceNo());
sqlite3_bind_text(stmt, sqlite3_bind_parameter_index(stmt, ":sold_at"),
sale->getTimestamp().c_str(), -1, SQLITE_TRANSIENT);
retCode = sqlite3_step(stmt);
if (retCode != SQLITE_DONE) {
std::string errMsg(sqlite3_errmsg(db_));
sqlite3_finalize(stmt);
throw std::runtime_error(errMsg);
}
++count;
sqlite3_finalize(stmt);
for (const auto& article : sale->getArticles()) {
retCode = sqlite3_prepare_v2(db_,
"INSERT INTO sales_items"
" (sale_id, article_id)"
" VALUES (:sale_id, :article_id)",
-1, &stmt, nullptr);
if (retCode != SQLITE_OK)
throw std::runtime_error(sqlite3_errmsg(db_));
sqlite3_bind_text(stmt, sqlite3_bind_parameter_index(stmt, ":sale_id"),
sale->getUuidAsString().c_str(), -1, SQLITE_TRANSIENT);
sqlite3_bind_text(stmt, sqlite3_bind_parameter_index(stmt, ":article_id"),
article->getUuidAsString().c_str(), -1, SQLITE_TRANSIENT);
retCode = sqlite3_step(stmt);
if (retCode != SQLITE_DONE) {
std::string errMsg(sqlite3_errmsg(db_));
sqlite3_finalize(stmt);
throw std::runtime_error(errMsg);
}
sqlite3_finalize(stmt);
}
} else if (sale->getState() == Sale::State::DELETE) {
retCode =
sqlite3_prepare_v2(db_, "DELETE FROM sales WHERE id = :uuid", -1, &stmt, nullptr);
if (retCode != SQLITE_OK)
throw std::runtime_error(sqlite3_errmsg(db_));
sqlite3_bind_text(stmt, sqlite3_bind_parameter_index(stmt, ":uuid"),
sale->getUuidAsString().c_str(), -1, SQLITE_TRANSIENT);
retCode = sqlite3_step(stmt);
if (retCode != SQLITE_DONE) {
std::string errMsg(sqlite3_errmsg(db_));
sqlite3_finalize(stmt);
throw std::runtime_error(errMsg);
}
++count;
sqlite3_finalize(stmt);
}
}
endTransaction();
// Everything went fine, so we can now update our objects
sales.erase(
std::remove_if(sales.begin(), sales.end(),
[](const auto& sale) { return (sale->getState() == Sale::State::DELETE); }),
sales.end());
for (auto& sale : sales) {
sale->setState(Sale::State::OK);
}
return count;
}
unsigned int Database::loadSellers(std::vector<std::unique_ptr<Seller>>& sellers)
{
int retCode{};
int count{};
sqlite3_stmt* stmt;
retCode = sqlite3_prepare_v2(db_,
"SELECT seller_no, first_name, last_name, "
"num_offered_articles FROM sellers ORDER BY seller_no",
-1, &stmt, nullptr);
if (retCode != SQLITE_OK)
throw std::runtime_error(sqlite3_errmsg(db_));
retCode = sqlite3_step(stmt);
sellers.clear();
while (retCode != SQLITE_DONE) {
++count;
auto seller = std::make_unique<Seller>();
seller->setSellerNo(sqlite3_column_int(stmt, 0));
seller->setFirstName(reinterpret_cast<const char*>(sqlite3_column_text(stmt, 1)));
seller->setLastName(reinterpret_cast<const char*>(sqlite3_column_text(stmt, 2)));
seller->setNumArticlesOffered(sqlite3_column_int(stmt, 3));
seller->setState(Seller::State::OK);
sellers.push_back(std::move(seller));
retCode = sqlite3_step(stmt);
}
sqlite3_finalize(stmt);
for (auto& seller : sellers) {
retCode = sqlite3_prepare_v2(db_,
"SELECT id, source_no, article_no, description, price"
" FROM articles"
" WHERE seller_no = :seller_id"
" ORDER BY article_no",
-1, &stmt, nullptr);
if (retCode != SQLITE_OK)
throw std::runtime_error(sqlite3_errmsg(db_));
sqlite3_bind_int(stmt, sqlite3_bind_parameter_index(stmt, ":seller_id"), seller->getId());
retCode = sqlite3_step(stmt);
while (retCode != SQLITE_DONE) {
++count;
auto article = std::make_unique<Article>();
article->setUuidFromString(reinterpret_cast<const char*>(sqlite3_column_text(stmt, 0)));
article->setSeller(seller.get());
article->setSourceNo(sqlite3_column_int(stmt, 1));
article->setArticleNo(sqlite3_column_int(stmt, 2));
article->setDescription(reinterpret_cast<const char*>(sqlite3_column_text(stmt, 3)));
article->setPrice(sqlite3_column_int(stmt, 4));
article->setState(Article::State::OK);
seller->addArticle(std::move(article));
retCode = sqlite3_step(stmt);
}
sqlite3_finalize(stmt);
}
return count;
}
unsigned int Database::loadSales(std::vector<std::unique_ptr<Sale>>& sales,
std::vector<std::unique_ptr<Seller>>& sellers)
{
int retCode{};
int count{};
sqlite3_stmt* stmt;
retCode = sqlite3_prepare_v2(db_,
"SELECT id, source_no, sold_at"
" FROM sales ORDER BY sold_at DESC",
-1, &stmt, nullptr);
if (retCode != SQLITE_OK)
throw std::runtime_error(sqlite3_errmsg(db_));
retCode = sqlite3_step(stmt);
sales.clear();
std::map<std::string, Sale*> saleMap;
while (retCode != SQLITE_DONE) {
++count;
auto sale = std::make_unique<Sale>();
sale->setUuidFromString(reinterpret_cast<const char*>(sqlite3_column_text(stmt, 0)));
sale->setSourceNo(sqlite3_column_int(stmt, 1));
sale->setTimestamp(reinterpret_cast<const char*>(sqlite3_column_text(stmt, 2)));
sale->setState(Sale::State::OK);
saleMap.insert(std::make_pair(sale->getUuidAsString(), sale.get()));
sales.push_back(std::move(sale));
retCode = sqlite3_step(stmt);
}
sqlite3_finalize(stmt);
std::map<std::string, Article*> artMap;
for (const auto& seller : sellers) {
for (const auto article : seller->getArticles(false)) {
artMap.insert(std::make_pair(article->getUuidAsString(), article));
}
}
retCode = sqlite3_prepare_v2(db_,
"SELECT sale_id, article_id"
" FROM sales_items",
-1, &stmt, nullptr);
if (retCode != SQLITE_OK)
throw std::runtime_error(sqlite3_errmsg(db_));
retCode = sqlite3_step(stmt);
while (retCode != SQLITE_DONE) {
saleMap[reinterpret_cast<const char*>(sqlite3_column_text(stmt, 0))]->addArticle(
artMap[reinterpret_cast<const char*>(sqlite3_column_text(stmt, 1))]);
retCode = sqlite3_step(stmt);
}
sqlite3_finalize(stmt);
return count;
}
void Database::updateCashPointNo(int oldCashPointNo, int newCashPointNo)
{
int retCode{};
sqlite3_stmt* stmt;
// Check if the new no ist already in use
retCode = sqlite3_prepare_v2(db_, "SELECT COUNT() FROM articles WHERE source_no = :source_no",
-1, &stmt, nullptr);
if (retCode != SQLITE_OK)
throw std::string(sqlite3_errmsg(db_));
sqlite3_bind_int(stmt, sqlite3_bind_parameter_index(stmt, ":source_no"), newCashPointNo);
retCode = sqlite3_step(stmt);
int count{};
if (retCode != SQLITE_ROW) {
std::string errMsg(sqlite3_errmsg(db_));
sqlite3_finalize(stmt);
throw std::runtime_error(errMsg);
}
count = sqlite3_column_int(stmt, 0);
sqlite3_finalize(stmt);
if (count > 0) {
throw std::runtime_error("The desired cashpoint no is aleady in use.");
}
beginTransaction();
retCode = sqlite3_prepare_v2(
db_, "UPDATE articles SET source_no = :new_source_no WHERE source_no = :old_source_no", -1,
&stmt, nullptr);
if (retCode != SQLITE_OK)
throw std::string(sqlite3_errmsg(db_));
sqlite3_bind_int(stmt, sqlite3_bind_parameter_index(stmt, ":new_source_no"), newCashPointNo);
sqlite3_bind_int(stmt, sqlite3_bind_parameter_index(stmt, ":old_source_no"), oldCashPointNo);
retCode = sqlite3_step(stmt);
if (retCode != SQLITE_DONE) {
std::string errMsg(sqlite3_errmsg(db_));
sqlite3_finalize(stmt);
throw std::runtime_error(errMsg);
}
sqlite3_finalize(stmt);
retCode = sqlite3_prepare_v2(
db_, "UPDATE sales SET source_no = :new_source_no WHERE source_no = :old_source_no", -1,
&stmt, nullptr);
if (retCode != SQLITE_OK)
throw std::string(sqlite3_errmsg(db_));
sqlite3_bind_int(stmt, sqlite3_bind_parameter_index(stmt, ":new_source_no"), newCashPointNo);
sqlite3_bind_int(stmt, sqlite3_bind_parameter_index(stmt, ":old_source_no"), oldCashPointNo);
retCode = sqlite3_step(stmt);
if (retCode != SQLITE_DONE) {
std::string errMsg(sqlite3_errmsg(db_));
sqlite3_finalize(stmt);
throw std::runtime_error(errMsg);
}
sqlite3_finalize(stmt);
endTransaction();
}