diff --git a/CMakeLists.txt b/CMakeLists.txt index 4672401..5238d80 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,8 +5,12 @@ set(CMAKE_CXX_STANDARD 17) include_directories(${PROJECT_SOURCE_DIR}/include) +find_package(ODBC REQUIRED) + add_subdirectory(src) add_subdirectory(test) -add_executable(query main.cpp) +add_executable(query main.cpp + include/matador/sql/connection_impl.hpp + include/matador/sql/connection_info.hpp) target_link_libraries(query matador) diff --git a/include/matador/sql/connection.hpp b/include/matador/sql/connection.hpp index a8d90cb..e5051cc 100644 --- a/include/matador/sql/connection.hpp +++ b/include/matador/sql/connection.hpp @@ -20,7 +20,7 @@ public: query_update_intermediate update(const std::string &table); query_delete_intermediate remove(); - result fetch(const std::string &sql); + query_result fetch(const std::string &sql); std::pair execute(const std::string &sql); private: diff --git a/include/matador/sql/connection_impl.hpp b/include/matador/sql/connection_impl.hpp new file mode 100644 index 0000000..d49ea5e --- /dev/null +++ b/include/matador/sql/connection_impl.hpp @@ -0,0 +1,30 @@ +#ifndef QUERY_CONNECTION_IMPL_HPP +#define QUERY_CONNECTION_IMPL_HPP + +#include "matador/sql/connection_info.hpp" + +namespace matador::sql { + +class connection_impl +{ +public: + virtual ~connection_impl() = default; + + virtual void open() = 0; + virtual void close() = 0; + virtual bool is_open() = 0; + + virtual void execute(const std::string &stmt) = 0; + virtual void prepare(const std::string &stmt) = 0; + +protected: + explicit connection_impl(connection_info info); + + [[nodiscard]] const connection_info &info() const; + +private: + connection_info info_; +}; + +} +#endif //QUERY_CONNECTION_IMPL_HPP diff --git a/include/matador/sql/connection_info.hpp b/include/matador/sql/connection_info.hpp new file mode 100644 index 0000000..78a6660 --- /dev/null +++ b/include/matador/sql/connection_info.hpp @@ -0,0 +1,23 @@ +#ifndef QUERY_CONNECTION_INFO_HPP +#define QUERY_CONNECTION_INFO_HPP + +#include + +namespace matador::sql { + +struct connection_info +{ + std::string type; + std::string user; + std::string password; + std::string hostname; + unsigned short port{}; + std::string database; + std::string driver; + + static connection_info parse(const std::string &info, unsigned short default_port = 0, const std::string &default_driver = ""); + static std::string to_string(const connection_info &ci); +}; + +} +#endif //QUERY_CONNECTION_INFO_HPP diff --git a/include/matador/sql/connection_intermediates.hpp b/include/matador/sql/connection_intermediates.hpp index b7e77f7..0329851 100644 --- a/include/matador/sql/connection_intermediates.hpp +++ b/include/matador/sql/connection_intermediates.hpp @@ -3,7 +3,8 @@ #include "matador/sql/column.hpp" #include "matador/sql/key_value_pair.hpp" -#include "matador/sql/result.hpp" +#include "matador/sql/query_result.hpp" +#include "matador/sql/record.hpp" #include @@ -27,15 +28,28 @@ private: query_builder &query_; }; +class query_execute_finish : public query_intermediate +{ +public: + using query_intermediate::query_intermediate; + + std::pair execute(); +}; + class query_select_finish : public query_intermediate { protected: using query_intermediate::query_intermediate; public: - result fetch_all(); - result fetch_one(); - result fetch_value(); + query_result fetch_all(); + record fetch_one(); + template + Type fetch_value() + { + auto result = fetch_all(); + return {}; + } }; class query_limit_intermediate : public query_select_finish @@ -108,14 +122,6 @@ public: }; -class query_execute_finish : public query_intermediate -{ -public: - using query_intermediate::query_intermediate; - - std::pair execute(); -}; - class query_into_intermediate : public query_intermediate { public: @@ -124,20 +130,12 @@ public: query_execute_finish values(std::initializer_list values); }; -class query_create_drop_finish : public query_intermediate -{ -public: - using query_intermediate::query_intermediate; - - void execute(); -}; - class query_create_intermediate : query_intermediate { public: using query_intermediate::query_intermediate; - query_create_drop_finish table(const std::string &table, std::initializer_list columns); + query_execute_finish table(const std::string &table, std::initializer_list columns); }; class query_drop_intermediate : query_intermediate @@ -145,7 +143,7 @@ class query_drop_intermediate : query_intermediate public: using query_intermediate::query_intermediate; - query_create_drop_finish table(const std::string &table); + query_execute_finish table(const std::string &table); }; class query_insert_intermediate : public query_intermediate diff --git a/include/matador/sql/query_result.hpp b/include/matador/sql/query_result.hpp new file mode 100644 index 0000000..7cfdd7e --- /dev/null +++ b/include/matador/sql/query_result.hpp @@ -0,0 +1,95 @@ +#ifndef QUERY_QUERY_RESULT_HPP +#define QUERY_QUERY_RESULT_HPP + +#include + +namespace matador::sql { + +template < typename Type > +class query_result; + +template < typename Type > +class query_result_iterator +{ +public: + using iterator_category = std::forward_iterator_tag; + using value_type = Type; + using difference_type = std::ptrdiff_t; + using pointer = value_type*; /**< Shortcut for the pointer type. */ + using reference = value_type&; /**< Shortcut for the reference type */ + +public: + query_result_iterator() = default; + explicit query_result_iterator(query_result &res, Type *obj = nullptr) + : obj_(obj) + , result_(res) + {} + query_result_iterator(query_result_iterator&& x) noexcept + : obj_(std::move(x.obj_)) + , result_(x.result_) + {} + + query_result_iterator& operator=(query_result_iterator&& x) noexcept + { + result_ = x.result_; + obj_ = std::move(x.obj_); + return *this; + } + + ~query_result_iterator() = default; + + bool operator==(const query_result_iterator& rhs) + { + return obj_ == rhs.obj_; + } + + bool operator!=(const query_result_iterator& rhs) + { + return obj_ != rhs.obj_; + } + + pointer operator->() + { + return obj_.get(); + } + + reference operator&() + { + return &obj_.get(); + } + + std::unique_ptr operator*() + { + return std::move(obj_); + } + + pointer get() + { + return obj_.get(); + } + + pointer release() + { + return obj_.release(); + } + +protected: + std::unique_ptr obj_; + query_result &result_; +}; + +template < typename Type > +class query_result +{ +public: + query_result(std::string sql) : sql_(std::move(sql)) {} + + [[nodiscard]] const std::string& str() const { return sql_; } + + Type begin() { return {}; } +private: + std::string sql_; +}; + +} +#endif //QUERY_QUERY_RESULT_HPP diff --git a/include/matador/sql/record.hpp b/include/matador/sql/record.hpp new file mode 100644 index 0000000..e89a99c --- /dev/null +++ b/include/matador/sql/record.hpp @@ -0,0 +1,56 @@ +#ifndef QUERY_RECORD_HPP +#define QUERY_RECORD_HPP + +#include "matador/sql/column.hpp" + +#include +#include + +namespace matador::sql { + +class record +{ +private: + using column_by_index = std::vector; + using column_index_pair = std::pair, size_t>; + using column_by_name_map = std::unordered_map; + +public: + using iterator = column_by_index::iterator; + using const_iterator = column_by_index::const_iterator; + + record() = default; + record(std::initializer_list columns); + ~record() = default; + + template < typename Type > + void append(const std::string &name, long size = -1) + { + append(make_column(name, size)); + } + + void append(column col); + + [[nodiscard]] const column& at(const std::string &name) const; + const column& at(size_t index); + + iterator find(const std::string &column_name); + const_iterator find(const std::string &column_name) const; + + iterator begin(); + [[nodiscard]] const_iterator begin() const; + [[nodiscard]] const_iterator cbegin() const; + + iterator end(); + [[nodiscard]] const_iterator end() const; + [[nodiscard]] const_iterator cend() const; + + [[nodiscard]] size_t size() const; + +private: + column_by_index columns_; + column_by_name_map columns_by_name_; +}; + +} +#endif //QUERY_RECORD_HPP diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 5eac1bf..9f7de44 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -2,22 +2,29 @@ set(SQL_SOURCES sql/dialect.cpp sql/query_builder.cpp sql/column.cpp - sql/key_value_pair.cpp + sql/key_value_pair.cpp sql/basic_condition.cpp sql/connection.cpp - sql/connection_intermediates.cpp) + sql/connection_intermediates.cpp + sql/record.cpp + sql/connection_info.cpp + sql/connection_impl.cpp) set(SQL_HEADER ../include/matador/sql/dialect.hpp ../include/matador/sql/query_builder.hpp ../include/matador/sql/column.hpp ../include/matador/sql/types.hpp - ../include/matador/sql/key_value_pair.hpp + ../include/matador/sql/key_value_pair.hpp ../include/matador/sql/basic_condition.hpp ../include/matador/sql/condition.hpp ../include/matador/sql/connection.hpp ../include/matador/sql/connection_intermediates.hpp - ../include/matador/sql/result.hpp) + ../include/matador/sql/result.hpp + ../include/matador/sql/record.hpp + ../include/matador/sql/query_result.hpp + ../include/matador/sql/connection_impl.hpp + ../include/matador/sql/connection_info.hpp) set(UTILS_HEADER ../include/matador/utils/field_attributes.hpp diff --git a/src/sql/connection.cpp b/src/sql/connection.cpp index fcc5727..22eff71 100644 --- a/src/sql/connection.cpp +++ b/src/sql/connection.cpp @@ -36,7 +36,7 @@ query_delete_intermediate connection::remove() return query_delete_intermediate{*this, query_.remove()}; } -result connection::fetch(const std::string &sql) +query_result connection::fetch(const std::string &sql) { return {sql}; } diff --git a/src/sql/connection_impl.cpp b/src/sql/connection_impl.cpp new file mode 100644 index 0000000..2f7ffbe --- /dev/null +++ b/src/sql/connection_impl.cpp @@ -0,0 +1,14 @@ +#include + +#include "matador/sql/connection_impl.hpp" + +namespace matador::sql { + +connection_impl::connection_impl(connection_info info) +: info_(std::move(info)){} + +const connection_info &connection_impl::info() const { + return info_; +} + +} \ No newline at end of file diff --git a/src/sql/connection_info.cpp b/src/sql/connection_info.cpp new file mode 100644 index 0000000..56e11d4 --- /dev/null +++ b/src/sql/connection_info.cpp @@ -0,0 +1,72 @@ +#include "matador/sql/connection_info.hpp" + +#include + +namespace matador::sql { + +connection_info connection_info::parse(const std::string &info, unsigned short default_port, const std::string &default_driver) { + static const std::regex DNS_RGX (R"((\w+):\/\/((([\w]+)(:([\w!]+))?)@)?((((([\w.\(\)\\]+)([:]([\d]+))?)?)\/)?(([\w.]+)( \((.*)\))?)))"); + + std::smatch what; + + if (!std::regex_match(info, what, DNS_RGX)) { + throw std::logic_error("connect invalid dns: " + info); + } + + connection_info ci{}; + ci.type = what[1].str(); + ci.user = what[4].str(); + ci.password = what[6].str(); + + // get connection part + ci.hostname = what[11].str(); + if (what[13].matched) { + char *end; + ci.port = static_cast(std::strtoul(what[13].str().c_str(), &end, 10)); + } else { + ci.port = default_port; + } + + // Should we just ignore the "instance" part? + // const std::string instance = what[5].str(); + ci.database = what[15].str(); + + if (what[17].matched) { + ci.driver = what[17].str(); + } else { + ci.driver = default_driver; + } + + return ci; +} + +std::string connection_info::to_string(const connection_info &ci) { + std::string dns{ci.type}; + dns += "://"; + + if (!ci.user.empty()) { + dns += ci.user; + if (!ci.password.empty()) { + dns += ":" + ci.password; + } + } + + if (!ci.hostname.empty()) { + dns += "@" + ci.hostname; + if (ci.port > 0) { + dns += ":" + std::to_string(ci.port); + } + } + + if (!dns.empty()) { + dns += "/"; + } + dns += ci.database; + + if (!ci.driver.empty()) { + dns += " (" + ci.driver +")"; + } + return dns; +} + +} \ No newline at end of file diff --git a/src/sql/connection_intermediates.cpp b/src/sql/connection_intermediates.cpp index 2847144..9ac2430 100644 --- a/src/sql/connection_intermediates.cpp +++ b/src/sql/connection_intermediates.cpp @@ -13,19 +13,14 @@ query_builder &query_intermediate::query() return query_; } -result query_select_finish::fetch_all() +query_result query_select_finish::fetch_all() { return db().fetch(query().compile()); } -result query_select_finish::fetch_one() +record query_select_finish::fetch_one() { - return db().fetch(query().compile()); -} - -result query_select_finish::fetch_value() -{ - return db().fetch(query().compile()); + return db().fetch(query().compile()).begin(); } query_intermediate::query_intermediate(connection &db, query_builder &query) @@ -106,17 +101,12 @@ query_execute_finish query_into_intermediate::values(std::initializer_list columns) +query_execute_finish query_create_intermediate::table(const std::string &table, std::initializer_list columns) { return {db(), query().table(table, columns)}; } -query_create_drop_finish query_drop_intermediate::table(const std::string &table) +query_execute_finish query_drop_intermediate::table(const std::string &table) { return {db(), query().table(table)}; } diff --git a/src/sql/record.cpp b/src/sql/record.cpp new file mode 100644 index 0000000..5d4961f --- /dev/null +++ b/src/sql/record.cpp @@ -0,0 +1,75 @@ +#include "matador/sql/record.hpp" + +namespace matador::sql { + +record::record(std::initializer_list columns) +: columns_(columns) { + size_t index{0}; + for(auto &col : columns_) { + columns_by_name_.emplace(col.name(), column_index_pair {std::ref(col), index++}); + } +} + +const column &record::at(const std::string &name) const +{ + return columns_by_name_.at(name).first; +} + +const column &record::at(size_t index) +{ + return columns_.at(index); +} + +record::iterator record::find(const std::string &column_name) +{ + auto it = columns_by_name_.find(column_name); + return it != columns_by_name_.end() ? columns_.begin() + it->second.second : columns_.end(); +} + +record::const_iterator record::find(const std::string &column_name) const { + auto it = columns_by_name_.find(column_name); + return it != columns_by_name_.end() ? columns_.begin() + it->second.second : columns_.end(); +} + +void record::append(column col) +{ + auto &ref = columns_.emplace_back(std::move(col)); + columns_by_name_.emplace(ref.name(), column_index_pair{std::ref(ref), columns_.size()-1}); +} + +record::iterator record::begin() +{ + return columns_.begin(); +} + +record::const_iterator record::begin() const +{ + return columns_.begin(); +} + +record::const_iterator record::cbegin() const +{ + return columns_.cbegin(); +} + +record::iterator record::end() +{ + return columns_.end(); +} + +record::const_iterator record::end() const +{ + return columns_.end(); +} + +record::const_iterator record::cend() const +{ + return columns_.cend(); +} + +size_t record::size() const +{ + return columns_.size(); +} + +} \ No newline at end of file diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 236652d..b7ff137 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -9,6 +9,7 @@ FetchContent_Declare( FetchContent_MakeAvailable(Catch2) add_executable(tests builder.cpp - connection.cpp) + connection.cpp + record.cpp) target_link_libraries(tests PRIVATE Catch2::Catch2WithMain matador) target_include_directories(tests PUBLIC $/include) \ No newline at end of file diff --git a/test/connection.cpp b/test/connection.cpp index 1f61d50..105d30e 100644 --- a/test/connection.cpp +++ b/test/connection.cpp @@ -9,7 +9,7 @@ using namespace matador::sql; TEST_CASE("Execute create table statement", "[connection]") { connection c; - c.create() + const auto res = c.create() .table("person", { make_pk_column("id"), make_column("name", 255), @@ -17,17 +17,17 @@ TEST_CASE("Execute create table statement", "[connection]") { }).execute(); REQUIRE(true); -// REQUIRE(res.sql == R"(SELECT "id", "name", "color" FROM "person" WHERE "id" = 8)"); + REQUIRE(res.second == R"(CREATE TABLE "person" ("id" BIGINT NOT NULL PRIMARY KEY, "name" VARCHAR(255), "age" INTEGER))"); } TEST_CASE("Execute drop table statement", "[connection]") { connection c; - c.drop() - .table("person").execute(); + const auto res = c.drop() + .table("person") + .execute(); - REQUIRE(true); -// REQUIRE(res.sql == R"(SELECT "id", "name", "color" FROM "person" WHERE "id" = 8)"); + REQUIRE(res.second == R"(DROP TABLE "person")"); } TEST_CASE("Execute select statement with where clause", "[connection]") { @@ -38,7 +38,7 @@ TEST_CASE("Execute select statement with where clause", "[connection]") { .where("id"_col == 8) .fetch_all(); - REQUIRE(res.sql == R"(SELECT "id", "name", "color" FROM "person" WHERE "id" = 8)"); + REQUIRE(res.str() == R"(SELECT "id", "name", "color" FROM "person" WHERE "id" = 8)"); } TEST_CASE("Execute select statement with order by", "[connection]") { @@ -50,7 +50,7 @@ TEST_CASE("Execute select statement with order by", "[connection]") { .order_by("name").desc() .fetch_all(); - REQUIRE(res.sql == R"(SELECT "id", "name", "color" FROM "person" WHERE "id" = 8 ORDER BY "name" DESC)"); + REQUIRE(res.str() == R"(SELECT "id", "name", "color" FROM "person" WHERE "id" = 8 ORDER BY "name" DESC)"); } TEST_CASE("Execute select statement with group by and order by", "[connection]") { @@ -63,7 +63,7 @@ TEST_CASE("Execute select statement with group by and order by", "[connection]") .order_by("name").asc() .fetch_all(); - REQUIRE(res.sql == R"(SELECT "id", "name", "color" FROM "person" WHERE "id" = 8 GROUP BY "color" ORDER BY "name" ASC)"); + REQUIRE(res.str() == R"(SELECT "id", "name", "color" FROM "person" WHERE "id" = 8 GROUP BY "color" ORDER BY "name" ASC)"); } TEST_CASE("Execute insert statement", "[connection]") { diff --git a/test/record.cpp b/test/record.cpp new file mode 100644 index 0000000..f9892f8 --- /dev/null +++ b/test/record.cpp @@ -0,0 +1,49 @@ +#include + +#include + +#include + +using namespace matador::sql; + +TEST_CASE("Create record", "[record]") { + record rec({ + make_pk_column("id"), + make_column("name", 255), + make_column("color", 255) + }); + + REQUIRE(rec.size() == 3); + + std::list expected_columns = {"id", "name", "color"}; + for(const auto &col : expected_columns) { + REQUIRE(rec.find(col) != rec.end()); + } + + for(const auto& col : rec) { + expected_columns.remove(col.name()); + } + + REQUIRE(expected_columns.empty()); +} + +TEST_CASE("Append to record", "[record]") { + record rec; + + rec.append(make_pk_column("id")); + rec.append("name", 255); + rec.append("color", 63); + + REQUIRE(rec.size() == 3); + + std::list expected_columns = {"id", "name", "color"}; + for(const auto &col : expected_columns) { + REQUIRE(rec.find(col) != rec.end()); + } + + for(const auto& col : rec) { + expected_columns.remove(col.name()); + } + + REQUIRE(expected_columns.empty()); +} \ No newline at end of file