diff --git a/Todo.md b/Todo.md index bcd1054..de3b18e 100644 --- a/Todo.md +++ b/Todo.md @@ -3,6 +3,4 @@ - Add is_valid() method to connection & connection_impl - Read in entity fields - Add special handling for update in backends -- Add PostgreSQL backend -- Add MySQL/MariaDB backend - Add ODBC/SQL Server backend \ No newline at end of file diff --git a/backends/postgres/src/postgres_connection.cpp b/backends/postgres/src/postgres_connection.cpp index 1ebb321..b51b34f 100644 --- a/backends/postgres/src/postgres_connection.cpp +++ b/backends/postgres/src/postgres_connection.cpp @@ -145,7 +145,7 @@ sql::record postgres_connection::describe(const std::string &table) while (reader.fetch()) { char *end = nullptr; // Todo: Handle error - auto index = strtoul(reader.column(0), &end, 10); + auto index = strtoul(reader.column(0), &end, 10) - 1; std::string name = reader.column(1); // Todo: extract size @@ -156,7 +156,7 @@ sql::record postgres_connection::describe(const std::string &table) null_opt = sql::null_option::NOT_NULL; } // f.default_value(res->column(4)); - prototype.append({name, type, utils::null_attributes, null_opt}); + prototype.append({name, type, utils::null_attributes, null_opt, index}); } return std::move(prototype); diff --git a/backends/tests/SessionRecordTest.cpp b/backends/tests/SessionRecordTest.cpp index d4daa96..8245e08 100644 --- a/backends/tests/SessionRecordTest.cpp +++ b/backends/tests/SessionRecordTest.cpp @@ -18,6 +18,7 @@ public: drop_table_if_exists("flight"); drop_table_if_exists("airplane"); drop_table_if_exists("person"); + drop_table_if_exists("quotes"); } protected: @@ -34,7 +35,7 @@ private: using namespace matador::sql; -TEST_CASE_METHOD(SessionRecordFixture, " Create and drop table statement", "[session record]") +TEST_CASE_METHOD(SessionRecordFixture, " Create and drop table statement", "[session][record]") { REQUIRE(!ses.table_exists("person")); ses.create() @@ -54,7 +55,7 @@ TEST_CASE_METHOD(SessionRecordFixture, " Create and drop table statement", "[ses REQUIRE(!ses.table_exists("person")); } -TEST_CASE_METHOD(SessionRecordFixture, " Create and drop table statement with foreign key", "[session record]") +TEST_CASE_METHOD(SessionRecordFixture, " Create and drop table statement with foreign key", "[session][record]") { ses.create() .table("airplane", { @@ -89,7 +90,7 @@ TEST_CASE_METHOD(SessionRecordFixture, " Create and drop table statement with fo REQUIRE(!ses.table_exists("airplane")); } -TEST_CASE_METHOD(SessionRecordFixture, " Execute insert record statement", "[session record]") +TEST_CASE_METHOD(SessionRecordFixture, " Execute insert record statement", "[session][record]") { ses.create() .table("person", { @@ -128,7 +129,7 @@ TEST_CASE_METHOD(SessionRecordFixture, " Execute insert record statement", "[ses .execute(); } -TEST_CASE_METHOD(SessionRecordFixture, " Execute insert record statement with foreign key", "[session record]") +TEST_CASE_METHOD(SessionRecordFixture, " Execute insert record statement with foreign key", "[session][record]") { ses.create() .table("airplane", { @@ -168,7 +169,7 @@ TEST_CASE_METHOD(SessionRecordFixture, " Execute insert record statement with fo REQUIRE(!ses.table_exists("airplane")); } -TEST_CASE_METHOD(SessionRecordFixture, " Execute update record statement", "[session record]") +TEST_CASE_METHOD(SessionRecordFixture, " Execute update record statement", "[session][record]") { ses.create() .table("person", { @@ -214,7 +215,7 @@ TEST_CASE_METHOD(SessionRecordFixture, " Execute update record statement", "[ses ses.drop().table("person").execute(); } -TEST_CASE_METHOD(SessionRecordFixture, " Execute select statement", "[session record]") +TEST_CASE_METHOD(SessionRecordFixture, " Execute select statement", "[session][record]") { ses.create() .table("person", { @@ -257,7 +258,7 @@ TEST_CASE_METHOD(SessionRecordFixture, " Execute select statement", "[session re ses.drop().table("person").execute(); } -TEST_CASE_METHOD(SessionRecordFixture, " Execute select statement with order by", "[session record]") +TEST_CASE_METHOD(SessionRecordFixture, " Execute select statement with order by", "[session][record]") { ses.create() .table("person", { @@ -291,7 +292,7 @@ TEST_CASE_METHOD(SessionRecordFixture, " Execute select statement with order by" ses.drop().table("person").execute(); } -TEST_CASE_METHOD(SessionRecordFixture, " Execute select statement with group by and order by", "[session record]") +TEST_CASE_METHOD(SessionRecordFixture, " Execute select statement with group by and order by", "[session][record]") { ses.create() .table("person", { @@ -330,7 +331,7 @@ TEST_CASE_METHOD(SessionRecordFixture, " Execute select statement with group by ses.drop().table("person").execute(); } -TEST_CASE_METHOD(SessionRecordFixture, " Execute delete statement", "[session record]") +TEST_CASE_METHOD(SessionRecordFixture, " Execute delete statement", "[session][record]") { ses.create() .table("person", { @@ -359,3 +360,37 @@ TEST_CASE_METHOD(SessionRecordFixture, " Execute delete statement", "[session re ses.drop().table("person").execute(); } + +TEST_CASE_METHOD(SessionRecordFixture, " Test quoted identifier", "[session][record]") { + ses.create() + .table("quotes", { + make_column("from", 255), + make_column("to", 255) + }).execute(); + + // check table description + std::vector columns = { "from", "to"}; + std::vector types = {data_type_t::type_varchar, data_type_t::type_varchar}; + auto fields = ses.describe_table("quotes"); + + for (const auto &field : fields) { + REQUIRE(field.name() == columns[field.index()]); + REQUIRE(field.type() == types[field.index()]); + } + + ses.insert().into("quotes", {"from", "to"}).values({"Berlin", "London"}).execute(); + + auto res = ses.select({"from", "to"}).from("quotes").fetch_one(); + + REQUIRE("Berlin" == res.at("from").str()); + REQUIRE("London" == res.at("to").str()); + + ses.update("quotes").set({{"from", "Hamburg"}, {"to", "New York"}}).where("from"_col == "Berlin").execute(); + + res = ses.select({"from", "to"}).from("quotes").fetch_one(); + + REQUIRE("Hamburg" == res.at("from").str()); + REQUIRE("New York" == res.at("to").str()); + + ses.drop().table("quotes").execute(); +} \ No newline at end of file diff --git a/include/matador/sql/column.hpp b/include/matador/sql/column.hpp index dfcbf29..9b8a4e1 100644 --- a/include/matador/sql/column.hpp +++ b/include/matador/sql/column.hpp @@ -37,7 +37,7 @@ public: : column(std::move(name), data_type_traits::builtin_type(attr.size()), attr, null_opt) {} - column(std::string name, data_type_t type, utils::field_attributes attr, null_option null_opt); + column(std::string name, data_type_t type, utils::field_attributes attr, null_option null_opt, size_t index = 0); template column(std::string name, std::string ref_table, std::string ref_column, utils::field_attributes attr, null_option null_opt) diff --git a/include/matador/sql/connection.hpp b/include/matador/sql/connection.hpp index 4ca909a..885bb6d 100644 --- a/include/matador/sql/connection.hpp +++ b/include/matador/sql/connection.hpp @@ -39,10 +39,13 @@ public: statement prepare(query_context &&query) const; + const class dialect& dialect() const; + private: connection_info connection_info_; std::unique_ptr connection_; utils::logger logger_; + const class dialect &dialect_; }; } diff --git a/include/matador/sql/record.hpp b/include/matador/sql/record.hpp index 60bbbeb..08d148b 100644 --- a/include/matador/sql/record.hpp +++ b/include/matador/sql/record.hpp @@ -24,8 +24,8 @@ public: record() = default; record(std::initializer_list columns); explicit record(const std::vector &columns); - record(const record&) = default; - record& operator=(const record&) = default; + record(const record &x); + record& operator=(const record &x); record(record&&) noexcept = default; record& operator=(record&&) noexcept = default; ~record() = default; @@ -45,8 +45,8 @@ public: void append(column col); - bool has_primary_key() const; - const column& primary_key() const; + [[nodiscard]] bool has_primary_key() const; + [[nodiscard]] const column& primary_key() const; [[nodiscard]] const std::vector& columns() const; diff --git a/src/sql/column.cpp b/src/sql/column.cpp index 15496c6..06dc701 100644 --- a/src/sql/column.cpp +++ b/src/sql/column.cpp @@ -16,8 +16,8 @@ column::column(std::string name, std::string alias) : name_(std::move(name)), attributes_(utils::null_attributes), alias_(std::move(alias)) {} -column::column(std::string name, data_type_t type, utils::field_attributes attr, null_option null_opt) - : name_(std::move(name)), type_(type), attributes_(attr), null_option_(null_opt) +column::column(std::string name, data_type_t type, utils::field_attributes attr, null_option null_opt, size_t index) + : name_(std::move(name)), index_(index), type_(type), attributes_(attr), null_option_(null_opt) {} column::column(std::string name, data_type_t type, size_t index, std::string ref_table, std::string ref_column, diff --git a/src/sql/connection.cpp b/src/sql/connection.cpp index 60b3c93..b18526b 100644 --- a/src/sql/connection.cpp +++ b/src/sql/connection.cpp @@ -11,6 +11,7 @@ namespace matador::sql { connection::connection(connection_info info) : connection_info_(std::move(info)) , logger_(stdout, "SQL") +, dialect_(backend_provider::instance().connection_dialect(info.type)) { connection_.reset(backend_provider::instance().create_connection(connection_info_.type, connection_info_)); } @@ -22,6 +23,7 @@ connection::connection(const std::string& dns) connection::connection(const connection &x) : connection_info_(x.connection_info_) , logger_(x.logger_) +, dialect_(x.dialect_) { if (x.connection_) { throw std::runtime_error("couldn't copy connection with valid connection impl"); @@ -93,4 +95,9 @@ statement connection::prepare(query_context &&query) const return statement(connection_->prepare(std::move(query)), logger_); } +const class dialect &connection::dialect() const +{ + return dialect_; +} + } diff --git a/src/sql/record.cpp b/src/sql/record.cpp index 10666d0..867807c 100644 --- a/src/sql/record.cpp +++ b/src/sql/record.cpp @@ -16,6 +16,30 @@ record::record(const std::vector &columns) init(); } +record::record(const record &x) +: columns_(x.columns_) +, pk_index_(x.pk_index_) +{ + for (auto& col : columns_) { + add_to_map(col, col.index()); + } +} + +record &record::operator=(const record &x) +{ + if (&x == this) { + return *this; + } + + columns_ = x.columns_; + columns_by_name_.clear(); + pk_index_ = x.pk_index_; + for (auto& col : columns_) { + add_to_map(col, col.index()); + } + return *this; +} + const std::vector &record::columns() const { return columns_; @@ -37,6 +61,7 @@ const column &record::primary_key() const const column &record::at(const std::string &name) const { + auto ref = columns_by_name_.at(name).first; return columns_by_name_.at(name).first; }