From 82a48fa3b46ab93825d71ea06e2548b44e3b5515 Mon Sep 17 00:00:00 2001 From: "Scott E. Graves" Date: Tue, 1 Oct 2024 08:44:54 -0500 Subject: [PATCH] continue sqlite3 mini-orm --- .../include/database/db_column.hpp | 314 ++++++++++++++++++ .../include/database/db_common.hpp | 87 ++++- .../include/database/db_delete.hpp | 40 +-- .../include/database/db_select.hpp | 50 +-- .../include/database/db_update.hpp | 54 +-- .../librepertory/src/database/db_common.cpp | 47 +++ .../librepertory/src/database/db_delete.cpp | 7 +- .../librepertory/src/database/db_select.cpp | 7 +- .../librepertory/src/database/db_update.cpp | 21 +- 9 files changed, 467 insertions(+), 160 deletions(-) create mode 100644 repertory/librepertory/include/database/db_column.hpp create mode 100644 repertory/librepertory/src/database/db_common.cpp diff --git a/repertory/librepertory/include/database/db_column.hpp b/repertory/librepertory/include/database/db_column.hpp new file mode 100644 index 00000000..19a45a8b --- /dev/null +++ b/repertory/librepertory/include/database/db_column.hpp @@ -0,0 +1,314 @@ +/* + Copyright <2018-2024> + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ +#ifndef INCLUDE_DATABASE_DB_COMMON_HPP_ +#define INCLUDE_DATABASE_DB_COMMON_HPP_ + +#include "utils/error_utils.hpp" + +namespace repertory::db { +using db_types_t = std::variant; + +struct sqlite3_deleter { + void operator()(sqlite3 *db3) { + if (db3 != nullptr) { + sqlite3_close_v2(db3); + } + } +}; + +using db3_t = std::unique_ptr; + +struct sqlite3_statement_deleter { + void operator()(sqlite3_stmt *stmt) { + if (stmt != nullptr) { + sqlite3_finalize(stmt); + } + } +}; + +using db3_stmt_t = std::unique_ptr; + +struct comp_data_t final { + std::string column_name; + std::string op_type; + db_types_t value; +}; + +class db_column final { +public: + db_column() noexcept = default; + db_column(const db_column &) = default; + db_column(db_column &&column) noexcept = default; + ~db_column() = default; + + auto operator=(const db_column &) -> db_column & = default; + auto operator=(db_column &&) -> db_column & = default; + + db_column(std::int32_t index, std::string name, db_types_t value) noexcept + : index_(index), name_(std::move(name)), value_(std::move(value)) {} + +private: + std::int32_t index_{}; + std::string name_; + db_types_t value_; + +public: + [[nodiscard]] auto get_index() const -> std::int32_t { return index_; } + + [[nodiscard]] auto get_name() const -> std::string { return name_; } + + template + [[nodiscard]] auto get_value() const -> data_type { + return std::visit( + overloaded{ + [](const data_type &value) -> data_type { return value; }, + [](auto &&) -> data_type { + throw std::runtime_error("data type not supported"); + }, + }, + value_); + } + + [[nodiscard]] auto get_value_as_json() const -> nlohmann::json { + return std::visit( + overloaded{ + [this](std::int64_t value) -> auto { + return nlohmann::json({{name_, value}}); + }, + [](auto &&value) -> auto { return nlohmann::json::parse(value); }, + }, + value_); + } +}; + +template class db_row final { +public: + db_row(std::shared_ptr context) { + auto column_count = sqlite3_column_count(context->stmt.get()); + for (std::int32_t col = 0; col < column_count; col++) { + std::string name{sqlite3_column_name(context->stmt.get(), col)}; + auto column_type = sqlite3_column_type(context->stmt.get(), col); + + db_types_t value; + switch (column_type) { + case SQLITE_INTEGER: { + value = sqlite3_column_int64(context->stmt.get(), col); + } break; + + case SQLITE_TEXT: { + const auto *text = reinterpret_cast( + sqlite3_column_text(context->stmt.get(), col)); + value = std::string(text == nullptr ? "" : text); + } break; + + default: + throw std::runtime_error("column type not implemented|" + name + '|' + + std::to_string(column_type)); + } + + columns_[name] = db_column{col, name, value}; + } + } + +private: + std::map columns_; + +public: + [[nodiscard]] auto get_columns() const -> std::vector { + std::vector ret; + for (const auto &item : columns_) { + ret.push_back(item.second); + } + return ret; + } + + [[nodiscard]] auto get_column(std::int32_t index) const -> db_column { + auto iter = std::find_if(columns_.begin(), columns_.end(), + [&index](auto &&col) -> bool { + return col.second.get_index() == index; + }); + if (iter == columns_.end()) { + throw std::out_of_range(""); + } + + return iter->second; + } + + [[nodiscard]] auto get_column(std::string name) const -> db_column { + return columns_.at(name); + } +}; + +template struct db_result final { + db_result(std::shared_ptr context, std::int32_t res) + : context_(std::move(context)), res_(res) { + constexpr const auto *function_name = + static_cast(__FUNCTION__); + + if (res == SQLITE_OK) { + set_res(sqlite3_step(context_->stmt.get()), function_name); + } + } + +private: + std::shared_ptr context_; + mutable std::int32_t res_; + +private: + void set_res(std::int32_t res, std::string_view function) const { + if (res != SQLITE_OK && res != SQLITE_DONE && res != SQLITE_ROW) { + utils::error::raise_error(function, "failed to step|" + + std::to_string(res) + '|' + + sqlite3_errstr(res)); + } + res_ = res; + } + +public: + [[nodiscard]] auto ok() const -> bool { + return res_ == SQLITE_DONE || res_ == SQLITE_ROW; + } + + [[nodiscard]] auto get_error() const -> std::int32_t { return res_; } + + [[nodiscard]] auto get_error_str() const -> std::string { + return sqlite3_errstr(res_); + } + + [[nodiscard]] auto + get_row(std::optional> &row) const -> bool { + constexpr const auto *function_name = + static_cast(__FUNCTION__); + + row.reset(); + if (has_row()) { + row = db_row{context_}; + set_res(sqlite3_step(context_->stmt.get()), function_name); + return true; + } + return false; + } + + [[nodiscard]] auto has_row() const -> bool { return res_ == SQLITE_ROW; } + + void next_row() const { + constexpr const auto *function_name = + static_cast(__FUNCTION__); + + if (has_row()) { + set_res(sqlite3_step(context_->stmt.get()), function_name); + } + } +}; + +inline void set_journal_mode(sqlite3 &db3) { + sqlite3_exec(&db3, "PRAGMA journal_mode = WAL;PRAGMA synchronous = NORMAL;", + nullptr, nullptr, nullptr); +} + +[[nodiscard]] inline auto execute_sql(sqlite3 &db3, const std::string &sql, + std::string &err) -> bool { + char *err_msg{nullptr}; + auto res = sqlite3_exec(&db3, sql.c_str(), nullptr, nullptr, &err_msg); + if (err_msg != nullptr) { + err = err_msg; + sqlite3_free(err_msg); + err_msg = nullptr; + } + if (res != SQLITE_OK) { + err = "failed to execute sql|" + sql + "|" + std::to_string(res) + '|' + + (err.empty() ? sqlite3_errstr(res) : err); + return false; + } + + return true; +} + +template struct db_where_t final { + db_where_t(std::shared_ptr ctx, std::string column_name) + : context_(std::move(ctx)), column_name_(std::move(column_name)) {} + +private: + std::shared_ptr context_; + std::string column_name_; + +public: + [[nodiscard]] auto equals(db_types_t value) const -> next_t { + context_->ands.emplace_back(comp_data_t{column_name_, "=", value}); + return next_t{context_}; + } +}; + +template +struct db_where_next_t final { + db_where_next_t(std::shared_ptr ctx) : context_(std::move(ctx)) {} + +private: + std::shared_ptr context_; + +public: + [[nodiscard]] auto + and_where(std::string column_name) const -> db_where_t { + return db_where_t{context_, column_name}; + } + + [[nodiscard]] auto dump() const -> std::string { + return operation_t{context_}.dump(); + } + + [[nodiscard]] auto go() const -> db_result { + return operation_t{context_}.go(); + } +}; + +template struct db_where_with_limit_next_t final { + db_where_next(std::shared_ptr ctx) : context_(std::move(ctx)) {} + +private: + std::shared_ptr context_; + +public: + [[nodiscard]] auto and_where(std::string column_name) const + -> db_where_t { + return db_where_t{context_, column_name}; + } + + [[nodiscard]] auto dump() const -> std::string { + return operation_t{context_}.dump(); + } + + [[nodiscard]] auto go() const -> db_result { + return operation_t{context_}.go(); + } + + [[nodiscard]] auto limit(std::int32_t value) const -> operation_t { + return operation_t{context_}.limit(value); + } + + [[nodiscard]] auto order_by(std::string column_name, + bool ascending) const -> operation_t { + return operation_t{context_}.order_by(column_name, ascending); + } +}; +} // namespace repertory::db + +#endif // INCLUDE_DATABASE_DB_COMMON_HPP_ diff --git a/repertory/librepertory/include/database/db_common.hpp b/repertory/librepertory/include/database/db_common.hpp index f6f1f92a..64e4c3c8 100644 --- a/repertory/librepertory/include/database/db_common.hpp +++ b/repertory/librepertory/include/database/db_common.hpp @@ -47,6 +47,10 @@ struct sqlite3_statement_deleter { using db3_stmt_t = std::unique_ptr; +[[nodiscard]] auto execute_sql(sqlite3 &db3, const std::string &sql, + std::string &err) -> bool; +void set_journal_mode(sqlite3 &db3); + struct comp_data_t final { std::string column_name; std::string op_type; @@ -220,28 +224,75 @@ public: } }; -inline void set_journal_mode(sqlite3 &db3) { - sqlite3_exec(&db3, "PRAGMA journal_mode = WAL;PRAGMA synchronous = NORMAL;", - nullptr, nullptr, nullptr); -} +template struct db_where_t final { + db_where_t(std::shared_ptr ctx, std::string column_name) + : context_(std::move(ctx)), column_name_(std::move(column_name)) {} -[[nodiscard]] inline auto execute_sql(sqlite3 &db3, const std::string &sql, - std::string &err) -> bool { - char *err_msg{nullptr}; - auto res = sqlite3_exec(&db3, sql.c_str(), nullptr, nullptr, &err_msg); - if (err_msg != nullptr) { - err = err_msg; - sqlite3_free(err_msg); - err_msg = nullptr; +private: + std::shared_ptr context_; + std::string column_name_; + +public: + [[nodiscard]] auto equals(db_types_t value) const -> next_t { + context_->ands.emplace_back(comp_data_t{column_name_, "=", value}); + return next_t{context_}; } - if (res != SQLITE_OK) { - err = "failed to execute sql|" + sql + "|" + std::to_string(res) + '|' + - (err.empty() ? sqlite3_errstr(res) : err); - return false; +}; + +template +struct db_where_next_t final { + db_where_next_t(std::shared_ptr ctx) : context_(std::move(ctx)) {} + +private: + std::shared_ptr context_; + +public: + [[nodiscard]] auto and_where(std::string column_name) const + -> db_where_t { + return db_where_t{context_, column_name}; } - return true; -} + [[nodiscard]] auto dump() const -> std::string { + return operation_t{context_}.dump(); + } + + [[nodiscard]] auto go() const -> db_result { + return operation_t{context_}.go(); + } +}; + +template +struct db_where_with_limit_next_t final { + db_where_with_limit_next_t(std::shared_ptr ctx) + : context_(std::move(ctx)) {} + +private: + std::shared_ptr context_; + +public: + [[nodiscard]] auto and_where(std::string column_name) const + -> db_where_t { + return db_where_t{context_, + column_name}; + } + + [[nodiscard]] auto dump() const -> std::string { + return operation_t{context_}.dump(); + } + + [[nodiscard]] auto go() const -> db_result { + return operation_t{context_}.go(); + } + + [[nodiscard]] auto limit(std::int32_t value) const -> operation_t { + return operation_t{context_}.limit(value); + } + + [[nodiscard]] auto order_by(std::string column_name, + bool ascending) const -> operation_t { + return operation_t{context_}.order_by(column_name, ascending); + } +}; } // namespace repertory::db #endif // INCLUDE_DATABASE_DB_COMMON_HPP_ diff --git a/repertory/librepertory/include/database/db_delete.hpp b/repertory/librepertory/include/database/db_delete.hpp index 38640b21..e3bbbfb8 100644 --- a/repertory/librepertory/include/database/db_delete.hpp +++ b/repertory/librepertory/include/database/db_delete.hpp @@ -47,43 +47,6 @@ public: db_delete(std::shared_ptr ctx) : context_(std::move(ctx)) {} -public: - struct db_where final { - db_where(std::shared_ptr ctx, std::string column_name) - : context_(std::move(ctx)), column_name_(std::move(column_name)) {} - - public: - struct db_where_next final { - db_where_next(std::shared_ptr ctx) : context_(std::move(ctx)) {} - - private: - std::shared_ptr context_; - - public: - [[nodiscard]] auto and_where(std::string column_name) const -> db_where { - return db_where{context_, column_name}; - } - - [[nodiscard]] auto dump() const -> std::string { - return db_delete{context_}.dump(); - } - - [[nodiscard]] auto go() const -> db_result { - return db_delete{context_}.go(); - } - }; - - private: - std::shared_ptr context_; - std::string column_name_; - - public: - [[nodiscard]] auto equals(db_types_t value) const -> db_where_next { - context_->ands.emplace_back(comp_data_t{column_name_, "=", value}); - return db_where_next{context_}; - } - }; - private: std::shared_ptr context_; @@ -92,7 +55,8 @@ public: [[nodiscard]] auto go() const -> db_result; - [[nodiscard]] auto where(std::string column_name) const -> db_where; + [[nodiscard]] auto where(std::string column_name) const + -> db_where_t>; }; } // namespace repertory::db diff --git a/repertory/librepertory/include/database/db_select.hpp b/repertory/librepertory/include/database/db_select.hpp index 0ace9477..526d6360 100644 --- a/repertory/librepertory/include/database/db_select.hpp +++ b/repertory/librepertory/include/database/db_select.hpp @@ -23,6 +23,7 @@ #define INCLUDE_DATABASE_DB_SELECT_HPP_ #include "database/db_common.hpp" +#include "db_common.hpp" #include "utils/error_utils.hpp" namespace repertory::db { @@ -51,52 +52,6 @@ public: db_select(std::shared_ptr ctx) : context_(std::move(ctx)) {} -public: - struct db_where final { - db_where(std::shared_ptr ctx, std::string column_name) - : context_(std::move(ctx)), column_name_(std::move(column_name)) {} - - public: - struct db_where_next final { - db_where_next(std::shared_ptr ctx) : context_(std::move(ctx)) {} - - private: - std::shared_ptr context_; - - public: - [[nodiscard]] auto and_where(std::string column_name) const -> db_where { - return db_where{context_, column_name}; - } - - [[nodiscard]] auto dump() const -> std::string { - return db_select{context_}.dump(); - } - - [[nodiscard]] auto go() const -> db_result { - return db_select{context_}.go(); - } - - [[nodiscard]] auto limit(std::int32_t value) const -> db_select { - return db_select{context_}.limit(value); - } - - [[nodiscard]] auto order_by(std::string column_name, - bool ascending) const -> db_select { - return db_select{context_}.order_by(column_name, ascending); - } - }; - - private: - std::shared_ptr context_; - std::string column_name_; - - public: - [[nodiscard]] auto equals(db_types_t value) const -> db_where_next { - context_->ands.emplace_back(comp_data_t{column_name_, "=", value}); - return db_where_next{context_}; - } - }; - private: std::shared_ptr context_; @@ -115,7 +70,8 @@ public: [[nodiscard]] auto order_by(std::string column_name, bool ascending) -> db_select &; - [[nodiscard]] auto where(std::string column_name) const -> db_where; + [[nodiscard]] auto where(std::string column_name) const + -> db_where_t>; }; } // namespace repertory::db diff --git a/repertory/librepertory/include/database/db_update.hpp b/repertory/librepertory/include/database/db_update.hpp index 8899493e..e2ab90f3 100644 --- a/repertory/librepertory/include/database/db_update.hpp +++ b/repertory/librepertory/include/database/db_update.hpp @@ -43,52 +43,6 @@ public: using row = db_row; -public: - struct db_where final { - db_where(std::shared_ptr ctx, std::string column_name) - : context_(std::move(ctx)), column_name_(std::move(column_name)) {} - - public: - struct db_where_next final { - db_where_next(std::shared_ptr ctx) : context_(std::move(ctx)) {} - - private: - std::shared_ptr context_; - - public: - [[nodiscard]] auto and_where(std::string column_name) const -> db_where { - return db_where{context_, column_name}; - } - - [[nodiscard]] auto dump() const -> std::string { - return db_update{context_}.dump(); - } - - [[nodiscard]] auto go() const -> db_result { - return db_update{context_}.go(); - } - - [[nodiscard]] auto limit(std::int32_t value) const -> db_update { - return db_update{context_}.limit(value); - } - - [[nodiscard]] auto order_by(std::string column_name, - bool ascending) const -> db_update { - return db_update{context_}.order_by(column_name, ascending); - } - }; - - private: - std::shared_ptr context_; - std::string column_name_; - - public: - [[nodiscard]] auto equals(db_types_t value) const -> db_where_next { - context_->ands.emplace_back(comp_data_t{column_name_, "=", value}); - return db_where_next{context_}; - } - }; - public: db_update(sqlite3 &db3, std::string table_name) : context_(std::make_shared(db3, table_name)) {} @@ -106,7 +60,13 @@ public: [[nodiscard]] auto go() const -> db_result; - [[nodiscard]] auto where(std::string column_name) const -> db_where; + [[nodiscard]] auto limit(std::int32_t value) -> db_update &; + + [[nodiscard]] auto order_by(std::string column_name, + bool ascending) -> db_update &; + + [[nodiscard]] auto where(std::string column_name) const + -> db_where_t>; }; } // namespace repertory::db diff --git a/repertory/librepertory/src/database/db_common.cpp b/repertory/librepertory/src/database/db_common.cpp new file mode 100644 index 00000000..f95ca3cd --- /dev/null +++ b/repertory/librepertory/src/database/db_common.cpp @@ -0,0 +1,47 @@ +/* + Copyright <2018-2024> + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ +#include "database/db_common.hpp" + +namespace repertory::db { +[[nodiscard]] auto execute_sql(sqlite3 &db3, const std::string &sql, + std::string &err) -> bool { + char *err_msg{nullptr}; + auto res = sqlite3_exec(&db3, sql.c_str(), nullptr, nullptr, &err_msg); + if (err_msg != nullptr) { + err = err_msg; + sqlite3_free(err_msg); + err_msg = nullptr; + } + if (res != SQLITE_OK) { + err = "failed to execute sql|" + sql + "|" + std::to_string(res) + '|' + + (err.empty() ? sqlite3_errstr(res) : err); + return false; + } + + return true; +} + +void set_journal_mode(sqlite3 &db3) { + sqlite3_exec(&db3, "PRAGMA journal_mode = WAL;PRAGMA synchronous = NORMAL;", + nullptr, nullptr, nullptr); +} +} // namespace repertory::db diff --git a/repertory/librepertory/src/database/db_delete.cpp b/repertory/librepertory/src/database/db_delete.cpp index 8009a289..4f0be276 100644 --- a/repertory/librepertory/src/database/db_delete.cpp +++ b/repertory/librepertory/src/database/db_delete.cpp @@ -22,7 +22,6 @@ #include "database/db_delete.hpp" namespace repertory::db { - auto db_delete::dump() const -> std::string { std::stringstream query; query << "DELETE FROM \"" << context_->table_name << "\""; @@ -88,7 +87,9 @@ auto db_delete::go() const -> db_result { return {context_, res}; } -auto db_delete::where(std::string column_name) const -> db_where { - return db_where{context_, column_name}; +auto db_delete::where(std::string column_name) const + -> db_where_t> { + return db_where_t>{context_, + column_name}; } } // namespace repertory::db diff --git a/repertory/librepertory/src/database/db_select.cpp b/repertory/librepertory/src/database/db_select.cpp index d6a54ffc..2ac78270 100644 --- a/repertory/librepertory/src/database/db_select.cpp +++ b/repertory/librepertory/src/database/db_select.cpp @@ -22,7 +22,6 @@ #include "database/db_select.hpp" namespace repertory::db { - auto db_select::column(std::string column_name) -> db_select & { context_->columns.push_back(column_name); return *this; @@ -145,7 +144,9 @@ auto db_select::order_by(std::string column_name, return *this; } -auto db_select::where(std::string column_name) const -> db_where { - return db_where{context_, column_name}; +auto db_select::where(std::string column_name) const + -> db_where_t> { + return db_where_t>{ + context_, column_name}; } } // namespace repertory::db diff --git a/repertory/librepertory/src/database/db_update.cpp b/repertory/librepertory/src/database/db_update.cpp index 0176e6b5..b27678ad 100644 --- a/repertory/librepertory/src/database/db_update.cpp +++ b/repertory/librepertory/src/database/db_update.cpp @@ -39,7 +39,7 @@ auto db_update::dump() const -> std::string { } auto column = std::next(context_->values.begin(), idx); - query << column->first << "=?" + std::to_string(idx + 1U); + query << column->first << "=?" + std::to_string(idx + 1); } if (not context_->ands.empty()) { @@ -52,7 +52,7 @@ auto db_update::dump() const -> std::string { auto &item = context_->ands.at(static_cast(idx)); query << '"' << item.column_name << '"' << item.op_type << "?" - << (idx + context_->values.size() + 1U); + << (idx + static_cast(context_->values.size()) + 1); } query << ")"; } @@ -133,7 +133,20 @@ auto db_update::go() const -> db_result { return {context_, res}; } -auto db_update::where(std::string column_name) const -> db_where { - return db_where{context_, column_name}; +auto db_update::limit(std::int32_t value) -> db_update & { + context_->limit = value; + return *this; +} + +auto db_update::order_by(std::string column_name, + bool ascending) -> db_update & { + context_->order_by = {column_name, ascending}; + return *this; +} + +auto db_update::where(std::string column_name) const + -> db_where_t> { + return db_where_t>{ + context_, column_name}; } } // namespace repertory::db