2018-07-06 13:30:23 +02:00
|
|
|
#include "database.h"
|
|
|
|
|
2018-07-10 14:12:37 +02:00
|
|
|
#include <iostream>
|
2018-07-10 15:46:55 +02:00
|
|
|
#include <stdexcept>
|
2018-07-11 11:53:06 +02:00
|
|
|
#include <vector>
|
2018-07-06 13:30:23 +02:00
|
|
|
|
2018-07-10 14:12:37 +02:00
|
|
|
Database::Database(const std::string& dbname) : db_(nullptr)
|
2018-07-06 13:30:23 +02:00
|
|
|
{
|
2018-07-11 09:43:40 +02:00
|
|
|
dbname_ = dbname;
|
|
|
|
const int errCode = sqlite3_open(dbname_.c_str(), &db_);
|
2018-07-09 13:03:03 +02:00
|
|
|
if (errCode) {
|
2018-07-06 13:30:23 +02:00
|
|
|
throw std::runtime_error("Could not open database file.");
|
|
|
|
}
|
2018-07-11 08:33:40 +02:00
|
|
|
sqlite3_db_config(db_, SQLITE_DBCONFIG_ENABLE_FKEY);
|
2018-07-06 13:30:23 +02:00
|
|
|
}
|
|
|
|
|
2018-07-10 14:12:37 +02:00
|
|
|
Database::~Database() { sqlite3_close(db_); }
|
2018-07-06 13:30:23 +02:00
|
|
|
|
|
|
|
void Database::exec(const std::string& sql)
|
|
|
|
{
|
2018-07-11 08:33:40 +02:00
|
|
|
char* errMsg;
|
|
|
|
const int errCode = sqlite3_exec(db_, sql.c_str(), nullptr, nullptr, &errMsg);
|
2018-07-09 13:03:03 +02:00
|
|
|
if (errCode) {
|
2018-07-11 08:33:40 +02:00
|
|
|
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);
|
2018-07-06 13:30:23 +02:00
|
|
|
}
|
2018-07-09 21:03:59 +02:00
|
|
|
}
|
|
|
|
|
2018-07-11 09:43:40 +02:00
|
|
|
void Database::createNew()
|
2018-07-10 14:12:37 +02:00
|
|
|
{
|
2018-07-11 11:53:06 +02:00
|
|
|
std::vector<std::string> sqlStrings{};
|
|
|
|
|
2018-07-11 09:43:40 +02:00
|
|
|
std::string sqlCreateKima2{"CREATE TABLE IF NOT EXISTS kima2 ("
|
|
|
|
"version INTEGER NOT NULL);"
|
|
|
|
"INSERT INTO kima2 (version) VALUES (1);"};
|
2018-07-11 11:53:06 +02:00
|
|
|
sqlStrings.push_back(sqlCreateKima2);
|
2018-07-10 15:46:55 +02:00
|
|
|
std::string sqlCreateSellers{"CREATE TABLE IF NOT EXISTS sellers ("
|
|
|
|
"id TEXT PRIMARY KEY NOT NULL, "
|
|
|
|
"seller_no INTEGER, "
|
|
|
|
"first_name TEXT, "
|
|
|
|
"last_name TEXT, "
|
2018-07-11 15:58:09 +02:00
|
|
|
"num_offered_articles INTEGER, "
|
2018-07-10 15:46:55 +02:00
|
|
|
"UNIQUE (seller_no)"
|
|
|
|
");"};
|
2018-07-11 11:53:06 +02:00
|
|
|
sqlStrings.push_back(sqlCreateSellers);
|
2018-07-10 14:12:37 +02:00
|
|
|
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)"
|
2018-07-10 15:46:55 +02:00
|
|
|
");"};
|
2018-07-11 11:53:06 +02:00
|
|
|
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);
|
2018-07-10 14:12:37 +02:00
|
|
|
|
|
|
|
beginTransaction();
|
2018-07-11 11:53:06 +02:00
|
|
|
for (const auto& sql : sqlStrings) {
|
|
|
|
exec(sql);
|
|
|
|
}
|
2018-07-10 14:12:37 +02:00
|
|
|
endTransaction();
|
|
|
|
}
|
|
|
|
|
2018-07-11 09:43:40 +02:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2018-07-10 12:51:23 +02:00
|
|
|
void Database::beginTransaction() { exec("BEGIN TRANSACTION"); }
|
2018-07-09 21:03:59 +02:00
|
|
|
|
2018-07-11 15:58:09 +02:00
|
|
|
void Database::endTransaction() { exec("END TRANSACTION"); }
|
|
|
|
|
2018-07-13 13:05:19 +02:00
|
|
|
unsigned int Database::storeSellers(std::vector<std::shared_ptr<Seller>>& sellers)
|
2018-07-11 15:58:09 +02:00
|
|
|
{
|
|
|
|
int retCode{};
|
|
|
|
int count{};
|
|
|
|
sqlite3_stmt* stmt;
|
|
|
|
|
|
|
|
beginTransaction();
|
|
|
|
|
|
|
|
for (auto& seller : sellers) {
|
2018-07-13 13:05:19 +02:00
|
|
|
if (seller->getState() == Seller::State::NEW) {
|
2018-07-11 15:58:09 +02:00
|
|
|
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_));
|
|
|
|
|
2018-07-13 13:05:19 +02:00
|
|
|
int test = sqlite3_bind_text(stmt, sqlite3_bind_parameter_index(stmt, ":uuid"),
|
2018-07-13 13:14:08 +02:00
|
|
|
boost::uuids::to_string(seller->getUuid()).c_str(), -1,
|
|
|
|
SQLITE_TRANSIENT);
|
2018-07-13 13:05:19 +02:00
|
|
|
std::cout << "!!! TEST: " << test << "\n";
|
|
|
|
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) {
|
|
|
|
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 id = :uuid",
|
|
|
|
-1, &stmt, nullptr);
|
|
|
|
|
|
|
|
if (retCode != SQLITE_OK)
|
|
|
|
throw std::runtime_error(sqlite3_errmsg(db_));
|
|
|
|
|
2018-07-11 15:58:09 +02:00
|
|
|
sqlite3_bind_text(stmt, sqlite3_bind_parameter_index(stmt, ":uuid"),
|
2018-07-13 13:14:08 +02:00
|
|
|
boost::uuids::to_string(seller->getUuid()).c_str(), -1,
|
|
|
|
SQLITE_TRANSIENT);
|
2018-07-11 15:58:09 +02:00
|
|
|
sqlite3_bind_int(stmt, sqlite3_bind_parameter_index(stmt, ":seller_no"),
|
2018-07-13 13:05:19 +02:00
|
|
|
seller->getSellerNo());
|
2018-07-11 15:58:09 +02:00
|
|
|
sqlite3_bind_text(stmt, sqlite3_bind_parameter_index(stmt, ":first_name"),
|
2018-07-13 13:05:19 +02:00
|
|
|
seller->getFirstName().c_str(), -1, SQLITE_TRANSIENT);
|
2018-07-11 15:58:09 +02:00
|
|
|
sqlite3_bind_text(stmt, sqlite3_bind_parameter_index(stmt, ":last_name"),
|
2018-07-13 13:05:19 +02:00
|
|
|
seller->getLastName().c_str(), -1, SQLITE_TRANSIENT);
|
2018-07-11 15:58:09 +02:00
|
|
|
sqlite3_bind_int(stmt, sqlite3_bind_parameter_index(stmt, ":num_offered_articles"),
|
2018-07-13 13:05:19 +02:00
|
|
|
seller->numArticlesOffered());
|
2018-07-11 15:58:09 +02:00
|
|
|
|
|
|
|
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);
|
2018-07-13 13:05:19 +02:00
|
|
|
} else if (seller->getState() == Seller::State::DELETE) {
|
|
|
|
count += static_cast<int>(seller->getArticles(false).size());
|
2018-07-13 13:58:32 +02:00
|
|
|
|
|
|
|
retCode =
|
|
|
|
sqlite3_prepare_v2(db_, "DELETE FROM sellers 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(seller->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);
|
2018-07-13 13:05:19 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if (seller->getState() != Seller::State::DELETE) {
|
2018-07-13 14:05:22 +02:00
|
|
|
count += storeArticles(seller->getArticles(false));
|
2018-07-11 15:58:09 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
endTransaction();
|
2018-07-13 13:58:32 +02:00
|
|
|
|
|
|
|
// Everything went fine, so we can now update our objects
|
|
|
|
sellers.erase(
|
|
|
|
std::remove_if(begin(sellers), end(sellers), [](const std::shared_ptr<Seller>& seller) {
|
|
|
|
return seller->getState() == Seller::State::DELETE;
|
|
|
|
}));
|
|
|
|
for (const auto& seller : sellers) {
|
|
|
|
seller->cleanupArticles();
|
|
|
|
seller->setState(Seller::State::OK);
|
|
|
|
}
|
|
|
|
|
2018-07-13 13:05:19 +02:00
|
|
|
return count;
|
|
|
|
}
|
|
|
|
|
2018-07-13 14:05:22 +02:00
|
|
|
unsigned int Database::storeArticles(std::vector<Article*> articles)
|
2018-07-13 13:05:19 +02:00
|
|
|
{
|
|
|
|
int retCode{};
|
|
|
|
int count{};
|
2018-07-13 14:05:22 +02:00
|
|
|
sqlite3_stmt* stmt;
|
2018-07-13 13:05:19 +02:00
|
|
|
|
|
|
|
for (auto& article : articles) {
|
|
|
|
if (article->getState() == Article::State::NEW) {
|
|
|
|
retCode = sqlite3_prepare_v2(
|
|
|
|
db_,
|
|
|
|
"INSERT INTO articles"
|
|
|
|
" (id, seller_id, 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"),
|
2018-07-13 13:14:08 +02:00
|
|
|
boost::uuids::to_string(article->getUuid()).c_str(), -1,
|
|
|
|
SQLITE_TRANSIENT);
|
2018-07-13 13:05:19 +02:00
|
|
|
sqlite3_bind_text(stmt, sqlite3_bind_parameter_index(stmt, ":seller_id"),
|
|
|
|
boost::uuids::to_string(article->getSeller()->getUuid()).c_str(), -1,
|
|
|
|
SQLITE_TRANSIENT);
|
|
|
|
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());
|
2018-07-13 13:14:08 +02:00
|
|
|
sqlite3_bind_text(stmt, sqlite3_bind_parameter_index(stmt, ":desc"),
|
2018-07-13 13:05:19 +02:00
|
|
|
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) {
|
2018-07-13 13:14:08 +02:00
|
|
|
retCode = sqlite3_prepare_v2(
|
|
|
|
db_,
|
|
|
|
"UPDATE articles SET"
|
|
|
|
" seller_id = 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_text(stmt, sqlite3_bind_parameter_index(stmt, ":seller_id"),
|
|
|
|
boost::uuids::to_string(article->getSeller()->getUuid()).c_str(), -1,
|
|
|
|
SQLITE_TRANSIENT);
|
|
|
|
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) {
|
2018-07-13 13:58:32 +02:00
|
|
|
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);
|
2018-07-13 13:05:19 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-07-11 15:58:09 +02:00
|
|
|
return count;
|
|
|
|
}
|