kima2/src/core/database.cpp

195 lines
6.7 KiB
C++

#include "database.h"
#include <iostream>
#include <stdexcept>
#include <vector>
Database::Database(const std::string& dbname) : db_(nullptr)
{
dbname_ = dbname;
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);
}
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 (1);"};
sqlStrings.push_back(sqlCreateKima2);
std::string sqlCreateSellers{"CREATE TABLE IF NOT EXISTS sellers ("
"id TEXT PRIMARY KEY NOT NULL, "
"seller_no INTEGER, "
"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_id 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_id) REFERENCES sellers(id) 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);
beginTransaction();
for (const auto& sql : sqlStrings) {
exec(sql);
}
endTransaction();
}
void Database::init()
{
int version = getVersion();
switch (version) {
case 0:
createNew();
break;
// perhaps handle upgrades for db schema here...
default:
// Do nothing because we are up-to-date.
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<Seller>& sellers)
{
int retCode{};
int count{};
sqlite3_stmt* stmt;
beginTransaction();
for (auto& seller : sellers) {
if (seller.getState() == Entity::State::NEW) {
retCode = sqlite3_prepare_v2(
db_,
"INSERT INTO sellers"
" (id, seller_no, first_name, last_name, num_offered_articles)"
" VALUES (:uuid, :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_text(stmt, sqlite3_bind_parameter_index(stmt, ":uuid"),
boost::uuids::to_string(seller.getUuid()).c_str(), -1, nullptr);
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, nullptr);
sqlite3_bind_text(stmt, sqlite3_bind_parameter_index(stmt, ":last_name"),
seller.getLastName().c_str(), -1, nullptr);
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);
}
seller.setState(Seller::State::OK);
++count;
sqlite3_finalize(stmt);
}
}
endTransaction();
return count;
}