diff --git a/.gitignore b/.gitignore index 8133b27..88f27dc 100644 --- a/.gitignore +++ b/.gitignore @@ -14,10 +14,17 @@ !*.h !README.md !CMakeLists.txt +!*.cmake !vendor/ +!vendor/* !vendor/**/ !LICENSE* !queries/ -!queries/*.sql \ No newline at end of file +!queries/*.sql + +!*.plantuml +!docs/ +!docs/**/ +!docs/**/*.svg \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json index c53eceb..ba95cb7 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -11,7 +11,7 @@ "program": "${workspaceFolder}/build/noLiteDb", "args": [], "preLaunchTask": "build", - "cwd": "${workspaceFolder}" + "cwd": "${workspaceFolder}/build" }, { "type": "lldb", @@ -20,7 +20,7 @@ "program": "${workspaceFolder}/build/tests", "args": [], "preLaunchTask": "build", - "cwd": "${workspaceFolder}" + "cwd": "${workspaceFolder}/build" } ] } \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 1a2f6b0..14b3614 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,68 +4,64 @@ project(noLiteDb) file(GLOB_RECURSE SOURCES src/*.cpp) -# REMOVE MAIN so we can don't need to compile the code again for the tests +# REMOVE MAIN from sources +# Note: this way we don't need to compile the code again for the tests list(REMOVE_ITEM SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/src/main.cpp) - -# Set cmake cxx flags +# Set std version +# Note: I do it this way because otherwise export compile commands doesn't export it SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++20") -# clangd: +# clangd set(CMAKE_EXPORT_COMPILE_COMMANDS ON) +set(CMAKE_BUILD_TYPE DEBUG) -include_directories(src/include) -include_directories(vendor/sqlite) -include_directories(vendor/logger/include) -include_directories(vendor/lrucache11) -include_directories(vendor/others) - -# --- YAMLCPP -# i thought to use yaml-cpp but doesn't have an easy way to get the data type -# and that was crucial for this library +include(Dependecy.cmake) -# --- json (nlohmann) -set(JSON_BuildTests OFF CACHE INTERNAL "") -set(JSON_Install OFF CACHE INTERNAL "") -add_subdirectory(vendor/json) +message(STATUS "NLDB dependencies: ${DEPENDENCY_LIST}") +message(STATUS "NLDB dependency libs: ${DEPENDENCY_LIBS}") +message(STATUS "NLDB dependency lib includes: ${DEPENDENCY_INCLUDE_DIR}") -# --- SQLITE library -add_library(c_sqllite3 vendor/sqlite/sqlite/sqlite3.c) +include_directories(src/include) +include_directories(src) -# --- Add nolitedb library +# --------------------- NOLITE --------------------- # add_library(noLitedb_lb ${SOURCES}) -target_link_libraries(noLitedb_lb c_sqllite3 nlohmann_json::nlohmann_json) -set(CMAKE_BUILD_TYPE DEBUG) +add_dependencies(noLitedb_lb ${DEPENDENCY_LIST}) + +target_include_directories(noLitedb_lb PUBLIC ${DEPENDENCY_INCLUDE_DIR} ${DEPENDENCY_INCLUDE_LIST}) +target_link_libraries(noLitedb_lb PUBLIC ${DEPENDENCY_LIBS}) +target_link_directories(noLitedb_lb PUBLIC ${DEPENDENCY_LIB_DIR}) -# --- Add nolitedb executable -add_executable(noLiteDb src/main.cpp) -target_link_libraries(noLiteDb noLitedb_lb) +# ------------------ NOLITE EXECUTABLE ----------------- # +add_executable(${PROJECT_NAME} src/main.cpp) +target_link_libraries(${PROJECT_NAME} noLitedb_lb) -# --- TESTS -add_subdirectory(vendor/googletest) -include_directories(vendor/googletest/googletest/include) +# ------------------------ TESTS ----------------------- # +# add_subdirectory(vendor/googletest) +# include_directories(vendor/googletest/googletest/include) -enable_testing() +# enable_testing() -file(GLOB TESTS_SRC tests/*.cpp) +# file(GLOB TESTS_SRC tests/*.cpp) -add_executable( - tests - ${TESTS_SRC} -) +# add_executable( +# tests +# ${TESTS_SRC} +# ) -target_link_libraries( - tests - noLitedb_lb - GTest::gtest -) +# target_link_libraries( +# tests +# noLitedb_lb +# GTest::gtest +# ) -if (CMAKE_BUILD_TYPE MATCHES "DEBUG") - add_definitions( -D LOGGER_MAX_LOG_LEVEL_PRINTED=6 ) # trace -else () - add_definitions( -D LOGGER_MAX_LOG_LEVEL_PRINTED=1 ) # error -endif() +# if (CMAKE_BUILD_TYPE MATCHES "DEBUG") +# add_definitions( -D LOGGER_MAX_LOG_LEVEL_PRINTED=6 ) # trace +# else () +# add_definitions( -D LOGGER_MAX_LOG_LEVEL_PRINTED=1 ) # error +# endif() -include(GoogleTest) -gtest_discover_tests(tests) +# include(GoogleTest) +# gtest_discover_tests(tests) diff --git a/Dependecy.cmake b/Dependecy.cmake new file mode 100644 index 0000000..0ac2087 --- /dev/null +++ b/Dependecy.cmake @@ -0,0 +1,64 @@ +include(ExternalProject) + +set(DEPENDENCY_INSTALL_DIR ${PROJECT_BINARY_DIR}/install) +set(DEPENDENCY_INCLUDE_DIR ${DEPENDENCY_INSTALL_DIR}/include) +set(DEPENDENCY_LIB_DIR ${DEPENDENCY_INSTALL_DIR}/lib) + +function(add_dep dep) + set(DEPENDENCY_LIST ${DEPENDENCY_LIST} ${dep} PARENT_SCOPE) +endfunction() + +function(add_include include_dir) + set(DEPENDENCY_INCLUDE_LIST ${DEPENDENCY_INCLUDE_LIST} ${include_dir} PARENT_SCOPE) +endfunction() + +function(add_lib lib) + set(DEPENDENCY_LIBS ${DEPENDENCY_LIBS} ${lib} PARENT_SCOPE) +endfunction(add_lib) + +# ----------------------- SPDLOG ----------------------- # +ExternalProject_Add( + dep-spdlog + SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/vendor/spdlog + UPDATE_COMMAND "" + PATCH_COMMAND "" + CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${DEPENDENCY_INSTALL_DIR} + TEST_COMMAND "" +) + +add_dep(dep-spdlog) +add_include(${CMAKE_CURRENT_SOURCE_DIR}/vendor/spdlog/include) +add_lib(spdlog) + +# ----------------------- SQLITE3 ---------------------- # +add_library(xsqllite3 ${CMAKE_CURRENT_SOURCE_DIR}/vendor/sqlite/sqlite/sqlite3.c) + +add_include(${CMAKE_CURRENT_SOURCE_DIR}/vendor/sqlite) +add_lib(xsqllite3) + +# -------------------- NLOHMANN JSON ------------------- # +ExternalProject_Add( + dep-json + SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/vendor/json + UPDATE_COMMAND "" + PATCH_COMMAND "" + CMAKE_ARGS + -DCMAKE_INSTALL_PREFIX=${DEPENDENCY_INSTALL_DIR} + -DNLOHMANN_JSON_INCLUDE_INSTALL_DIR=${DEPENDENCY_INSTALL_DIR} + -DJSON_Install=ON + -DJSON_BuildTests=OFF + TEST_COMMAND "" +) + +add_dep(dep-json) +add_include(${CMAKE_CURRENT_SOURCE_DIR}/vendor/json/include) +# add_lib(nlohmann_json) # dependecy adds the .Targets file and handles the link_library + +# --------------------- MAGIC ENUM --------------------- # +add_include(${CMAKE_CURRENT_SOURCE_DIR}/vendor/magic_enum/include) + +# ---------------------- LRU CACHE --------------------- # +add_include(${CMAKE_CURRENT_SOURCE_DIR}/vendor/lrucache11) + +# ----------------------- OTHERS ----------------------- # +add_include(${CMAKE_CURRENT_SOURCE_DIR}/vendor/others) \ No newline at end of file diff --git a/queries/create.sql b/queries/create.sql deleted file mode 100644 index 93db0c0..0000000 --- a/queries/create.sql +++ /dev/null @@ -1,36 +0,0 @@ -CREATE TABLE `collection` ( - `id` INTEGER PRIMARY KEY, - `name` varchar(255) -); - -CREATE TABLE `property` ( - `id` INTEGER PRIMARY KEY, - `coll_id` int, - `name` varchar(255), - `type` int, - FOREIGN KEY (coll_id) REFERENCES collection(id) -); - -CREATE TABLE `document` ( - `id` INTEGER PRIMARY KEY, - `coll_id` int, - FOREIGN KEY (coll_id) REFERENCES collection(id) -); - -CREATE TABLE `value_string` ( - `id` INTEGER PRIMARY KEY, - `doc_id` int, - `prop_id` int, - `value` varchar(255), - FOREIGN KEY (doc_id) REFERENCES document(id), - FOREIGN KEY (prop_id) REFERENCES property(id) -); - -CREATE TABLE `value_int` ( - `id` INTEGER PRIMARY KEY, - `doc_id` int, - `prop_id` int, - `value` int, - FOREIGN KEY (doc_id) REFERENCES document(id), - FOREIGN KEY (prop_id) REFERENCES property(id) -); diff --git a/src/Collection.cpp b/src/Collection.cpp index 4a1ae13..962d680 100644 --- a/src/Collection.cpp +++ b/src/Collection.cpp @@ -1,130 +1,10 @@ -#include "Collection.hpp" +#include "nldb/Collection.hpp" -#include -#include -#include -#include +namespace nldb { + Collection::Collection(int id, const std::string& name) + : id(id), name(name) {} -#include "Enums.hpp" -#include "PropertyRep.hpp" -#include "SqlExpression.hpp" -#include "dbwrapper/IDB.hpp" -#include "logger/Logger.h" -// max to 20 collections -lru11::Cache> - Collection::propertyCache(20); + int Collection::getId() const { return id; } -Collection::Collection(IDB* pCtx, int pId, const std::string& pName) - : ctx(pCtx), id(pId), name(pName) {} - -int Collection::getID() { return this->id; } - -bool Collection::hasProperty(const std::string& key) { - return tryGetProperty(key).has_value(); -} - -bool Collection::addProperty(const std::string& key, PropertyType type) { - const std::string sql = - "insert into property (coll_id, name, type) values (@colid, @name, " - "@type);"; - - (void)ctx->executeOneStep( - sql, {{"@colid", this->id}, {"@name", key}, {"@type", (int)type}}); - - int id = ctx->getLastInsertedRowId(); - - if (id >= 0) updatePropCache(key, PropertyRep(key, id, type)); - - return id >= 0; -} - -std::optional Collection::tryGetProperty(const std::string& key) { - // check cache - if (propertyCache.contains(this->id)) { - if (propertyCache.get(this->id).contains(key)) { - return propertyCache.get(this->id).at(key); - } - } - - LogDebug("Cache miss with key '%s'", key.c_str()); - - // else check db - std::optional id; - auto property = PropertyRep::find(ctx, this->id, key); - if (property.has_value()) { - id = property.value().getId(); - updatePropCache(key, property.value()); - } - - return property; -} - -PropertyRep Collection::getProperty(const std::string& key) { - if (key == "id") { - return PropertyRep("id", -1, PropertyType::ID); - } - - auto prop = this->tryGetProperty(key); - if (prop.has_value()) { - return prop.value(); - } else { - LogError("Property '%s' was requested but wasn't found.", key.c_str()); - - throw std::runtime_error("Missing property"); - } -} - -std::vector Collection::getAllTheProperties() { - const std::string sql = - "select id, name, type from property where coll_id = @id;"; - - auto reader = ctx->executeReader(sql, {{"@id", this->id}}); - - std::vector props = {PropertyRep("id", -1, PropertyType::ID)}; - std::shared_ptr row; - while (reader->readRow(row)) { - props.push_back(PropertyRep(row->readString(1), row->readInt64(0), - (PropertyType)row->readInt64(2))); - } - - return std::move(props); -} - -Collection Collection::find(IDB* ctx, const std::string& name) { - const std::string sql = "SELECT id FROM collection where name = @name;"; - - auto id = ctx->executeAndGetFirstInt(sql, {{"@name", name}}); - - if (id.has_value()) { - LogTrace("Collection %s found", name.c_str()); - return Collection(ctx, id.value(), name); - } else { - LogTrace("Collection %s not found, creating it", name.c_str()); - return Collection(ctx, create(*ctx, name), name); - } -} - -int Collection::create(IDB& ctx, const std::string& name) { - const std::string sql = "insert into collection (name) values (@name);"; - - (void)ctx.executeOneStep(sql, {{"@name", name}}); - - return ctx.getLastInsertedRowId(); -} - -int Collection::documentExists(int doc_id) { - const auto sql = - "select d.id from document as d where coll_id=@coll_id and d.id = @id;"; - - auto id = ctx->executeAndGetFirstInt( - sql, {{"@coll_id", this->id}, {"@id", doc_id}}); - - return id.has_value(); -} - -void Collection::updatePropCache(const std::string& key, PropertyRep prop) { - std::unordered_map map; - propertyCache.tryGet(this->id, map); - map.insert({key, prop}); - propertyCache.insert(this->id, map); // updates the position -} + std::string Collection::getName() const { return name; } +} // namespace nldb \ No newline at end of file diff --git a/src/Document.cpp b/src/Document.cpp deleted file mode 100644 index 0f24a0d..0000000 --- a/src/Document.cpp +++ /dev/null @@ -1,13 +0,0 @@ -#include "Document.hpp" - -Document::Document(int pId) : id(pId) {} - -int Document::getID() { return this->id; } - -int Document::create(IDB& ctx, int collectionID) { - const std::string sql = "insert into document (coll_id) values (@id);"; - - (void)ctx.executeOneStep(sql, {{"@id", collectionID}}); - - return ctx.getLastInsertedRowId(); -} \ No newline at end of file diff --git a/src/LOG/LogManagerSPD.cpp b/src/LOG/LogManagerSPD.cpp new file mode 100644 index 0000000..89162c9 --- /dev/null +++ b/src/LOG/LogManagerSPD.cpp @@ -0,0 +1,35 @@ +#include "nldb/LOG/managers/LogManagerSPD.hpp" + +#include "spdlog/common.h" + +namespace nldb { + std::shared_ptr LogManager::logger; + + void LogManager::Initialize() { + // console sink + auto console_sink = + std::make_shared(); + + console_sink->set_level(spdlog::level::trace); + + console_sink->set_pattern("%^[%l] %T %D %v%$"); + + // file sink for errors + auto file_sink = std::make_shared( + "logs/nldb.txt", true); + + file_sink->set_level(spdlog::level::trace); + + // create logger + std::vector sinks {console_sink, file_sink}; + logger = std::make_shared(DEFAULT_LOGGER_NAME, + sinks.begin(), sinks.end()); + logger->set_level(spdlog::level::trace); + logger->flush_on(spdlog::level::trace); + + // register logger + spdlog::register_logger(logger); + } + + void LogManager::Shutdown() { spdlog::shutdown(); } +} // namespace nldb \ No newline at end of file diff --git a/src/Property/AggregatedProperty.cpp b/src/Property/AggregatedProperty.cpp new file mode 100644 index 0000000..90795b8 --- /dev/null +++ b/src/Property/AggregatedProperty.cpp @@ -0,0 +1,8 @@ +#include "nldb/Property/AggregatedProperty.hpp" + +namespace nldb { + AggregatedProperty::AggregatedProperty(Property pProp, + AggregationType pType, + const char* pAlias) + : property(pProp), type(pType), alias(pAlias) {} +} // namespace nldb \ No newline at end of file diff --git a/src/Property/Property.cpp b/src/Property/Property.cpp new file mode 100644 index 0000000..a8167c6 --- /dev/null +++ b/src/Property/Property.cpp @@ -0,0 +1,89 @@ +#include "nldb/Property/Property.hpp" + +#include "nldb/Property/AggregatedProperty.hpp" +#include "nldb/Property/PropertyExpression.hpp" +#include "nldb/Property/SortedProperty.hpp" + +namespace nldb { + + Property::Property(int pId, const std::string& pName, PropertyType pType) + : id(pId), name(pName), type(pType) {} + + // getters + std::string Property::getName() const { return name; } + + PropertyType Property::getType() const { return type; } + + int Property::getId() const { return id; } + + int Property::getCollectionId() const { return collectionId; } + + // aggregate functions + AggregatedProperty Property::countAs(const char* alias) { + return AggregatedProperty(*this, AggregationType::COUNT, alias); + } + + AggregatedProperty Property::maxAs(const char* alias) { + return AggregatedProperty(*this, AggregationType::MAX, alias); + } + + AggregatedProperty Property::minAs(const char* alias) { + return AggregatedProperty(*this, AggregationType::MIN, alias); + } + + AggregatedProperty Property::sumAs(const char* alias) { + return AggregatedProperty(*this, AggregationType::SUM, alias); + } + + AggregatedProperty Property::averageAs(const char* alias) { + return AggregatedProperty(*this, AggregationType::AVG, alias); + } + + // sort functions + SortedProperty Property::desc() { + return SortedProperty(*this, SortType::DESC); + } + + SortedProperty Property::asc() { + return SortedProperty(*this, SortType::ASC); + } + + // logical + PropertyExpression Property::operator>(const LogicConstValue& right) { + return PropertyExpression(PropertyExpressionOperator::GT, *this, right); + } + + PropertyExpression Property::operator>=(const LogicConstValue& right) { + return PropertyExpression(PropertyExpressionOperator::GTE, *this, + right); + } + + PropertyExpression Property::operator<(const LogicConstValue& right) { + return PropertyExpression(PropertyExpressionOperator::LT, *this, right); + } + + PropertyExpression Property::operator<=(const LogicConstValue& right) { + return PropertyExpression(PropertyExpressionOperator::LTE, *this, + right); + } + + PropertyExpression Property::operator==(const LogicConstValue& right) { + return PropertyExpression(PropertyExpressionOperator::EQ, *this, right); + } + + PropertyExpression Property::operator!=(const LogicConstValue& right) { + return PropertyExpression(PropertyExpressionOperator::NEQ, *this, + right); + } + + PropertyExpression Property::operator%(const LogicConstValue& right) { + return PropertyExpression(PropertyExpressionOperator::LIKE, *this, + right); + } + + PropertyExpression Property::operator^(const LogicConstValue& right) { + return PropertyExpression(PropertyExpressionOperator::NLIKE, *this, + right); + } + +} // namespace nldb \ No newline at end of file diff --git a/src/Property/PropertyExpression.cpp b/src/Property/PropertyExpression.cpp new file mode 100644 index 0000000..42c585a --- /dev/null +++ b/src/Property/PropertyExpression.cpp @@ -0,0 +1,21 @@ +#include "nldb/Property/PropertyExpression.hpp" + +namespace nldb { + PropertyExpression::PropertyExpression() {} + + PropertyExpression PropertyExpression::operator&&( + PropertyExpression right) { + return PropertyExpression(PropertyExpressionOperator::AND, *this, + right); + } + + PropertyExpression PropertyExpression::operator||( + PropertyExpression right) { + return PropertyExpression(PropertyExpressionOperator::OR, *this, right); + } + + PropertyExpression PropertyExpression::operator~() { + return PropertyExpression(PropertyExpressionOperator::NOT, *this, + LogicConstValue {-1}); + } +} // namespace nldb \ No newline at end of file diff --git a/src/Property/SortedProperty.cpp b/src/Property/SortedProperty.cpp new file mode 100644 index 0000000..c3f7d6e --- /dev/null +++ b/src/Property/SortedProperty.cpp @@ -0,0 +1,6 @@ +#include "nldb/Property/SortedProperty.hpp" + +namespace nldb { + SortedProperty::SortedProperty(Property pProp, SortType pType) + : property(pProp), type(pType) {} +} // namespace nldb \ No newline at end of file diff --git a/src/PropertyRep.cpp b/src/PropertyRep.cpp deleted file mode 100644 index 75fe230..0000000 --- a/src/PropertyRep.cpp +++ /dev/null @@ -1,240 +0,0 @@ -#include "PropertyRep.hpp" - -#include -#include -#include - -#include "Constants.hpp" -#include "Enums.hpp" -#include "dbwrapper/ParamsBind.hpp" -#include "logger/Logger.h" - -namespace tables { - std::unordered_map& getPropertyTables() { - static std::unordered_map propertyTypeTable = - {{PropertyType::STRING, "value_string"}, - {PropertyType::INTEGER, "value_int"}, - {PropertyType::DOUBLE, "value_double"}}; - - return propertyTypeTable; - } -}; // namespace tables - -PropertyRep::PropertyRep(const std::string& pName, int pId, PropertyType pType) - : name(pName), id(pId), type(pType) {} - -SqlStatement getStatement(PropertyRep* lf, Operator op, - PropertyRep& rt) { - return SqlStatement(lf->getStatement() + " " + - OperatorToString(op) + " " + - rt.getStatement()); -} - -SqlStatement getStatement(PropertyRep* lf, Operator op, - const std::string& rt) { - return SqlStatement(lf->getStatement() + ".value " + - OperatorToString(op) + " " + rt); -} - -SqlStatement getStatement(PropertyRep* lf, Operator op, - const char* rt) { - return SqlStatement(lf->getStatement() + ".value " + - OperatorToString(op) + " " + rt); -} - -int PropertyRep::getId() const { return id; } - -PropertyType PropertyRep::getType() const { return type; } -std::string_view PropertyRep::getName() const { return name; } - -std::string PropertyRep::getStatement() const { - switch (this->type) { - case PropertyType::INTEGER: - return this->name + "_vi"; - case PropertyType::DOUBLE: - return this->name + "_vd"; - case PropertyType::STRING: - return this->name + "_vs"; - case PropertyType::ID: - return this->name; - default: - return this->name; - } -} - -std::string PropertyRep::getTableNameForTypeValue(PropertyType type) { - return tables::getPropertyTables().at(type); -} - -std::optional PropertyRep::find(IDB* ctx, int collectionID, - const std::string& name) { - // else check db - auto reader = ctx->executeReader( - "SELECT id, type FROM property where coll_id = @colid and name = @name", - {{"@colid", collectionID}, {"@name", name}}); - - int id; - int type; - - std::shared_ptr row; - if (reader->readRow(row)) { - id = row->readInt32(0); - type = row->readInt32(1); - - return PropertyRep(name, id, (PropertyType)type); - } else { - LogWarning("Missing property from col %i with name %s", collectionID, - name.c_str()); - return std::nullopt; - } -} - -std::string getValAsString(const RightValue& val) { - if (std::holds_alternative(val)) { - return std::to_string(std::get(val)); - } else if (std::holds_alternative(val)) { - return std::to_string(std::get(val)); - } else if (std::holds_alternative(val)) { - return utils::paramsbind::encloseQuotesConst( - std::get(val)); - } else if (std::holds_alternative(val)) { - return utils::paramsbind::encloseQuotesConst( - std::get(val)); - } else { - throw std::runtime_error("Type not supported"); - } -} - -std::string PropertyRep::getValueExpression() const { - if (this->type != PropertyType::ID) { - return utils::paramsbind::encloseQuotesConst(this->getStatement()) + - ".value"; - } else { - return std::string(g_documentTableAlias) + ".id"; - } -} - -std::string PropertyRep::generateConditionStatement(Operator op, - PropertyRep& rt) { - return this->getValueExpression() + " " + OperatorToString(op) + " " + - rt.getValueExpression(); -} -std::string PropertyRep::generateConditionStatement(Operator op, - RightValue rt) { - return this->getValueExpression() + " " + OperatorToString(op) + " " + - getValAsString(rt); -} - -AggregateFunction PropertyRep::countAs(const char* alias) { - return AggregateFunction(this, alias, AggregateType::COUNT); -} - -AggregateFunction PropertyRep::averageAs(const char* alias) { - return AggregateFunction(this, alias, AggregateType::AVG); -} - -AggregateFunction PropertyRep::minAs(const char* alias) { - return AggregateFunction(this, alias, AggregateType::MIN); -} - -AggregateFunction PropertyRep::maxAs(const char* alias) { - return AggregateFunction(this, alias, AggregateType::MAX); -} - -AggregateFunction PropertyRep::sumAs(const char* alias) { - return AggregateFunction(this, alias, AggregateType::SUM); -} - -SortProp PropertyRep::asc() { return SortProp(this, ASC); } - -SortProp PropertyRep::desc() { return SortProp(this, DESC); } - -SqlLogicExpression PropertyRep::operator<(PropertyRep& rt) { - return SqlLogicExpression(generateConditionStatement(Operator::LT, rt), - {this, &rt}); -} - -SqlLogicExpression PropertyRep::operator<=(PropertyRep& rt) { - return SqlLogicExpression(generateConditionStatement(Operator::LTE, rt), - {this, &rt}); -} - -SqlLogicExpression PropertyRep::operator>(PropertyRep& rt) { - return SqlLogicExpression(generateConditionStatement(Operator::GT, rt), - {this, &rt}); -} - -SqlLogicExpression PropertyRep::operator>=(PropertyRep& rt) { - return SqlLogicExpression(generateConditionStatement(Operator::GTE, rt), - {this, &rt}); -} - -// EQUAL -SqlLogicExpression PropertyRep::operator==(PropertyRep& rt) { - return SqlLogicExpression(generateConditionStatement(Operator::EQ, rt), - {this, &rt}); -} - -// NOT EQUAL -SqlLogicExpression PropertyRep::operator!=(PropertyRep& rt) { - return SqlLogicExpression(generateConditionStatement(Operator::NEQ, rt), - {this, &rt}); -} - -// LIKE -SqlLogicExpression PropertyRep::operator%(PropertyRep& rt) { - return SqlLogicExpression(generateConditionStatement(Operator::LIKE, rt), - {this, &rt}); -} - -// NOT LIKE -SqlLogicExpression PropertyRep::operator^(PropertyRep& rt) { - return SqlLogicExpression(generateConditionStatement(Operator::NLIKE, rt), - {this, &rt}); -} - -// ---- Right value - -SqlLogicExpression PropertyRep::operator<(RightValue rt) { - return SqlLogicExpression(generateConditionStatement(Operator::LT, rt), - {this}); -} - -SqlLogicExpression PropertyRep::operator<=(RightValue rt) { - return SqlLogicExpression(generateConditionStatement(Operator::LTE, rt), - {this}); -} - -SqlLogicExpression PropertyRep::operator>(RightValue rt) { - return SqlLogicExpression(generateConditionStatement(Operator::GT, rt), - {this}); -} - -SqlLogicExpression PropertyRep::operator>=(RightValue rt) { - return SqlLogicExpression(generateConditionStatement(Operator::GTE, rt), - {this}); -} - -// EQUAL -SqlLogicExpression PropertyRep::operator==(RightValue rt) { - return SqlLogicExpression(generateConditionStatement(Operator::EQ, rt), - {this}); -} - -// NOT EQUAL -SqlLogicExpression PropertyRep::operator!=(RightValue rt) { - return SqlLogicExpression(generateConditionStatement(Operator::NEQ, rt), - {this}); -} - -// LIKE -SqlLogicExpression PropertyRep::operator%(RightValue rt) { - return SqlLogicExpression(generateConditionStatement(Operator::LIKE, rt), - {this}); -} - -// NOT LIKE -SqlLogicExpression PropertyRep::operator^(RightValue rt) { - return SqlLogicExpression(generateConditionStatement(Operator::NLIKE, rt), - {this}); -} \ No newline at end of file diff --git a/src/Query.cpp b/src/Query.cpp deleted file mode 100644 index b2916e3..0000000 --- a/src/Query.cpp +++ /dev/null @@ -1,306 +0,0 @@ -#include "Query.hpp" - -#include -#include -#include -#include -#include -#include - -#include "Document.hpp" -#include "Enums.hpp" -#include "PropertyRep.hpp" -#include "SqlExpression.hpp" -#include "dbwrapper/ParamsBind.hpp" -#include "logger/Logger.h" - -PropertyType mapJsonType(json::value_t t) { - switch (t) { - case json::value_t::number_integer: - return PropertyType::INTEGER; - case json::value_t::number_float: - return PropertyType::DOUBLE; - case json::value_t::string: - return PropertyType::STRING; - default: - auto msg = "Type is not supported" + std::to_string((int)t); - throw std::runtime_error(msg); - } -} - -BaseQuery::BaseQuery(const std::shared_ptr& ctx) : qctx(ctx) {} - -Query::Query(const std::shared_ptr& ctx) : BaseQuery(ctx) {} - -void Query::buildPropertyInsert( - std::stringstream& sql, Paramsbind& bind, json element, - std::map>& insertMap) { - auto& cl = qctx->cl; - - int docId = Document::create(*qctx->db, cl.getID()); - - // iterate properties - for (auto& [key, value] : element.items()) { - PropertyType propType = mapJsonType(value.type()); - if (!cl.hasProperty(key)) { - cl.addProperty(key, propType); - } - - int propertyID = cl.getProperty(key).getId(); - - auto bindValue = "@value_" + std::to_string(docId) + "_" + - std::to_string(propertyID); - - if (!insertMap.contains(propType)) { - insertMap.insert({propType, {}}); - } - - sql << "(" << docId << "," << propertyID << "," << bindValue << ")"; - - insertMap.at(propType).push_back(sql.str()); - - if (propType == PropertyType::INTEGER) { - bind.insert({bindValue, value.get()}); - } else if (propType == PropertyType::DOUBLE) { - bind.insert({bindValue, value.get()}); - } else if (propType == PropertyType::STRING) { - bind.insert({bindValue, value.get()}); - } - - sql.str(""); - } -} - -int Query::update(int documentID, json updatedProperties) { - qctx->resetQuery(); - - auto& cl = this->qctx->cl; - auto& sql = this->qctx->sql; - auto& binds = this->qctx->bind; - - if (!cl.documentExists(documentID)) - throw std::runtime_error("Document doesn't exists"); - - if (!updatedProperties.is_object()) - throw std::runtime_error("JSON object is not an object"); - - for (auto& [propName, value] : updatedProperties.items()) { - PropertyType propType = mapJsonType(value.type()); - bool isNewProperty = false; - - if (!cl.hasProperty(propName)) { - cl.addProperty(propName, propType); - isNewProperty = true; - } - - int propertyID = cl.getProperty(propName).getId(); - - binds = { - {"@table_name", PropertyRep::getTableNameForTypeValue(propType)}, - {"@doc_id", documentID}, - {"@prop_id", propertyID}}; - - if (propType == PropertyType::INTEGER) { - binds.insert({"@prop_value", value.get()}); - } else if (propType == PropertyType::DOUBLE) { - binds.insert({"@prop_value", value.get()}); - } else if (propType == PropertyType::STRING) { - binds.insert({"@prop_value", value.get()}); - } - - if (isNewProperty) { - sql << utils::paramsbind::parseSQL( - "insert into @table_name (doc_id, prop_id, value) values " - "(@doc_id, @prop_id, @prop_value);", - binds); - } else { - sql << utils::paramsbind::parseSQL( - "update @table_name as pr set value = @prop_value where " - "doc_id = @doc_id and prop_id = @prop_id;", - binds); - } - } - - return qctx->db->executeMultipleOnOneStepRaw(sql.str(), binds); -} - -int Query::insert(const json& obj) { - qctx->resetQuery(); - - auto& sql = qctx->sql; - std::map> valuesInsert; - - if (obj.is_array()) { - for (auto& element : obj) { - buildPropertyInsert(sql, qctx->bind, element, valuesInsert); - } - } else { - buildPropertyInsert(sql, qctx->bind, obj, valuesInsert); - } - - sql.str(""); // clear - - // build query based on the values - for (auto& property : valuesInsert) { - sql << "insert into " - << PropertyRep::getTableNameForTypeValue(property.first) - << " (doc_id, prop_id, value) " - << " values "; - for (const auto& insertValue : property.second) { - sql << insertValue << ","; - } - sql.seekp(sql.tellp() - 1l); - sql << ";"; - } - - return qctx->db->executeMultipleOnOneStepRaw(sql.str(), qctx->bind); -} - -int Query::remove(int documentID) { - qctx->resetQuery(); - auto& sql = qctx->sql; - - auto& tables = tables::getPropertyTables(); - - sql << "delete from document where id = @id; "; - - for (auto& [type, tab_name] : tables) { - sql << "delete from " << tab_name << " where doc_id = @id;"; - } - - return qctx->db->executeMultipleOnOneStepRaw(sql.str(), - {{"@id", documentID}}); -} - -Query QueryFactory::create(IDB* ctx, const std::string& collName) { - return Query( - std::make_shared(ctx, Collection::find(ctx, collName))); -} - -SelectQuery::SelectQuery(const std::shared_ptr& pCtx, - std::vector&& pProperties) - : ExecutableQuery(pCtx, nullptr), properties(pProperties) { - qctx->selectCtx->addFromClause(); - - // add the joins needed for the select clause, where, order by,... are added - // by them - qctx->selectCtx->addJoinClauses(this->properties); - - this->setExecutableFunction([this](QueryCtx& ctx) { - ctx.buildSelectQuery(); // sets ctx.sql - - json result = json::array(); - - auto reader = ctx.db->executeReader(ctx.sql.str(), ctx.bind); - - auto& props = this->properties; - std::shared_ptr row; - while (reader->readRow(row)) { - json jrow = json::object(); - - // each row will have exactly props.size() columns, and each one - // will have the same type as the property in its index. - for (int i = 0; i < props.size(); i++) { - auto& v_prop = props[i]; - - if (std::holds_alternative(v_prop)) { - const auto& prop = std::get(v_prop); - - if (row->isNull(i)) { - // Should we set the field to null? - // jrow[prop.getName()] = nullptr; - continue; - } - - switch (prop.getType()) { - case PropertyType::INTEGER: - jrow[prop.getName()] = row->readInt64(i); - break; - case PropertyType::DOUBLE: - jrow[prop.getName()] = row->readDouble(i); - break; - case PropertyType::STRING: - jrow[prop.getName()] = row->readString(i); - break; - case PropertyType::ID: - jrow["id"] = row->readInt32(i); - break; - default: - throw std::runtime_error( - "Select properties should not hold a reserved " - "property directly!"); - break; - } - } else { - auto& prop = std::get(v_prop); - jrow[prop.alias] = row->readInt64(i); - } - } - - result.push_back(jrow); - } - - return result; - }); -} - -void SelectQueryData::addFromClause() { - from_join << " from \"document\" as " << g_documentTableAlias << " "; -} - -bool SelectQueryData::propAlreadyHasJoin(PropertyRep& prop) { - // I overloaded the == operator! No worries - return std::find_if(propsWithJoin.begin(), propsWithJoin.end(), - [&prop](PropertyRep itProp) { - return itProp.getId() == prop.getId(); - }) != propsWithJoin.end(); -} - -void SelectQueryData::addJoinClauseIfNotExists(PropertyRep& prop) { - if (!this->propAlreadyHasJoin(prop) && prop.getType() != PropertyType::ID) { - from_join << utils::paramsbind::parseSQL( - " left join @value_table as @p_a on (@doc_alias.id = " - "@p_a.doc_id and @p_a.prop_id = @p_id)", - {{"@value_table", prop.getTableNameForTypeValue(prop.getType())}, - {"@p_a", prop.getStatement()}, - {"@p_id", prop.getId()}, - {"@doc_alias", g_documentTableAlias}}); - this->propsWithJoin.push_back(prop); // yes, copy it - } -} - -void SelectQueryData::addJoinClauses(std::vector& props) { - for (auto& prop : props) { - this->addJoinClauseIfNotExists(prop); - } -} - -void SelectQueryData::addJoinClauses(const std::set& props) { - for (const auto& prop : props) { - this->addJoinClauseIfNotExists(*prop); - } -} - -void SelectQueryData::addJoinClauses(std::vector& props) { - for (auto& v_prop : props) { - if (std::holds_alternative(v_prop)) { - addJoinClauseIfNotExists(std::get(v_prop)); - } else { - addJoinClauseIfNotExists(*std::get(v_prop).prop); - } - } -} - -SelectQuery& SelectQuery::where(const SqlLogicExpression& st) { - qctx->selectCtx->addJoinClauses(st.getActingProps()); - - this->qctx->selectCtx->where << " where " << st.getStatement(); - return *this; -} - -SelectQuery& SelectQuery::page(int pageNumber, int elementsPerPage) { - this->qctx->selectCtx->limit_offset << " limit " << elementsPerPage - << " offset " - << (pageNumber - 1) * elementsPerPage; - return *this; -} \ No newline at end of file diff --git a/src/Query/CollectionQuery.cpp b/src/Query/CollectionQuery.cpp new file mode 100644 index 0000000..772c030 --- /dev/null +++ b/src/Query/CollectionQuery.cpp @@ -0,0 +1,32 @@ +#include "nldb/Query/CollectionQuery.hpp" + +#include "nldb/DAL/Repositories.hpp" + +namespace nldb { + CollectionQuery::CollectionQuery(Repositories* repos, + const std::string& name, std::string alias) + : Collection(-1, name), repositories(repos) {} + + CollectionQuery::CollectionQuery(Repositories* repos, Collection col, + std::string alias) + : Collection(std::move(col)), repositories(repos) {} + + CollectionQuery& CollectionQuery::as(std::string alias) { + this->alias = alias; + return *this; + } + + void CollectionQuery::setCollection(const Collection& c) { + this->id = c.getId(); + this->name = c.getName(); + } + + std::optional CollectionQuery::getAlias() const { + return alias; + } + + bool CollectionQuery::hasCollectionAssigned() const { + return this->id != -1; + } + +} // namespace nldb \ No newline at end of file diff --git a/src/Query/Query.cpp b/src/Query/Query.cpp new file mode 100644 index 0000000..9de6d84 --- /dev/null +++ b/src/Query/Query.cpp @@ -0,0 +1,27 @@ +#include "nldb/Query/Query.hpp" + +#include "nldb/Exceptions.hpp" + +namespace nldb { + // Query::Query(IDB* pConnection) : connection(pConnection) { + // repositories = this->connection->getRepositoriesInstance(); + // } + + // QueryPlannerSources Query::from(const char* collection1Name) { + // auto coll = this->collection(collection1Name); + + // // create a new repositories object, query might be reused. + // return QueryPlannerSources( + // {{CollectionQuery(&this->repositories, coll)}, + // this->connection->getRepositoriesInstance()}); + // } + + // QueryPlannerSources Query::from(CollectionQuery collection1) { + // return QueryPlannerSources(QueryPlannerContext { + // {collection1}, this->connection->getRepositoriesInstance()}); + // } + + // CollectionQuery Query::collection(const char* name) { + // return CollectionQuery(&this->repositories, name); + // } +} // namespace nldb \ No newline at end of file diff --git a/src/Query/QueryPlanner.cpp b/src/Query/QueryPlanner.cpp new file mode 100644 index 0000000..87f7a64 --- /dev/null +++ b/src/Query/QueryPlanner.cpp @@ -0,0 +1,28 @@ +#include "nldb/Query/QueryPlanner.hpp" + +namespace nldb { + QueryPlanner::QueryPlanner(QueryPlannerContext&& ctx) + : context(std::move(ctx)) {} + + void QueryPlanner::insert(json object) { + QueryPlannerContextInsert ctx(std::move(this->context)); + ctx.documents = object; + + ctx.queryRunner->insert(std::move(ctx)); + } + + void QueryPlanner::update(int docId, json newValue) { + QueryPlannerContextUpdate ctx(std::move(this->context)); + ctx.documentID = docId; + ctx.object = newValue; + + ctx.queryRunner->update(std::move(ctx)); + } + + void QueryPlanner::remove(int docId) { + QueryPlannerContextRemove ctx(std::move(this->context)); + ctx.documentID = docId; + + ctx.queryRunner->remove(std::move(ctx)); + } +} // namespace nldb \ No newline at end of file diff --git a/src/Query/QueryPlannerSelect.cpp b/src/Query/QueryPlannerSelect.cpp new file mode 100644 index 0000000..ad5bf17 --- /dev/null +++ b/src/Query/QueryPlannerSelect.cpp @@ -0,0 +1,33 @@ +#include "nldb/Query/QueryPlannerSelect.hpp" + +#include "nldb/Property/Property.hpp" + +namespace nldb { + QueryPlannerSelect::QueryPlannerSelect(QueryPlannerContextSelect&& pContext) + : context(std::move(pContext)) {} + + QueryPlannerSelect& QueryPlannerSelect::where( + const PropertyExpression& val) { + if (this->context.where_value.has_value()) { + this->context.where_value = + PropertyExpression(PropertyExpressionOperator::AND, val, + this->context.where_value); + } else { + this->context.where_value = val; + } + + return *this; + } + + QueryPlannerSelect& QueryPlannerSelect::page(int pageNumber, + int elementsPerPage) { + this->context.pagination_value = {.pageNumber = pageNumber, + .elementsPerPage = elementsPerPage}; + + return *this; + } + + json QueryPlannerSelect::execute() { + return context.queryRunner->select(std::move(context)); + } +} // namespace nldb \ No newline at end of file diff --git a/src/Query/QueryPlannerSources.cpp b/src/Query/QueryPlannerSources.cpp new file mode 100644 index 0000000..2ced45e --- /dev/null +++ b/src/Query/QueryPlannerSources.cpp @@ -0,0 +1,24 @@ +#include "nldb/Query/QueryPlannerSources.hpp" + +#include "nldb/Exceptions.hpp" + +namespace nldb { + QueryPlannerSources::QueryPlannerSources(QueryPlannerContext&& ctx) + : QueryPlanner(std::move(ctx)) {} + + QueryPlannerSources& QueryPlannerSources::with(const char* collection) { + auto coll = context.repos.repositoryCollection->find(collection); + + if (!coll.has_value()) throw CollectionNotFound(); + + this->context.from.push_back( + CollectionQuery(&context.repos, coll.value())); + + return *this; + } + + QueryPlannerSources& QueryPlannerSources::with(CollectionQuery collection) { + this->context.from.push_back(collection); + return *this; + } +} // namespace nldb \ No newline at end of file diff --git a/src/Query/QueryRunner.cpp b/src/Query/QueryRunner.cpp new file mode 100644 index 0000000..60522ed --- /dev/null +++ b/src/Query/QueryRunner.cpp @@ -0,0 +1,216 @@ +#include "nldb/Query/QueryRunner.hpp" + +#include +#include +#include +#include + +#include "magic_enum.hpp" +#include "nldb/Collection.hpp" +#include "nldb/Common.hpp" +#include "nldb/Exceptions.hpp" +#include "nldb/LOG/log.hpp" +#include "nldb/Property/Property.hpp" +#include "nldb/Query/QueryContext.hpp" +#include "nldb/Utils/ParamsBindHelpers.hpp" +#include "nldb/nldb_json.hpp" + +namespace nldb { + using namespace nldb::common; + + int AddCollectionIfMissing(const std::string& collName, + Repositories& repos); + + QueryRunner::QueryRunner(IDB* pConnection) : connection(pConnection) {} + + json QueryRunner::select(QueryPlannerContextSelect&& data) { + // TODO: make this function virtual + NLDB_ASSERT(false, "Not implemented."); + } + + void QueryRunner::update(QueryPlannerContextUpdate&& data) { + auto& repos = data.repos; + + // check if doc exists + int& docID = data.documentID; + if (!repos.repositoryDocument->exists(docID)) { + throw DocumentNotFound("id: " + std::to_string(docID)); + } + + // get the collection + auto& from = *data.from.begin(); + + std::optional collection; + if (from.hasCollectionAssigned()) { + collection = (Collection)from; + } else { + collection = repos.repositoryCollection->find(from.getName()); + } + + if (!collection.has_value()) { + throw CollectionNotFound(from.getName()); + } + + updateDocumentRecursive(docID, collection.value(), data.object, repos); + } + + void QueryRunner::insert(QueryPlannerContextInsert&& data) { + NLDB_ASSERT(data.from.size() > 0, "missing target collection"); + + if (data.from.size() > 1) { + NLDB_WARN("multiple collections given in an insert clause"); + } + + CollectionQuery& queryFrom = *data.from.begin(); + + if (data.documents.is_array()) { + for (auto& doc : data.documents) { + insertDocumentRecursive(doc, queryFrom.getName(), data.repos); + } + } else { + insertDocumentRecursive(data.documents, queryFrom.getName(), + data.repos); + } + } + + void QueryRunner::remove(QueryPlannerContextRemove&& data) { + data.repos.repositoryDocument->remove(data.documentID); + + data.repos.valuesDAO->removeAllFromDocument(data.documentID); + } + + int AddCollectionIfMissing(const std::string& collName, + Repositories& repos) { + if (auto val = repos.repositoryCollection->find(collName)) { + return val->getId(); + } else { + NLDB_TRACE("added missing collection '{}'", collName); + return repos.repositoryCollection->add(collName); + } + } + + std::pair QueryRunner::insertDocumentRecursive( + json& doc, const std::string& collName, Repositories& repos) { + // - Add collection if missing + // - Create document + // - Create missing collection properties + // - if property.type is Object + // - create a sub-collection with the name + // "_{collName}_{propertyName}" + // - repeat the steps from the start + int collID = AddCollectionIfMissing(collName, repos); + + int docID = repos.repositoryDocument->add(collID); + + for (auto& [propertyName, value] : doc.items()) { + int propID = -1; + PropertyType type = JsonTypeToPropertyType((int)value.type()); + + if (auto prop = + repos.repositoryProperty->find(collID, propertyName)) { + propID = prop->getId(); + } else { + propID = + repos.repositoryProperty->add(propertyName, collID, type); + } + + if (type == PropertyType::OBJECT) { + auto [sub_coll_id, sub_doc_id] = this->insertDocumentRecursive( + value, getSubCollectionName(collName, propertyName), repos); + repos.valuesDAO->addObject(propID, docID, sub_coll_id, + sub_doc_id); + } else { + repos.valuesDAO->addStringLike(propID, docID, type, + ValueToString(value)); + } + } + + return {collID, docID}; + } + + void QueryRunner::updateDocumentRecursive(int docID, + const Collection& collection, + json& object, + Repositories& repos) { + for (auto& [propName, valueJson] : object.items()) { + std::optional found = + repos.repositoryProperty->find(collection.getId(), propName); + + auto type = JsonTypeToPropertyType((int)valueJson.type()); + + int propID; + + if (found.has_value()) { + if (found->getType() != type) { + throw WrongPropertyType( + propName, + std::string(magic_enum::enum_name(found->getType())), + std::string(magic_enum::enum_name(type))); + } + + propID = found->getId(); + } else { + propID = repos.repositoryProperty->add( + propName, collection.getId(), type); + } + + if (type != PropertyType::OBJECT) { + std::string valueString = ValueToString(valueJson); + + // update the value or create a new one + if (repos.valuesDAO->exists(propID, docID, type)) { + repos.valuesDAO->updateStringLike(propID, docID, type, + valueString); + } else { + repos.valuesDAO->addStringLike(propID, docID, type, + valueString); + } + } else { + // If the object property points has a value, update that + // sub-document. Else a new value object should be added. + + auto objectValue = repos.valuesDAO->findObject(propID, docID); + if (objectValue.has_value()) { + auto collection = repos.repositoryCollection->find( + objectValue->sub_coll_id); + + updateDocumentRecursive(objectValue->sub_doc_id, + collection.value(), valueJson, + repos); + } else { + // is a new object (sub-collection) of propID + // to get the collection we use the same format as the + // insert + auto subCollName = + getSubCollectionName(collection.getName(), propName); + // we need to get it before we create a new one because + // maybe another document of this collection already added a + // sub-collection for that property. + auto subCollFound = + repos.repositoryCollection->find(subCollName); + + int sub_coll_id; + if (subCollFound) { + // found it, we will extend it with more properties + sub_coll_id = subCollFound->getId(); + } else { + // create a new sub-collection + sub_coll_id = + repos.repositoryCollection->add(subCollName); + } + + int sub_doc_id = repos.repositoryDocument->add(sub_coll_id); + + // add the new properties/values and update the existing + updateDocumentRecursive( + sub_doc_id, Collection(sub_coll_id, subCollName), + valueJson, repos); + + // value didn't exist before, add it. + repos.valuesDAO->addObject(propID, docID, sub_coll_id, + sub_doc_id); + } + } + } + } +} // namespace nldb \ No newline at end of file diff --git a/src/SqlExpression.cpp b/src/SqlExpression.cpp deleted file mode 100644 index 268d3fa..0000000 --- a/src/SqlExpression.cpp +++ /dev/null @@ -1,28 +0,0 @@ -#include "SqlExpression.hpp" - -SqlLogicExpression::SqlLogicExpression(std::string st, - std::set pPr) - : st(std::move(st)), actingProps(std::move(pPr)) {} - -std::string SqlLogicExpression::getStatement() const { return std::move(st); } - -std::set SqlLogicExpression::getActingProps() const { - return std::move(actingProps); -} - -SqlLogicExpression SqlLogicExpression::operator&&(const SqlLogicExpression& q) { - utils::concat(actingProps, q.actingProps); - return SqlLogicExpression(st + " " + OperatorToString(AND) + " " + q.st, - std::move(actingProps)); -} - -SqlLogicExpression SqlLogicExpression::operator||(const SqlLogicExpression& q) { - utils::concat(actingProps, q.actingProps); - return SqlLogicExpression(st + " " + OperatorToString(OR) + " " + q.st, - std::move(actingProps)); -} - -SqlLogicExpression SqlLogicExpression::operator~() { - return SqlLogicExpression(OperatorToString(NOT) + std::string(" ") + st, - std::move(actingProps)); -} \ No newline at end of file diff --git a/src/ParamsBind.cpp b/src/Utils/ParamsBindHelper.cpp similarity index 93% rename from src/ParamsBind.cpp rename to src/Utils/ParamsBindHelper.cpp index 5562fed..34b1fe4 100644 --- a/src/ParamsBind.cpp +++ b/src/Utils/ParamsBindHelper.cpp @@ -1,6 +1,7 @@ -#include "dbwrapper/ParamsBind.hpp" +#include -#include "Utils.hpp" +#include "nldb/Utils/ParamsBindHelpers.hpp" +#include "nldb/Utils/String.hpp" namespace utils::paramsbind { std::string getBindValueAsString(const ParamsBindValue& val, diff --git a/src/Utils.cpp b/src/Utils/String.cpp similarity index 92% rename from src/Utils.cpp rename to src/Utils/String.cpp index b0932d9..1799a76 100644 --- a/src/Utils.cpp +++ b/src/Utils/String.cpp @@ -1,6 +1,4 @@ -#include "Utils.hpp" - -#include +#include "nldb/Utils/String.hpp" namespace utils { void replaceAllOccurrences(std::string& str, const std::string& from, diff --git a/src/backends/sqlite3/DAL/Definitions.cpp b/src/backends/sqlite3/DAL/Definitions.cpp new file mode 100644 index 0000000..adc45f1 --- /dev/null +++ b/src/backends/sqlite3/DAL/Definitions.cpp @@ -0,0 +1,17 @@ +#include + +#include "nldb/Property/Property.hpp" + +namespace nldb::definitions { + namespace tables { + std::unordered_map& getPropertyTypesTable() { + static std::unordered_map + propertyTypeTable = {{PropertyType::STRING, "value_string"}, + {PropertyType::INTEGER, "value_int"}, + {PropertyType::DOUBLE, "value_double"}, + {PropertyType::OBJECT, "value_object"}}; + + return propertyTypeTable; + } + } // namespace tables +} // namespace nldb::definitions \ No newline at end of file diff --git a/src/backends/sqlite3/DAL/Definitions.hpp b/src/backends/sqlite3/DAL/Definitions.hpp new file mode 100644 index 0000000..97e45c5 --- /dev/null +++ b/src/backends/sqlite3/DAL/Definitions.hpp @@ -0,0 +1,20 @@ +#pragma once + +#include + +#include "nldb/Property/Property.hpp" + +namespace nldb::definitions { + /** + * @brief Why this file is in SQLite 3 implementation? + * Because there might be implementations that doesn't need SQL tables. + */ + namespace tables { + std::unordered_map& getPropertyTypesTable(); + } + + std::string inline getSubCollectionName(const std::string& collName, + const std::string& propName) { + return "_" + collName + "_" + propName; + } +} // namespace nldb::definitions \ No newline at end of file diff --git a/src/backends/sqlite3/DAL/RepositoryCollection.cpp b/src/backends/sqlite3/DAL/RepositoryCollection.cpp new file mode 100644 index 0000000..5ca6693 --- /dev/null +++ b/src/backends/sqlite3/DAL/RepositoryCollection.cpp @@ -0,0 +1,51 @@ +#include "RepositoryCollection.hpp" + +#include + +#include "nldb/Collection.hpp" +#include "nldb/Property/Property.hpp" + +namespace nldb { + RepositoryCollection::RepositoryCollection(IDB* connection) + : conn(connection) {} + + int RepositoryCollection::add(const std::string& name) { + const std::string sql = "insert into collection (name) values (@name);"; + conn->execute(sql, {{"@name", name}}); + return conn->getLastInsertedRowId(); + } + + std::optional RepositoryCollection::find( + const std::string& name) { + const std::string sql = "select id from collection where name = @name;"; + + auto id = conn->executeAndGetFirstInt(sql, {{"@name", name}}); + + if (id.has_value()) { + return Collection(id.value(), name); + } else { + return std::nullopt; + } + } + + bool RepositoryCollection::exists(const std::string& name) { + return find(name).has_value(); + } + + std::optional RepositoryCollection::find(int id) { + const std::string sql = "select name from collection where id = @id;"; + + auto reader = conn->executeReader(sql, {{"@id", id}}); + + std::shared_ptr row; + if (reader->readRow(row)) { + return Collection(id, row->readString(0)); + } else { + return std::nullopt; + } + } + + bool RepositoryCollection::exists(int id) { + return this->find(id).has_value(); + } +} // namespace nldb diff --git a/src/backends/sqlite3/DAL/RepositoryCollection.hpp b/src/backends/sqlite3/DAL/RepositoryCollection.hpp new file mode 100644 index 0000000..278701a --- /dev/null +++ b/src/backends/sqlite3/DAL/RepositoryCollection.hpp @@ -0,0 +1,19 @@ +#include "nldb/DAL/IRepositoryCollection.hpp" +#include "nldb/DB/IDB.hpp" + +namespace nldb { + class RepositoryCollection : public IRepositoryCollection { + public: + RepositoryCollection(IDB* connection); + + public: + int add(const std::string& name) override; + std::optional find(const std::string& name) override; + bool exists(const std::string& name) override; + std::optional find(int id) override; + bool exists(int id) override; + + private: + IDB* conn; + }; +} // namespace nldb \ No newline at end of file diff --git a/src/backends/sqlite3/DAL/RepositoryDocument.cpp b/src/backends/sqlite3/DAL/RepositoryDocument.cpp new file mode 100644 index 0000000..c7d4b88 --- /dev/null +++ b/src/backends/sqlite3/DAL/RepositoryDocument.cpp @@ -0,0 +1,37 @@ +#include "RepositoryDocument.hpp" + +#include + +#include "Definitions.hpp" +#include "nldb/Collection.hpp" +#include "nldb/DAL/Repositories.hpp" +#include "nldb/Exceptions.hpp" +#include "nldb/LOG/log.hpp" +#include "nldb/Query/QueryContext.hpp" + +namespace nldb { + using namespace nldb::definitions; + + RepositoryDocument::RepositoryDocument(IDB* connection) + : conn(connection) {} + + int RepositoryDocument::add(int collectionID) { + const std::string sql = "insert into document (coll_id) values (@id);"; + + conn->execute(sql, {{"@id", collectionID}}); + + return conn->getLastInsertedRowId(); + } + + void RepositoryDocument::remove(int id) { + const std::string sql = "delete from document where id = @id;"; + + conn->execute(sql, {{"@id", id}}); + } + + bool RepositoryDocument::exists(int id) { + const std::string sql = "select id from document where id = @id;"; + + return conn->executeAndGetFirstInt(sql, {{"@id", id}}).has_value(); + } +} // namespace nldb \ No newline at end of file diff --git a/src/backends/sqlite3/DAL/RepositoryDocument.hpp b/src/backends/sqlite3/DAL/RepositoryDocument.hpp new file mode 100644 index 0000000..a8277b4 --- /dev/null +++ b/src/backends/sqlite3/DAL/RepositoryDocument.hpp @@ -0,0 +1,23 @@ +#pragma once + +#include "nldb/Collection.hpp" +#include "nldb/DAL/IRepositoryDocument.hpp" +#include "nldb/DAL/Repositories.hpp" +#include "nldb/DB/IDB.hpp" + +namespace nldb { + class RepositoryDocument : public IRepositoryDocument { + public: + RepositoryDocument(IDB* connection); + + public: + int add(int collid) override; + + void remove(int id) override; + + bool exists(int id) override; + + private: + IDB* conn; + }; +} // namespace nldb \ No newline at end of file diff --git a/src/backends/sqlite3/DAL/RepositoryProperty.cpp b/src/backends/sqlite3/DAL/RepositoryProperty.cpp new file mode 100644 index 0000000..bb96916 --- /dev/null +++ b/src/backends/sqlite3/DAL/RepositoryProperty.cpp @@ -0,0 +1,58 @@ +#include "RepositoryProperty.hpp" + +#include "nldb/Property/Property.hpp" + +namespace nldb { + RepositoryProperty::RepositoryProperty(IDB* connection) + : conn(connection) {} + + int RepositoryProperty::add(const std::string& name, int collectionID, + PropertyType type) { + const std::string sql = + "insert into property (coll_id, name, type) values (@colid, @name, " + "@type);"; + + conn->execute( + sql, + {{"@colid", collectionID}, {"@name", name}, {"@type", (int)type}}); + + return conn->getLastInsertedRowId(); + } + + std::optional RepositoryProperty::find( + int collectionID, const std::string& propName) { + auto reader = conn->executeReader( + "SELECT id, type FROM property where coll_id = @colid and name = " + "@name", + {{"@colid", collectionID}, {"@name", propName}}); + + std::shared_ptr row; + if (reader->readRow(row)) { + return Property(row->readInt32(0), propName, + (PropertyType)row->readInt32(1)); + } else { + return std::nullopt; + } + } + + bool RepositoryProperty::exists(int collectionID, + const std::string& propName) { + return this->find(collectionID, propName).has_value(); + } + + std::vector RepositoryProperty::find(int collectionId) { + const std::string sql = + "select id, name, type from property where coll_id = @id;"; + + auto reader = conn->executeReader(sql, {{"@id", collectionId}}); + + std::vector props = {Property(-1, "id", PropertyType::ID)}; + std::shared_ptr row; + while (reader->readRow(row)) { + props.push_back(Property(row->readInt64(0), row->readString(1), + (PropertyType)row->readInt64(2))); + } + + return std::move(props); + } +} // namespace nldb \ No newline at end of file diff --git a/src/backends/sqlite3/DAL/RepositoryProperty.hpp b/src/backends/sqlite3/DAL/RepositoryProperty.hpp new file mode 100644 index 0000000..b9ce352 --- /dev/null +++ b/src/backends/sqlite3/DAL/RepositoryProperty.hpp @@ -0,0 +1,27 @@ +#pragma once + +#include +#include +#include + +#include "nldb/DAL/IRepositoryProperty.hpp" +#include "nldb/DB/IDB.hpp" +#include "nldb/Property/Property.hpp" + +namespace nldb { + class RepositoryProperty : public IRepositoryProperty { + public: + RepositoryProperty(IDB* connection); + + public: + int add(const std::string& name, int collectionID, + PropertyType type) override; + std::optional find(int collectionID, + const std::string& propName) override; + bool exists(int collectionID, const std::string& propName) override; + std::vector find(int collectionId) override; + + private: + IDB* conn; + }; +} // namespace nldb \ No newline at end of file diff --git a/src/backends/sqlite3/DAL/ValuesDAO.cpp b/src/backends/sqlite3/DAL/ValuesDAO.cpp new file mode 100644 index 0000000..61eae68 --- /dev/null +++ b/src/backends/sqlite3/DAL/ValuesDAO.cpp @@ -0,0 +1,116 @@ +#include "ValuesDAO.hpp" + +#include + +#include "backends/sqlite3/DAL/Definitions.hpp" +#include "nldb/LOG/log.hpp" +#include "nldb/Property/Property.hpp" +#include "nldb/Utils/ParamsBindHelpers.hpp" + +namespace nldb { + using namespace definitions; + + ValuesDAO::ValuesDAO(IDB* pConnection) : conn(pConnection) {} + + void ValuesDAO::addStringLike(int propID, int docID, PropertyType type, + std::string value) { + const std::string sql = + "insert into @table (doc_id, prop_id, value) values (@doc_id, " + "@prop_id, @value);"; + + conn->execute(sql, {{"@table", tables::getPropertyTypesTable()[type]}, + {"@doc_id", docID}, + {"@prop_id", propID}, + {"@value", value}}); + } + + void ValuesDAO::addObject(int propID, int docID, int subCollID, + int subDocId) { + const std::string sql = + "insert into @table (doc_id, prop_id, sub_coll_id, sub_doc_id) " + "values (@doc_id, @prop_id, @sub_coll_id, @sub_doc_id);"; + + conn->execute( + sql, + {{"@table", tables::getPropertyTypesTable()[PropertyType::OBJECT]}, + {"@doc_id", docID}, + {"@prop_id", propID}, + {"@sub_coll_id", subCollID}, + {"@sub_doc_id", subDocId}}); + } + + void ValuesDAO::updateStringLike(int propID, int docID, PropertyType type, + std::string value) { + const std::string sql = + "update @table set value = @prop_value where " + "doc_id = @doc_id and prop_id = @prop_id;"; + + conn->execute( + utils::paramsbind::parseSQL( + sql, {{"@table", tables::getPropertyTypesTable()[type]}, + {"@prop_value", value}, + {"@doc_id", docID}, + {"@prop_id", propID}}), + {}); + } + + void ValuesDAO::updateObject(int propID, int docID, int subCollID, + int subDocId) { + NLDB_CRITICAL("MISSING IMPL"); + } + + bool ValuesDAO::exists(int propID, int docID, PropertyType type) { + const std::string sql = + "select id from @table where prop_id = @prop_id and doc_id = " + "@doc_id;"; + + auto result = conn->executeAndGetFirstInt( + utils::paramsbind::parseSQL( + sql, {{"@table", tables::getPropertyTypesTable()[type]}, + {"@doc_id", docID}, + {"@prop_id", propID}}), + {}); + + return result.has_value(); + } + + std::optional ValuesDAO::findObject(int propID, + int docID) { + const std::string sql = + "select id, sub_coll_id, sub_doc_id from @table where prop_id = " + "@prop_id and doc_id = " + "@doc_id;"; + + auto reader = conn->executeReader( + utils::paramsbind::parseSQL( + sql, {{"@table", + tables::getPropertyTypesTable()[PropertyType::OBJECT]}, + {"@prop_id", propID}, + {"@doc_id", docID}}), + {}); + + std::shared_ptr row; + while (reader->readRow(row)) { + ValueObjectMapped obj = {.doc_id = docID, .prop_id = propID}; + obj.id = row->readInt32(0); + obj.sub_coll_id = row->readInt32(1); + obj.sub_doc_id = row->readInt32(2); + return obj; + } + + return std::nullopt; + } + + void ValuesDAO::removeAllFromDocument(int docID) { + std::stringstream sql; + + auto& tables = tables::getPropertyTypesTable(); + + for (auto& [type, tab_name] : tables) { + sql << "delete from " << tab_name << " where doc_id = " << docID + << ";"; + } + + conn->execute(sql.str(), {}); + } +} // namespace nldb \ No newline at end of file diff --git a/src/backends/sqlite3/DAL/ValuesDAO.hpp b/src/backends/sqlite3/DAL/ValuesDAO.hpp new file mode 100644 index 0000000..1179259 --- /dev/null +++ b/src/backends/sqlite3/DAL/ValuesDAO.hpp @@ -0,0 +1,34 @@ +#pragma once + +#include "nldb/DAL/IValuesDAO.hpp" +#include "nldb/DB/IDB.hpp" + +namespace nldb { + class ValuesDAO : public IValuesDAO { + public: + ValuesDAO(IDB* connection); + + public: + void addStringLike(int propID, int docID, PropertyType type, + std::string value) override; + + void addObject(int propID, int docID, int subCollID, + int subDocId) override; + + void updateStringLike(int propID, int docID, PropertyType type, + std::string value) override; + + void updateObject(int propID, int docID, int subCollID = -1, + int subDocId = -1) override; + + bool exists(int propID, int docID, PropertyType type) override; + + std::optional findObject(int propID, + int docID) override; + + void removeAllFromDocument(int docID) override; + + private: + IDB* conn; + }; +} // namespace nldb \ No newline at end of file diff --git a/src/backends/sqlite3/DB/DB.cpp b/src/backends/sqlite3/DB/DB.cpp new file mode 100644 index 0000000..39435f7 --- /dev/null +++ b/src/backends/sqlite3/DB/DB.cpp @@ -0,0 +1,157 @@ +#include "DB.hpp" + +#include +#include +#include +#include + +#include "DBInitializer.hpp" +#include "nldb/DB/IDBQueryReader.hpp" +#include "nldb/LOG/log.hpp" +#include "nldb/Utils/ParamsBindHelpers.hpp" + +#define SQL3_EXEC_ERR_HNDL(db, SQL_STR, ERROR_MSG) \ + char* err = 0; \ + int rc = sqlite3_exec(db, SQL_STR, 0, 0, &err); \ + if (rc != SQLITE_OK) { \ + NLDB_ERROR(ERROR_MSG ": '{}'", err); \ + sqlite3_free(err); \ + this->throwLastError(); \ + } + +namespace nldb { + + bool DBSL3::open(const std::string& path) { + bool fileExists = std::filesystem::exists(path); + + int rc = sqlite3_open(path.c_str(), &this->db); + if (rc != SQLITE_OK) { + this->close(); + + return false; + } + + if (!fileExists) { + DBInitializer::createTablesAndFKeys(this); + } + + return true; + } + + bool DBSL3::close() { + if (this->db != nullptr) { + int rc = sqlite3_close(this->db); + + if (rc != SQLITE_OK) { + return false; + } + } + + return true; + } + + std::unique_ptr DBSL3::executeReader( + const std::string& query, const Paramsbind& params) { + NLDB_TRACE("Executing: {}", query.c_str()); + + if (query.empty()) { + NLDB_ERROR("Empty query"); + throw std::runtime_error("Empty query"); + } + + // prepare sql + sqlite3_stmt* stmt; + int rc = sqlite3_prepare_v2(db, query.c_str(), -1, &stmt, 0); + + if (rc == SQLITE_OK) { + // bind params + for (const auto& it : params) { + if (!it.first.starts_with("@") && !it.first.starts_with("$") && + !it.first.starts_with("?") && !it.first.starts_with(":")) { + NLDB_WARN( + "The parameter '{}' doesn't start with @,$,: or ?", + it.first.c_str()); + } + + int idx = sqlite3_bind_parameter_index(stmt, it.first.c_str()); + if (std::holds_alternative(it.second)) { + sqlite3_bind_int(stmt, idx, std::get(it.second)); + } else if (std::holds_alternative(it.second)) { + sqlite3_bind_double(stmt, idx, std::get(it.second)); + } else if (std::holds_alternative(it.second)) { + sqlite3_bind_text(stmt, idx, + std::get(it.second).c_str(), + -1, SQLITE_STATIC); + } else { + // variant protects against this but anyway... in case we + // change it + throw std::runtime_error("Type not supported"); + } + } + + // we are done, now the user can read the results + return std::make_unique(this, stmt); + } else { + this->throwLastError(); + return nullptr; + } + } + + void DBSL3::begin() { + SQL3_EXEC_ERR_HNDL(db, "BEGIN TRANSACTION;", + "Couldn't start the transaction"); + } + + void DBSL3::commit() { + SQL3_EXEC_ERR_HNDL(db, "END TRANSACTION;", + "Couldn't commit the transaction"); + } + + void DBSL3::rollback() { + SQL3_EXEC_ERR_HNDL(db, "ROLLBACK;", + "Couldn't rollback the transaction"); + } + + void DBSL3::execute(const std::string& query, const Paramsbind& params) { + /** + * https://www.sqlite.org/lang_transaction.html#deferred_immediate_and_exclusive_transactions + * To do it in one step use sqlite3_finalize after prepare or simply use + * sqlite3_exec. + */ + std::string sql = utils::paramsbind::parseSQL(query, params); + + NLDB_TRACE("Executing: {}", sql); + + char* err = 0; + int rc = sqlite3_exec(db, sql.c_str(), 0, 0, &err); + + if (rc != SQLITE_OK) { + NLDB_ERROR("Could not execute query, error: {}", err); + sqlite3_free(err); + this->throwLastError(); + } + } + + std::optional DBSL3::executeAndGetFirstInt(const std::string& query, + const Paramsbind& params) { + auto res = this->executeReader(query, params); + std::shared_ptr row; + int first {-1}; + while (res->readRow(row)) { + return row->readInt32(0); + } + + return std::nullopt; + } + + std::optional DBSL3::getChangesCount() { + return this->executeAndGetFirstInt("SELECT changes();", {}); + } + + int DBSL3::getLastInsertedRowId() { return sqlite3_last_insert_rowid(db); } + + void DBSL3::throwLastError() { + throw std::runtime_error(sqlite3_errmsg(this->db)); + } + +} // namespace nldb \ No newline at end of file diff --git a/src/backends/sqlite3/DB/DB.hpp b/src/backends/sqlite3/DB/DB.hpp new file mode 100644 index 0000000..fa42367 --- /dev/null +++ b/src/backends/sqlite3/DB/DB.hpp @@ -0,0 +1,37 @@ +#pragma once + +#include "DBQueryReader.hpp" +#include "nldb/DB/IDB.hpp" +#include "sqlite/sqlite3.h" + +namespace nldb { + + class DBSL3 : public IDB { + public: + bool open(const std::string& path) override; + bool close() override; + std::unique_ptr executeReader( + const std::string& query, const Paramsbind& params) override; + + void execute(const std::string& query, + const Paramsbind& params) override; + + void begin() override; + + void commit() override; + + void rollback() override; + + std::optional executeAndGetFirstInt( + const std::string& query, const Paramsbind& params) override; + + int getLastInsertedRowId() override; + + std::optional getChangesCount() override; + + void throwLastError() override; + + private: + sqlite3* db; + }; +} // namespace nldb \ No newline at end of file diff --git a/src/backends/sqlite3/DB/DBInitializer.cpp b/src/backends/sqlite3/DB/DBInitializer.cpp new file mode 100644 index 0000000..0b9acf8 --- /dev/null +++ b/src/backends/sqlite3/DB/DBInitializer.cpp @@ -0,0 +1,94 @@ +#include "DBInitializer.hpp" + +#include "nldb/LOG/log.hpp" + +namespace nldb { + + void createCollectionTable(IDB* db) { + const auto sql = + "CREATE TABLE `collection` (" + "`id` INTEGER PRIMARY KEY," + "`name` varchar(255)" + ");"; + + db->execute(sql, {}); + } + + void createDocumentTable(IDB* db) { + const auto sql = + "CREATE TABLE `document` (" + "`id` INTEGER PRIMARY KEY," + "`coll_id` int," + "FOREIGN KEY (coll_id) REFERENCES collection(id)" + ");"; + + db->execute(sql, {}); + } + + void createPropertyTable(IDB* db) { + const auto sql = + "CREATE TABLE `property` (" + "`id` INTEGER PRIMARY KEY," + "`coll_id` int," + "`name` varchar(255)," + "`type` int," + "FOREIGN KEY (coll_id) REFERENCES collection(id)" + ");"; + + db->execute(sql, {}); + } + + void createValuesTable(IDB* db) { + const auto sql = + // int + "CREATE TABLE `value_int` (" + "`id` INTEGER PRIMARY KEY," + "`doc_id` int," + "`prop_id` int," + "`value` int," + "FOREIGN KEY (doc_id) REFERENCES document(id)," + "FOREIGN KEY (prop_id) REFERENCES property(id)" + ");" + // double + "CREATE TABLE `value_double` (" + "`id` INTEGER PRIMARY KEY," + "`doc_id` int," + "`prop_id` int," + "`value` DOUBLE," + "FOREIGN KEY (doc_id) REFERENCES document(id)," + "FOREIGN KEY (prop_id) REFERENCES property(id)" + ");" + // string + "CREATE TABLE `value_string` (" + "`id` INTEGER PRIMARY KEY," + "`doc_id` int," + "`prop_id` int," + "`value` varchar(255)," + "FOREIGN KEY (doc_id) REFERENCES document(id)," + "FOREIGN KEY (prop_id) REFERENCES property(id)" + ");" + // object + "CREATE TABLE `value_object` (" + "`id` INTEGER PRIMARY KEY," + "`doc_id` int," + "`prop_id` int," + "`sub_coll_id` int," + "`sub_doc_id` int," + "FOREIGN KEY (doc_id) REFERENCES document(id)," + "FOREIGN KEY (prop_id) REFERENCES property(id)," + "FOREIGN KEY (sub_coll_id) REFERENCES collection(id)" + "FOREIGN KEY (sub_doc_id) REFERENCES document(id)" + ");"; + + db->execute(sql, {}); + } + + void DBInitializer::createTablesAndFKeys(IDB* db) { + db->begin(); + createCollectionTable(db); + createDocumentTable(db); + createPropertyTable(db); + createValuesTable(db); + db->commit(); + } +} // namespace nldb \ No newline at end of file diff --git a/src/backends/sqlite3/DB/DBInitializer.hpp b/src/backends/sqlite3/DB/DBInitializer.hpp new file mode 100644 index 0000000..3854abd --- /dev/null +++ b/src/backends/sqlite3/DB/DBInitializer.hpp @@ -0,0 +1,9 @@ +#pragma once +#include "nldb/DB/IDB.hpp" + +namespace nldb { + class DBInitializer { + public: + static void createTablesAndFKeys(IDB* db); + }; +} // namespace nldb \ No newline at end of file diff --git a/src/backends/sqlite3/DB/DBQueryReader.cpp b/src/backends/sqlite3/DB/DBQueryReader.cpp new file mode 100644 index 0000000..19bfa4c --- /dev/null +++ b/src/backends/sqlite3/DB/DBQueryReader.cpp @@ -0,0 +1,66 @@ +#include "DBQueryReader.hpp" + +#include +#include +#include + +namespace nldb { + + DBRowReaderSL3::DBRowReaderSL3(IDB* pDb, sqlite3_stmt* pStmt) + : stmt(pStmt), db(pDb) {} + + std::string DBRowReaderSL3::readString(uint16_t i) { + auto str = sqlite3_column_text(stmt, i); + if (str != nullptr) + return std::string(reinterpret_cast(str)); + throw std::runtime_error("Missing column"); + } + + int64_t DBRowReaderSL3::readInt64(uint16_t i) { + return sqlite3_column_int64(stmt, i); + } + + int DBRowReaderSL3::readInt32(uint16_t i) { + return sqlite3_column_int(stmt, i); + } + + double DBRowReaderSL3::readDouble(uint16_t i) { + return sqlite3_column_double(stmt, i); + } + + bool DBRowReaderSL3::readBoolean(uint16_t i) { return readInt32(i) == 1; } + + bool DBRowReaderSL3::isNull(uint16_t i) { + return sqlite3_column_type(stmt, i) == SQLITE_NULL; + } + + // --- + bool DBQueryReaderSL3::readRow(std::shared_ptr& row) { + if (this->allWasRead) return false; + + if (!row) { + row = std::make_shared(this->db, this->stmt); + } + + int rc = sqlite3_step(stmt); + if (rc == SQLITE_ROW) { + return true; + } else if (rc == SQLITE_DONE) { + this->allWasRead = true; + sqlite3_finalize(stmt); + return false; + } else { + this->db->throwLastError(); + return false; + } + } + + DBQueryReaderSL3::DBQueryReaderSL3(IDB* pDb, sqlite3_stmt* pStmt) + : stmt(pStmt), db(pDb) {} + + DBQueryReaderSL3::~DBQueryReaderSL3() { + // Invoking sqlite3_finalize() on a NULL pointer is a harmless no-op. + sqlite3_finalize(stmt); + std::cout << "Query ended. Cleaning up stmt\n"; + } +} // namespace nldb \ No newline at end of file diff --git a/src/backends/sqlite3/DB/DBQueryReader.hpp b/src/backends/sqlite3/DB/DBQueryReader.hpp new file mode 100644 index 0000000..5ad247f --- /dev/null +++ b/src/backends/sqlite3/DB/DBQueryReader.hpp @@ -0,0 +1,39 @@ +#pragma once +#include "nldb/DB/IDB.hpp" +#include "nldb/DB/IDBQueryReader.hpp" +#include "sqlite/sqlite3.h" + +namespace nldb { + class DBRowReaderSL3 : public IDBRowReader { + public: + DBRowReaderSL3(IDB* db, sqlite3_stmt* stmt); + + public: + // Note: SQLITE3 - The leftmost column of the result set has the index 0 + + std::string readString(uint16_t i) override; + int64_t readInt64(uint16_t i) override; + int readInt32(uint16_t i) override; + double readDouble(uint16_t i) override; + bool readBoolean(uint16_t i) override; + bool isNull(uint16_t i) override; + + private: + IDB* db; + sqlite3_stmt* stmt; + }; + + class DBQueryReaderSL3 : public IDBQueryReader { + public: + DBQueryReaderSL3(IDB* db, sqlite3_stmt* stmt); + ~DBQueryReaderSL3(); + + public: + bool readRow(std::shared_ptr& row) override; + + private: + bool allWasRead {false}; + IDB* db; + sqlite3_stmt* stmt; + }; +} // namespace nldb \ No newline at end of file diff --git a/src/backends/sqlite3/Query/QueryRunner.cpp b/src/backends/sqlite3/Query/QueryRunner.cpp new file mode 100644 index 0000000..19f8559 --- /dev/null +++ b/src/backends/sqlite3/Query/QueryRunner.cpp @@ -0,0 +1,337 @@ +#include "QueryRunner.hpp" + +#include +#include +#include +#include +#include +#include + +#include "backends/sqlite3/DAL/Definitions.hpp" +#include "magic_enum.hpp" +#include "nldb/Collection.hpp" +#include "nldb/Exceptions.hpp" +#include "nldb/LOG/log.hpp" +#include "nldb/Property/Property.hpp" +#include "nldb/Property/PropertyExpression.hpp" +#include "nldb/Property/SortedProperty.hpp" +#include "nldb/Query/QueryContext.hpp" +#include "nldb/Utils/Enums.hpp" +#include "nldb/Utils/ParamsBindHelpers.hpp" +#include "nldb/Utils/Variant.hpp" +#include "nldb/nldb_json.hpp" + +#define IN_VEC(vec, x) (std::find(vec.begin(), vec.end(), x) != vec.end()) + +namespace nldb { + QueryRunnerSQ3::QueryRunnerSQ3(IDB* pConnection) + : QueryRunner(pConnection) {} + + using namespace ::utils::paramsbind; + + const char* doc_alias = "__doc"; + + /** + * @brief Get an alias. Because a property can have the same name as other + * property from another collection. + */ + std::string getAlias(const Property& prop) { + return prop.getName() + "_" + std::to_string(prop.getId()); + } + + std::string getAlias(const AggregatedProperty& agProp) { + return "ag_" + std::string(magic_enum::enum_name(agProp.type)) + "_" + + getAlias(agProp.property); + } + + /* -------------- EXPAND OBJECT PROPERTIES -------------- */ + struct ExpandedProperty { + const char* alias; + std::vector props; + }; + + /** + * If a collection has {name, contact: {address, email}} and + * we select(name, contact) then we expect the resulting json + * to be {name: "..", contact: {address: "..", email: ".."}}, + * but as properties in the query planner represents a row in + * the property table, this wouldn't happen. + * + * The objective of this function is to convert each property + * of type object its sub-properties and leave some record of + * that so when we read the resulting query we know what to + * expect. + * + * I will choose the approach where we get the collection from + * the value_object table. This have the main drawback that if + * a sub-collection doesn't have at least one document, we will + * not be able to expand it, which i think is ok since that + * is just like an empty json object. + * + * A different way of doing it would be creating a new table + * and relate a property with its sub-collection. + */ + void expandObjectProperties(QueryPlannerContextSelect& data) { + // allocate a new vector, because we need to keep the order of the + // given select. + std::vector expanded; + expanded.reserve(data.select_value.size()); // min len = select len + + auto cb = overloaded { + [&expanded](const Property& prop) { + if (prop.getType() == PropertyType::OBJECT) { + } else { + expanded.push_back(std::move(prop)); + } + }, + [](const AggregatedProperty& prop) { + // do nothing + }, + }; + + for (auto& prop : data.select_value) { + std::visit(cb, prop); + } + + data.select_value = std::move(expanded); + } + + /* -------------------- SELECT CLAUSE ------------------- */ + void addSelectClause(std::stringstream& sql, + std::vector& props) { + sql << "select "; + + auto cb = overloaded { + [&sql](const Property& prop) { + sql << encloseQuotesConst(getAlias(prop)); + }, + [&sql](const AggregatedProperty& agProp) { + sql << parseSQL( + "@ag_type(@ag_prop) as @alias", + {{"@ag_type", + std::string(magic_enum::enum_name(agProp.type))}, + {"@ag_prop", + encloseQuotesConst(getAlias(agProp.property))}, + {"@alias", encloseQuotesConst(getAlias(agProp))}}, + false); + }, + }; + + for (int i = 0; i < props.size(); i++) { + std::visit(cb, props[i]); + + if (i != props.size() - 1) { + sql << ", "; + } + } + } + + /* ---------------- FROM/JOIN CLAUSE ---------------- */ + /** + * @brief This is where all the addFrom callbacks converge. + * + * @param sql sql query + * @param prop property + * @param ids list of ids used. + */ + void addFromClause(std::stringstream& sql, const Property& prop, + std::vector& ids) { + if (!IN_VEC(ids, prop.getId())) { + auto& tables = definitions::tables::getPropertyTypesTable(); + + sql << parseSQL( + " left join @table as @row_alias on (@doc_alias.id = " + "@row_alias.doc_id and @row_alias.prop_id = @prop_id)\n", + {{"@table", tables[prop.getType()]}, + {"@row_alias", encloseQuotesConst(getAlias(prop))}, + {"@prop_id", prop.getId()}, + {"@doc_alias", doc_alias}}, + false); + + ids.push_back(prop.getId()); + } + } + + void addFromClause(std::stringstream& sql, + std::vector& props, + std::vector& ids) { + auto cb = overloaded { + [&sql, &ids](const Property& prop) { + addFromClause(sql, prop, ids); + }, + [&sql, &ids](const AggregatedProperty& agProp) { + addFromClause(sql, agProp.property, ids); + }, + }; + + for (auto& p : props) { + std::visit(cb, p); + } + } + + void addFromClause(std::stringstream& sql, std::vector& props, + std::vector& ids) { + for (auto& p : props) { + addFromClause(sql, p, ids); + } + } + + void addFromClause(std::stringstream& sql, + std::vector& props, + std::vector& ids) { + for (auto& p : props) { + addFromClause(sql, p.property, ids); + } + } + + void addFromClause(std::stringstream& sql, + const PropertyExpressionOperand& prop, + std::vector& ids) { + auto cb = overloaded { + [&sql, &ids](const LogicConstValue& prop) { + if (std::holds_alternative(prop)) { + addFromClause(sql, prop, ids); + } + }, + [&sql, &ids](const box& agProp) { + addFromClause(sql, agProp->left, ids); + addFromClause(sql, agProp->right, ids); + }, + [&sql, &ids](const PropertyExpressionOperand& agProp) { + addFromClause(sql, agProp, ids); + }}; + + std::visit(cb, prop); + } + + void addFromClause(std::stringstream& sql, const PropertyExpression& props, + std::vector& ids) { + addFromClause(sql, props.left, ids); + addFromClause(sql, props.right, ids); + } + + void addFromClause(std::stringstream& sql, + QueryPlannerContextSelect& data) { + // main from is document + sql << " from 'document' " << doc_alias << "\n"; + + // add all properties that appear in the data + std::vector ids; + addFromClause(sql, data.select_value, ids); + addFromClause(sql, data.groupBy_value, ids); + addFromClause(sql, data.sortBy_value, ids); + + if (data.where_value) addFromClause(sql, data.where_value.value(), ids); + } + + /* ------------------ WHERE CLAUSE ------------------ */ + void addWhereClause(std::stringstream& sql, const PropertyExpression& expr); + + void addWhereExpression(std::stringstream& sql, + const PropertyExpressionOperand& expr) { + auto cbConstVal = overloaded { + [&sql](const Property& prop) { + sql << encloseQuotesConst(getAlias(prop)); + }, + [&sql](const std::string& str) { sql << encloseQuotesConst(str); }, + [&sql](int val) { sql << val; }, [&sql](double val) { sql << val; }, + [&sql](const char* str) { sql << encloseQuotesConst(str); }}; + + auto cbOperand = + overloaded {[&sql, &cbConstVal](const LogicConstValue& prop) { + std::visit(cbConstVal, prop); + }, + [&sql](const box& agProp) { + addWhereClause(sql, *agProp); + }, + [&sql](const PropertyExpressionOperand& agProp) { + addWhereExpression(sql, agProp); + }}; + } + + void addWhereClause(std::stringstream& sql, + const PropertyExpression& expr) { + if (expr.type != PropertyExpressionOperator::NOT) { + addWhereExpression(sql, expr.left); + sql << " " << utils::OperatorToString(expr.type) << " "; + addWhereExpression(sql, expr.right); + } else { + sql << " " << utils::OperatorToString(expr.type) << " "; + addWhereExpression(sql, expr.left); + } + } + + void addWhereClause(std::stringstream& sql, + const QueryPlannerContextSelect& data) { + if (data.where_value) { + sql << " WHERE "; + + addWhereExpression(sql, data.where_value.value()); + } + } + + /* ----------------- GROUP BY CLAUSE ---------------- */ + void addGroupByClause(std::stringstream& sql, + std::vector& props) { + if (props.empty()) return; + + sql << " GROUP BY "; + + for (int i = 0; i < props.size(); i++) { + sql << encloseQuotesConst(getAlias(props[i])); + + if (i != props.size() - 1) { + sql << ", "; + } + } + } + + /* --------------------- SORT CLAUSE -------------------- */ + void addOrderByClause(std::stringstream& sql, + std::vector& props) { + if (props.empty()) return; + + sql << " ORDER BY "; + + for (int i = 0; i < props.size(); i++) { + sql << encloseQuotesConst(getAlias(props[i].property)) << " " + << magic_enum::enum_name(props[i].type); + + if (i != props.size() - 1) { + sql << ", "; + } + } + } + + /* ------------------ LIMIT CLAUSE ------------------ */ + void addPaginationClause(std::stringstream& sql, + const QueryPagination& limit) { + sql << " limit " << limit.elementsPerPage << " offset " + << (limit.pageNumber - 1) * limit.elementsPerPage; + } + + /* ------------------- EXECUTE SELECT ------------------- */ + json QueryRunnerSQ3::select(QueryPlannerContextSelect&& data) { + std::stringstream sql; + + auto expanded = expandObjectProperties(data); + + addSelectClause(sql, data.select_value); + addFromClause(sql, data); + addWhereClause(sql, data); + addGroupByClause(sql, data.groupBy_value); + addOrderByClause(sql, data.sortBy_value); + if (data.pagination_value) + addPaginationClause(sql, data.pagination_value.value()); + + // execute it + auto reader = this->connection->executeReader(sql.str(), {}); + std::shared_ptr row; + while (reader->readRow(row)) { + NLDB_TRACE(":) \n"); + } + + // read output and build object + return {}; + } +} // namespace nldb \ No newline at end of file diff --git a/src/backends/sqlite3/Query/QueryRunner.hpp b/src/backends/sqlite3/Query/QueryRunner.hpp new file mode 100644 index 0000000..fa4e25c --- /dev/null +++ b/src/backends/sqlite3/Query/QueryRunner.hpp @@ -0,0 +1,12 @@ +#include "nldb/DB/IDB.hpp" +#include "nldb/Query/QueryRunner.hpp" + +namespace nldb { + class QueryRunnerSQ3 : public QueryRunner { + public: + QueryRunnerSQ3(IDB* connection); + + public: + json select(QueryPlannerContextSelect&& data) override; + }; +} // namespace nldb \ No newline at end of file diff --git a/src/include/Collection.hpp b/src/include/Collection.hpp deleted file mode 100644 index 6dcebc9..0000000 --- a/src/include/Collection.hpp +++ /dev/null @@ -1,41 +0,0 @@ -#pragma once - -#include - -#include "Enums.hpp" -#include "PropertyRep.hpp" -#include "SqlExpression.hpp" -#include "dbwrapper/IDB.hpp" -#include "lrucache11/LRUCache11.hpp" - -class Collection { - public: - Collection(IDB* ctx, int id, const std::string& name); - - public: - int getID(); - - bool hasProperty(const std::string& key); - bool addProperty(const std::string& key, PropertyType type); - - std::optional tryGetProperty(const std::string& key); - PropertyRep getProperty(const std::string& key); - std::vector getAllTheProperties(); - - int documentExists(int doc_id); - - public: - static Collection find(IDB* ctx, const std::string& name); - static int create(IDB& ctx, const std::string& name); - - private: - int id; - std::string name; - IDB* ctx; - - private: - void updatePropCache(const std::string& key, PropertyRep prop); - - static lru11::Cache> - propertyCache; -}; \ No newline at end of file diff --git a/src/include/CommonTypes.hpp b/src/include/CommonTypes.hpp deleted file mode 100644 index 04b86b0..0000000 --- a/src/include/CommonTypes.hpp +++ /dev/null @@ -1,7 +0,0 @@ -#pragma once -#include -#include - -#include "ISqlStatement.hpp" - -// typedef std::variant RightValue; \ No newline at end of file diff --git a/src/include/Concepts.hpp b/src/include/Concepts.hpp deleted file mode 100644 index ee7fefa..0000000 --- a/src/include/Concepts.hpp +++ /dev/null @@ -1,7 +0,0 @@ -#pragma once -#include -#include -#include - -template -concept StringLike = std::is_convertible_v; \ No newline at end of file diff --git a/src/include/Constants.hpp b/src/include/Constants.hpp deleted file mode 100644 index 7042dc2..0000000 --- a/src/include/Constants.hpp +++ /dev/null @@ -1,3 +0,0 @@ -#pragma once - -#define g_documentTableAlias "__doc" \ No newline at end of file diff --git a/src/include/Document.hpp b/src/include/Document.hpp deleted file mode 100644 index ca78011..0000000 --- a/src/include/Document.hpp +++ /dev/null @@ -1,23 +0,0 @@ -#pragma once -#include "dbwrapper/IDB.hpp" - -class Document { - public: - Document(int id); - - public: - int getID(); - - public: - /** - * @brief creates a document and return the id - * - * @param ctx - * @param collectionID id of the collection the document belongs to - * @return int - */ - static int create(IDB& ctx, int collectionID); - - private: - int id; -}; \ No newline at end of file diff --git a/src/include/Enums.hpp b/src/include/Enums.hpp deleted file mode 100644 index 4cfa2cf..0000000 --- a/src/include/Enums.hpp +++ /dev/null @@ -1,67 +0,0 @@ -#pragma once - -#include -enum Operator { EQ, NEQ, GT, GTE, LT, LTE, LIKE, NLIKE, AND, OR, NOT }; - -enum PropertyType { INTEGER = 1, DOUBLE, STRING, ID }; - -enum AggregateType { COUNT, AVG, SUM, MAX, MIN }; - -enum SortType { ASC, DESC }; - -inline const char* OperatorToString(Operator v) { - switch (v) { - case EQ: - return "="; - case NEQ: - return "<>"; - case GT: - return ">"; - case GTE: - return ">="; - case LT: - return "<"; - case LTE: - return "<="; - case LIKE: - return "LIKE"; - case NLIKE: - return "NOT LIKE"; - case AND: - return "AND"; - case OR: - return "OR"; - case NOT: - return "NOT"; - default: - throw std::runtime_error("Uknown operator"); - } -} - -inline const char* AggregatefunctionTypeToString(AggregateType v) { - switch (v) { - case AVG: - return "AVG"; - case COUNT: - return "COUNT"; - case MAX: - return "MAX"; - case MIN: - return "MIN"; - case SUM: - return "SUM"; - default: - throw std::runtime_error("Uknown aggregate function"); - } -} - -inline const char* SortTypeToString(SortType v) { - switch (v) { - case ASC: - return "ASC"; - case DESC: - return "DESC"; - default: - throw std::runtime_error("Uknown sort type"); - } -} \ No newline at end of file diff --git a/src/include/ISqlStatement.hpp b/src/include/ISqlStatement.hpp deleted file mode 100644 index eeef68e..0000000 --- a/src/include/ISqlStatement.hpp +++ /dev/null @@ -1,7 +0,0 @@ -#pragma once -#include - -class ISqlStatement { - public: - virtual std::string getStatement() const = 0; -}; \ No newline at end of file diff --git a/src/include/PropertyRep.hpp b/src/include/PropertyRep.hpp deleted file mode 100644 index aea8042..0000000 --- a/src/include/PropertyRep.hpp +++ /dev/null @@ -1,147 +0,0 @@ -#pragma once -#include -#include -#include -#include -#include - -#include "CommonTypes.hpp" -#include "Concepts.hpp" -#include "Enums.hpp" -#include "ISqlStatement.hpp" -#include "SqlExpression.hpp" -#include "SqlStatement.hpp" -#include "dbwrapper/IDB.hpp" - -class PropertyRep; - -template -concept StringLikeOrProperty = (StringLike || - std::is_same::value); - -SqlStatement getStatement(PropertyRep* lf, Operator op, - PropertyRep& rt); - -SqlStatement getStatement(PropertyRep* lf, Operator op, - const std::string& rt); - -SqlStatement getStatement(PropertyRep* lf, Operator op, - const char* rt); - -typedef std::variant RightValue; - -struct SortProp { - SortProp(PropertyRep* pProp, SortType pType) : prop(pProp), type(pType) {} - - SortType type; - PropertyRep* prop; -}; - -struct AggregateFunction { - AggregateFunction(PropertyRep* pProp, const char* pAlias, - AggregateType pType) - : prop(pProp), alias(pAlias), type(pType) {} - const char* alias; - AggregateType type; - PropertyRep* prop; -}; - -namespace tables { - std::unordered_map& getPropertyTables(); -}; - -class PropertyRep : public ISqlStatement { - public: - PropertyRep(const std::string& pName, int id, PropertyType type); - - public: - /** - * @brief Get the internal id of the property - * @return int - */ - int getId() const; - - /** - * @brief Get its type - * - * @return PropertyType - */ - PropertyType getType() const; - - /** - * @brief Get the property name or alias - * - * @return std::string_view - */ - std::string_view getName() const; - std::string getStatement() const override; - - std::string getValueExpression() const; - - static std::string getTableNameForTypeValue(PropertyType type); - - static std::optional find(IDB* ctx, int collectionID, - const std::string& name); - - public: // aggregate functions - AggregateFunction countAs(const char* alias); - AggregateFunction averageAs(const char* alias); - AggregateFunction minAs(const char* alias); - AggregateFunction maxAs(const char* alias); - AggregateFunction sumAs(const char* alias); - - public: // sort order - SortProp asc(); - SortProp desc(); - - public: - SqlLogicExpression operator<(PropertyRep& rt); - - SqlLogicExpression operator<=(PropertyRep& rt); - - SqlLogicExpression operator>(PropertyRep& rt); - - SqlLogicExpression operator>=(PropertyRep& rt); - - // EQUAL - SqlLogicExpression operator==(PropertyRep& rt); - - // NOT EQUAL - SqlLogicExpression operator!=(PropertyRep& rt); - - // LIKE - SqlLogicExpression operator%(PropertyRep& rt); - - // NOT LIKE - SqlLogicExpression operator^(PropertyRep& rt); - - // -- Same operator but for other types - SqlLogicExpression operator<(RightValue rt); - - SqlLogicExpression operator<=(RightValue rt); - - SqlLogicExpression operator>(RightValue rt); - - SqlLogicExpression operator>=(RightValue rt); - - // EQUAL - SqlLogicExpression operator==(RightValue rt); - - // NOT EQUAL - SqlLogicExpression operator!=(RightValue rt); - - // LIKE - SqlLogicExpression operator%(RightValue rt); - - // NOT LIKE - SqlLogicExpression operator^(RightValue rt); - - private: - std::string generateConditionStatement(Operator, PropertyRep& rt); - std::string generateConditionStatement(Operator, RightValue rt); - - protected: - std::string name; - PropertyType type; - int id {-1}; -}; diff --git a/src/include/Query.hpp b/src/include/Query.hpp deleted file mode 100644 index ca37a67..0000000 --- a/src/include/Query.hpp +++ /dev/null @@ -1,336 +0,0 @@ -#pragma once -#include -#include -#include -#include -#include -#include -#include -#include - -#include "Collection.hpp" -#include "Concepts.hpp" -#include "Constants.hpp" -#include "Enums.hpp" -#include "MoveOnlyFunction.h" -#include "PropertyRep.hpp" -#include "SqlExpression.hpp" -#include "dbwrapper/IDB.hpp" -#include "dbwrapper/ParamsBind.hpp" -#include "logger/Logger.h" -#include "nlohmann/json.hpp" - -using namespace nlohmann; - -template -concept IsPropertyRep = std::is_same::value; - -template -concept IsSelectProperty = - IsPropertyRep || std::is_same::value; - -template -concept IsSorteableProp = std::is_same::value; - -typedef std::variant SelectProperty; - -/** - * @brief Holds data to build a SQL Select query with all the possible clauses - * that you can have. - * - */ -struct SelectQueryData { - std::stringstream select; - std::stringstream from_join; - std::stringstream where; - std::stringstream groupBy; - std::stringstream orderBy; - std::stringstream having; - std::stringstream limit_offset; - - // properties to which the union of their tables has already been added to - // the from clause - std::vector propsWithJoin; - - private: - friend class SelectQuery; - friend class QueryCtx; - - void addFromClause(); - // adds a join clausure for a property if it wasn't added. - void addJoinClauseIfNotExists(PropertyRep&); - void addJoinClauses(std::vector&); - void addJoinClauses(std::vector&); - void addJoinClauses(const std::set&); - - bool propAlreadyHasJoin(PropertyRep&); - - void resetQuery() { - select.str(""); - from_join.str(""); - where.str(""); - groupBy.str(""); - orderBy.str(""); - having.str(""); - limit_offset.str(""); - - propsWithJoin = {}; - } -}; - -/** - * @brief Hold enough data to make a SQL query to a database. - * An instance of this class gets passed between classes like in a state - * pattern. - */ -struct QueryCtx { - QueryCtx(IDB* db, const Collection& cl) : db(db), cl(cl) {} - - std::unique_ptr selectCtx; - std::stringstream sql; - Paramsbind bind; - Collection cl; - IDB* db; - - void resetQuery() { - sql.str(""); - bind = {}; - - if (selectCtx) { - selectCtx->resetQuery(); - } - } - - void buildSelectQuery() { - if (!selectCtx) { - throw std::runtime_error( - "Invalid query context use, 'select ctx' was not initilized"); - } - - sql.str(""); // just making sure - - sql << selectCtx->select.str() << selectCtx->from_join.str() - << selectCtx->where.str() << selectCtx->groupBy.str() - << selectCtx->having.str() << selectCtx->orderBy.str() - << selectCtx->limit_offset.str() << ";"; - } -}; - -class BaseQuery { - public: - BaseQuery(const std::shared_ptr& qctx); - - protected: - std::shared_ptr qctx; -}; - -template -class ExecutableQuery : public BaseQuery { - public: - typedef std::function ExecFunc; - - public: - ExecutableQuery(const std::shared_ptr& ctx, ExecFunc&& func) - : BaseQuery(ctx) { - executable = std::move(func); - } - - public: - R execute() { return executable(*qctx.get()); } - - protected: - void setExecutableFunction(ExecFunc&& func) { - this->executable = std::move(func); - } - - private: - MoveOnlyFunction executable = nullptr; -}; - -class SelectQuery : public ExecutableQuery { - public: - SelectQuery(const std::shared_ptr& qctx, - std::vector&& properties); - - public: - SelectQuery& where(const SqlLogicExpression&); - SelectQuery& page(int pageNumber, int elementsPerPage); - - template - SelectQuery& groupBy(PR&... cols) { - this->qctx->selectCtx->groupBy << " GROUP BY "; - - std::set props = {&cols...}; - - qctx->selectCtx->addJoinClauses(props); - - for (auto it = props.begin(); it != props.end(); it++) { - this->qctx->selectCtx->groupBy << (*it)->getValueExpression(); - - if (std::next(it) != props.end()) { - this->qctx->selectCtx->groupBy << ", "; - } - } - - return *this; - } - - template - SelectQuery& sort(const SR&... colsSort) { - this->qctx->selectCtx->orderBy << " ORDER BY "; - - std::vector props = {colsSort...}; - - { // add joins - std::set onlyPropsReps; - - for (int i = 0; i < props.size(); i++) { - onlyPropsReps.insert(props[i].prop); - } - - this->qctx->selectCtx->addJoinClauses(onlyPropsReps); - } - - for (int i = 0; i < props.size(); i++) { - this->qctx->selectCtx->orderBy - << props[i].prop->getValueExpression() << " " - << SortTypeToString(props[i].type); - - if (i != props.size() - 1) { - this->qctx->selectCtx->orderBy << ", "; - } - } - - return *this; - } - - private: - std::vector properties; -}; - -class Query : public BaseQuery { - public: - Query(const std::shared_ptr& ctx); - - public: - /** - * @brief Get the properties needed to do a select query with fields. - * - * @tparam F string like - * @param props properties to prepare - * @return auto properties - */ - template - auto prepareProperties(const F&... props) { - // use array so we can iterate them but tuple also works fine - std::array representations = { - qctx->cl.getProperty(props)...}; - - return representations; - } - - // TODO: lets suppose that all are PropertyRep for now but we could have - // sum(prop), ... - template - SelectQuery select(const Q&... props) { - if (!this->qctx->selectCtx) { - this->qctx->selectCtx = std::make_unique(); - } - - qctx->resetQuery(); - - this->qctx->selectCtx->select << "select "; - - // to variant - std::vector unpackedProps; - - if (sizeof...(props) > 0) { - unpackedProps = {props...}; - } else { - auto all = this->qctx->cl.getAllTheProperties(); - unpackedProps.insert(unpackedProps.begin(), - std::make_move_iterator(all.begin()), - std::make_move_iterator(all.end())); - } - - if (unpackedProps.size() > 0) { - for (int i = 0; i < unpackedProps.size(); i++) { - const auto& v_prop = unpackedProps[i]; - - if (std::holds_alternative(v_prop)) { - const auto& prop = std::get(v_prop); - - this->qctx->selectCtx->select - << utils::paramsbind::parseSQL( - "@val_expr as @prop_name", - {{"@val_expr", prop.getValueExpression()}, - {"@prop_name", - utils::paramsbind::encloseQuotesConst( - std::string(prop.getName()))}}, - false); - } else { - const auto& prop = std::get(v_prop); - - this->qctx->selectCtx->select - << AggregatefunctionTypeToString(prop.type) << "(" - << utils::paramsbind::parseSQL( - "@val_expr) as @agg_alias", - {{"@val_expr", prop.prop->getValueExpression()}, - {"@agg_alias", - utils::paramsbind::encloseQuotesConst( - prop.alias)}}, - false); - } - - if (i != unpackedProps.size() - 1) { - this->qctx->selectCtx->select << ","; - } - } - } else { - std::runtime_error("Collection has no props"); - } - - return SelectQuery(this->qctx, std::move(unpackedProps)); - } - - /** - * @brief inserts documents into the collection. - * - * @param obj json, can be an array of json objects or just an json object - * @return int number of affected - * rows - */ - int insert(const json& obj); - - /** - * @brief Removes/deletes a document from the collection - * - * @param documentID - * @return int the number of affected rows. This can be greater than 1 since - * the document is distributed across different tables - */ - int remove(int documentID); - - /** - * @brief Updates a document with new properties values, you can also add - * new ones to the document. - * - * @param documentID - * @param updatedProperties properties to update/add - * @return int - */ - int update(int documentID, json updatedProperties); - - protected: - void buildPropertyInsert( - std::stringstream& sql, Paramsbind& bind, json element, - std::map>& insertMap); -}; - -class QueryFactory { - private: - QueryFactory(); - - public: - static Query create(IDB* ctx, const std::string& collName); -}; diff --git a/src/include/SqlExpression.hpp b/src/include/SqlExpression.hpp deleted file mode 100644 index 5f78bc3..0000000 --- a/src/include/SqlExpression.hpp +++ /dev/null @@ -1,34 +0,0 @@ -#pragma once -#include -#include -#include - -#include "CommonTypes.hpp" -#include "Enums.hpp" -#include "ISqlStatement.hpp" -#include "SqlStatement.hpp" -#include "Utils.hpp" - -class PropertyRep; - -/** - * @brief Represents what its name says. - * Holds pointers to the properties that are in the logic expression. - */ -struct SqlLogicExpression : ISqlStatement { - SqlLogicExpression(std::string st, std::set pPr); - - std::string getStatement() const override; - - std::set getActingProps() const; - - SqlLogicExpression operator&&(const SqlLogicExpression& q); - - SqlLogicExpression operator||(const SqlLogicExpression& q); - - SqlLogicExpression operator~(); - - protected: - std::set actingProps; - std::string st; -}; \ No newline at end of file diff --git a/src/include/SqlStatement.hpp b/src/include/SqlStatement.hpp deleted file mode 100644 index 2eff61c..0000000 --- a/src/include/SqlStatement.hpp +++ /dev/null @@ -1,74 +0,0 @@ -#pragma once - -#include - -#include "Enums.hpp" -#include "ISqlStatement.hpp" - -template -class SqlStatement : public ISqlStatement { - public: - SqlStatement(T val); - - public: - std::string getStatement() const override; - - private: - T val; -}; - -template <> -class SqlStatement : public ISqlStatement { - public: - SqlStatement(const std::string& str) : val(str) {}; - - public: - std::string getStatement() const override { return this->val; } - - public: - SqlStatement operator~() { - return std::string(OperatorToString(Operator::NOT)) + " " + - this->getStatement(); - } - - SqlStatement operator&&(SqlStatement rt) { - return this->getStatement() + " " + OperatorToString(Operator::AND) + - " " + rt.getStatement(); - } - - SqlStatement operator||(SqlStatement rt) { - return this->getStatement() + " " + OperatorToString(Operator::OR) + - " " + rt.getStatement(); - } - - private: - std::string val; -}; - -template <> -class SqlStatement : public ISqlStatement { - public: - SqlStatement(double v) : val(v) {}; - - public: - std::string getStatement() const override { - return std::to_string(this->val); - } - - private: - double val; -}; - -template <> -class SqlStatement : public ISqlStatement { - public: - SqlStatement(int v) : val(v) {}; - - public: - std::string getStatement() const override { - return std::to_string(this->val); - } - - private: - int val; -}; \ No newline at end of file diff --git a/src/include/Utils.hpp b/src/include/Utils.hpp deleted file mode 100644 index b1c4b90..0000000 --- a/src/include/Utils.hpp +++ /dev/null @@ -1,20 +0,0 @@ -#pragma once - -#include -#include -#include - -namespace utils { - void replaceAllOccurrences(std::string& str, const std::string& from, - const std::string& to); - - template - inline void concat(std::vector& dst, const std::vector& src) { - dst.insert(dst.end(), src.begin(), src.end()); - } - - template - void inline concat(std::set& dst, const std::set& src) { - dst.insert(src.begin(), src.end()); - } -} // namespace utils \ No newline at end of file diff --git a/src/include/dbwrapper/IDB.hpp b/src/include/dbwrapper/IDB.hpp deleted file mode 100644 index 30223fe..0000000 --- a/src/include/dbwrapper/IDB.hpp +++ /dev/null @@ -1,95 +0,0 @@ -#pragma once - -#include -#include -#include - -#include "ParamsBind.hpp" -#include "dbwrapper/IDBQueryReader.hpp" - -class IDB { - public: - /** - * @brief Open a database and creates the nosql tables. - * - * @param path - * @return true - * @return false - */ - virtual bool open(const std::string& path) = 0; - - virtual bool close() = 0; - - /** - * @brief executes a query in the database. - * - * @param query query - * @param params values to bind in the query. - * if using sqlite, always use ?,:,@,$ before the parameter id in both, the - * query and the key value passed in this argument. - * - * e.g. (insert into "a" values (@num), {{"@num", 42}}) - * @return std::unique_ptr - */ - virtual std::unique_ptr executeReader( - const std::string& query, const Paramsbind& params) = 0; - - /** - * @brief Is a convenience wrapper around execute reader. - * Executes the query handles the steps and the reader life cicle. - * Generally used for everything else than a select. - * Params are still bind through the db api. - * - * for sqlite3: - * - multiple insert values: insert into ... values (,,), (,,,) - * - multiple tables, inserts, delete: call the function below, it doesn't - * work with this one. - * @param query - * @param params - * @return int rows affected - */ - virtual int executeOneStep(const std::string& query, - const Paramsbind& params) = 0; - - /** - * @brief Same as above but assures that all the statements - * will be executed. This is because some db engines do not support - * multiple create, insert statements in a single query. - * - * Raw because it probably won't use the database api to clean the - * parameters, but still will perform a basic cleanup of them to avoid a sql - * injection. - * - * @param query - * @param params - * @return int - */ - virtual int executeMultipleOnOneStepRaw(const std::string& query, - const Paramsbind& params) = 0; - - /** - * @brief Executes the query and returns the first column as int. - * Given for convenience since is a common task to do. - * @param query - * @param params same as above - * @return std::optional - */ - virtual std::optional executeAndGetFirstInt( - const std::string& query, const Paramsbind& params) = 0; - - /** - * @brief Get the Last Inserted Row Id. - * - * @return int - */ - virtual int getLastInsertedRowId() = 0; - - /** - * @brief Get the number of rows affected by the last query. - * - * @return int - */ - virtual std::optional getChangesCount() = 0; - - virtual void throwLastError() = 0; -}; \ No newline at end of file diff --git a/src/include/dbwrapper/IDBQueryReader.hpp b/src/include/dbwrapper/IDBQueryReader.hpp deleted file mode 100644 index 90f27c6..0000000 --- a/src/include/dbwrapper/IDBQueryReader.hpp +++ /dev/null @@ -1,20 +0,0 @@ -#pragma once - -#include -#include -#include - -class IDBRowReader { - public: - virtual std::string readString(uint16_t i) = 0; - virtual int64_t readInt64(uint16_t i) = 0; - virtual int readInt32(uint16_t i) = 0; - virtual double readDouble(uint16_t i) = 0; - virtual bool readBoolean(uint16_t i) = 0; - virtual bool isNull(uint16_t i) = 0; -}; - -class IDBQueryReader { - public: - virtual bool readRow(std::shared_ptr& row) = 0; -}; \ No newline at end of file diff --git a/src/include/dbwrapper/sq3wrapper/DB.hpp b/src/include/dbwrapper/sq3wrapper/DB.hpp deleted file mode 100644 index b8244b0..0000000 --- a/src/include/dbwrapper/sq3wrapper/DB.hpp +++ /dev/null @@ -1,29 +0,0 @@ -#include "dbwrapper/IDB.hpp" -#include "dbwrapper/sq3wrapper/DBQueryReader.hpp" -#include "sqlite/sqlite3.h" - -class DBSL3 : public IDB { - public: - bool open(const std::string& path) override; - bool close() override; - std::unique_ptr executeReader( - const std::string& query, const Paramsbind& params) override; - - int executeOneStep(const std::string& query, - const Paramsbind& params) override; - - std::optional executeAndGetFirstInt(const std::string& query, - const Paramsbind& params) override; - - int executeMultipleOnOneStepRaw(const std::string& query, - const Paramsbind& params) override; - - int getLastInsertedRowId() override; - - std::optional getChangesCount() override; - - void throwLastError() override; - - private: - sqlite3* db; -}; \ No newline at end of file diff --git a/src/include/dbwrapper/sq3wrapper/DBInitializer.hpp b/src/include/dbwrapper/sq3wrapper/DBInitializer.hpp deleted file mode 100644 index 35d485a..0000000 --- a/src/include/dbwrapper/sq3wrapper/DBInitializer.hpp +++ /dev/null @@ -1,7 +0,0 @@ -#pragma once -#include "dbwrapper/IDB.hpp" - -class DBInitializer { - public: - static void createTablesAndFKeys(IDB* db); -}; \ No newline at end of file diff --git a/src/include/dbwrapper/sq3wrapper/DBQueryReader.hpp b/src/include/dbwrapper/sq3wrapper/DBQueryReader.hpp deleted file mode 100644 index 17214ef..0000000 --- a/src/include/dbwrapper/sq3wrapper/DBQueryReader.hpp +++ /dev/null @@ -1,38 +0,0 @@ -#pragma once -#include "dbwrapper/IDB.hpp" -#include "dbwrapper/IDBQueryReader.hpp" -#include "sqlite/sqlite3.h" - -class DBRowReaderSL3 : public IDBRowReader { - public: - DBRowReaderSL3(IDB* db, sqlite3_stmt* stmt); - - public: - // Note: SQLITE3 - The leftmost column of the result set has the index 0 - - std::string readString(uint16_t i) override; - int64_t readInt64(uint16_t i) override; - int readInt32(uint16_t i) override; - double readDouble(uint16_t i) override; - bool readBoolean(uint16_t i) override; - bool isNull(uint16_t i) override; - - private: - bool allReaded {false}; - IDB* db; - sqlite3_stmt* stmt; -}; - -class DBQueryReaderSL3 : public IDBQueryReader { - public: - DBQueryReaderSL3(IDB* db, sqlite3_stmt* stmt); - ~DBQueryReaderSL3(); - - public: - bool readRow(std::shared_ptr& row) override; - - private: - bool allReaded {false}; - IDB* db; - sqlite3_stmt* stmt; -}; \ No newline at end of file diff --git a/src/include/nldb/Collection.hpp b/src/include/nldb/Collection.hpp new file mode 100644 index 0000000..8dc4011 --- /dev/null +++ b/src/include/nldb/Collection.hpp @@ -0,0 +1,18 @@ +#pragma once + +#include + +namespace nldb { + class Collection { + public: + Collection(int id, const std::string& name); + + public: + int getId() const; + std::string getName() const; + + protected: + int id; + std::string name; + }; +} // namespace nldb \ No newline at end of file diff --git a/src/include/nldb/Common.hpp b/src/include/nldb/Common.hpp new file mode 100644 index 0000000..dd60292 --- /dev/null +++ b/src/include/nldb/Common.hpp @@ -0,0 +1,9 @@ +#pragma once +#include + +namespace nldb::common { + std::string inline getSubCollectionName(const std::string& collName, + const std::string& propName) { + return "_" + collName + "_" + propName; + } +} // namespace nldb::common diff --git a/src/include/nldb/CommonConcepts.hpp b/src/include/nldb/CommonConcepts.hpp new file mode 100644 index 0000000..8407941 --- /dev/null +++ b/src/include/nldb/CommonConcepts.hpp @@ -0,0 +1,5 @@ +#pragma once +#include +#include + +namespace nldb {} \ No newline at end of file diff --git a/src/include/nldb/DAL/IRepositoryCollection.hpp b/src/include/nldb/DAL/IRepositoryCollection.hpp new file mode 100644 index 0000000..bceec64 --- /dev/null +++ b/src/include/nldb/DAL/IRepositoryCollection.hpp @@ -0,0 +1,25 @@ +#pragma once + +#include +#include +#include + +#include "nldb/Collection.hpp" +#include "nldb/Property/Property.hpp" + +namespace nldb { + class IRepositoryCollection { + public: + /** + * @brief Adds a collection and return the id + * + * @param name + * @return int + */ + virtual int add(const std::string& name) = 0; + virtual std::optional find(const std::string& name) = 0; + virtual std::optional find(int id) = 0; + virtual bool exists(const std::string& name) = 0; + virtual bool exists(int id) = 0; + }; +} // namespace nldb \ No newline at end of file diff --git a/src/include/nldb/DAL/IRepositoryDocument.hpp b/src/include/nldb/DAL/IRepositoryDocument.hpp new file mode 100644 index 0000000..606e6fd --- /dev/null +++ b/src/include/nldb/DAL/IRepositoryDocument.hpp @@ -0,0 +1,23 @@ +#pragma once + +#include +#include + +#include "nldb/nldb_json.hpp" + +namespace nldb { + class IRepositoryDocument { + public: + /** + * @brief Adds a document and return the id. + * + * @param collectionID + * @return int + */ + virtual int add(int collectionID) = 0; + + virtual void remove(int id) = 0; + + virtual bool exists(int id) = 0; + }; +} // namespace nldb \ No newline at end of file diff --git a/src/include/nldb/DAL/IRepositoryProperty.hpp b/src/include/nldb/DAL/IRepositoryProperty.hpp new file mode 100644 index 0000000..5b7084e --- /dev/null +++ b/src/include/nldb/DAL/IRepositoryProperty.hpp @@ -0,0 +1,19 @@ +#pragma once + +#include +#include +#include + +#include "nldb/Property/Property.hpp" + +namespace nldb { + class IRepositoryProperty { + public: + virtual int add(const std::string& name, int collectionID, + PropertyType type) = 0; + virtual std::optional find(int collectionID, + const std::string& propName) = 0; + virtual bool exists(int collectionID, const std::string& propName) = 0; + virtual std::vector find(int collectionId) = 0; + }; +} // namespace nldb \ No newline at end of file diff --git a/src/include/nldb/DAL/IValuesDAO.hpp b/src/include/nldb/DAL/IValuesDAO.hpp new file mode 100644 index 0000000..68a3f06 --- /dev/null +++ b/src/include/nldb/DAL/IValuesDAO.hpp @@ -0,0 +1,90 @@ +#pragma once + +#include + +#include "nldb/Property/Property.hpp" + +namespace nldb { + struct ValueObjectMapped { + int id; + int doc_id; + int prop_id; + int sub_coll_id; + int sub_doc_id; + }; + + class IValuesDAO { + public: + /** + * @brief Adds a new value to a document property. + * + * @param propID + * @param docID + * @param type any type but OBJECT is allowed. + * @param value + */ + virtual void addStringLike(int propID, int docID, PropertyType type, + std::string value) = 0; + + /** + * @brief adds a new value object to a document property. + * + * @param propID + * @param docID + * @param subCollID + * @param subDocId + */ + virtual void addObject(int propID, int docID, int subCollID, + int subDocId) = 0; + + /** + * @brief Updates a document property value. + * + * @param propID + * @param docID + * @param type any type but OBJECT is allowed. + * @param value + */ + virtual void updateStringLike(int propID, int docID, PropertyType type, + std::string value) = 0; + + /** + * @brief updates a value of type object + * + * @param propID + * @param docID + * @param subCollID new sub collection id or -1 to not update + * @param subDocId new document id or -1 to not update + */ + virtual void updateObject(int propID, int docID, int subCollID = -1, + int subDocId = -1) = 0; + + /** + * @brief Check if a document property has value. + * + * @param propID + * @param docID + * @param type + * @return true + * @return false + */ + virtual bool exists(int propID, int docID, PropertyType type) = 0; + + /** + * @brief finds a value of type object. + * + * @param propID + * @param docID + * @return ValueObjectMapped + */ + virtual std::optional findObject(int propID, + int docID) = 0; + + /** + * @brief Removes all the values associated with a document. + * + * @param docID + */ + virtual void removeAllFromDocument(int docID) = 0; + }; +} // namespace nldb \ No newline at end of file diff --git a/src/include/nldb/DAL/Repositories.hpp b/src/include/nldb/DAL/Repositories.hpp new file mode 100644 index 0000000..49c7865 --- /dev/null +++ b/src/include/nldb/DAL/Repositories.hpp @@ -0,0 +1,20 @@ +#pragma once + +#include + +#include "IRepositoryCollection.hpp" +#include "IRepositoryDocument.hpp" +#include "IRepositoryProperty.hpp" +#include "IValuesDAO.hpp" + +namespace nldb { + /** + * @brief Ensures that all the repositories have the same DB connection. + */ + struct Repositories { + std::unique_ptr repositoryCollection; + std::unique_ptr repositoryDocument; + std::unique_ptr repositoryProperty; + std::unique_ptr valuesDAO; + }; +} // namespace nldb \ No newline at end of file diff --git a/src/include/nldb/DB/IDB.hpp b/src/include/nldb/DB/IDB.hpp new file mode 100644 index 0000000..87129a3 --- /dev/null +++ b/src/include/nldb/DB/IDB.hpp @@ -0,0 +1,91 @@ +#pragma once + +#include +#include +#include + +#include "ParameterBinder.hpp" +#include "nldb/DAL/Repositories.hpp" +#include "nldb/DB/IDBQueryReader.hpp" + +namespace nldb { + class IDB { + public: + /** + * @brief Open a database and creates the nosql tables. + * + * @param path + * @return true + * @return false + */ + virtual bool open(const std::string& path) = 0; + + virtual bool close() = 0; + + /** + * @brief executes a query in the database. + * + * @param query query + * @param params values to bind in the query. + * if using sqlite, always use ?,:,@,$ before the parameter id in both, + * the query and the key value passed in this argument. + * + * e.g. (insert into "a" values (@num), {{"@num", 42}}) + * @return std::unique_ptr + */ + virtual std::unique_ptr executeReader( + const std::string& query, const Paramsbind& params) = 0; + + /** + * @brief Starts a transaction. + */ + virtual void begin() = 0; + + /** + * @brief Commits the transaction. + */ + virtual void commit() = 0; + + /** + * @brief Rollback the transaction. + */ + virtual void rollback() = 0; + + /** + * @brief Executes a query immediately (if no transaction was started). + * Should support multiple inserts. + * + * @param query + * @param params + * @return void + */ + virtual void execute(const std::string& query, + const Paramsbind& params) = 0; + + /** + * @brief Executes the query and returns the first column as int. + * Given for convenience since is a common task to do. + * @param query + * @param params same as above + * @return std::optional + */ + virtual std::optional executeAndGetFirstInt( + const std::string& query, const Paramsbind& params) = 0; + + /** + * @brief Get the Last Inserted Row Id. + * + * @return int + */ + virtual int getLastInsertedRowId() = 0; + + /** + * @brief Get the number of rows affected by the last query. + * + * @return int + */ + virtual std::optional getChangesCount() = 0; + + virtual void throwLastError() = 0; + }; +} // namespace nldb \ No newline at end of file diff --git a/src/include/nldb/DB/IDBQueryReader.hpp b/src/include/nldb/DB/IDBQueryReader.hpp new file mode 100644 index 0000000..33a971e --- /dev/null +++ b/src/include/nldb/DB/IDBQueryReader.hpp @@ -0,0 +1,22 @@ +#pragma once + +#include +#include +#include + +namespace nldb { + class IDBRowReader { + public: + virtual std::string readString(uint16_t i) = 0; + virtual int64_t readInt64(uint16_t i) = 0; + virtual int readInt32(uint16_t i) = 0; + virtual double readDouble(uint16_t i) = 0; + virtual bool readBoolean(uint16_t i) = 0; + virtual bool isNull(uint16_t i) = 0; + }; + + class IDBQueryReader { + public: + virtual bool readRow(std::shared_ptr& row) = 0; + }; +} // namespace nldb \ No newline at end of file diff --git a/src/include/nldb/DB/ParameterBinder.hpp b/src/include/nldb/DB/ParameterBinder.hpp new file mode 100644 index 0000000..ed0551b --- /dev/null +++ b/src/include/nldb/DB/ParameterBinder.hpp @@ -0,0 +1,9 @@ +#pragma once + +#include +#include +#include + +typedef std::variant ParamsBindValue; +typedef std::unordered_map + Paramsbind; // use std::string_view \ No newline at end of file diff --git a/src/include/nldb/Exceptions.hpp b/src/include/nldb/Exceptions.hpp new file mode 100644 index 0000000..308a583 --- /dev/null +++ b/src/include/nldb/Exceptions.hpp @@ -0,0 +1,39 @@ +#pragma once + +#include + +typedef const std::string& str; + +class CollectionNotFound : public std::runtime_error { + public: + CollectionNotFound(str extra = "") + : std::runtime_error("Collection not found " + extra) {} +}; + +class PropertyNotFound : public std::runtime_error { + public: + PropertyNotFound(str extra = "") + : std::runtime_error("Property not found " + extra) {} +}; + +class DocumentNotFound : public std::runtime_error { + public: + DocumentNotFound(str extra = "") + : std::runtime_error("Document not found " + extra) {} +}; + +class WrongPropertyType : public std::runtime_error { + public: + WrongPropertyType(str pName = "", str pExpected = "", str pActual = "") + : propertyName(pName), + expected(pExpected), + actual(pActual), + std::runtime_error("Wrong property type for " + pName + + ", expected " + pExpected + " actual " + pActual) { + } + + public: + std::string propertyName; + std::string expected; + std::string actual; +}; \ No newline at end of file diff --git a/src/include/nldb/Implementation.hpp b/src/include/nldb/Implementation.hpp new file mode 100644 index 0000000..102fb87 --- /dev/null +++ b/src/include/nldb/Implementation.hpp @@ -0,0 +1,9 @@ +#pragma once + +namespace nldb { + template + struct RepositoriesImpl; + + template + struct QueryRunnerImpl; +} // namespace nldb \ No newline at end of file diff --git a/src/include/nldb/LOG/log.hpp b/src/include/nldb/LOG/log.hpp new file mode 100644 index 0000000..bd74f45 --- /dev/null +++ b/src/include/nldb/LOG/log.hpp @@ -0,0 +1,38 @@ +#pragma once + +// use spdlog implementation +#include "managers/LogManagerSPD.hpp" + +#ifdef __MINGW32__ +#define NLDB_BREAK __debugbreak(); +#elif defined(__APPLE__) +#define NLDB_BREAK __builtin_debugtrap(); +#elif defined(__linux__) +#define NLDB_BREAK __builtin_trap(); +#else + +#endif + +#ifndef DISABLE_LOGGING +#define NLDB_TRACE(...) nldb::LogManager::GetLogger()->trace(__VA_ARGS__) +#define NLDB_INFO(...) nldb::LogManager::GetLogger()->info(__VA_ARGS__) +#define NLDB_WARN(...) nldb::LogManager::GetLogger()->warn(__VA_ARGS__) +#define NLDB_ERROR(...) nldb::LogManager::GetLogger()->error(__VA_ARGS__) +#define NLDB_CRITICAL(...) nldb::LogManager::GetLogger()->critical(__VA_ARGS__) +#define NLDB_ASSERT(x, msg) \ + if ((x)) { \ + } else { \ + NLDB_CRITICAL("ASSERT FAILED - {}, \n\tFile: {}\n\tLine: {} ", msg, \ + __FILE__, __LINE__); \ + NLDB_BREAK \ + } +#else +#define +#define NLDB_TRACE(...) (void)0 +#define NLDB_INFO(...) (void)0 +#define NLDB_WARN(...) Observer::LogManager::GetLogger()->warn(__VA_ARGS__) +#define NLDB_ERROR(...) Observer::LogManager::GetLogger()->error(__VA_ARGS__) +#define NLDB_CRITICAL(...) \ + Observer::LogManager::GetLogger()->critical(__VA_ARGS__) +#define NLDB_ASSERT(x, msg) (void)0 +#endif \ No newline at end of file diff --git a/src/include/nldb/LOG/managers/LogManagerSPD.hpp b/src/include/nldb/LOG/managers/LogManagerSPD.hpp new file mode 100644 index 0000000..08d9228 --- /dev/null +++ b/src/include/nldb/LOG/managers/LogManagerSPD.hpp @@ -0,0 +1,27 @@ +#pragma once + +#include +#include + +#include "log_constants.hpp" +#include "spdlog/common.h" +#include "spdlog/logger.h" +#include "spdlog/sinks/basic_file_sink.h" +#include "spdlog/sinks/stdout_color_sinks.h" +#include "spdlog/spdlog.h" + +namespace nldb { + class LogManager { + public: + static void Initialize(); + static void Shutdown(); + + static std::shared_ptr GetLogger() { return logger; } + + private: + static std::shared_ptr logger; + + LogManager() = default; + ~LogManager() = default; + }; +} // namespace nldb \ No newline at end of file diff --git a/src/include/nldb/LOG/managers/log_constants.hpp b/src/include/nldb/LOG/managers/log_constants.hpp new file mode 100644 index 0000000..80cb532 --- /dev/null +++ b/src/include/nldb/LOG/managers/log_constants.hpp @@ -0,0 +1,3 @@ +#pragma once + +#define DEFAULT_LOGGER_NAME "nldblog" \ No newline at end of file diff --git a/src/include/nldb/Property/AggregatedProperty.hpp b/src/include/nldb/Property/AggregatedProperty.hpp new file mode 100644 index 0000000..90d4c98 --- /dev/null +++ b/src/include/nldb/Property/AggregatedProperty.hpp @@ -0,0 +1,14 @@ +#pragma once + +#include "nldb/Property/Property.hpp" + +namespace nldb { + enum AggregationType { COUNT, AVG, SUM, MAX, MIN }; + + struct AggregatedProperty { + AggregatedProperty(Property, AggregationType, const char* alias); + AggregationType type; + Property property; + const char* alias; + }; +} // namespace nldb \ No newline at end of file diff --git a/src/include/nldb/Property/Property.hpp b/src/include/nldb/Property/Property.hpp new file mode 100644 index 0000000..9becf20 --- /dev/null +++ b/src/include/nldb/Property/Property.hpp @@ -0,0 +1,53 @@ +#pragma once +#include +#include + +namespace nldb { + enum PropertyType { ID, STRING, DOUBLE, INTEGER, OBJECT }; + + class AggregatedProperty; + class SortedProperty; + class PropertyExpression; + + typedef std::variant + LogicConstValue; + + class Property { + public: // constructors + Property(int id, const std::string& name, PropertyType type); + + public: // getters + std::string getName() const; + PropertyType getType() const; + int getId() const; + int getCollectionId() const; + + public: // aggregate functions + AggregatedProperty countAs(const char* alias); + AggregatedProperty maxAs(const char* alias); + AggregatedProperty minAs(const char* alias); + AggregatedProperty sumAs(const char* alias); + AggregatedProperty averageAs(const char* alias); + + public: // sort functions + SortedProperty desc(); + SortedProperty asc(); + + public: // logical r values + PropertyExpression operator>(const LogicConstValue& right); + PropertyExpression operator>=(const LogicConstValue& right); + PropertyExpression operator<(const LogicConstValue& right); + PropertyExpression operator<=(const LogicConstValue& right); + PropertyExpression operator==(const LogicConstValue& right); + PropertyExpression operator!=( + const LogicConstValue& right); // not equal + PropertyExpression operator%(const LogicConstValue& right); // like + PropertyExpression operator^(const LogicConstValue& right); // not like + + private: + std::string name; + PropertyType type; + int id; + int collectionId; + }; +} // namespace nldb \ No newline at end of file diff --git a/src/include/nldb/Property/PropertyExpression.hpp b/src/include/nldb/Property/PropertyExpression.hpp new file mode 100644 index 0000000..f163b9a --- /dev/null +++ b/src/include/nldb/Property/PropertyExpression.hpp @@ -0,0 +1,45 @@ +#pragma once + +#include + +#include "nldb/Property/Property.hpp" +#include "nldb/box.hpp" + +namespace nldb { + enum PropertyExpressionOperator { + EQ, + NEQ, + GT, + GTE, + LT, + LTE, + LIKE, + NLIKE, + AND, + OR, + NOT + }; + + typedef std::variant> + PropertyExpressionOperand; + + struct PropertyExpression { + PropertyExpression(); + + template + PropertyExpression(PropertyExpressionOperator type, L left, R right) { + left = left; + right = right; + } + + PropertyExpressionOperator type; + PropertyExpressionOperand left {0}; + PropertyExpressionOperand right {0}; + + PropertyExpression operator&&(PropertyExpression right); + PropertyExpression operator||(PropertyExpression right); + PropertyExpression operator~(); + }; + + // i guess one could have PropertyExpressionAnd, PropertyExpressionOr, ... +} // namespace nldb \ No newline at end of file diff --git a/src/include/nldb/Property/PropertyObject.hpp b/src/include/nldb/Property/PropertyObject.hpp new file mode 100644 index 0000000..e69de29 diff --git a/src/include/nldb/Property/SortedProperty.hpp b/src/include/nldb/Property/SortedProperty.hpp new file mode 100644 index 0000000..32d52c0 --- /dev/null +++ b/src/include/nldb/Property/SortedProperty.hpp @@ -0,0 +1,13 @@ +#pragma once + +#include "nldb/Property/Property.hpp" + +namespace nldb { + enum SortType { ASC, DESC }; + + struct SortedProperty { + SortedProperty(Property, SortType); + SortType type; + Property property; + }; +} // namespace nldb \ No newline at end of file diff --git a/src/include/nldb/Query/CollectionQuery.hpp b/src/include/nldb/Query/CollectionQuery.hpp new file mode 100644 index 0000000..f9e1fa8 --- /dev/null +++ b/src/include/nldb/Query/CollectionQuery.hpp @@ -0,0 +1,71 @@ +#pragma once + +#include +#include +#include +#include + +#include "nldb/Collection.hpp" +#include "nldb/DAL/Repositories.hpp" +#include "nldb/Exceptions.hpp" +#include "nldb/Property/Property.hpp" + +namespace nldb { + template + concept StringLike = std::is_convertible_v; + + /** + * @brief As its name indicates, it serves a placeholder to a collection + * during the query planner stage. + */ + class CollectionQuery : public Collection { + public: + CollectionQuery(Repositories* repositories, const std::string& name, + std::string alias = ""); + CollectionQuery(Repositories* repositories, Collection col, + std::string alias = ""); + + public: + CollectionQuery& as(std::string alias); + + std::optional getAlias() const; + + /** + * @brief Copies the collection into the internal from this class. + */ + void setCollection(const Collection&); + + bool hasCollectionAssigned() const; + + public: + template + auto get(const F&... names) { + if (!this->hasCollectionAssigned()) { + auto coll = + this->repositories->repositoryCollection->find(this->name); + + if (!coll.has_value()) { + throw CollectionNotFound( + "Can't get properties from a collection that doesn't " + "exists"); + } else { + this->setCollection(coll.value()); + } + } + + try { + std::array properties = { + repositories->repositoryProperty->find(this->id, names) + .value()...}; + + return properties; + } catch (const std::bad_optional_access& e) { + throw PropertyNotFound(); + } + } + + private: + std::optional alias; + Repositories* repositories; + }; +} // namespace nldb \ No newline at end of file diff --git a/src/include/nldb/Query/IQueryRunner.hpp b/src/include/nldb/Query/IQueryRunner.hpp new file mode 100644 index 0000000..00841ba --- /dev/null +++ b/src/include/nldb/Query/IQueryRunner.hpp @@ -0,0 +1,20 @@ +#pragma once +#include "nldb/nldb_json.hpp" + +namespace nldb { + + // IQueryRunner -> QueryContext -> queryRunner:IQueryRunner -> IQueryRunner + // -> ... + class QueryPlannerContextUpdate; + class QueryPlannerContextInsert; + class QueryPlannerContextRemove; + class QueryPlannerContextSelect; + + class IQueryRunner { + public: + virtual json select(QueryPlannerContextSelect&& data) = 0; + virtual void update(QueryPlannerContextUpdate&& data) = 0; + virtual void insert(QueryPlannerContextInsert&& data) = 0; + virtual void remove(QueryPlannerContextRemove&& data) = 0; + }; +} // namespace nldb \ No newline at end of file diff --git a/src/include/nldb/Query/Query.hpp b/src/include/nldb/Query/Query.hpp new file mode 100644 index 0000000..3568cbc --- /dev/null +++ b/src/include/nldb/Query/Query.hpp @@ -0,0 +1,51 @@ +#pragma once + +#include "nldb/DB/IDB.hpp" +#include "nldb/Implementation.hpp" +#include "nldb/Query/CollectionQuery.hpp" +#include "nldb/Query/QueryPlannerSources.hpp" + +namespace nldb { + template + class Query { + public: + Query(T* pConnection) : connection(pConnection) { + repositories = RepositoriesImpl::create(connection); + } + + QueryPlannerSources from(const char* collection1Name) { + auto coll = this->collection(collection1Name); + + // create new repositories, query repositories might be reused. + auto newCtx = QueryPlannerContext { + .from = {CollectionQuery(&this->repositories, coll)}, + .repos = RepositoriesImpl::create(connection), + .queryRunner = QueryRunnerImpl::create(connection)}; + + return std::move(newCtx); + } + + QueryPlannerSources from(CollectionQuery collection1) { + auto newCtx = QueryPlannerContext { + .from = {collection1}, + .repos = RepositoriesImpl::create(connection), + .queryRunner = QueryRunnerImpl::create(connection)}; + + return std::move(newCtx); + } + + /** + * @brief Get a collection from the db based on the name. + * + * @param name + * @return CollectionQuery + */ + CollectionQuery collection(const char* name) { + return CollectionQuery(&this->repositories, name); + } + + private: + T* connection; + Repositories repositories; + }; +} // namespace nldb \ No newline at end of file diff --git a/src/include/nldb/Query/QueryContext.hpp b/src/include/nldb/Query/QueryContext.hpp new file mode 100644 index 0000000..597dd2a --- /dev/null +++ b/src/include/nldb/Query/QueryContext.hpp @@ -0,0 +1,65 @@ +#pragma once + +#include +#include +#include + +#include "nldb/DAL/Repositories.hpp" +#include "nldb/Property/AggregatedProperty.hpp" +#include "nldb/Property/Property.hpp" +#include "nldb/Property/PropertyExpression.hpp" +#include "nldb/Property/SortedProperty.hpp" +#include "nldb/Query/CollectionQuery.hpp" +#include "nldb/Query/IQueryRunner.hpp" +#include "nldb/nldb_json.hpp" + +namespace nldb { + // They are all here bc they are just DTO without any functionality. + + typedef std::variant SelectableProperty; + + // TODO: Rename to QueryContext + struct QueryPlannerContext { + std::vector from; + Repositories repos; + std::unique_ptr queryRunner; + }; + + struct QueryPagination { + int pageNumber; + int elementsPerPage; + }; + + struct QueryPlannerContextSelect : public QueryPlannerContext { + QueryPlannerContextSelect(QueryPlannerContext&& ctx) + : QueryPlannerContext(std::move(ctx)) {} + + std::vector select_value; + std::optional where_value; + std::optional pagination_value; + std::vector groupBy_value; + std::vector sortBy_value; + }; + + struct QueryPlannerContextRemove : public QueryPlannerContext { + QueryPlannerContextRemove(QueryPlannerContext&& ctx) + : QueryPlannerContext(std::move(ctx)) {} + + int documentID; + }; + + struct QueryPlannerContextUpdate : public QueryPlannerContext { + QueryPlannerContextUpdate(QueryPlannerContext&& ctx) + : QueryPlannerContext(std::move(ctx)) {} + + int documentID; + json object; + }; + + struct QueryPlannerContextInsert : public QueryPlannerContext { + QueryPlannerContextInsert(QueryPlannerContext&& ctx) + : QueryPlannerContext(std::move(ctx)) {} + + json documents; // json can be an array of object + }; +} // namespace nldb \ No newline at end of file diff --git a/src/include/nldb/Query/QueryPlanner.hpp b/src/include/nldb/Query/QueryPlanner.hpp new file mode 100644 index 0000000..33c216e --- /dev/null +++ b/src/include/nldb/Query/QueryPlanner.hpp @@ -0,0 +1,52 @@ +#pragma once + +#include + +#include "QueryPlannerSelect.hpp" +#include "nldb/Property/Property.hpp" +#include "nldb/nldb_json.hpp" + +namespace nldb { + template + concept IsSelectProperty = + IsProperty || std::is_same::value; + + class QueryPlanner { + public: + QueryPlanner(QueryPlannerContext&& ctx); + + public: + template + QueryPlannerSelect select(const PropOrAggregatedProp&... props) { + QueryPlannerContextSelect ctx(std::move(context)); + ctx.select_value = {props...}; + + return QueryPlannerSelect(std::move(ctx)); + } + + void insert(json object); + + /** + * @brief Update a document. + * You can update every property and sub-property of the document. + * + * Updating sub-properties + * make sure to indicate the parent property that if it's a + * sub-property, e.g. from the collection user {name, contact: + * {address}} to update the address you must pass + * {{"contact", {{"address", "new_value"}}}}. + * + * If a property doesn't exists + * In this case a new property (and all its sub-properties if it's an + * object) for the document collection is added + * + * @param docId + * @param newValue + */ + void update(int docId, json newValue); + void remove(int docId); + + protected: + QueryPlannerContext context; + }; +} // namespace nldb \ No newline at end of file diff --git a/src/include/nldb/Query/QueryPlannerSelect.hpp b/src/include/nldb/Query/QueryPlannerSelect.hpp new file mode 100644 index 0000000..05fa164 --- /dev/null +++ b/src/include/nldb/Query/QueryPlannerSelect.hpp @@ -0,0 +1,53 @@ +#pragma once + +#include +#include + +#include "nldb/Property/Property.hpp" +#include "nldb/Property/SortedProperty.hpp" +#include "nldb/Query/QueryContext.hpp" + +namespace nldb { + + template + concept IsProperty = std::is_same::value; + + template + concept IsSortedProperty = std::is_same::value; + + class QueryPlannerSelect { + public: + QueryPlannerSelect(QueryPlannerContextSelect&& context); + + public: + /** + * @brief filters elements by some condition. + * Multiple calls to this functions results in an "AND" between the + * conditions. + * + * @return QueryPlannerSelect& + */ + QueryPlannerSelect& where(const PropertyExpression&); + + QueryPlannerSelect& page(int pageNumber, int elementsPerPage); + + template + QueryPlannerSelect& groupBy(PR&... cols) { + this->context.groupBy_value = {cols...}; + + return *this; + } + + template + QueryPlannerSelect& sortBy(PR&... cols) { + this->context.sortBy_value = {cols...}; + + return *this; + } + + json execute(); + + protected: + QueryPlannerContextSelect context; + }; +}; // namespace nldb \ No newline at end of file diff --git a/src/include/nldb/Query/QueryPlannerSources.hpp b/src/include/nldb/Query/QueryPlannerSources.hpp new file mode 100644 index 0000000..140b311 --- /dev/null +++ b/src/include/nldb/Query/QueryPlannerSources.hpp @@ -0,0 +1,16 @@ +#pragma once + +#include "nldb/Query/CollectionQuery.hpp" +#include "nldb/Query/QueryContext.hpp" +#include "nldb/Query/QueryPlanner.hpp" + +namespace nldb { + class QueryPlannerSources : public QueryPlanner { + public: + QueryPlannerSources(QueryPlannerContext&& ctx); + + public: + QueryPlannerSources& with(const char* collection); + QueryPlannerSources& with(CollectionQuery collection); + }; +} // namespace nldb \ No newline at end of file diff --git a/src/include/nldb/Query/QueryRunner.hpp b/src/include/nldb/Query/QueryRunner.hpp new file mode 100644 index 0000000..a8553b7 --- /dev/null +++ b/src/include/nldb/Query/QueryRunner.hpp @@ -0,0 +1,44 @@ +#include "nldb/DB/IDB.hpp" +#include "nldb/Query/IQueryRunner.hpp" + +namespace nldb { + /** + * @brief A repository oriented implementation of IQueryRunner. + * Can be optimized by working in a more handcrafted implementation. + */ + class QueryRunner : public IQueryRunner { + public: + QueryRunner(IDB* connection); + + public: + virtual json select(QueryPlannerContextSelect&& data) override; + virtual void update(QueryPlannerContextUpdate&& data) override; + virtual void insert(QueryPlannerContextInsert&& data) override; + virtual void remove(QueryPlannerContextRemove&& data) override; + + protected: + /** + * @brief inserts a new document and returns its id. + * @param doc object to insert + * @param collName target collection + * @param repos repositories/data access objects + * @return std::pair inserted in (collection_id, document_id) + */ + virtual std::pair insertDocumentRecursive( + json& doc, const std::string& collName, Repositories& repos); + + /** + * @brief updates a document that can contain more documents (objects) + * @param docID document to update + * @param collection collection that the document belong to + * @param object new/updated properties with their values + * @param repos repositories/data access objects + */ + virtual void updateDocumentRecursive(int docID, + const Collection& collection, + json& object, Repositories& repos); + + protected: + IDB* connection; + }; +} // namespace nldb \ No newline at end of file diff --git a/src/include/nldb/SQL3Implementation.hpp b/src/include/nldb/SQL3Implementation.hpp new file mode 100644 index 0000000..4f02d31 --- /dev/null +++ b/src/include/nldb/SQL3Implementation.hpp @@ -0,0 +1,41 @@ +#pragma once + +#include + +#include "DAL/Repositories.hpp" +#include "DB/IDB.hpp" +#include "Implementation.hpp" +#include "backends/sqlite3/DAL/RepositoryCollection.hpp" +#include "backends/sqlite3/DAL/RepositoryDocument.hpp" +#include "backends/sqlite3/DAL/RepositoryProperty.hpp" +#include "backends/sqlite3/DAL/ValuesDAO.hpp" +#include "backends/sqlite3/DB/DB.hpp" +#include "backends/sqlite3/Query/QueryRunner.hpp" + +/** + * This is the default implementation for the sqlite 3 backend. + * Include this file to use this implementation. + */ + +namespace nldb { + template <> + struct RepositoriesImpl { + static Repositories create(IDB* conn) { + return Repositories { + .repositoryCollection = + std::make_unique(conn), + .repositoryDocument = + std::make_unique(conn), + .repositoryProperty = + std::make_unique(conn), + .valuesDAO = std::make_unique(conn)}; + } + }; + + template <> + struct QueryRunnerImpl { + static std::unique_ptr create(IDB* conn) { + return std::make_unique(conn); + } + }; +} // namespace nldb \ No newline at end of file diff --git a/src/include/nldb/Utils/Enums.hpp b/src/include/nldb/Utils/Enums.hpp new file mode 100644 index 0000000..023da75 --- /dev/null +++ b/src/include/nldb/Utils/Enums.hpp @@ -0,0 +1,35 @@ +#pragma once + +#include "Enums.hpp" +#include "nldb/Property/PropertyExpression.hpp" + +namespace nldb::utils { + inline const char* OperatorToString(PropertyExpressionOperator v) { + switch (v) { + case EQ: + return "="; + case NEQ: + return "<>"; + case GT: + return ">"; + case GTE: + return ">="; + case LT: + return "<"; + case LTE: + return "<="; + case LIKE: + return "LIKE"; + case NLIKE: + return "NOT LIKE"; + case AND: + return "AND"; + case OR: + return "OR"; + case NOT: + return "NOT"; + default: + throw std::runtime_error("Uknown operator"); + } + } +} // namespace nldb::utils \ No newline at end of file diff --git a/src/include/dbwrapper/ParamsBind.hpp b/src/include/nldb/Utils/ParamsBindHelpers.hpp similarity index 78% rename from src/include/dbwrapper/ParamsBind.hpp rename to src/include/nldb/Utils/ParamsBindHelpers.hpp index 2d867f2..f7a56f5 100644 --- a/src/include/dbwrapper/ParamsBind.hpp +++ b/src/include/nldb/Utils/ParamsBindHelpers.hpp @@ -1,15 +1,6 @@ #pragma once -#include -#include -#include -#include - -#include "logger/Logger.h" - -typedef std::variant ParamsBindValue; -typedef std::unordered_map - Paramsbind; // use std::string_view +#include "nldb/DB/ParameterBinder.hpp" namespace utils::paramsbind { /** diff --git a/src/include/nldb/Utils/String.hpp b/src/include/nldb/Utils/String.hpp new file mode 100644 index 0000000..daa3cf7 --- /dev/null +++ b/src/include/nldb/Utils/String.hpp @@ -0,0 +1,9 @@ +#pragma once + +#include + +namespace utils { + void replaceAllOccurrences(std::string& str, const std::string& from, + const std::string& to); + +} // namespace utils \ No newline at end of file diff --git a/src/include/nldb/Utils/Variant.hpp b/src/include/nldb/Utils/Variant.hpp new file mode 100644 index 0000000..a9d673d --- /dev/null +++ b/src/include/nldb/Utils/Variant.hpp @@ -0,0 +1,14 @@ +#pragma once + +namespace nldb { + // from https://en.cppreference.com/w/cpp/utility/variant/visit + // ------ >= c++17 + // helper type for the visitor #4 + template + struct overloaded : Ts... { + using Ts::operator()...; + }; + // explicit deduction guide (not needed as of C++20) + template + overloaded(Ts...) -> overloaded; +} // namespace nldb \ No newline at end of file diff --git a/src/include/nldb/box.hpp b/src/include/nldb/box.hpp new file mode 100644 index 0000000..640e956 --- /dev/null +++ b/src/include/nldb/box.hpp @@ -0,0 +1,32 @@ +// c++ implentation of Rust box +// TL;DR; Recursive data structures requires heap allocations to compile +// see https://www.foonathan.net/2022/05/recursive-variant-box/ for more details +#include + +template +class box { + // Wrapper over unique_ptr. + std::unique_ptr _impl; + + public: + // Automatic construction from a `T`, not a `T*`. + box(T&& obj) : _impl(new T(std::move(obj))) {} + box(const T& obj) : _impl(new T(obj)) {} + + // Copy constructor copies `T`. + box(const box& other) : box(*other._impl) {} + box& operator=(const box& other) { + *_impl = *other._impl; + return *this; + } + + // unique_ptr destroys `T` for us. + ~box() = default; + + // Access propagates constness. + T& operator*() { return *_impl; } + const T& operator*() const { return *_impl; } + + T* operator->() { return _impl.get(); } + const T* operator->() const { return _impl.get(); } +}; \ No newline at end of file diff --git a/src/include/nldb/nldb_json.hpp b/src/include/nldb/nldb_json.hpp new file mode 100644 index 0000000..6d1a0ed --- /dev/null +++ b/src/include/nldb/nldb_json.hpp @@ -0,0 +1,12 @@ +#pragma once + +#include "nldb/Property/Property.hpp" +#include "nlohmann/json.hpp" + +namespace nldb { + typedef nlohmann::json json; + + PropertyType JsonTypeToPropertyType(int type); + + std::string ValueToString(json& val); +} // namespace nldb \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index d24a279..ebb6e2a 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -6,44 +6,48 @@ #include #include -#include "Enums.hpp" -#include "dbwrapper/sq3wrapper/DB.hpp" -#include "dbwrapper/sq3wrapper/DBInitializer.hpp" -#include "include/PropertyRep.hpp" -#include "include/Query.hpp" -#include "logger/Logger.h" -#include "nlohmann/json.hpp" +#include "backends/sqlite3/DB/DB.hpp" +#include "nldb/SQL3Implementation.hpp" +// #include "Enums.hpp" +#include "nldb/LOG/log.hpp" +#include "nldb/LOG/managers/LogManagerSPD.hpp" +#include "nldb/Property/Property.hpp" +#include "nldb/Query/Query.hpp" +// #include "nlohmann/json.hpp" +#include "nldb/nldb_json.hpp" -using namespace nlohmann; +using namespace nldb; -void numbersExamples() { - DBSL3 db; - if (!db.open("./numbers.db")) { - std::cerr << "Could not open the database \n"; - db.throwLastError(); - } +// void numbersExamples() { +// DBSL3 db; +// if (!db.open("./numbers.db")) { +// std::cerr << "Could not open the database \n"; +// db.throwLastError(); +// } - auto numbersCollection = QueryFactory::create(&db, "numbers"); +// auto numbersCollection = QueryFactory::create(&db, "numbers"); - json numbers_json = { - {{"number_name", "pi"}, {"double_rep", M_PI}, {"integer_rep", 3}}, +// json numbers_json = { +// {{"number_name", "pi"}, {"double_rep", M_PI}, {"integer_rep", 3}}, - {{"number_name", "e"}, {"double_rep", M_E}, {"integer_rep", 2}}, +// {{"number_name", "e"}, {"double_rep", M_E}, {"integer_rep", 2}}, - {{"number_name", "log2(e)"}, - {"double_rep", M_LOG2E}, - {"integer_rep", 1}}}; +// {{"number_name", "log2(e)"}, +// {"double_rep", M_LOG2E}, +// {"integer_rep", 1}}}; - numbersCollection.insert(numbers_json); +// numbersCollection.insert(numbers_json); - auto numbers = numbersCollection.select().execute(); +// auto numbers = numbersCollection.select().execute(); - std::cout << numbers << std::endl; -} +// std::cout << numbers << std::endl; +// } int main() { - auto md = PropertyRep("md", -1, PropertyType::STRING); - auto yy = PropertyRep("yy", -1, PropertyType::STRING); + nldb::LogManager::Initialize(); + + auto md = Property(-1, "mds", PropertyType::STRING); + auto yy = Property(-1, "yy", PropertyType::STRING); auto c = (md > yy) && (yy != md); @@ -54,8 +58,6 @@ int main() { auto c4 = c1 || c3; auto c5 = ~c4; - std::cout << "Result: " << c4.getStatement() << std::endl; - // numbersExamples(); // return 0; @@ -66,50 +68,84 @@ int main() { db.throwLastError(); } - auto collQuery = QueryFactory::create(&db, "cars"); + auto collQuery = Query(&db); + + json data = {{"name", "enzo"}, + {"contact", + {{"phone", "12344"}, + {"address", "fake st 99"}, + {"email", "c.enzo@at.com"}}}}; - json cars = {{{"maker", "ford"}, {"model", "focus"}, {"year", 2011}}, - {{"maker", "ford"}, {"model", "focus"}, {"year", 2015}}, - {{"maker", "subaru"}, {"model", "impreza"}, {"year", 2003}}}; + // collQuery.from("persona").insert(data); + // collQuery.from("persona").update(1, {{"name", "enzo a."}}); + // collQuery.from("persona").update( + // 1, {{"contact", {{"email", "fake@fake.fake"}}}}); - collQuery.insert(cars); + auto [name, contact] = + collQuery.collection("persona").get("name", "contact"); - auto [id, model, maker, year] = - collQuery.prepareProperties("id", "model", "maker", "year"); + collQuery.from("persona").select(name, contact).execute(); + + // auto [address, email] = + // collQuery.collection("persona").collection("contact").get("address", + // "email"); + + // auto [name, address, email] = + // collQuery.collection("persona").get("name", "contact.address", + // "contact.email"); + + // auto cars = collQuery.collection("cars"); + + // json data_cars = { + // {{"maker", "ford"}, {"model", "focus"}, {"year", 2011}}, + // {{"maker", "ford"}, {"model", "focus"}, {"year", 2015}}, + // {{"maker", "subaru"}, {"model", "impreza"}, {"year", 2003}}}; + + // collQuery.from("cars").insert(data_cars); + + // auto [id, model, maker, year] = cars.get("id", "model", "maker", + // "year"); + + // coll // maybe auto [car_id, ...] = collection("cars").prepare(...) // auto [race_winner, ...] = collection("races").prepare(...) - // query.select(car_id, c2, ..., cn, race_winner, ..., r2, ..., rn) + // query.select(car_id, c2, ..., cn, race_winner, ..., r2, ..., + // rn) // .from("cars", "races") // .where(car_id == race_winner).execute() // select id, model, maker and max year from each model - auto res1 = - collQuery.select(id, model, maker, year.maxAs("year_newest_model")) - .where(year > 1990) - .page(1, 10) - .groupBy(model, maker) - .execute(); + // auto res1 = collQuery.from("cars") + // .select(id, model, maker, + // year.maxAs("year_newest_model")) .where(year > 1990) + // .page(1, 10) + // .groupBy(model, maker) + // .execute(); + + // std::cout << "\n\nRES1: " << res1 << std::endl << std::endl; - std::cout << "\n\nRES1: " << res1 << std::endl << std::endl; + // update first car, set year to 2100 and add a new property called + // price int affected_update = + // collQuery.update(res1[0]["id"], {{"year", 2100}, {"price", + // 50000}}); - // update first car, set year to 2100 and add a new property called price - int affected_update = - collQuery.update(res1[0]["id"], {{"year", 2100}, {"price", 50000}}); + // auto res2 = collQuery.select().page(1, + // 10).sort(year.desc()).execute(); - auto res2 = collQuery.select().page(1, 10).sort(year.desc()).execute(); + // std::cout << "\n\nRES2: " << res2 << std::endl; - std::cout << "\n\nRES2: " << res2 << std::endl; + // auto final = collQuery.select(model, maker, year).execute(); - auto final = collQuery.select(model, maker, year).execute(); + // std::cout << "\n\nall before: " << final << std::endl; - std::cout << "\n\nall before: " << final << std::endl; + // int affected = collQuery.remove(1); - int affected = collQuery.remove(1); + // std::cout << "\n\naffected: " << affected << std::endl; - std::cout << "\n\naffected: " << affected << std::endl; + // auto finalthen = collQuery.select(model, maker, year).execute(); - auto finalthen = collQuery.select(model, maker, year).execute(); + // std::cout << "\n\nall: " << finalthen << std::endl; - std::cout << "\n\nall: " << finalthen << std::endl; + nldb::LogManager::Shutdown(); } diff --git a/src/nldb_json.cpp b/src/nldb_json.cpp new file mode 100644 index 0000000..e2d54f2 --- /dev/null +++ b/src/nldb_json.cpp @@ -0,0 +1,49 @@ +#include "nldb/nldb_json.hpp" + +#include "magic_enum.hpp" +#include "nldb/Property/Property.hpp" + +namespace nldb { + PropertyType JsonTypeToPropertyType(int type) { + auto t = (json::value_t)type; + switch (t) { + case json::value_t::number_integer: + return PropertyType::INTEGER; + case json::value_t::number_float: + return PropertyType::DOUBLE; + case json::value_t::string: + return PropertyType::STRING; + case json::value_t::object: + return PropertyType::OBJECT; + default: + auto msg = "Type is not supported: " + + std::string(magic_enum::enum_name((json::value_t)t)); + throw std::runtime_error(msg); + } + } + + std::string ValueToString(json& value) { + PropertyType t = JsonTypeToPropertyType((int)value.type()); + + std::string str; + switch (t) { + case PropertyType::INTEGER: + str = std::to_string(value.get()); + break; + case PropertyType::DOUBLE: + str = std::to_string(value.get()); + break; + case PropertyType::STRING: + str = value.get(); + break; + case PropertyType::OBJECT: + throw std::runtime_error( + "object type is not allowed to stringify"); + break; + default: + throw std::runtime_error("uknown type"); + } + + return str; + } +} // namespace nldb \ No newline at end of file diff --git a/src/sqlwrapper/sq3wrapper/DB.cpp b/src/sqlwrapper/sq3wrapper/DB.cpp deleted file mode 100644 index 02dc8c7..0000000 --- a/src/sqlwrapper/sq3wrapper/DB.cpp +++ /dev/null @@ -1,140 +0,0 @@ -#include "dbwrapper/sq3wrapper/DB.hpp" - -#include -#include -#include -#include - -#include "Utils.hpp" -#include "dbwrapper/IDBQueryReader.hpp" -#include "dbwrapper/ParamsBind.hpp" -#include "dbwrapper/sq3wrapper/DBInitializer.hpp" -#include "logger/Logger.h" - -bool DBSL3::open(const std::string& path) { - bool fileExists = std::filesystem::exists(path); - - int rc = sqlite3_open(path.c_str(), &this->db); - if (rc != SQLITE_OK) { - this->close(); - - return false; - } - - if (!fileExists) { - DBInitializer::createTablesAndFKeys(this); - } - - return true; -} - -bool DBSL3::close() { - if (this->db != nullptr) { - int rc = sqlite3_close(this->db); - - if (rc != SQLITE_OK) { - return false; - } - } - - return true; -} - -std::unique_ptr DBSL3::executeReader(const std::string& query, - const Paramsbind& params) { - LogTrace("Executing: %s", query.c_str()); - - if (query.empty()) { - LogFatal("Empty query"); - throw std::runtime_error("Empty query"); - } - - // prepare sql - sqlite3_stmt* stmt; - int rc = sqlite3_prepare_v2(db, query.c_str(), -1, &stmt, 0); - - if (rc == SQLITE_OK) { - // bind params - for (const auto& it : params) { - if (!it.first.starts_with("@") && !it.first.starts_with("$") && - !it.first.starts_with("?") && !it.first.starts_with(":")) { - LogWarning("The parameter '%s' doesn't start with @,$,: or ?", - it.first.c_str()); - } - - int idx = sqlite3_bind_parameter_index(stmt, it.first.c_str()); - if (std::holds_alternative(it.second)) { - sqlite3_bind_int(stmt, idx, std::get(it.second)); - } else if (std::holds_alternative(it.second)) { - sqlite3_bind_double(stmt, idx, std::get(it.second)); - } else if (std::holds_alternative(it.second)) { - sqlite3_bind_text(stmt, idx, - std::get(it.second).c_str(), -1, - SQLITE_STATIC); - } else { - // variant protects againts this but anyway... in case we change - // it - throw std::runtime_error("Type not supported"); - } - } - - // we are done, now the user can read the results - return std::make_unique(this, stmt); - } else { - this->throwLastError(); - return nullptr; - } -} - -int DBSL3::executeMultipleOnOneStepRaw(const std::string& query, - const Paramsbind& params) { - std::string sql = utils::paramsbind::parseSQL(query, params); - - LogTrace("Executing: %s", sql.c_str()); - - char* err = 0; - int rc = sqlite3_exec(db, sql.c_str(), 0, 0, &err); - - if (rc != SQLITE_OK) { - LogError("Could not execute query, error: %s", err); - sqlite3_free(err); - this->throwLastError(); - return -1; // clangd is blind - } else { - return this->getChangesCount().value(); - } -} - -int DBSL3::executeOneStep(const std::string& query, const Paramsbind& params) { - int rowsCount {0}; - - auto reader = this->executeReader(query, params); - std::shared_ptr row; - while (reader->readRow(row)) { - ++rowsCount; - } - - return rowsCount; -} - -std::optional DBSL3::executeAndGetFirstInt(const std::string& query, - const Paramsbind& params) { - auto res = this->executeReader(query, params); - std::shared_ptr row; - int first {-1}; - while (res->readRow(row)) { - return row->readInt32(0); - } - - return std::nullopt; -} - -std::optional DBSL3::getChangesCount() { - return this->executeAndGetFirstInt("SELECT changes();", {}); -} - -int DBSL3::getLastInsertedRowId() { return sqlite3_last_insert_rowid(db); } - -void DBSL3::throwLastError() { - throw std::runtime_error(sqlite3_errmsg(this->db)); -} diff --git a/src/sqlwrapper/sq3wrapper/DBInitializer.cpp b/src/sqlwrapper/sq3wrapper/DBInitializer.cpp deleted file mode 100644 index 2176786..0000000 --- a/src/sqlwrapper/sq3wrapper/DBInitializer.cpp +++ /dev/null @@ -1,77 +0,0 @@ -#include "dbwrapper/sq3wrapper/DBInitializer.hpp" - -#include "logger/Logger.h" - -void createCollectionTable(IDB* db) { - const auto sql = - "CREATE TABLE `collection` (" - "`id` INTEGER PRIMARY KEY," - "`name` varchar(255)" - ")"; - - db->executeMultipleOnOneStepRaw(sql, {}); -} - -void createDocumentTable(IDB* db) { - const auto sql = - "CREATE TABLE `document` (" - "`id` INTEGER PRIMARY KEY," - "`coll_id` int," - "FOREIGN KEY (coll_id) REFERENCES collection(id)" - ")"; - - db->executeMultipleOnOneStepRaw(sql, {}); -} - -void createPropertyTable(IDB* db) { - const auto sql = - "CREATE TABLE `property` (" - "`id` INTEGER PRIMARY KEY," - "`coll_id` int," - "`name` varchar(255)," - "`type` int," - "FOREIGN KEY (coll_id) REFERENCES collection(id)" - ")"; - - db->executeMultipleOnOneStepRaw(sql, {}); -} - -void createValuesTable(IDB* db) { - const auto sql = - // int - "CREATE TABLE `value_int` (" - "`id` INTEGER PRIMARY KEY," - "`doc_id` int," - "`prop_id` int," - "`value` int," - "FOREIGN KEY (doc_id) REFERENCES document(id)," - "FOREIGN KEY (prop_id) REFERENCES property(id)" - ");" - // double - "CREATE TABLE `value_double` (" - "`id` INTEGER PRIMARY KEY," - "`doc_id` int," - "`prop_id` int," - "`value` DOUBLE," - "FOREIGN KEY (doc_id) REFERENCES document(id)," - "FOREIGN KEY (prop_id) REFERENCES property(id)" - ");" - // string - "CREATE TABLE `value_string` (" - "`id` INTEGER PRIMARY KEY," - "`doc_id` int," - "`prop_id` int," - "`value` varchar(255)," - "FOREIGN KEY (doc_id) REFERENCES document(id)," - "FOREIGN KEY (prop_id) REFERENCES property(id)" - ")"; - - db->executeMultipleOnOneStepRaw(sql, {}); -} - -void DBInitializer::createTablesAndFKeys(IDB* db) { - createCollectionTable(db); - createDocumentTable(db); - createPropertyTable(db); - createValuesTable(db); -} \ No newline at end of file diff --git a/src/sqlwrapper/sq3wrapper/DBQueryReader.cpp b/src/sqlwrapper/sq3wrapper/DBQueryReader.cpp deleted file mode 100644 index fd3b430..0000000 --- a/src/sqlwrapper/sq3wrapper/DBQueryReader.cpp +++ /dev/null @@ -1,60 +0,0 @@ -#include "dbwrapper/sq3wrapper/DBQueryReader.hpp" - -#include -#include -#include - -DBRowReaderSL3::DBRowReaderSL3(IDB* pDb, sqlite3_stmt* pStmt) - : stmt(pStmt), db(pDb) {} - -std::string DBRowReaderSL3::readString(uint16_t i) { - auto str = sqlite3_column_text(stmt, i); - if (str != nullptr) return std::string(reinterpret_cast(str)); - throw std::runtime_error("Missing column"); -} - -int64_t DBRowReaderSL3::readInt64(uint16_t i) { - return sqlite3_column_int64(stmt, i); -} - -int DBRowReaderSL3::readInt32(uint16_t i) { - return sqlite3_column_int(stmt, i); -} - -double DBRowReaderSL3::readDouble(uint16_t i) { - return sqlite3_column_double(stmt, i); -} - -bool DBRowReaderSL3::readBoolean(uint16_t i) { return readInt32(i) == 1; } - -bool DBRowReaderSL3::isNull(uint16_t i) { - return sqlite3_column_type(stmt, i) == SQLITE_NULL; -} - -// --- -bool DBQueryReaderSL3::readRow(std::shared_ptr& row) { - if (allReaded) return false; - - if (!row) { - row = std::make_shared(this->db, this->stmt); - } - - int rc = sqlite3_step(stmt); - if (rc == SQLITE_ROW) { - return true; - } else if (rc == SQLITE_DONE) { - allReaded = true; - return false; - } else { - this->db->throwLastError(); - return false; - } -} - -DBQueryReaderSL3::DBQueryReaderSL3(IDB* pDb, sqlite3_stmt* pStmt) - : stmt(pStmt), db(pDb) {} - -DBQueryReaderSL3::~DBQueryReaderSL3() { - sqlite3_finalize(stmt); - std::cout << "Query eneded. Cleaning up stmt\n"; -} \ No newline at end of file diff --git a/vendor/googletest/googletest/cmake/internal_utils.cmake b/vendor/googletest/googletest/cmake/internal_utils.cmake new file mode 100644 index 0000000..5a34c07 --- /dev/null +++ b/vendor/googletest/googletest/cmake/internal_utils.cmake @@ -0,0 +1,342 @@ +# Defines functions and macros useful for building Google Test and +# Google Mock. +# +# Note: +# +# - This file will be run twice when building Google Mock (once via +# Google Test's CMakeLists.txt, and once via Google Mock's). +# Therefore it shouldn't have any side effects other than defining +# the functions and macros. +# +# - The functions/macros defined in this file may depend on Google +# Test and Google Mock's option() definitions, and thus must be +# called *after* the options have been defined. + +if (POLICY CMP0054) + cmake_policy(SET CMP0054 NEW) +endif (POLICY CMP0054) + +# Tweaks CMake's default compiler/linker settings to suit Google Test's needs. +# +# This must be a macro(), as inside a function string() can only +# update variables in the function scope. +macro(fix_default_compiler_settings_) + if (MSVC) + # For MSVC, CMake sets certain flags to defaults we want to override. + # This replacement code is taken from sample in the CMake Wiki at + # https://gitlab.kitware.com/cmake/community/wikis/FAQ#dynamic-replace. + foreach (flag_var + CMAKE_C_FLAGS CMAKE_C_FLAGS_DEBUG CMAKE_C_FLAGS_RELEASE + CMAKE_C_FLAGS_MINSIZEREL CMAKE_C_FLAGS_RELWITHDEBINFO + CMAKE_CXX_FLAGS CMAKE_CXX_FLAGS_DEBUG CMAKE_CXX_FLAGS_RELEASE + CMAKE_CXX_FLAGS_MINSIZEREL CMAKE_CXX_FLAGS_RELWITHDEBINFO) + if (NOT BUILD_SHARED_LIBS AND NOT gtest_force_shared_crt) + # When Google Test is built as a shared library, it should also use + # shared runtime libraries. Otherwise, it may end up with multiple + # copies of runtime library data in different modules, resulting in + # hard-to-find crashes. When it is built as a static library, it is + # preferable to use CRT as static libraries, as we don't have to rely + # on CRT DLLs being available. CMake always defaults to using shared + # CRT libraries, so we override that default here. + string(REPLACE "/MD" "-MT" ${flag_var} "${${flag_var}}") + endif() + + # We prefer more strict warning checking for building Google Test. + # Replaces /W3 with /W4 in defaults. + string(REPLACE "/W3" "/W4" ${flag_var} "${${flag_var}}") + + # Prevent D9025 warning for targets that have exception handling + # turned off (/EHs-c- flag). Where required, exceptions are explicitly + # re-enabled using the cxx_exception_flags variable. + string(REPLACE "/EHsc" "" ${flag_var} "${${flag_var}}") + endforeach() + endif() +endmacro() + +# Defines the compiler/linker flags used to build Google Test and +# Google Mock. You can tweak these definitions to suit your need. A +# variable's value is empty before it's explicitly assigned to. +macro(config_compiler_and_linker) + # Note: pthreads on MinGW is not supported, even if available + # instead, we use windows threading primitives + unset(GTEST_HAS_PTHREAD) + if (NOT gtest_disable_pthreads AND NOT MINGW) + # Defines CMAKE_USE_PTHREADS_INIT and CMAKE_THREAD_LIBS_INIT. + find_package(Threads) + if (CMAKE_USE_PTHREADS_INIT) + set(GTEST_HAS_PTHREAD ON) + endif() + endif() + + fix_default_compiler_settings_() + if (MSVC) + # Newlines inside flags variables break CMake's NMake generator. + # TODO(vladl@google.com): Add -RTCs and -RTCu to debug builds. + set(cxx_base_flags "-GS -W4 -WX -wd4251 -wd4275 -nologo -J") + set(cxx_base_flags "${cxx_base_flags} -D_UNICODE -DUNICODE -DWIN32 -D_WIN32") + set(cxx_base_flags "${cxx_base_flags} -DSTRICT -DWIN32_LEAN_AND_MEAN") + set(cxx_exception_flags "-EHsc -D_HAS_EXCEPTIONS=1") + set(cxx_no_exception_flags "-EHs-c- -D_HAS_EXCEPTIONS=0") + set(cxx_no_rtti_flags "-GR-") + # Suppress "unreachable code" warning + # http://stackoverflow.com/questions/3232669 explains the issue. + set(cxx_base_flags "${cxx_base_flags} -wd4702") + # Ensure MSVC treats source files as UTF-8 encoded. + set(cxx_base_flags "${cxx_base_flags} -utf-8") + elseif (CMAKE_CXX_COMPILER_ID STREQUAL "Clang") + set(cxx_base_flags "-Wall -Wshadow -Wconversion") + set(cxx_exception_flags "-fexceptions") + set(cxx_no_exception_flags "-fno-exceptions") + set(cxx_strict_flags "-W -Wpointer-arith -Wreturn-type -Wcast-qual -Wwrite-strings -Wswitch -Wunused-parameter -Wcast-align -Wchar-subscripts -Winline -Wredundant-decls") + set(cxx_no_rtti_flags "-fno-rtti") + elseif (CMAKE_COMPILER_IS_GNUCXX) + set(cxx_base_flags "-Wall -Wshadow") + if(NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 7.0.0) + set(cxx_base_flags "${cxx_base_flags} -Wno-error=dangling-else") + endif() + set(cxx_exception_flags "-fexceptions") + set(cxx_no_exception_flags "-fno-exceptions") + # Until version 4.3.2, GCC doesn't define a macro to indicate + # whether RTTI is enabled. Therefore we define GTEST_HAS_RTTI + # explicitly. + set(cxx_no_rtti_flags "-fno-rtti -DGTEST_HAS_RTTI=0") + set(cxx_strict_flags + "-Wextra -Wno-unused-parameter -Wno-missing-field-initializers") + elseif (CMAKE_CXX_COMPILER_ID STREQUAL "SunPro") + set(cxx_exception_flags "-features=except") + # Sun Pro doesn't provide macros to indicate whether exceptions and + # RTTI are enabled, so we define GTEST_HAS_* explicitly. + set(cxx_no_exception_flags "-features=no%except -DGTEST_HAS_EXCEPTIONS=0") + set(cxx_no_rtti_flags "-features=no%rtti -DGTEST_HAS_RTTI=0") + elseif (CMAKE_CXX_COMPILER_ID STREQUAL "VisualAge" OR + CMAKE_CXX_COMPILER_ID STREQUAL "XL") + # CMake 2.8 changes Visual Age's compiler ID to "XL". + set(cxx_exception_flags "-qeh") + set(cxx_no_exception_flags "-qnoeh") + # Until version 9.0, Visual Age doesn't define a macro to indicate + # whether RTTI is enabled. Therefore we define GTEST_HAS_RTTI + # explicitly. + set(cxx_no_rtti_flags "-qnortti -DGTEST_HAS_RTTI=0") + elseif (CMAKE_CXX_COMPILER_ID STREQUAL "HP") + set(cxx_base_flags "-AA -mt") + set(cxx_exception_flags "-DGTEST_HAS_EXCEPTIONS=1") + set(cxx_no_exception_flags "+noeh -DGTEST_HAS_EXCEPTIONS=0") + # RTTI can not be disabled in HP aCC compiler. + set(cxx_no_rtti_flags "") + endif() + + # The pthreads library is available and allowed? + if (DEFINED GTEST_HAS_PTHREAD) + set(GTEST_HAS_PTHREAD_MACRO "-DGTEST_HAS_PTHREAD=1") + else() + set(GTEST_HAS_PTHREAD_MACRO "-DGTEST_HAS_PTHREAD=0") + endif() + set(cxx_base_flags "${cxx_base_flags} ${GTEST_HAS_PTHREAD_MACRO}") + + # For building gtest's own tests and samples. + set(cxx_exception "${cxx_base_flags} ${cxx_exception_flags}") + set(cxx_no_exception + "${CMAKE_CXX_FLAGS} ${cxx_base_flags} ${cxx_no_exception_flags}") + set(cxx_default "${cxx_exception}") + set(cxx_no_rtti "${cxx_default} ${cxx_no_rtti_flags}") + + # For building the gtest libraries. + set(cxx_strict "${cxx_default} ${cxx_strict_flags}") +endmacro() + +# Defines the gtest & gtest_main libraries. User tests should link +# with one of them. +function(cxx_library_with_type name type cxx_flags) + # type can be either STATIC or SHARED to denote a static or shared library. + # ARGN refers to additional arguments after 'cxx_flags'. + add_library(${name} ${type} ${ARGN}) + add_library(${cmake_package_name}::${name} ALIAS ${name}) + set_target_properties(${name} + PROPERTIES + COMPILE_FLAGS "${cxx_flags}") + # Set the output directory for build artifacts + set_target_properties(${name} + PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin" + LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib" + ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib" + PDB_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin") + # make PDBs match library name + get_target_property(pdb_debug_postfix ${name} DEBUG_POSTFIX) + set_target_properties(${name} + PROPERTIES + PDB_NAME "${name}" + PDB_NAME_DEBUG "${name}${pdb_debug_postfix}" + COMPILE_PDB_NAME "${name}" + COMPILE_PDB_NAME_DEBUG "${name}${pdb_debug_postfix}") + + if (BUILD_SHARED_LIBS OR type STREQUAL "SHARED") + set_target_properties(${name} + PROPERTIES + COMPILE_DEFINITIONS "GTEST_CREATE_SHARED_LIBRARY=1") + if (NOT "${CMAKE_VERSION}" VERSION_LESS "2.8.11") + target_compile_definitions(${name} INTERFACE + $) + endif() + endif() + if (DEFINED GTEST_HAS_PTHREAD) + if ("${CMAKE_VERSION}" VERSION_LESS "3.1.0") + set(threads_spec ${CMAKE_THREAD_LIBS_INIT}) + else() + set(threads_spec Threads::Threads) + endif() + target_link_libraries(${name} PUBLIC ${threads_spec}) + endif() + + if (NOT "${CMAKE_VERSION}" VERSION_LESS "3.8") + target_compile_features(${name} PUBLIC cxx_std_11) + endif() +endfunction() + +######################################################################## +# +# Helper functions for creating build targets. + +function(cxx_shared_library name cxx_flags) + cxx_library_with_type(${name} SHARED "${cxx_flags}" ${ARGN}) +endfunction() + +function(cxx_library name cxx_flags) + cxx_library_with_type(${name} "" "${cxx_flags}" ${ARGN}) +endfunction() + +# cxx_executable_with_flags(name cxx_flags libs srcs...) +# +# creates a named C++ executable that depends on the given libraries and +# is built from the given source files with the given compiler flags. +function(cxx_executable_with_flags name cxx_flags libs) + add_executable(${name} ${ARGN}) + if (MSVC) + # BigObj required for tests. + set(cxx_flags "${cxx_flags} -bigobj") + endif() + if (cxx_flags) + set_target_properties(${name} + PROPERTIES + COMPILE_FLAGS "${cxx_flags}") + endif() + if (BUILD_SHARED_LIBS) + set_target_properties(${name} + PROPERTIES + COMPILE_DEFINITIONS "GTEST_LINKED_AS_SHARED_LIBRARY=1") + endif() + # To support mixing linking in static and dynamic libraries, link each + # library in with an extra call to target_link_libraries. + foreach (lib "${libs}") + target_link_libraries(${name} ${lib}) + endforeach() +endfunction() + +# cxx_executable(name dir lib srcs...) +# +# creates a named target that depends on the given libs and is built +# from the given source files. dir/name.cc is implicitly included in +# the source file list. +function(cxx_executable name dir libs) + cxx_executable_with_flags( + ${name} "${cxx_default}" "${libs}" "${dir}/${name}.cc" ${ARGN}) +endfunction() + +# Sets PYTHONINTERP_FOUND and PYTHON_EXECUTABLE. +if ("${CMAKE_VERSION}" VERSION_LESS "3.12.0") + find_package(PythonInterp) +else() + find_package(Python COMPONENTS Interpreter) + set(PYTHONINTERP_FOUND ${Python_Interpreter_FOUND}) + set(PYTHON_EXECUTABLE ${Python_EXECUTABLE}) +endif() + +# cxx_test_with_flags(name cxx_flags libs srcs...) +# +# creates a named C++ test that depends on the given libs and is built +# from the given source files with the given compiler flags. +function(cxx_test_with_flags name cxx_flags libs) + cxx_executable_with_flags(${name} "${cxx_flags}" "${libs}" ${ARGN}) + add_test(NAME ${name} COMMAND "$") +endfunction() + +# cxx_test(name libs srcs...) +# +# creates a named test target that depends on the given libs and is +# built from the given source files. Unlike cxx_test_with_flags, +# test/name.cc is already implicitly included in the source file list. +function(cxx_test name libs) + cxx_test_with_flags("${name}" "${cxx_default}" "${libs}" + "test/${name}.cc" ${ARGN}) +endfunction() + +# py_test(name) +# +# creates a Python test with the given name whose main module is in +# test/name.py. It does nothing if Python is not installed. +function(py_test name) + if (PYTHONINTERP_FOUND) + if ("${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}" VERSION_GREATER 3.1) + if (CMAKE_CONFIGURATION_TYPES) + # Multi-configuration build generators as for Visual Studio save + # output in a subdirectory of CMAKE_CURRENT_BINARY_DIR (Debug, + # Release etc.), so we have to provide it here. + add_test(NAME ${name} + COMMAND ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/test/${name}.py + --build_dir=${CMAKE_CURRENT_BINARY_DIR}/$ ${ARGN}) + else (CMAKE_CONFIGURATION_TYPES) + # Single-configuration build generators like Makefile generators + # don't have subdirs below CMAKE_CURRENT_BINARY_DIR. + add_test(NAME ${name} + COMMAND ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/test/${name}.py + --build_dir=${CMAKE_CURRENT_BINARY_DIR} ${ARGN}) + endif (CMAKE_CONFIGURATION_TYPES) + else() + # ${CMAKE_CURRENT_BINARY_DIR} is known at configuration time, so we can + # directly bind it from cmake. ${CTEST_CONFIGURATION_TYPE} is known + # only at ctest runtime (by calling ctest -c ), so + # we have to escape $ to delay variable substitution here. + add_test(NAME ${name} + COMMAND ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/test/${name}.py + --build_dir=${CMAKE_CURRENT_BINARY_DIR}/\${CTEST_CONFIGURATION_TYPE} ${ARGN}) + endif() + # Make the Python import path consistent between Bazel and CMake. + set_tests_properties(${name} PROPERTIES ENVIRONMENT PYTHONPATH=${CMAKE_SOURCE_DIR}) + endif(PYTHONINTERP_FOUND) +endfunction() + +# install_project(targets...) +# +# Installs the specified targets and configures the associated pkgconfig files. +function(install_project) + if(INSTALL_GTEST) + install(DIRECTORY "${PROJECT_SOURCE_DIR}/include/" + DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}") + # Install the project targets. + install(TARGETS ${ARGN} + EXPORT ${targets_export_name} + RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" + ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}" + LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}") + if(CMAKE_CXX_COMPILER_ID MATCHES "MSVC") + # Install PDBs + foreach(t ${ARGN}) + get_target_property(t_pdb_name ${t} COMPILE_PDB_NAME) + get_target_property(t_pdb_name_debug ${t} COMPILE_PDB_NAME_DEBUG) + get_target_property(t_pdb_output_directory ${t} PDB_OUTPUT_DIRECTORY) + install(FILES + "${t_pdb_output_directory}/\${CMAKE_INSTALL_CONFIG_NAME}/$<$:${t_pdb_name_debug}>$<$>:${t_pdb_name}>.pdb" + DESTINATION ${CMAKE_INSTALL_LIBDIR} + OPTIONAL) + endforeach() + endif() + # Configure and install pkgconfig files. + foreach(t ${ARGN}) + set(configured_pc "${generated_dir}/${t}.pc") + configure_file("${PROJECT_SOURCE_DIR}/cmake/${t}.pc.in" + "${configured_pc}" @ONLY) + install(FILES "${configured_pc}" + DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig") + endforeach() + endif() +endfunction() diff --git a/vendor/logger b/vendor/logger deleted file mode 160000 index 9c893e3..0000000 --- a/vendor/logger +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 9c893e359a890c178c02dae3ca9c1e1dc211f2a5