diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 495ae59a..c7979ff6 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -46,6 +46,7 @@ jobs: gtest/* googletest/* build/* + tests/* fail-under-line: 80 html-out: coverage.html html-details: true diff --git a/CMakeLists.txt b/CMakeLists.txt index 8a2f4f2e..6fd66204 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -61,18 +61,55 @@ endif() # --------------------------------------------------------------------------------------- # Build the library # --------------------------------------------------------------------------------------- -add_library(datamanagement INTERFACE) +set(DATAMANAGEMENT_SRCS + src/modeldata/model_data.cpp + src/source/csv_source.cpp + src/source/db_source.cpp + src/utils/logging.cpp +) + +if(DATAMANAGEMENT_BUILD_SHARED_LIBS) + if(WIN32) + configure_file(${CMAKE_CURRENT_SOURCE_DIR}/cmake/version.rc.in ${CMAKE_CURRENT_BINARY_DIR}/version.rc @ONLY) + list(APPEND DATAMANAGEMENT_SRCS ${CMAKE_CURRENT_BINARY_DIR}/version.rc) + endif() + add_library(datamanagement SHARED ${DATAMANAGEMENT_SRCS} ${DATAMANAGEMENT_ALL_HEADERS}) + target_compile_definitions(datamanagement PUBLIC DATAMANAGEMENT_SHARED_LIB) + if(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") + target_compile_options(datamanagement PUBLIC $<$,$>>:/wd4251 + /wd4275>) + endif() +else() + add_library(datamanagement STATIC ${DATAMANAGEMENT_SRCS} ${DATAMANAGEMENT_ALL_HEADERS}) +endif() + +# --------------------------------------------------------------------------------------- +# Alias for importing +# --------------------------------------------------------------------------------------- add_library(datamanagement::datamanagement ALIAS datamanagement) +set(DATAMANAGEMENT_INCLUDES_LEVEL "") +if(DATAMANAGEMENT_SYSTEM_INCLUDES) + set(DATAMANAGEMENT_INCLUDES_LEVEL "SYSTEM") +endif() + +target_compile_definitions(datamanagement PUBLIC DATAMANAGEMENT_COMPILED_LIB) +target_include_directories(datamanagement ${DATAMANAGEMENT_INCLUDES_LEVEL} PUBLIC "$" + "$" + PRIVATE "$") + option(BUILD_TESTS "enable datamanagement unit tests" OFF) if (NOT BUILD_TESTS STREQUAL OFF) add_subdirectory(tests) endif() -target_include_directories(${PROJECT_NAME} - INTERFACE - $ - $ +target_compile_definitions(datamanagement PUBLIC DATAMANAGEMENT_COMPILED_LIB) +target_include_directories(datamanagement ${DATAMANAGEMENT_INCLUDES_LEVEL} + PUBLIC + "$" + "$" + PRIVATE + "$" ) # --------------------------------------------------------------------------------------- @@ -80,13 +117,26 @@ target_include_directories(${PROJECT_NAME} # --------------------------------------------------------------------------------------- include(cmake/MakeDependenciesAvailable.cmake) -target_link_libraries(${PROJECT_NAME} - INTERFACE - SQLiteCpp - Boost::boost - spdlog::spdlog - Eigen3::Eigen -) +if (DATAMANAGEMENT_CALCULATE_COVERAGE) + target_link_libraries(datamanagement + PUBLIC + SQLiteCpp + Eigen3::Eigen + PRIVATE + spdlog::spdlog + Boost::boost + gcov + ) +else() + target_link_libraries(datamanagement + PUBLIC + SQLiteCpp + Eigen3::Eigen + PRIVATE + spdlog::spdlog + Boost::boost + ) +endif() set_target_properties(datamanagement PROPERTIES VERSION ${DATAMANAGEMENT_VERSION} SOVERSION ${DATAMANAGEMENT_VERSION_MAJOR}.${DATAMANAGEMENT_VERSION_MINOR}) diff --git a/cmake/LoadEigen.cmake b/cmake/LoadEigen.cmake index 9a20b063..9877076d 100644 --- a/cmake/LoadEigen.cmake +++ b/cmake/LoadEigen.cmake @@ -3,21 +3,16 @@ message(CHECK_START "Fetching Eigen3...") list(APPEND CMAKE_MESSAGE_INDENT " ") FetchContent_Declare( - Eigen + Eigen3 GIT_REPOSITORY https://gitlab.com/libeigen/eigen.git - GIT_TAG master - GIT_SHALLOW TRUE - GIT_PROGRESS TRUE + GIT_TAG "3.4.0" FIND_PACKAGE_ARGS NAMES Eigen3 ) -# note: To disable eigen tests, -# you should put this code in a add_subdirectory to avoid to change -# BUILD_TESTING for your own project too since variables are directory -# scoped -set(BUILD_TESTING OFF) + +# Turn off Eigen Testing and Docs set(EIGEN_BUILD_TESTING OFF) -set(EIGEN_MPL2_ONLY ON) -set(EIGEN_BUILD_PKGCONFIG OFF) set(EIGEN_BUILD_DOC OFF) +set(EIGEN_MPL2_ONLY ON) + list(POP_BACK CMAKE_MESSAGE_INDENT) message(CHECK_PASS "Eigen3 Fetched") \ No newline at end of file diff --git a/cmake/LoadSpdlog.cmake b/cmake/LoadSpdlog.cmake index 2aa2b046..49a9b109 100644 --- a/cmake/LoadSpdlog.cmake +++ b/cmake/LoadSpdlog.cmake @@ -6,5 +6,6 @@ FetchContent_Declare( GIT_TAG v1.x GIT_PROGRESS TRUE ) +set(SPDLOG_INSTALL ON) list(POP_BACK CMAKE_MESSAGE_INDENT) message(CHECK_PASS "spdlog Fetched") \ No newline at end of file diff --git a/cmake/MakeDependenciesAvailable.cmake b/cmake/MakeDependenciesAvailable.cmake index 2c9306a2..d6d755f3 100644 --- a/cmake/MakeDependenciesAvailable.cmake +++ b/cmake/MakeDependenciesAvailable.cmake @@ -8,9 +8,9 @@ include(cmake/LoadEigen.cmake) include(cmake/LoadSpdlog.cmake) if(DATAMANAGEMENT_BUILD_TESTS STREQUAL "OFF") - FetchContent_MakeAvailable(Eigen SQLiteCpp Boost spdlog) + FetchContent_MakeAvailable(Eigen3 SQLiteCpp Boost spdlog) elseif(DATAMANAGEMENT_BUILD_TESTS STREQUAL "ON") include(cmake/LoadGtest.cmake) - FetchContent_MakeAvailable(Eigen SQLiteCpp Boost spdlog googletest) + FetchContent_MakeAvailable(Eigen3 SQLiteCpp Boost spdlog googletest) include(GoogleTest) endif() diff --git a/cmake/datamanagement.pc.in b/cmake/datamanagement.pc.in index bd8558ae..691a4eb9 100644 --- a/cmake/datamanagement.pc.in +++ b/cmake/datamanagement.pc.in @@ -8,5 +8,5 @@ Description: Syndemics Data Management URL: https://github.com/SyndemicsLab Version: @DATAMANAGEMENT_VERSION@ CFlags: -I${includedir} @PKG_CONFIG_DEFINES@ -Libs: -L${libdir} -lspdlog -lboost +Libs: -L${libdir} -lspdlog -lboost -libeigen -libsqlitecpp Requires: @PKG_CONFIG_REQUIRES@ \ No newline at end of file diff --git a/include/datamanagement/datamanagement.hpp b/include/datamanagement/datamanagement.hpp index 15666cfe..074f0aee 100644 --- a/include/datamanagement/datamanagement.hpp +++ b/include/datamanagement/datamanagement.hpp @@ -4,7 +4,7 @@ // Created Date: Th Feb 2025 // // Author: Matthew Carroll // // ----- // -// Last Modified: Thu Feb 20 2025 // +// Last Modified: 2025-05-01 // // Modified By: Matthew Carroll // // ----- // // Copyright (c) 2025 Syndemics Lab at Boston Medical Center // @@ -18,7 +18,6 @@ #define DATAMANAGEMENT_DATAMANAGEMENT_HPP_ #include -#include #include #include diff --git a/include/datamanagement/modeldata/model_data.hpp b/include/datamanagement/modeldata/model_data.hpp index 2e146d5f..c61e01a4 100644 --- a/include/datamanagement/modeldata/model_data.hpp +++ b/include/datamanagement/modeldata/model_data.hpp @@ -4,7 +4,7 @@ // Created Date: Th Feb 2025 // // Author: Matthew Carroll // // ----- // -// Last Modified: 2025-04-30 // +// Last Modified: 2025-05-01 // // Modified By: Matthew Carroll // // ----- // // Copyright (c) 2025 Syndemics Lab at Boston Medical Center // @@ -17,66 +17,33 @@ #ifndef DATAMANAGEMENT_MODELDATA_MODELDATA_HPP_ #define DATAMANAGEMENT_MODELDATA_MODELDATA_HPP_ -#include #include #include #include -#include #include #include namespace datamanagement { class ModelData { -private: - datamanagement::source::Config config; - std::unordered_map - _csv_sources = {}; - std::unordered_map - _db_sources = {}; - public: - ModelData(const std::string &cfgfile) : config(cfgfile) {} - ~ModelData() = default; - datamanagement::source::Config GetConfig() const { return config; } + virtual ~ModelData() = default; + virtual std::string GetFromConfig(const std::string &key) const = 0; + + virtual std::vector + GetConfigSectionCategories(const std::string §ion) const = 0; + + virtual void AddSource(const std::string &path) = 0; - void AddSource(const std::string &path) { - std::filesystem::path p = path; - if (p.extension() == ".csv") { - datamanagement::source::CSVSource s; - _csv_sources[p.stem()] = std::move(s); - _csv_sources[p.stem()].ConnectToFile(path); - } else if (p.extension() == ".db") { - datamanagement::source::DBSource s; - _db_sources[p.stem()] = std::move(s); - _db_sources[p.stem()].SetDatabasePath(path); - } else { - // Not a valid source file - } - } + virtual std::vector GetCSVSourceNames() const = 0; + virtual std::vector GetDBSourceNames() const = 0; - std::vector GetCSVSourceNames() const { - std::vector names = {}; - for (const auto &[k, v] : _csv_sources) { - names.push_back(k); - } - return names; - } - std::vector GetDBSourceNames() const { - std::vector names = {}; - for (const auto &[k, v] : _db_sources) { - names.push_back(k); - } - return names; - } + virtual source::CSVSource &GetCSVSource(const std::string &name) = 0; - datamanagement::source::CSVSource &GetCSVSource(const std::string &name) { - return _csv_sources[name]; - } + virtual source::DBSource &GetDBSource(const std::string &name) = 0; - datamanagement::source::DBSource &GetDBSource(const std::string &name) { - return _db_sources[name]; - } + static std::unique_ptr + Create(const std::string &config, const std::string &log_name = "console"); }; } // namespace datamanagement diff --git a/include/datamanagement/source/config.hpp b/include/datamanagement/source/config.hpp deleted file mode 100644 index f51f5d07..00000000 --- a/include/datamanagement/source/config.hpp +++ /dev/null @@ -1,55 +0,0 @@ -//////////////////////////////////////////////////////////////////////////////// -// File: config.hpp // -// Project: source // -// Created Date: Th Feb 2025 // -// Author: Matthew Carroll // -// ----- // -// Last Modified: Thu Feb 20 2025 // -// Modified By: Matthew Carroll // -// ----- // -// Copyright (c) 2025 Syndemics Lab at Boston Medical Center // -// ----- // -// HISTORY: // -// Date By Comments // -// ---------- --- --------------------------------------------------------- // -//////////////////////////////////////////////////////////////////////////////// - -#ifndef DATAMANAGEMENT_SOURCE_CONFIG_HPP_ -#define DATAMANAGEMENT_SOURCE_CONFIG_HPP_ - -#include -#include -#include -#include - -namespace datamanagement::source { -class Config { -private: - boost::property_tree::ptree ptree; - -public: - Config(const std::string &path) { read_ini(path, ptree); } - ~Config() = default; - - void GetFromConfig(std::string const key, std::string &data) const { - try { - data = ptree.get(key); - } catch (const std::exception &e) { - // log bad cast - } - } - - void GetConfigSectionCategories(std::string const section, - std::vector &data) const { - boost::property_tree::ptree sub_tree = this->ptree.get_child(section); - std::vector key_list; - - for (auto &key : sub_tree) { - key_list.push_back(key.first); - } - data = key_list; - } -}; -} // namespace datamanagement::source - -#endif // DATAMANAGEMENT_SOURCE_CONFIG_HPP_ \ No newline at end of file diff --git a/include/datamanagement/source/csv_source.hpp b/include/datamanagement/source/csv_source.hpp index a52b1e4c..f90f498d 100644 --- a/include/datamanagement/source/csv_source.hpp +++ b/include/datamanagement/source/csv_source.hpp @@ -4,7 +4,7 @@ // Created Date: Th Feb 2025 // // Author: Matthew Carroll // // ----- // -// Last Modified: Fri Feb 21 2025 // +// Last Modified: 2025-05-01 // // Modified By: Matthew Carroll // // ----- // // Copyright (c) 2025 Syndemics Lab at Boston Medical Center // @@ -17,81 +17,40 @@ #ifndef DATAMANAGEMENT_SOURCE_CSVSOURCE_HPP_ #define DATAMANAGEMENT_SOURCE_CSVSOURCE_HPP_ -#include -#include -#include #include #include #include +#include +#include +#include + namespace datamanagement::source { class CSVSource { -private: - std::string filepath; - - bool CheckWhereConditions(const csv::CSVRow &row, - const std::unordered_map - &where_conditions) const { - if (where_conditions.empty()) { - return true; - } - for (auto &condition : where_conditions) { - if (row[condition.first].get() != condition.second) { - return false; - } - } - return true; - } - public: - CSVSource() {} + CSVSource() = default; ~CSVSource() = default; - void ConnectToFile(const std::string &s) { filepath = s; } + inline void ConnectToFile(const std::string &s) { _filepath = s; } - std::string GetName() const { - std::filesystem::path p = filepath; + inline std::string GetName() const { + std::filesystem::path p = _filepath; return p.filename().string(); } Eigen::MatrixXd GetData(const std::vector &select_columns, const std::unordered_map - &where_conditions) const { - std::vector> temp_data; - csv::CSVReader reader(filepath); - for (csv::CSVRow &row : reader) { - if (CheckWhereConditions(row, where_conditions)) { - std::vector temp_row; - for (const std::string &col : select_columns) { - temp_row.push_back(row[col].get()); - } - temp_data.push_back(temp_row); - } - } - Eigen::MatrixXd data(temp_data.size(), select_columns.size()); - for (int i = 0; i < temp_data.size(); ++i) { - data.row(i) = Eigen::Map(temp_data[i].data(), - temp_data[i].size()); - } - return data; - } + &where_conditions) const; - virtual void WriteCSV(std::string const &filepath, - const std::vector &columns) const { - std::ofstream file(filepath.c_str()); - for (const std::string &col : columns) { - file << col; - if (&col != &columns.back()) { - file << ","; - } else { - file << "\n"; - } - } - file << GetData(columns, {}) - .format(Eigen::IOFormat(Eigen::StreamPrecision, - Eigen::DontAlignCols, ",", "\n")); - file.close(); - } + void WriteCSV(std::string const &filepath, + const std::vector &columns) const; + +private: + std::string _filepath; + + bool CheckWhereConditions(const csv::CSVRow &row, + const std::unordered_map + &where_conditions) const; }; } // namespace datamanagement::source diff --git a/include/datamanagement/source/db_source.hpp b/include/datamanagement/source/db_source.hpp index 3c0bd25d..aabae16c 100644 --- a/include/datamanagement/source/db_source.hpp +++ b/include/datamanagement/source/db_source.hpp @@ -4,7 +4,7 @@ // Created Date: Th Feb 2025 // // Author: Matthew Carroll // // ----- // -// Last Modified: 2025-04-30 // +// Last Modified: 2025-05-01 // // Modified By: Matthew Carroll // // ----- // // Copyright (c) 2025 Syndemics Lab at Boston Medical Center // @@ -17,7 +17,6 @@ #ifndef DATAMANAGEMENT_SOURCE_DBDATASOURCE_HPP_ #define DATAMANAGEMENT_SOURCE_DBDATASOURCE_HPP_ -#include #include #include #include @@ -26,20 +25,22 @@ #include #include +#include + namespace datamanagement::source { using BindingVariant = std::variant; class DBSource { public: - DBSource() {} + DBSource() = default; ~DBSource() = default; // Move Constructor DBSource(DBSource &&old) = default; DBSource &operator=(DBSource &&) = default; - void SetDatabasePath(const std::string &p) { _path = p; } + inline void SetDatabasePath(const std::string &p) { _path = p; } - std::string GetDatabaseFileName() const { + inline std::string GetDatabaseFileName() const { std::filesystem::path p = _path; return p.stem(); } @@ -49,64 +50,11 @@ class DBSource { std::function callback, std::any &storage, - const std::unordered_map &bindings = {}) { - try { - SQLite::Database db(_path, - SQLite::OPEN_READWRITE | SQLite::OPEN_CREATE); - SQLite::Statement stmt(db, query); - - for (const auto &[index, value] : bindings) { - if (value.index() == 0) { - stmt.bind(index, std::get(value)); - } else if (value.index() == 1) { - stmt.bind(index, std::get(value)); - } else { - stmt.bind(index, std::get(value)); - } - } - - SQLite::Transaction transaction(db); - - while (stmt.executeStep()) { - callback(storage, stmt); - } - - transaction.commit(); - } catch (const std::exception &e) { - throw std::runtime_error("Error executing query: " + query + "\n" + - e.what()); - } - } + const std::unordered_map &bindings = {}); void BatchExecute(const std::string &query, const std::vector> - &bindings_batch = {}) { - try { - SQLite::Database db(_path, - SQLite::OPEN_READWRITE | SQLite::OPEN_CREATE); - SQLite::Transaction transaction(db); - SQLite::Statement stmt(db, query); - - for (auto &bindings : bindings_batch) { - for (const auto &[index, value] : bindings) { - if (value.index() == 0) { - stmt.bind(index, std::get(value)); - } else if (value.index() == 1) { - stmt.bind(index, std::get(value)); - } else { - stmt.bind(index, std::get(value)); - } - } - stmt.exec(); - stmt.reset(); - } - transaction.commit(); - - } catch (const std::exception &e) { - throw std::runtime_error("Error executing query: " + query + "\n" + - e.what()); - } - } + &bindings_batch = {}); private: std::string _path = ""; diff --git a/include/datamanagement/utils/csv.hpp b/include/datamanagement/utils/csv.hpp index 86be188e..3ed322a9 100644 --- a/include/datamanagement/utils/csv.hpp +++ b/include/datamanagement/utils/csv.hpp @@ -1,4 +1,6 @@ #pragma once +// GCOVR_EXCL_START +// clang-format off /* CSV for C++, version 2.3.0 https://github.com/vincentlaucsb/csv-parser @@ -8828,4 +8830,6 @@ namespace csv { } } // namespace csv +// clang-format on +// GCOVR_EXCL_STOP #endif \ No newline at end of file diff --git a/include/datamanagement/utils/logging.hpp b/include/datamanagement/utils/logging.hpp new file mode 100644 index 00000000..54a96856 --- /dev/null +++ b/include/datamanagement/utils/logging.hpp @@ -0,0 +1,38 @@ +//////////////////////////////////////////////////////////////////////////////// +// File: logging.hpp // +// Project: utils // +// Created Date: 2025-05-01 // +// Author: Matthew Carroll // +// ----- // +// Last Modified: 2025-05-01 // +// Modified By: Matthew Carroll // +// ----- // +// Copyright (c) 2025 Syndemics Lab at Boston Medical Center // +//////////////////////////////////////////////////////////////////////////////// +#ifndef DATAMANAGEMENT_UTILS_LOGGING_HPP_ +#define DATAMANAGEMENT_UTILS_LOGGING_HPP_ + +#include + +namespace datamanagement { +namespace utils { +enum class LogType : int { kInfo, kWarn, kError, kDebug, kCount }; +enum class CreationStatus : int { + kError = -1, + kSuccess = 0, + kExists = 1, + kNotCreated = 2, + kCount = 4 +}; + +CreationStatus CreateFileLogger(const std::string &logger_name, + const std::string &filepath); +void LogInfo(const std::string &logger_name, const std::string &message); +void LogWarning(const std::string &logger_name, const std::string &message); +void LogError(const std::string &logger_name, const std::string &message); +void LogDebug(const std::string &logger_name, const std::string &message); + +} // namespace utils +} // namespace datamanagement + +#endif // DATAMANAGEMENT_UTILS_LOGGING_HPP_ \ No newline at end of file diff --git a/src/modeldata/internals/model_data_internals.hpp b/src/modeldata/internals/model_data_internals.hpp new file mode 100644 index 00000000..162be13c --- /dev/null +++ b/src/modeldata/internals/model_data_internals.hpp @@ -0,0 +1,60 @@ +//////////////////////////////////////////////////////////////////////////////// +// File: model_data_internals.hpp // +// Project: internals // +// Created Date: 2025-05-01 // +// Author: Matthew Carroll // +// ----- // +// Last Modified: 2025-05-01 // +// Modified By: Matthew Carroll // +// ----- // +// Copyright (c) 2025 Syndemics Lab at Boston Medical Center // +//////////////////////////////////////////////////////////////////////////////// +#ifndef DATAMANAGEMENT_SRC_MODELDATA_MODELDATAINTERNALS_HPP_ +#define DATAMANAGEMENT_SRC_MODELDATA_MODELDATAINTERNALS_HPP_ + +#include + +#include +#include +#include +#include + +#include +#include + +namespace datamanagement { +class ModelDataImpl : public virtual ModelData { +public: + ModelDataImpl(const std::string &cfgfile, + const std::string &log_name = "console"); + ~ModelDataImpl() = default; + + std::string GetFromConfig(const std::string &key) const override; + + std::vector + GetConfigSectionCategories(const std::string §ion) const override; + + void AddSource(const std::string &path) override; + + std::vector GetCSVSourceNames() const override; + std::vector GetDBSourceNames() const override; + + inline datamanagement::source::CSVSource & + GetCSVSource(const std::string &name) override { + return _csv_sources[name]; + } + + inline datamanagement::source::DBSource & + GetDBSource(const std::string &name) override { + return _db_sources.at(name); + } + +private: + const std::string _log_name; + boost::property_tree::ptree _ptree; + std::unordered_map _csv_sources = {}; + std::unordered_map _db_sources = {}; +}; +} // namespace datamanagement + +#endif // DATAMANAGEMENT_LIB_MODELDATA_MODELDATAINTERNALS_HPP_ \ No newline at end of file diff --git a/src/modeldata/model_data.cpp b/src/modeldata/model_data.cpp new file mode 100644 index 00000000..6d76c4d5 --- /dev/null +++ b/src/modeldata/model_data.cpp @@ -0,0 +1,86 @@ +//////////////////////////////////////////////////////////////////////////////// +// File: model_data.cpp // +// Project: modeldata // +// Created Date: 2025-05-01 // +// Author: Matthew Carroll // +// ----- // +// Last Modified: 2025-05-01 // +// Modified By: Matthew Carroll // +// ----- // +// Copyright (c) 2025 Syndemics Lab at Boston Medical Center // +//////////////////////////////////////////////////////////////////////////////// + +#include + +#include + +#include "internals/model_data_internals.hpp" + +namespace datamanagement { +ModelDataImpl::ModelDataImpl(const std::string &config, + const std::string &log_name) + : _log_name(log_name) { + read_ini(config, _ptree); +} + +void ModelDataImpl::AddSource(const std::string &path) { + std::filesystem::path p = path; + if (p.extension() == ".csv") { + datamanagement::source::CSVSource s; + _csv_sources[p.stem()] = std::move(s); + _csv_sources[p.stem()].ConnectToFile(path); + } else if (p.extension() == ".db") { + datamanagement::source::DBSource s; + _db_sources[p.stem()] = std::move(s); + _db_sources[p.stem()].SetDatabasePath(path); + } else { + datamanagement::utils::LogWarning( + _log_name, path + " is an invalid invalid source file..."); + } +} + +std::vector ModelDataImpl::GetCSVSourceNames() const { + std::vector names = {}; + for (const auto &[k, v] : _csv_sources) { + names.push_back(k); + } + return names; +} + +std::vector ModelDataImpl::GetDBSourceNames() const { + std::vector names = {}; + for (const auto &[k, v] : _db_sources) { + names.push_back(k); + } + return names; +} + +// Public Methods +std::string ModelDataImpl::GetFromConfig(const std::string &key) const { + std::string result; + try { + result = _ptree.get(key); + } catch (const std::exception &e) { + datamanagement::utils::LogError(_log_name, + "Error in attempting to extract " + + key + " from config file..."); + } + return result; +} + +std::vector +ModelDataImpl::GetConfigSectionCategories(const std::string §ion) const { + boost::property_tree::ptree sub_tree = _ptree.get_child(section); + std::vector key_list; + + for (auto &key : sub_tree) { + key_list.push_back(key.first); + } + return key_list; +} + +std::unique_ptr ModelData::Create(const std::string &config, + const std::string &log_name) { + return std::make_unique(config, log_name); +} +} // namespace datamanagement \ No newline at end of file diff --git a/src/source/csv_source.cpp b/src/source/csv_source.cpp new file mode 100644 index 00000000..8199ef11 --- /dev/null +++ b/src/source/csv_source.cpp @@ -0,0 +1,72 @@ +//////////////////////////////////////////////////////////////////////////////// +// File: csv_source.cpp // +// Project: source // +// Created Date: 2025-05-01 // +// Author: Matthew Carroll // +// ----- // +// Last Modified: 2025-05-01 // +// Modified By: Matthew Carroll // +// ----- // +// Copyright (c) 2025 Syndemics Lab at Boston Medical Center // +//////////////////////////////////////////////////////////////////////////////// + +#include + +namespace datamanagement { +namespace source { +bool CSVSource::CheckWhereConditions( + const csv::CSVRow &row, + const std::unordered_map &where_conditions) + const { + if (where_conditions.empty()) { + return true; + } + for (auto &condition : where_conditions) { + if (row[condition.first].get() != condition.second) { + return false; + } + } + return true; +} + +Eigen::MatrixXd +CSVSource::GetData(const std::vector &select_columns, + const std::unordered_map + &where_conditions) const { + std::vector> temp_data; + csv::CSVReader reader(_filepath); + for (csv::CSVRow &row : reader) { + if (CheckWhereConditions(row, where_conditions)) { + std::vector temp_row; + for (const std::string &col : select_columns) { + temp_row.push_back(row[col].get()); + } + temp_data.push_back(temp_row); + } + } + Eigen::MatrixXd data(temp_data.size(), select_columns.size()); + for (int i = 0; i < temp_data.size(); ++i) { + data.row(i) = Eigen::Map(temp_data[i].data(), + temp_data[i].size()); + } + return data; +} + +void CSVSource::WriteCSV(std::string const &filepath, + const std::vector &columns) const { + std::ofstream file(filepath.c_str()); + for (const std::string &col : columns) { + file << col; + if (&col != &columns.back()) { + file << ","; + } else { + file << "\n"; + } + } + file << GetData(columns, {}) + .format(Eigen::IOFormat(Eigen::StreamPrecision, + Eigen::DontAlignCols, ",", "\n")); + file.close(); +} +} // namespace source +} // namespace datamanagement \ No newline at end of file diff --git a/src/source/db_source.cpp b/src/source/db_source.cpp new file mode 100644 index 00000000..f8d50fd7 --- /dev/null +++ b/src/source/db_source.cpp @@ -0,0 +1,83 @@ +//////////////////////////////////////////////////////////////////////////////// +// File: db_source.cpp // +// Project: source // +// Created Date: 2025-05-01 // +// Author: Matthew Carroll // +// ----- // +// Last Modified: 2025-05-01 // +// Modified By: Matthew Carroll // +// ----- // +// Copyright (c) 2025 Syndemics Lab at Boston Medical Center // +//////////////////////////////////////////////////////////////////////////////// + +#include + +namespace datamanagement { +namespace source { + +void DBSource::Select( + const std::string &query, + std::function + callback, + std::any &storage, + const std::unordered_map &bindings) { + try { + SQLite::Database db(_path, + SQLite::OPEN_READWRITE | SQLite::OPEN_CREATE); + SQLite::Statement stmt(db, query); + + for (const auto &[index, value] : bindings) { + if (value.index() == 0) { + stmt.bind(index, std::get(value)); + } else if (value.index() == 1) { + stmt.bind(index, std::get(value)); + } else { + stmt.bind(index, std::get(value)); + } + } + + SQLite::Transaction transaction(db); + + while (stmt.executeStep()) { + callback(storage, stmt); + } + + transaction.commit(); + } catch (const std::exception &e) { + throw std::runtime_error("Error executing query: " + query + "\n" + + e.what()); + } +} + +void DBSource::BatchExecute( + const std::string &query, + const std::vector> + &bindings_batch) { + try { + SQLite::Database db(_path, + SQLite::OPEN_READWRITE | SQLite::OPEN_CREATE); + SQLite::Transaction transaction(db); + SQLite::Statement stmt(db, query); + + for (auto &bindings : bindings_batch) { + for (const auto &[index, value] : bindings) { + if (value.index() == 0) { + stmt.bind(index, std::get(value)); + } else if (value.index() == 1) { + stmt.bind(index, std::get(value)); + } else { + stmt.bind(index, std::get(value)); + } + } + stmt.exec(); + stmt.reset(); + } + transaction.commit(); + + } catch (const std::exception &e) { + throw std::runtime_error("Error executing query: " + query + "\n" + + e.what()); + } +} +} // namespace source +} // namespace datamanagement \ No newline at end of file diff --git a/src/utils/internals/logging_internals.hpp b/src/utils/internals/logging_internals.hpp new file mode 100644 index 00000000..bb554b84 --- /dev/null +++ b/src/utils/internals/logging_internals.hpp @@ -0,0 +1,67 @@ +//////////////////////////////////////////////////////////////////////////////// +// File: logging_internals.hpp // +// Project: internals // +// Created Date: 2025-05-01 // +// Author: Matthew Carroll // +// ----- // +// Last Modified: 2025-05-01 // +// Modified By: Matthew Carroll // +// ----- // +// Copyright (c) 2025 Syndemics Lab at Boston Medical Center // +//////////////////////////////////////////////////////////////////////////////// +#ifndef DATAMANAGEMENT_UTILS_LOGGINGINTERNALS_HPP_ +#define DATAMANAGEMENT_UTILS_LOGGINGINTERNALS_HPP_ + +#include + +#include +#include + +#include +#include +#include + +namespace datamanagement { +namespace utils { +CreationStatus CheckIfExists(const std::string &logger_name) { + return (spdlog::get(logger_name) != nullptr) ? CreationStatus::kExists + : CreationStatus::kNotCreated; +} +void log(const std::string &logger_name, const std::string &message, + LogType type = LogType::kInfo) { + CreationStatus status = CheckIfExists(logger_name); + if ((status == CreationStatus::kNotCreated) && + (CreateFileLogger(logger_name, "log.txt") == CreationStatus::kError)) { + std::cerr << "Failed to create logger: " << logger_name << std::endl; + return; + } + auto logger = spdlog::get(logger_name); + if (logger) { + switch (type) { + case LogType::kInfo: + logger->info(message); + break; + case LogType::kWarn: + logger->warn(message); + break; + case LogType::kError: + logger->error(message); + break; + case LogType::kDebug: + logger->set_level(spdlog::level::debug); + logger->debug(message); + break; + default: + logger->info(message); + break; + } + logger->flush(); + } else { + spdlog::error("Logger {} not found", logger_name); + } +} + +} // namespace utils +} // namespace datamanagement + +#endif // DATAMANAGEMENT_UTILS_LOGGINGINTERNALS_HPP_ \ No newline at end of file diff --git a/src/utils/logging.cpp b/src/utils/logging.cpp new file mode 100644 index 00000000..c9ec4143 --- /dev/null +++ b/src/utils/logging.cpp @@ -0,0 +1,51 @@ +//////////////////////////////////////////////////////////////////////////////// +// File: logging.cpp // +// Project: utils // +// Created Date: 2025-05-01 // +// Author: Matthew Carroll // +// ----- // +// Last Modified: 2025-05-01 // +// Modified By: Matthew Carroll // +// ----- // +// Copyright (c) 2025 Syndemics Lab at Boston Medical Center // +//////////////////////////////////////////////////////////////////////////////// +#include + +#include "internals/logging_internals.hpp" + +namespace datamanagement { +namespace utils { +CreationStatus CreateFileLogger(const std::string &logger_name, + const std::string &filepath) { + if (CheckIfExists(logger_name) == CreationStatus::kExists) { + return CreationStatus::kExists; + } + try { + spdlog::cfg::load_env_levels(); + spdlog::set_pattern("[%H:%M:%S %z] [%n] [%^---%L---%$] [thread %t] %v"); + spdlog::basic_logger_mt(logger_name, filepath); + spdlog::flush_every(std::chrono::seconds(3)); + } catch (const spdlog::spdlog_ex &ex) { + std::cout << "Log init failed: " << ex.what() << std::endl; + return CreationStatus::kError; + } + return CreationStatus::kSuccess; +} + +void LogInfo(const std::string &logger_name, const std::string &message) { + log(logger_name, message, LogType::kInfo); +} + +void LogWarning(const std::string &logger_name, const std::string &message) { + log(logger_name, message, LogType::kWarn); +} + +void LogError(const std::string &logger_name, const std::string &message) { + log(logger_name, message, LogType::kError); +} + +void LogDebug(const std::string &logger_name, const std::string &message) { + log(logger_name, message, LogType::kDebug); +} +} // namespace utils +} // namespace datamanagement \ No newline at end of file diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index b7c4a753..5d02098e 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -9,10 +9,10 @@ endif() enable_testing() add_executable(${PROJECT_NAME} - src/test_config.cpp src/test_csv_source.cpp src/test_db_source.cpp src/test_model_data.cpp + src/test_logging.cpp ) target_link_libraries(datamanagement_tests diff --git a/tests/src/test_config.cpp b/tests/src/test_config.cpp deleted file mode 100644 index e1c10fc0..00000000 --- a/tests/src/test_config.cpp +++ /dev/null @@ -1,53 +0,0 @@ -//////////////////////////////////////////////////////////////////////////////// -// File: TEST_Config.cpp // -// Project: src // -// Created Date: Th Feb 2025 // -// Author: Matthew Carroll // -// ----- // -// Last Modified: Thu Feb 20 2025 // -// Modified By: Matthew Carroll // -// ----- // -// Copyright (c) 2025 Syndemics Lab at Boston Medical Center // -// ----- // -// HISTORY: // -// Date By Comments // -// ---------- --- --------------------------------------------------------- // -//////////////////////////////////////////////////////////////////////////////// - -#include -#include - -class ConfigTest : public ::testing::Test { - void SetUp() override { - std::ofstream file("test.conf"); - file << "[simulation]" << std::endl; - file << "duration = 52" << std::endl; - file << "aging_interval = 260" << std::endl; - file << "[state]" << std::endl; - file << "ouds = Active_Noninjection, Active_Injection, " - "Nonactive_Noninjection, Nonactive_Injection" - << std::endl; - file.close(); - } - - void TearDown() override { std::remove("test.conf"); } -}; - -TEST_F(ConfigTest, GetFromConfig) { - datamanagement::source::Config config("test.conf"); - std::string data = ""; - config.GetFromConfig("simulation.duration", data); - ASSERT_EQ(data, "52"); -} - -TEST_F(ConfigTest, GetConfigSectionCategories) { - datamanagement::source::Config config("test.conf"); - std::vector data = {}; - config.GetConfigSectionCategories("simulation", data); - std::vector expected = {"duration", "aging_interval"}; - int i = 0; - for (std::string d : data) { - ASSERT_EQ(expected[i], d); - ++i; - } -} diff --git a/tests/src/test_db_source.cpp b/tests/src/test_db_source.cpp index 260d19d5..210a4d41 100644 --- a/tests/src/test_db_source.cpp +++ b/tests/src/test_db_source.cpp @@ -4,7 +4,7 @@ // Created Date: Th Feb 2025 // // Author: Matthew Carroll // // ----- // -// Last Modified: 2025-04-30 // +// Last Modified: 2025-05-01 // // Modified By: Matthew Carroll // // ----- // // Copyright (c) 2025 Syndemics Lab at Boston Medical Center // @@ -23,17 +23,22 @@ #include +using db_vec = std::vector>; + class DBSourceTest : public ::testing::Test { protected: void SetUp() override { SQLite::Database db("test.db", SQLite::OPEN_READWRITE | SQLite::OPEN_CREATE); db.exec("CREATE TABLE IF NOT EXISTS test (id INTEGER PRIMARY KEY, name " - "TEXT, age INTEGER);"); + "TEXT, age INTEGER, score REAL);"); SQLite::Transaction transaction(db); - db.exec("INSERT INTO test (name, age) VALUES ('Alice', 30);"); - db.exec("INSERT INTO test (name, age) VALUES ('Bob', 25);"); - db.exec("INSERT INTO test (name, age) VALUES ('Charlie', 35);"); + db.exec( + "INSERT INTO test (name, age, score) VALUES ('Alice', 30, 2.45);"); + db.exec( + "INSERT INTO test (name, age, score) VALUES ('Bob', 25, 1.23);"); + db.exec("INSERT INTO test (name, age, score) VALUES ('Charlie', 35, " + "7.54);"); transaction.commit(); } void TearDown() override { std::remove("test.db"); } @@ -43,22 +48,19 @@ TEST_F(DBSourceTest, Select) { datamanagement::source::DBSource db_source; db_source.SetDatabasePath("test.db"); - std::any storage = std::vector>{}; + std::any storage = db_vec{}; db_source.Select( "SELECT * FROM test;", [](std::any &storage, const SQLite::Statement &stmt) { - std::vector> *results = - std::any_cast>>( - &storage); - results->emplace_back(stmt.getColumn(0).getInt(), - stmt.getColumn(1).getText(), - stmt.getColumn(2).getInt()); + db_vec *results = std::any_cast(&storage); + results->emplace_back( + stmt.getColumn(0).getInt(), stmt.getColumn(1).getText(), + stmt.getColumn(2).getInt(), stmt.getColumn(3).getInt()); }, storage); - std::vector> results = - std::any_cast>>(storage); + db_vec results = std::any_cast(storage); EXPECT_EQ(results.size(), 3); EXPECT_EQ(std::get<1>(results[0]), "Alice"); @@ -69,24 +71,67 @@ TEST_F(DBSourceTest, Select) { TEST_F(DBSourceTest, SelectWithBindings) { datamanagement::source::DBSource db_source; db_source.SetDatabasePath("test.db"); - std::any storage = std::vector>{}; + std::any storage = db_vec{}; std::unordered_map bindings; bindings[1] = 1; db_source.Select( "SELECT * FROM test WHERE id = ?;", [](std::any &storage, const SQLite::Statement &stmt) { - std::vector> *results = - std::any_cast>>( - &storage); - results->emplace_back(stmt.getColumn(0).getInt(), - stmt.getColumn(1).getText(), - stmt.getColumn(2).getInt()); + db_vec *results = std::any_cast(&storage); + results->emplace_back( + stmt.getColumn(0).getInt(), stmt.getColumn(1).getText(), + stmt.getColumn(2).getInt(), stmt.getColumn(3).getDouble()); + }, + storage, bindings); + + db_vec results = std::any_cast(storage); + + EXPECT_EQ(results.size(), 1); + EXPECT_EQ(std::get<1>(results[0]), "Alice"); +} + +TEST_F(DBSourceTest, SelectWithDoubleBindings) { + datamanagement::source::DBSource db_source; + db_source.SetDatabasePath("test.db"); + std::any storage = db_vec{}; + + std::unordered_map bindings; + bindings[1] = 2.45; + db_source.Select( + "SELECT * FROM test WHERE score = ?;", + [](std::any &storage, const SQLite::Statement &stmt) { + db_vec *results = std::any_cast(&storage); + results->emplace_back( + stmt.getColumn(0).getInt(), stmt.getColumn(1).getText(), + stmt.getColumn(2).getInt(), stmt.getColumn(3).getDouble()); + }, + storage, bindings); + + db_vec results = std::any_cast(storage); + + EXPECT_EQ(results.size(), 1); + EXPECT_EQ(std::get<1>(results[0]), "Alice"); +} + +TEST_F(DBSourceTest, SelectWithStringBindings) { + datamanagement::source::DBSource db_source; + db_source.SetDatabasePath("test.db"); + std::any storage = db_vec{}; + + std::unordered_map bindings; + bindings[1] = "Alice"; + db_source.Select( + "SELECT * FROM test WHERE name = ?;", + [](std::any &storage, const SQLite::Statement &stmt) { + db_vec *results = std::any_cast(&storage); + results->emplace_back( + stmt.getColumn(0).getInt(), stmt.getColumn(1).getText(), + stmt.getColumn(2).getInt(), stmt.getColumn(3).getDouble()); }, storage, bindings); - std::vector> results = - std::any_cast>>(storage); + db_vec results = std::any_cast(storage); EXPECT_EQ(results.size(), 1); EXPECT_EQ(std::get<1>(results[0]), "Alice"); @@ -95,7 +140,7 @@ TEST_F(DBSourceTest, SelectWithBindings) { TEST_F(DBSourceTest, BatchExecute) { datamanagement::source::DBSource db_source; db_source.SetDatabasePath("test.db"); - std::string query = "INSERT INTO test (name, age) VALUES (?, ?);"; + std::string query = "INSERT INTO test (name, age, score) VALUES (?, ?, ?);"; std::vector> batch_bindings; for (int i = 0; i < 10; ++i) { @@ -103,24 +148,22 @@ TEST_F(DBSourceTest, BatchExecute) { bindings; bindings[1] = "Test" + std::to_string(i); bindings[2] = i; + bindings[3] = i + 0.3; batch_bindings.emplace_back(bindings); } db_source.BatchExecute(query, batch_bindings); - std::any storage = std::vector>{}; + std::any storage = db_vec{}; db_source.Select( "SELECT * FROM test;", [](std::any &storage, const SQLite::Statement &stmt) { - std::vector> *results = - std::any_cast>>( - &storage); - results->emplace_back(stmt.getColumn(0).getInt(), - stmt.getColumn(1).getText(), - stmt.getColumn(2).getInt()); + db_vec *results = std::any_cast(&storage); + results->emplace_back( + stmt.getColumn(0).getInt(), stmt.getColumn(1).getText(), + stmt.getColumn(2).getInt(), stmt.getColumn(3).getDouble()); }, storage); - std::vector> results = - std::any_cast>>(storage); + db_vec results = std::any_cast(storage); EXPECT_EQ(results.size(), 13); EXPECT_EQ(std::get<1>(results[3]), "Test0"); } diff --git a/tests/src/test_logging.cpp b/tests/src/test_logging.cpp new file mode 100644 index 00000000..a4067b6e --- /dev/null +++ b/tests/src/test_logging.cpp @@ -0,0 +1,98 @@ +//////////////////////////////////////////////////////////////////////////////// +// File: test_logging.cpp // +// Project: src // +// Created Date: 2025-05-01 // +// Author: Matthew Carroll // +// ----- // +// Last Modified: 2025-05-01 // +// Modified By: Matthew Carroll // +// ----- // +// Copyright (c) 2025 Syndemics Lab at Boston Medical Center // +//////////////////////////////////////////////////////////////////////////////// + +#include + +#include +#include +#include +#include + +#include + +class LoggingTest : public ::testing::Test { +protected: + void SetUp() override {} + void TearDown() override {} +}; + +TEST_F(LoggingTest, LogInfo) { + const std::string LOG_NAME = "info"; + const std::string LOG_FILE = "test.log"; + std::string expected = "Testing"; + std::string line; + + ASSERT_EQ(datamanagement::utils::CreateFileLogger(LOG_NAME, LOG_FILE), + datamanagement::utils::CreationStatus::kSuccess); + datamanagement::utils::LogInfo(LOG_NAME, expected); + + std::ifstream f(LOG_FILE); + std::getline(f, line); + f.close(); + + ASSERT_TRUE(line.find(expected) != std::string::npos); + std::filesystem::remove(LOG_FILE); +} + +TEST_F(LoggingTest, LogWarning) { + const std::string LOG_NAME = "warn"; + const std::string LOG_FILE = "test.log"; + std::string expected = "Testing"; + std::string line; + + ASSERT_EQ(datamanagement::utils::CreateFileLogger(LOG_NAME, LOG_FILE), + datamanagement::utils::CreationStatus::kSuccess); + datamanagement::utils::LogWarning(LOG_NAME, expected); + + std::ifstream f(LOG_FILE); + std::getline(f, line); + f.close(); + + ASSERT_TRUE(line.find(expected) != std::string::npos); + std::filesystem::remove(LOG_FILE); +} + +TEST_F(LoggingTest, LogError) { + const std::string LOG_NAME = "error"; + const std::string LOG_FILE = "test.log"; + std::string expected = "Testing"; + std::string line; + + ASSERT_EQ(datamanagement::utils::CreateFileLogger(LOG_NAME, LOG_FILE), + datamanagement::utils::CreationStatus::kSuccess); + datamanagement::utils::LogError(LOG_NAME, expected); + + std::ifstream f(LOG_FILE); + std::getline(f, line); + f.close(); + + ASSERT_TRUE(line.find(expected) != std::string::npos); + std::filesystem::remove(LOG_FILE); +} + +TEST_F(LoggingTest, LogDebug) { + const std::string LOG_NAME = "debug"; + const std::string LOG_FILE = "test.log"; + std::string expected = "Testing"; + std::string line; + + ASSERT_EQ(datamanagement::utils::CreateFileLogger(LOG_NAME, LOG_FILE), + datamanagement::utils::CreationStatus::kSuccess); + datamanagement::utils::LogDebug(LOG_NAME, expected); + + std::ifstream f(LOG_FILE); + std::getline(f, line); + f.close(); + + ASSERT_TRUE(line.find(expected) != std::string::npos); + std::filesystem::remove(LOG_FILE); +} \ No newline at end of file diff --git a/tests/src/test_model_data.cpp b/tests/src/test_model_data.cpp index f55169f7..e6296253 100644 --- a/tests/src/test_model_data.cpp +++ b/tests/src/test_model_data.cpp @@ -4,7 +4,7 @@ // Created Date: Th Feb 2025 // // Author: Matthew Carroll // // ----- // -// Last Modified: Thu Feb 20 2025 // +// Last Modified: 2025-05-01 // // Modified By: Matthew Carroll // // ----- // // Copyright (c) 2025 Syndemics Lab at Boston Medical Center // @@ -14,9 +14,15 @@ // ---------- --- --------------------------------------------------------- // //////////////////////////////////////////////////////////////////////////////// +// File Under Test #include + +// 3rd Party Libraries #include +// Include Libraries +#include + class ModelDataTest : public ::testing::Test { void SetUp() override { // Setup Config @@ -57,34 +63,61 @@ class ModelDataTest : public ::testing::Test { } }; -TEST_F(ModelDataTest, ConfigTesting) { - datamanagement::ModelData md("test.conf"); - datamanagement::source::Config cf = md.GetConfig(); +TEST_F(ModelDataTest, GetFromConfig) { + auto model_data = datamanagement::ModelData::Create("test.conf"); + ASSERT_EQ(model_data->GetFromConfig("simulation.duration"), "52"); +} + +TEST_F(ModelDataTest, GetFromConfigError) { + const std::string LOG_NAME = "GetFromConfigError"; + const std::string LOG_FILE = "test.log"; + datamanagement::utils::CreateFileLogger(LOG_NAME, LOG_FILE); + auto model_data = datamanagement::ModelData::Create("test.conf", LOG_NAME); + ASSERT_EQ(model_data->GetFromConfig("find_an_error"), ""); + + std::string expected = + "Error in attempting to extract find_an_error from config file..."; + std::string line; - std::string data = ""; - cf.GetFromConfig("simulation.duration", data); - ASSERT_EQ(data, "52"); + std::ifstream f(LOG_FILE); + std::getline(f, line); + f.close(); + + ASSERT_TRUE(line.find(expected) != std::string::npos); + std::filesystem::remove(LOG_FILE); +} + +TEST_F(ModelDataTest, GetConfigSectionCategories) { + auto model_data = datamanagement::ModelData::Create("test.conf"); + std::vector data = + model_data->GetConfigSectionCategories("simulation"); + std::vector expected = {"duration", "aging_interval"}; + int i = 0; + for (std::string d : data) { + ASSERT_EQ(expected[i], d); + ++i; + } } TEST_F(ModelDataTest, GetSourceNames) { - datamanagement::ModelData md("test.conf"); - md.AddSource("test.db"); - md.AddSource("test.csv"); + auto model_data = datamanagement::ModelData::Create("test.conf"); + model_data->AddSource("test.db"); + model_data->AddSource("test.csv"); - std::vector csv_names = md.GetCSVSourceNames(); + std::vector csv_names = model_data->GetCSVSourceNames(); ASSERT_EQ(csv_names[0], "test"); - std::vector db_names = md.GetDBSourceNames(); + std::vector db_names = model_data->GetDBSourceNames(); ASSERT_EQ(db_names[0], "test"); } TEST_F(ModelDataTest, Select) { - datamanagement::ModelData md("test.conf"); - md.AddSource("test.db"); + auto model_data = datamanagement::ModelData::Create("test.conf"); + model_data->AddSource("test.db"); std::any storage = std::vector>{}; - md.GetDBSource("test").Select( + model_data->GetDBSource("test").Select( "SELECT * FROM test;", [](std::any &storage, const SQLite::Statement &stmt) { std::vector> *results = @@ -106,8 +139,8 @@ TEST_F(ModelDataTest, Select) { } TEST_F(ModelDataTest, BatchExecute) { - datamanagement::ModelData md("test.conf"); - md.AddSource("test.db"); + auto model_data = datamanagement::ModelData::Create("test.conf"); + model_data->AddSource("test.db"); std::string query = "INSERT INTO test (name, age) VALUES (?, ?);"; std::vector> @@ -119,10 +152,10 @@ TEST_F(ModelDataTest, BatchExecute) { bindings[2] = i; batch_bindings.emplace_back(bindings); } - md.GetDBSource("test").BatchExecute(query, batch_bindings); + model_data->GetDBSource("test").BatchExecute(query, batch_bindings); std::any storage = std::vector>{}; - md.GetDBSource("test").Select( + model_data->GetDBSource("test").Select( "SELECT * FROM test;", [](std::any &storage, const SQLite::Statement &stmt) { std::vector> *results = @@ -140,31 +173,33 @@ TEST_F(ModelDataTest, BatchExecute) { } TEST_F(ModelDataTest, GetData) { - datamanagement::ModelData md("test.conf"); - md.AddSource("test.csv"); + auto model_data = datamanagement::ModelData::Create("test.conf"); + model_data->AddSource("test.csv"); // Test without where conditions - Eigen::MatrixXd data = md.GetCSVSource("test").GetData({"id", "age"}, {}); + Eigen::MatrixXd data = + model_data->GetCSVSource("test").GetData({"id", "age"}, {}); EXPECT_EQ(data.rows(), 3); EXPECT_EQ(data.cols(), 2); // Test with where conditions - Eigen::MatrixXd filtered_data = - md.GetCSVSource("test").GetData({"id", "age"}, {{"name", "Alice"}}); + Eigen::MatrixXd filtered_data = model_data->GetCSVSource("test").GetData( + {"id", "age"}, {{"name", "Alice"}}); EXPECT_EQ(filtered_data.rows(), 1); EXPECT_EQ(filtered_data.cols(), 2); } TEST_F(ModelDataTest, WriteCSV) { - datamanagement::ModelData md("test.conf"); - md.AddSource("test.csv"); + auto model_data = datamanagement::ModelData::Create("test.conf"); + model_data->AddSource("test.csv"); - md.GetCSVSource("test").WriteCSV("output.csv", {"id", "age"}); + model_data->GetCSVSource("test").WriteCSV("output.csv", {"id", "age"}); datamanagement::source::CSVSource csv_source2; csv_source2.ConnectToFile("output.csv"); // Test without where conditions - Eigen::MatrixXd data1 = md.GetCSVSource("test").GetData({"id", "age"}, {}); + Eigen::MatrixXd data1 = + model_data->GetCSVSource("test").GetData({"id", "age"}, {}); Eigen::MatrixXd data2 = csv_source2.GetData({"id", "age"}, {}); ASSERT_TRUE(data2.isApprox(data1)); std::remove("output.csv");