diff --git a/README.md b/README.md index 990d2f8..f85cdb2 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,8 @@ A fluent sql query_context builder +```MATADOR_BACKENDS_PATH=/home/sascha/Develop/query/cmake-build-debug/backends``` + Object definition ```cpp enum class Color { diff --git a/backends/postgres/src/postgres_connection.cpp b/backends/postgres/src/postgres_connection.cpp index 18db9e9..12a7aed 100644 --- a/backends/postgres/src/postgres_connection.cpp +++ b/backends/postgres/src/postgres_connection.cpp @@ -66,7 +66,7 @@ std::unique_ptr postgres_connection::fetch(const std::st std::string postgres_connection::generate_statement_name(const sql::query_context &query) { std::stringstream name; - name << query.table_name << "_" << query.command_name; + name << query.table.name << "_" << query.command_name; auto result = postgres_connection::statement_name_map_.find(name.str()); if (result == postgres_connection::statement_name_map_.end()) { diff --git a/backends/postgres/test/CMakeLists.txt b/backends/postgres/test/CMakeLists.txt index 12af877..efa28ee 100644 --- a/backends/postgres/test/CMakeLists.txt +++ b/backends/postgres/test/CMakeLists.txt @@ -12,7 +12,7 @@ list(APPEND CMAKE_MODULE_PATH ${catch2_SOURCE_DIR}/extras) include(CTest) include(Catch) -set(POSTGRES_CONNECTION_STRING "postgres://test:test123@127.0.0.1:15432/test") +set(POSTGRES_CONNECTION_STRING "postgres://test:test123@127.0.0.1:5432/matador_test") configure_file(Connection.hpp.in ${PROJECT_BINARY_DIR}/backends/postgres/test/connection.hpp @ONLY IMMEDIATE) diff --git a/backends/tests/QueryTest.cpp b/backends/tests/QueryTest.cpp index 46c7e39..1465c5a 100644 --- a/backends/tests/QueryTest.cpp +++ b/backends/tests/QueryTest.cpp @@ -90,7 +90,7 @@ TEST_CASE_METHOD(QueryFixture, "Execute select statement with where clause", "[s for (const auto &i: result_record) { REQUIRE(i.size() == 4); REQUIRE(i.at(0).name() == "id"); - REQUIRE(i.at(0).type() == data_type_t::type_unsigned_long); + REQUIRE(i.at(0).type() == data_type_t::type_long_long); REQUIRE(i.at(0).as() == george.id); REQUIRE(i.at(1).name() == "name"); REQUIRE(i.at(1).type() == data_type_t::type_varchar); diff --git a/demo/main.cpp b/demo/main.cpp index f891a01..0d36884 100644 --- a/demo/main.cpp +++ b/demo/main.cpp @@ -6,7 +6,7 @@ #include "matador/utils/access.hpp" -#include "query_helper.hpp" +#include "matador/sql/query_helper.hpp" #include #include @@ -20,8 +20,9 @@ struct author unsigned short year_of_birth{}; bool distinguished{false}; - template < typename Operator > - void process(Operator &op) { + template + void process(Operator &op) + { namespace field = matador::utils::access; field::primary_key(op, "id", id); field::attribute(op, "first_name", first_name, 63); @@ -39,8 +40,9 @@ struct book std::string title; unsigned short published_in{}; - template < typename Operator > - void process(Operator &op) { + template + void process(Operator &op) + { namespace field = matador::utils::access; field::primary_key(op, "id", id); field::attribute(op, "title", title, 511); @@ -60,7 +62,6 @@ int main() const std::string env_var{"MATADOR_BACKENDS_PATH"}; std::string dns{"sqlite://demo.db"}; -// std::string dns{"memory://test"}; auto s = std::make_shared("main"); s->attach("authors"); s->attach("books"); @@ -71,10 +72,12 @@ int main() auto create_authors_sql = c.query(s) .create() .table(qh::authors) -// .str(); .execute(); - c.query(s).create().table(qh::books).execute(); + c.query(s) + .create() + .table(qh::books) + .execute(); std::cout << "SQL: " << create_authors_sql << "\n"; @@ -90,7 +93,6 @@ int main() .into(qh::authors) .values(*mc) .execute(); -// .str(); std::cout << "SQL: " << insert_authors_sql << "\n"; @@ -99,16 +101,16 @@ int main() .from(qh::authors) .fetch_all(); - for (const auto &row : result) { + for (const auto &row: result) { std::cout << "Author " << row.at(qh::authors.first_name) << "\n"; } auto update_authors_sql = c.query(s) .update(qh::authors) - .set({{qh::authors.first_name, "Stephen"}, {qh::authors.last_name, "King"}}) + .set({{qh::authors.first_name, "Stephen"}, + {qh::authors.last_name, "King"}}) .where(qh::authors.last_name == "Crichton") .execute(); -// .str(); std::cout << "SQL: " << update_authors_sql << "\n"; @@ -117,7 +119,7 @@ int main() .from(qh::authors) .fetch_all(); - for (const auto &a : authors) { + for (const auto &a: authors) { std::cout << "Author " << a.first_name << "\n"; } @@ -143,13 +145,9 @@ int main() .order_by(qh::books.title).asc() .limit(5) .offset(2) -// .str(); .fetch_all(); -// .fetch_all(); -// std::cout << "SQL: " << select_books_sql << "\n"; - - for (const auto &r : select_books_sql) { + for (const auto &r: select_books_sql) { std::cout << "R: " << r.at(qh::books.title) << ", " << r.at(qh::authors.last_name) << "\n"; } // SELECT book.title, book.id, book.author_id, book.published_in, author.name @@ -159,21 +157,11 @@ int main() // ORDER BY "book.title" ASC // OFFSET 2 LIMIT 5 -// char var[1024]; -// size_t len{}; -// const auto error = getenv_s(&len, var, 1024, env_var.c_str()); -// if (error > 0) { -// std::cout << "error: unknown env var " << env_var << "\n"; -// } else { -// std::cout << "env var: " << var << "\n"; -// } - c.query(s).drop().table(qh::books).execute(); auto drop_authors_sql = c.query(s) .drop() .table(qh::authors) -// .str(); .execute(); std::cout << "SQL: " << drop_authors_sql << "\n"; diff --git a/include/matador/sql/any_type_to_string_visitor.hpp b/include/matador/sql/any_type_to_string_visitor.hpp new file mode 100644 index 0000000..2d076d6 --- /dev/null +++ b/include/matador/sql/any_type_to_string_visitor.hpp @@ -0,0 +1,53 @@ +#ifndef QUERY_ANY_TYPE_TO_STRING_VISITOR_HPP +#define QUERY_ANY_TYPE_TO_STRING_VISITOR_HPP + +#include "matador/utils/types.hpp" + +#include "matador/sql/placeholder.hpp" + +#include + +namespace matador::sql { + +class dialect; +class query_context; + +struct any_type_to_string_visitor +{ + explicit any_type_to_string_visitor(const dialect &d, query_context &query); + + void operator()(char &x) { to_string(x); } + void operator()(short &x) { to_string(x); } + void operator()(int &x) { to_string(x); } + void operator()(long &x) { to_string(x); } + void operator()(long long &x) { to_string(x); } + void operator()(unsigned char &x) { to_string(x); } + void operator()(unsigned short &x) { to_string(x); } + void operator()(unsigned int &x) { to_string(x); } + void operator()(unsigned long &x) { to_string(x); } + void operator()(unsigned long long &x) { to_string(x); } + void operator()(bool &x) { to_string(x); } + void operator()(float &x) { to_string(x); } + void operator()(double &x) { to_string(x); } + void operator()(const char *x) { to_string(x); } + void operator()(std::string &x) { to_string(x); } + void operator()(utils::blob &x) { to_string(x); } + void operator()(placeholder &x) { to_string(x); } + + template + void to_string(Type &val) + { + result = std::to_string(val); + } + void to_string(const char *val); + void to_string(std::string &val); + void to_string(utils::blob &val); + void to_string(placeholder &val); + + const dialect &d; + query_context &query; + std::string result; +}; + +} +#endif //QUERY_ANY_TYPE_TO_STRING_VISITOR_HPP diff --git a/include/matador/sql/query_compiler.hpp b/include/matador/sql/query_compiler.hpp index 3373b5b..93fe835 100644 --- a/include/matador/sql/query_compiler.hpp +++ b/include/matador/sql/query_compiler.hpp @@ -41,6 +41,7 @@ private: void visit(query_set_part &set_part) override; void visit(query_delete_part &delete_part) override; + void visit(query_delete_from_part &delete_from_part) override; void visit(query_create_part &create_part) override; void visit(query_create_table_part &create_table_part) override; diff --git a/include/matador/sql/query_context.hpp b/include/matador/sql/query_context.hpp index 861791f..836ac9f 100644 --- a/include/matador/sql/query_context.hpp +++ b/include/matador/sql/query_context.hpp @@ -2,6 +2,7 @@ #define QUERY_QUERY_CONTEXT_HPP #include "matador/sql/record.hpp" +#include "matador/sql/table.hpp" namespace matador::sql { @@ -9,7 +10,7 @@ struct query_context { std::string sql; std::string command_name; - std::string table_name; + sql::table table{""}; record prototype; std::vector result_vars; std::vector bind_vars; diff --git a/demo/query_helper.hpp b/include/matador/sql/query_helper.hpp similarity index 100% rename from demo/query_helper.hpp rename to include/matador/sql/query_helper.hpp diff --git a/include/matador/sql/query_intermediates.hpp b/include/matador/sql/query_intermediates.hpp index 79ffefb..f62c0a3 100644 --- a/include/matador/sql/query_intermediates.hpp +++ b/include/matador/sql/query_intermediates.hpp @@ -47,7 +47,7 @@ public: size_t execute(); statement prepare(); - [[nodiscard]] std::string str() const; + [[nodiscard]] query_context build() const; }; class query_select_finish : public query_intermediate @@ -72,7 +72,7 @@ public: statement prepare(); - [[nodiscard]] std::string str() const; + [[nodiscard]] query_context build() const; private: std::unique_ptr fetch(); @@ -275,7 +275,7 @@ class query_execute_where_intermediate : public query_execute_finish public: using query_execute_finish::query_execute_finish; - query_execute_finish limit(int limit); + query_order_by_intermediate order_by(const column &col); }; class query_set_intermediate : public query_execute_finish diff --git a/include/matador/sql/query_part.hpp b/include/matador/sql/query_part.hpp index 18147da..4e6266b 100644 --- a/include/matador/sql/query_part.hpp +++ b/include/matador/sql/query_part.hpp @@ -16,6 +16,8 @@ public: virtual ~query_part() = default; virtual void accept(query_part_visitor &visitor) = 0; + [[nodiscard]] dialect::token_t token() const; + protected: sql::dialect::token_t token_; }; diff --git a/include/matador/sql/query_part_visitor.hpp b/include/matador/sql/query_part_visitor.hpp index 00f5123..bb42cf4 100644 --- a/include/matador/sql/query_part_visitor.hpp +++ b/include/matador/sql/query_part_visitor.hpp @@ -20,6 +20,7 @@ class query_values_part; class query_update_part; class query_set_part; class query_delete_part; +class query_delete_from_part; class query_create_part; class query_create_table_part; class query_drop_part; @@ -50,6 +51,7 @@ public: virtual void visit(query_set_part &set_part) = 0; virtual void visit(query_delete_part &delete_part) = 0; + virtual void visit(query_delete_from_part &delete_from_part) = 0; virtual void visit(query_create_part &create_part) = 0; virtual void visit(query_create_table_part &create_table_part) = 0; diff --git a/include/matador/sql/query_parts.hpp b/include/matador/sql/query_parts.hpp index 3f3b1b3..7e15741 100644 --- a/include/matador/sql/query_parts.hpp +++ b/include/matador/sql/query_parts.hpp @@ -157,7 +157,7 @@ class query_offset_part : public query_part public: explicit query_offset_part(size_t offset); - size_t offset() const; + [[nodiscard]] size_t offset() const; private: void accept(query_part_visitor &visitor) override; @@ -171,7 +171,7 @@ class query_limit_part : public query_part public: explicit query_limit_part(size_t limit); - size_t limit() const; + [[nodiscard]] size_t limit() const; private: void accept(query_part_visitor &visitor) override; @@ -210,7 +210,7 @@ private: class query_values_part : public query_part { public: - query_values_part(std::vector &&values); + explicit query_values_part(std::vector &&values); [[nodiscard]] const std::vector& values() const; @@ -258,6 +258,20 @@ private: void accept(query_part_visitor &visitor) override; }; +class query_delete_from_part : public query_part +{ +public: + query_delete_from_part(sql::table table); + + [[nodiscard]] const sql::table& table() const; + +private: + void accept(query_part_visitor &visitor) override; + +private: + sql::table table_; +}; + class query_create_part : public query_part { public: @@ -273,7 +287,7 @@ public: query_create_table_part(sql::table table, std::vector columns); [[nodiscard]] const sql::table& table() const; - const std::vector& columns() const; + [[nodiscard]] const std::vector& columns() const; private: void accept(query_part_visitor &visitor) override; diff --git a/include/matador/sql/schema.hpp b/include/matador/sql/schema.hpp index 2780c66..b11e85d 100644 --- a/include/matador/sql/schema.hpp +++ b/include/matador/sql/schema.hpp @@ -17,8 +17,8 @@ struct table_info { std::string name; record prototype; - void create(connection &conn) const; - void drop(connection &conn) const; + void create(connection &conn, schema &scm) const; + void drop(connection &conn, schema &scm) const; }; class schema diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index ea33cb7..4197c36 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -34,6 +34,7 @@ set(SQL_SOURCES sql/query_compiler.cpp sql/noop_connection.cpp sql/query_part.cpp + sql/any_type_to_string_visitor.cpp ) set(SQL_HEADER @@ -83,6 +84,8 @@ set(SQL_HEADER ../include/matador/sql/table.hpp ../include/matador/sql/noop_connection.hpp ../include/matador/sql/query_part.hpp + ../include/matador/sql/any_type_to_string_visitor.hpp + ../include/matador/sql/query_helper.hpp ) set(QUERY_SOURCES diff --git a/src/sql/any_type_to_string_visitor.cpp b/src/sql/any_type_to_string_visitor.cpp new file mode 100644 index 0000000..7112c8a --- /dev/null +++ b/src/sql/any_type_to_string_visitor.cpp @@ -0,0 +1,40 @@ +#include "matador/sql/any_type_to_string_visitor.hpp" + +#include "matador/sql/dialect.hpp" +#include "matador/sql/query_context.hpp" + +#include "matador/utils/string.hpp" + +namespace matador::sql { + +any_type_to_string_visitor::any_type_to_string_visitor(const dialect &d, query_context &query) +: d(d), query(query) +{} + +void any_type_to_string_visitor::to_string(const char *val) +{ + result = "'" + d.prepare_literal(val) + "'"; +} + +void any_type_to_string_visitor::to_string(std::string &val) +{ + result = "'" + d.prepare_literal(val) + "'"; +} + +void any_type_to_string_visitor::to_string(utils::blob &val) +{ + // "This is a binary Data string" as binary data: + // MySQL: X'5468697320697320612062616E617279204461746120737472696E67' + // Postgres: E'\\x5468697320697320612062616E617279204461746120737472696E67' + // MSSQL: 0x5468697320697320612062616E617279204461746120737472696E67 + // Sqlite: X'5468697320697320612062616E617279204461746120737472696E67' + result = d.token_at(dialect::token_t::BEGIN_BINARY_DATA) + utils::to_string(val) + d.token_at(dialect::token_t::END_BINARY_DATA); +} + +void any_type_to_string_visitor::to_string(placeholder &/*val*/) +{ + query.bind_vars.emplace_back("unknown"); + result = d.next_placeholder(query.bind_vars); +} + +} \ No newline at end of file diff --git a/src/sql/connection.cpp b/src/sql/connection.cpp index 7ebbf64..7e93172 100644 --- a/src/sql/connection.cpp +++ b/src/sql/connection.cpp @@ -97,7 +97,7 @@ sql::query connection::query(const std::shared_ptr &schema) const query_result connection::fetch(const query_context &q) const { if (q.prototype.empty() || q.prototype.unknown()) { - const auto table_prototype = describe(q.table_name); + const auto table_prototype = describe(q.table.name); for (auto &col : q.prototype) { if (const auto rit = table_prototype.find(col.name()); col.type() == data_type_t::type_unknown && rit != table_prototype.end()) { const_cast(col).type(rit->type()); diff --git a/src/sql/query_builder.cpp b/src/sql/query_builder.cpp index fbdf79e..9631ed2 100644 --- a/src/sql/query_builder.cpp +++ b/src/sql/query_builder.cpp @@ -173,7 +173,7 @@ query_builder &query_builder::update(const std::string &table) { initialize(command_t::UPDATE, state_t::QUERY_UPDATE); - query_.table_name = table; + query_.table = {table}; query_parts_.emplace_back(dialect::token_t::UPDATE, dialect_.token_at(dialect::token_t::UPDATE) + " " + dialect_.prepare_identifier(table)); return *this; @@ -213,7 +213,7 @@ query_builder &query_builder::table(const std::string &table, const std::vector< transition_to(state_t::QUERY_TABLE_CREATE); query_parts_.emplace_back(dialect::token_t::TABLE, " " + dialect_.token_at(dialect::token_t::TABLE) + " " + dialect_.prepare_identifier(table) + " "); - query_.table_name = table; + query_.table = {table}; std::string result = "("; @@ -252,7 +252,7 @@ query_builder &query_builder::table(const std::string &table) transition_to(state_t::QUERY_TABLE_DROP); query_parts_.emplace_back(dialect::token_t::TABLE, " " + dialect_.token_at(dialect::token_t::TABLE) + " " + dialect_.prepare_identifier(table)); - query_.table_name = table; + query_.table = {table}; return *this; } @@ -267,7 +267,7 @@ query_builder &query_builder::into(const std::string &table, const std::vector query_select_finish::fetch() @@ -184,10 +184,10 @@ statement query_execute_finish::prepare() return connection_.prepare(compiler.compile(&data_)); } -std::string query_execute_finish::str() const +query_context query_execute_finish::build() const { query_compiler compiler(connection_.dialect()); - return compiler.compile(&data_).sql; + return compiler.compile(&data_); } query_execute_finish query_into_intermediate::values(std::initializer_list values) @@ -229,10 +229,10 @@ query_execute_finish query_drop_intermediate::table(const sql::table &table) return {connection_, schema_, data_}; } -query_execute_finish query_execute_where_intermediate::limit(int limit) +query_order_by_intermediate query_execute_where_intermediate::order_by(const column &col) { + data_.parts.push_back(std::make_unique(col)); return {connection_, schema_, data_}; -// return {connection_, builder_.limit(limit)}; } query_execute_where_intermediate query_set_intermediate::where_clause(std::unique_ptr &&cond) @@ -273,6 +273,7 @@ query_delete_intermediate::query_delete_intermediate(connection &db, const std:: query_delete_from_intermediate query_delete_intermediate::from(const sql::table &table) { + data_.parts.push_back(std::make_unique(table)); return {connection_, schema_, data_}; } diff --git a/src/sql/query_part.cpp b/src/sql/query_part.cpp index 6782690..8e52e60 100644 --- a/src/sql/query_part.cpp +++ b/src/sql/query_part.cpp @@ -5,5 +5,9 @@ namespace matador::sql { query_part::query_part(sql::dialect::token_t token) : token_(token) {} +dialect::token_t query_part::token() const +{ + return token_; +} } \ No newline at end of file diff --git a/src/sql/query_parts.cpp b/src/sql/query_parts.cpp index a39f4a1..aa554d4 100644 --- a/src/sql/query_parts.cpp +++ b/src/sql/query_parts.cpp @@ -233,6 +233,20 @@ void query_delete_part::accept(query_part_visitor &visitor) visitor.visit(*this); } +query_delete_from_part::query_delete_from_part(sql::table table) +: query_part(sql::dialect::token_t::FROM) +, table_(std::move(table)) {} + +const sql::table &query_delete_from_part::table() const +{ + return table_; +} + +void query_delete_from_part::accept(query_part_visitor &visitor) +{ + visitor.visit(*this); +} + query_create_part::query_create_part() : query_part(sql::dialect::token_t::CREATE) {} diff --git a/src/sql/schema.cpp b/src/sql/schema.cpp index eaa3d61..1802656 100644 --- a/src/sql/schema.cpp +++ b/src/sql/schema.cpp @@ -5,12 +5,12 @@ namespace matador::sql { -void table_info::create(connection &conn) const +void table_info::create(connection &conn, schema &scm) const { // conn.query().create().table(name, prototype.columns()).execute(); } -void table_info::drop(connection &conn) const +void table_info::drop(connection &conn, schema &scm) const { // conn.query().drop().table(name).execute(); } diff --git a/src/sql/session.cpp b/src/sql/session.cpp index eb5be91..a1cb688 100644 --- a/src/sql/session.cpp +++ b/src/sql/session.cpp @@ -15,7 +15,7 @@ void session::create_schema() { auto c = pool_.acquire(); for (const auto &t : *schema_) { - t.second.create(*c); + t.second.create(*c, *schema_); } } @@ -35,9 +35,9 @@ query_result session::fetch(const query_context &q) const if (!c.valid()) { throw std::logic_error("no database connection available"); } - auto it = prototypes_.find(q.table_name); + auto it = prototypes_.find(q.table.name); if (it == prototypes_.end()) { - it = prototypes_.emplace(q.table_name, c->describe(q.table_name)).first; + it = prototypes_.emplace(q.table.name, c->describe(q.table.name)).first; } // adjust columns from given query for (auto &col : q.prototype) { diff --git a/test/QueryBuilderTest.cpp b/test/QueryBuilderTest.cpp index db87278..dc5e148 100644 --- a/test/QueryBuilderTest.cpp +++ b/test/QueryBuilderTest.cpp @@ -10,190 +10,242 @@ using namespace matador::sql; using namespace matador::utils; -TEST_CASE("Create table sql statement string", "[query]") { +TEST_CASE("Create table sql statement string", "[query]") +{ connection noop("noop://noop.db"); auto scm = std::make_shared("noop"); - query qu(noop, scm); - auto s = qu.create().table({"person"}, { - make_pk_column("id"), - make_column("name", 255), - make_column("age") - }).str(); + query q(noop, scm); + auto result = q.create().table({"person"}, { + make_pk_column("id"), + make_column("name", 255), + make_column("age") + }).build(); - dialect d = dialect_builder::builder().create().build(); - query_builder query(d); - auto q = query.create().table("person", { - make_pk_column("id"), - make_column("name", 255), - make_column("age") - }).compile(); + REQUIRE(result.sql == R"##(CREATE TABLE "person" ("id" BIGINT NOT NULL, "name" VARCHAR(255) NOT NULL, "age" INTEGER NOT NULL, CONSTRAINT PK_person PRIMARY KEY (id)))##"); + REQUIRE(result.table.name == "person"); - REQUIRE(s == R"##(CREATE TABLE "person" ("id" BIGINT NOT NULL, "name" VARCHAR(255) NOT NULL, "age" INTEGER NOT NULL, CONSTRAINT PK_person PRIMARY KEY (id)))##"); -// REQUIRE(q.sql == R"##(CREATE TABLE "person" ("id" BIGINT NOT NULL, "name" VARCHAR(255) NOT NULL, "age" INTEGER NOT NULL, CONSTRAINT PK_person PRIMARY KEY (id)))##"); - REQUIRE(q.table_name == "person"); + result = q.create().table("person", { + make_pk_column("id"), + make_column("name", {255, constraints::UNIQUE}, null_option::NOT_NULL), + make_column("age"), + make_fk_column("address", "address", "id") + }).build(); - q = query.create().table("person", { - make_pk_column("id"), - make_column("name", { 255, constraints::UNIQUE }, null_option::NOT_NULL), - make_column("age"), - make_fk_column("address", "address", "id") - }).compile(); - - REQUIRE(q.sql == R"##(CREATE TABLE "person" ("id" BIGINT NOT NULL, "name" VARCHAR(255) NOT NULL UNIQUE, "age" INTEGER NOT NULL, "address" BIGINT NOT NULL, CONSTRAINT PK_person PRIMARY KEY (id), CONSTRAINT FK_person_address FOREIGN KEY (address) REFERENCES address(id)))##"); - REQUIRE(q.table_name == "person"); + REQUIRE(result.sql == R"##(CREATE TABLE "person" ("id" BIGINT NOT NULL, "name" VARCHAR(255) NOT NULL UNIQUE, "age" INTEGER NOT NULL, "address" BIGINT NOT NULL, CONSTRAINT PK_person PRIMARY KEY (id), CONSTRAINT FK_person_address FOREIGN KEY (address) REFERENCES address(id)))##"); + REQUIRE(result.table.name == "person"); } -TEST_CASE("Drop table sql statement string", "[query]") { - dialect d = dialect_builder::builder().create().build(); - query_builder query(d); - const auto q = query.drop().table("person").compile(); +TEST_CASE("Drop table sql statement string", "[query]") +{ + connection noop("noop://noop.db"); + auto scm = std::make_shared("noop"); + query q(noop, scm); + const auto result = q.drop().table("person").build(); - REQUIRE(q.sql == R"(DROP TABLE "person")"); - REQUIRE(q.table_name == "person"); + REQUIRE(result.sql == R"(DROP TABLE "person")"); + REQUIRE(result.table.name == "person"); } -TEST_CASE("Select sql statement string", "[query]") { - dialect d = dialect_builder::builder().create().build(); - query_builder query(d); - const auto q = query.select({"id", "name", "age"}).from("person").compile(); +TEST_CASE("Select sql statement string", "[query]") +{ + connection noop("noop://noop.db"); + auto scm = std::make_shared("noop"); + query q(noop, scm); + const auto result = q.select({"id", "name", "age"}).from("person").build(); - REQUIRE(q.sql == R"(SELECT "id", "name", "age" FROM "person")"); - REQUIRE(q.table_name == "person"); + REQUIRE(result.sql == R"(SELECT "id", "name", "age" FROM "person")"); + REQUIRE(result.table.name == "person"); } -TEST_CASE("Insert sql statement string", "[query]") { - dialect d = dialect_builder::builder().create().build(); - query_builder query(d); - const auto q = query.insert().into("person", { - "id", "name", "age" - }).values({7UL, "george", 65U}).compile(); - - REQUIRE(q.sql == R"(INSERT INTO "person" ("id", "name", "age") VALUES (7, 'george', 65))"); - REQUIRE(q.table_name == "person"); -} - -TEST_CASE("Update sql statement string", "[query]") { - dialect d = dialect_builder::builder().create().build(); - query_builder query(d); - const auto q = query.update("person").set({ - {"id", 7UL}, - {"name", "george"}, - {"age", 65U} - }).compile(); - - REQUIRE(q.sql == R"(UPDATE "person" SET "id"=7, "name"='george', "age"=65)"); - REQUIRE(q.table_name == "person"); -} - -TEST_CASE("Delete sql statement string", "[query]") { - dialect d = dialect_builder::builder().create().build(); - query_builder query(d); - const auto q = query.remove().from("person").compile(); - - REQUIRE(q.sql == R"(DELETE FROM "person")"); - REQUIRE(q.table_name == "person"); -} - -TEST_CASE("Select sql statement string with where clause", "[query]") { - dialect d = dialect_builder::builder().create().build(); - query_builder query(d); - auto q = query.select({"id", "name", "age"}) - .from("person") - .where("id"_col == 8 && "age"_col > 50) - .compile(); - - REQUIRE(q.sql == R"(SELECT "id", "name", "age" FROM "person" WHERE ("id" = 8 AND "age" > 50))"); - REQUIRE(q.table_name == "person"); - - q = query.select({"id", "name", "age"}) - .from("person") - .where("id"_col == _ && "age"_col > 50) - .compile(); - - REQUIRE(q.sql == R"(SELECT "id", "name", "age" FROM "person" WHERE ("id" = ? AND "age" > 50))"); - REQUIRE(q.table_name == "person"); -} - -TEST_CASE("Insert sql statement with placeholder", "[query]") { - dialect d = dialect_builder::builder().create().build(); - query_builder query(d); - const auto q = query.insert().into("person", { +TEST_CASE("Insert sql statement string", "[query]") +{ + connection noop("noop://noop.db"); + auto scm = std::make_shared("noop"); + query q(noop, scm); + const auto result = q.insert().into("person", { "id", "name", "age" - }).values({_, _, _}).compile(); + }).values({7UL, "george", 65U}).build(); - REQUIRE(q.sql == R"(INSERT INTO "person" ("id", "name", "age") VALUES (?, ?, ?))"); - REQUIRE(q.table_name == "person"); - REQUIRE(q.bind_vars.size() == 3); + REQUIRE(result.sql == R"(INSERT INTO "person" ("id", "name", "age") VALUES (7, 'george', 65))"); + REQUIRE(result.table.name == "person"); } -TEST_CASE("Select sql statement string with order by", "[query]") { - dialect d = dialect_builder::builder().create().build(); - query_builder query(d); - const auto q = query.select({"id", "name", "age"}) - .from("person") - .order_by("name").asc() - .compile(); +TEST_CASE("Update sql statement string", "[query]") +{ + connection noop("noop://noop.db"); + auto scm = std::make_shared("noop"); + query q(noop, scm); + const auto result = q.update("person").set({ + {"id", 7UL}, + {"name", "george"}, + {"age", 65U} + }).build(); - REQUIRE(q.sql == R"(SELECT "id", "name", "age" FROM "person" ORDER BY "name" ASC)"); - REQUIRE(q.table_name == "person"); + REQUIRE(result.sql == R"(UPDATE "person" SET "id"=7, "name"='george', "age"=65)"); + REQUIRE(result.table.name == "person"); } -TEST_CASE("Select sql statement string with group by", "[query]") { - dialect d = dialect_builder::builder().create().build(); - query_builder query(d); - const auto q = query.select({"id", "name", "age"}) - .from("person") - .group_by("age") - .compile(); +TEST_CASE("Update limit sql statement", "[query][update][limit]") +{ + connection noop("noop://noop.db"); + auto scm = std::make_shared("noop"); + query q(noop, scm); + const auto result = q.update("person") + .set({{"id", 7UL}, {"name", "george"}, {"age", 65U}}) + .where("name"_col == "george") + .order_by("id"_col).asc() + .limit(2) + .build(); - REQUIRE(q.sql == R"(SELECT "id", "name", "age" FROM "person" GROUP BY "age")"); - REQUIRE(q.table_name == "person"); + REQUIRE(result.sql == R"(UPDATE "person" SET "id"=7, "name"='george', "age"=65 WHERE "name" = 'george' ORDER BY "id" ASC LIMIT 2)"); + REQUIRE(result.table.name == "person"); } -TEST_CASE("Select sql statement string with offset and limit", "[query]") { - dialect d = dialect_builder::builder().create().build(); - query_builder query(d); - const auto q = query.select({"id", "name", "age"}) - .from("person") - .offset(10) - .limit(20) - .compile(); +TEST_CASE("Delete sql statement string", "[query]") +{ + connection noop("noop://noop.db"); + auto scm = std::make_shared("noop"); + query q(noop, scm); + const auto result = q.remove().from("person").build(); - REQUIRE(q.sql == R"(SELECT "id", "name", "age" FROM "person" OFFSET 10 LIMIT 20)"); - REQUIRE(q.table_name == "person"); + REQUIRE(result.sql == R"(DELETE FROM "person")"); + REQUIRE(result.table.name == "person"); } -TEST_CASE("Create, insert and select a blob column", "[query][blob]") { - dialect d = dialect_builder::builder().create().build(); - query_builder query(d); - auto q = query.create().table("person", { - make_pk_column("id"), - make_column("name", 255), - make_column("data") - }).compile(); +TEST_CASE("Delete limit sql statement", "[query][delete][limit]") +{ + connection noop("noop://noop.db"); + auto scm = std::make_shared("noop"); + query q(noop, scm); + const auto result = q.remove() + .from("person") + .where("name"_col == "george") + .order_by("id"_col).asc() + .limit(2) + .build(); - REQUIRE(q.sql == R"##(CREATE TABLE "person" ("id" BIGINT NOT NULL, "name" VARCHAR(255) NOT NULL, "data" BLOB NOT NULL, CONSTRAINT PK_person PRIMARY KEY (id)))##"); - REQUIRE(q.table_name == "person"); - - q = query.insert().into("person", { - { "id", "name", "data" } - }).values({7UL, "george", blob{1, 'A', 3, 4}}).compile(); - - REQUIRE(q.sql == R"(INSERT INTO "person" ("id", "name", "data") VALUES (7, 'george', X'01410304'))"); - REQUIRE(q.table_name == "person"); - - q = query.select({"id", "name", "data"}).from("person").compile(); - - REQUIRE(q.sql == R"(SELECT "id", "name", "data" FROM "person")"); - REQUIRE(q.table_name == "person"); + REQUIRE(result.sql == R"(DELETE FROM "person" WHERE "name" = 'george' ORDER BY "id" ASC LIMIT 2)"); + REQUIRE(result.table.name == "person"); } -TEST_CASE("Select statement with join_left", "[query][join_left]") { - dialect d = dialect_builder::builder().create().build(); - query_builder query(d); +TEST_CASE("Select sql statement string with where clause", "[query]") +{ + connection noop("noop://noop.db"); + auto scm = std::make_shared("noop"); + query q(noop, scm); + auto result = q.select({"id", "name", "age"}) + .from("person") + .where("id"_col == 8 && "age"_col > 50) + .build(); - auto q = query.select({"f.id", "ap.brand", "f.pilot_name"}).from("flight", "f").join("airplane", join_type_t::INNER, "ap").on("f.airplane_id", "ap.id").compile(); + REQUIRE(result.sql == R"(SELECT "id", "name", "age" FROM "person" WHERE ("id" = 8 AND "age" > 50))"); + REQUIRE(result.table.name == "person"); - REQUIRE(q.sql == R"(SELECT "f.id", "ap.brand", "f.pilot_name" FROM "flight" AS "f" INNER JOIN "airplane" AS "ap" ON "f.airplane_id"="ap.id")"); - REQUIRE(q.table_name == "flight"); + result = q.select({"id", "name", "age"}) + .from("person") + .where("id"_col == _ && "age"_col > 50) + .build(); + + REQUIRE(result.sql == R"(SELECT "id", "name", "age" FROM "person" WHERE ("id" = ? AND "age" > 50))"); + REQUIRE(result.table.name == "person"); +} + +TEST_CASE("Insert sql statement with placeholder", "[query]") +{ + connection noop("noop://noop.db"); + auto scm = std::make_shared("noop"); + query q(noop, scm); + const auto result = q.insert().into("person", { + "id", "name", "age" + }).values({_, _, _}).build(); + + REQUIRE(result.sql == R"(INSERT INTO "person" ("id", "name", "age") VALUES (?, ?, ?))"); + REQUIRE(result.table.name == "person"); + REQUIRE(result.bind_vars.size() == 3); +} + +TEST_CASE("Select sql statement string with order by", "[query]") +{ + connection noop("noop://noop.db"); + auto scm = std::make_shared("noop"); + query q(noop, scm); + const auto result = q.select({"id", "name", "age"}) + .from("person") + .order_by("name").asc() + .build(); + + REQUIRE(result.sql == R"(SELECT "id", "name", "age" FROM "person" ORDER BY "name" ASC)"); + REQUIRE(result.table.name == "person"); +} + +TEST_CASE("Select sql statement string with group by", "[query]") +{ + connection noop("noop://noop.db"); + auto scm = std::make_shared("noop"); + query q(noop, scm); + const auto result = q.select({"id", "name", "age"}) + .from("person") + .group_by("age") + .build(); + + REQUIRE(result.sql == R"(SELECT "id", "name", "age" FROM "person" GROUP BY "age")"); + REQUIRE(result.table.name == "person"); +} + +TEST_CASE("Select sql statement string with offset and limit", "[query]") +{ + connection noop("noop://noop.db"); + auto scm = std::make_shared("noop"); + query q(noop, scm); + const auto result = q.select({"id", "name", "age"}) + .from("person") + .order_by("id"_col).asc() + .limit(20) + .offset(10) + .build(); + + REQUIRE(result.sql == R"(SELECT "id", "name", "age" FROM "person" ORDER BY "id" ASC LIMIT 20 OFFSET 10)"); + REQUIRE(result.table.name == "person"); +} + +TEST_CASE("Create, insert and select a blob column", "[query][blob]") +{ + connection noop("noop://noop.db"); + auto scm = std::make_shared("noop"); + query q(noop, scm); + auto result = q.create().table("person", { + make_pk_column("id"), + make_column("name", 255), + make_column("data") + }).build(); + + REQUIRE(result.sql == R"##(CREATE TABLE "person" ("id" BIGINT NOT NULL, "name" VARCHAR(255) NOT NULL, "data" BLOB NOT NULL, CONSTRAINT PK_person PRIMARY KEY (id)))##"); + REQUIRE(result.table.name == "person"); + + result = q.insert().into("person", { + "id", "name", "data" + }).values({7UL, "george", blob{1, 'A', 3, 4}}).build(); + + REQUIRE(result.sql == R"(INSERT INTO "person" ("id", "name", "data") VALUES (7, 'george', X'01410304'))"); + REQUIRE(result.table.name == "person"); + + result = q.select({"id", "name", "data"}).from("person").build(); + + REQUIRE(result.sql == R"(SELECT "id", "name", "data" FROM "person")"); + REQUIRE(result.table.name == "person"); +} + +TEST_CASE("Select statement with join_left", "[query][join_left]") +{ + connection noop("noop://noop.db"); + auto scm = std::make_shared("noop"); + query q(noop, scm); + auto result = q.select({"f.id", "ap.brand", "f.pilot_name"}) + .from({"flight", "f"}) + .join_left({"airplane", "ap"}) + .on("f.airplane_id"_col == "ap.id"_col) + .build(); + + REQUIRE(result.sql == R"(SELECT "f"."id", "ap"."brand", "f"."pilot_name" FROM "flight" AS "f" INNER JOIN "airplane" AS "ap" ON "f"."airplane_id" = "ap"."id")"); + REQUIRE(result.table.name == "flight"); } \ No newline at end of file