#include "catch2/catch_test_macros.hpp" #include "matador/sql/column_definition.hpp" #include "matador/sql/condition.hpp" #include "matador/sql/query_builder.hpp" #include "matador/sql/session.hpp" #include "connection.hpp" #include "models/airplane.hpp" #include "models/flight.hpp" #include "models/person.hpp" #include "models/recipe.hpp" #include using namespace matador::sql; using namespace matador::test; class QueryFixture { public: QueryFixture() : db(matador::test::connection::dns) , schema(db.dialect().default_schema_name()) { db.open(); } ~QueryFixture() { drop_table_if_exists("flight"); drop_table_if_exists("airplane"); drop_table_if_exists("person"); drop_table_if_exists("recipe_ingredients"); drop_table_if_exists("recipes"); drop_table_if_exists("ingredients"); } protected: matador::sql::connection db; matador::sql::schema schema; private: void drop_table_if_exists(const std::string &table_name) { if (db.exists(table_name)) { db.query(schema).drop().table(table_name).execute(); } } }; TEST_CASE_METHOD(QueryFixture, "Create table with foreign key relation", "[session]") { schema.attach("airplane"); schema.attach("flight"); db.query(schema).create() .table("airplane") .execute(); REQUIRE(db.exists("airplane")); db.query(schema).create() .table("flight") .execute(); REQUIRE(db.exists("flight")); db.query(schema).drop().table("flight").execute(); db.query(schema).drop().table("airplane").execute(); REQUIRE(!db.exists("flight")); REQUIRE(!db.exists("airplane")); } TEST_CASE_METHOD(QueryFixture, "Execute select statement with where clause", "[session]") { schema.attach("person"); db.query(schema).create() .table("person") .execute(); person george{7, "george", 45}; george.image.push_back(37); auto res = db.query(schema) .insert() .into("person", column_generator::generate(schema, true)) .values(george) .execute(); REQUIRE(res == 1); // fetch person as record auto result_record = db.query(schema) .select(column_generator::generate(schema, true)) .from("person") .where("id"_col == 7) .fetch_all(); for (const auto &i: result_record) { REQUIRE(i.size() == 4); REQUIRE(i.at(0).name() == "id"); REQUIRE(i.at(0).is_integer()); REQUIRE(i.at(0).as() == george.id); REQUIRE(i.at(1).name() == "name"); REQUIRE(i.at(1).is_varchar()); REQUIRE(i.at(1).as() == george.name); REQUIRE(i.at(2).name() == "age"); REQUIRE(i.at(2).is_integer()); REQUIRE(i.at(2).as() == george.age); } // fetch person as person auto result_person = db.query(schema) .select(column_generator::generate(schema, true)) .from("person") .where("id"_col == 7) .fetch_all(); for (const auto &i: result_person) { REQUIRE(i.id == 7); REQUIRE(i.name == "george"); REQUIRE(i.age == 45); } db.query(schema).drop().table("person").execute(); } TEST_CASE_METHOD(QueryFixture, "Execute insert statement", "[session]") { db.query(schema).create() .table("person", { make_pk_column("id"), make_column("name", 255), make_column("color", 63) }) .execute(); auto res = db.query(schema).insert() .into("person", {{"", "id", ""}, {"", "name", ""}, {"", "color", ""}}) .values({7, "george", "green"}) .execute(); REQUIRE(res == 1); // fetch person as record auto result_record = db.query(schema).select({"id", "name", "color"}) .from("person") .where("id"_col == 7) .fetch_all(); for (const auto &i: result_record) { REQUIRE(i.size() == 3); REQUIRE(i.at(0).name() == "id"); REQUIRE(i.at(0).is_integer()); REQUIRE(i.at(0).as() == 7); REQUIRE(i.at(1).name() == "name"); REQUIRE(i.at(1).is_varchar()); REQUIRE(i.at(1).as() == "george"); REQUIRE(i.at(2).name() == "color"); REQUIRE(i.at(2).is_varchar()); REQUIRE(i.at(2).as() == "green"); } db.query(schema).drop().table("person").execute(); } TEST_CASE_METHOD(QueryFixture, "Select statement with foreign key", "[session]") { schema.attach("airplane"); schema.attach("flight"); db.query(schema).create() .table("airplane") .execute(); db.query(schema).create() .table("flight") .execute(); std::vector> planes{ make_entity(1, "Airbus", "A380"), make_entity(2, "Boeing", "707"), make_entity(3, "Boeing", "747") }; for (const auto &plane: planes) { auto res = db.query(schema) .insert() .into("airplane", column_generator::generate(schema, true)) .values(*plane) .execute(); REQUIRE(res == 1); } auto count = db.query(schema) .select({count_all()}) .from("airplane") .fetch_value().value(); REQUIRE(count == 3); flight f4711{4, planes.at(1), "hans"}; auto res = db.query(schema) .insert() .into("flight", column_generator::generate(schema, true)) .values(f4711) .execute(); REQUIRE(res == 1); auto f = *db.query(schema) .select(column_generator::generate(schema, true)) .from("flight") .fetch_all().begin(); REQUIRE(f.at(0).as() == 4); REQUIRE(f.at(1).as() == 2); REQUIRE(f.at(2).as() == "hans"); db.query(schema).drop().table("flight").execute(); db.query(schema).drop().table("airplane").execute(); } TEST_CASE_METHOD(QueryFixture, "Select statement with foreign key and join_left", "[session][join_left]") { schema.attach("airplane"); schema.attach("flight"); db.query(schema).create() .table("airplane") .execute(); db.query(schema).create() .table("flight") .execute(); std::vector> planes{ make_entity(1, "Airbus", "A380"), make_entity(2, "Boeing", "707"), make_entity(3, "Boeing", "747") }; for (const auto &plane: planes) { auto res = db.query(schema) .insert() .into("airplane", column_generator::generate(schema, true)) .values(*plane) .execute(); REQUIRE(res == 1); } auto count = db.query(schema) .select({count_all()}) .from("airplane") .fetch_value().value(); REQUIRE(count == 3); std::vector> flights{ make_entity(4, planes.at(0), "hans"), make_entity(5, planes.at(0), "otto"), make_entity(6, planes.at(1), "george"), make_entity(7, planes.at(2), "paul") }; for (const auto &f: flights) { auto res = db.query(schema) .insert() .into("flight", {"id", "airplane_id", "pilot_name"}) .values(*f) .execute(); REQUIRE(res == 1); } auto f = *db.query(schema) .select(column_generator::generate(schema, true)) .from("flight") .fetch_all().begin(); REQUIRE(f.at(0).as() == 4); REQUIRE(f.at(1).as() == 1); REQUIRE(f.at(2).as() == "hans"); auto result = db.query(schema).select({"f.id", "ap.brand", "ap.model", "f.pilot_name"}) .from({"flight", "f"}) .join_left({"airplane", "ap"}) .on("f.airplane_id"_col == "ap.id"_col) .order_by("f.id").asc() .fetch_all(); std::vector> expected_result { {4, "hans"}, {5, "otto"}, {6, "george"}, {7, "paul"} }; size_t index{0}; for (const auto &r: result) { REQUIRE(r.size() == 4); REQUIRE(r.at(0).as() == expected_result[index].first); REQUIRE(r.at(3).as() == expected_result[index++].second); } db.query(schema).drop().table("flight").execute(); db.query(schema).drop().table("airplane").execute(); } TEST_CASE_METHOD(QueryFixture, "Select statement with foreign key and for single entity", "[session][join_left][find]") { schema.attach("airplane"); schema.attach("flight"); db.query(schema).create() .table("airplane") .execute(); db.query(schema).create() .table("flight") .execute(); std::vector> planes{ make_entity(1, "Airbus", "A380"), make_entity(2, "Boeing", "707"), make_entity(3, "Boeing", "747") }; for (const auto &plane: planes) { auto res = db .query(schema) .insert() .into("airplane", column_generator::generate(schema, true)) .values(*plane) .execute(); REQUIRE(res == 1); } auto count = db .query(schema) .select({count_all()}) .from("airplane") .fetch_value().value(); REQUIRE(count == 3); std::vector> flights{ make_entity(4, planes.at(0), "hans"), make_entity(5, planes.at(0), "otto"), make_entity(6, planes.at(1), "george"), make_entity(7, planes.at(2), "paul") }; for (const auto &f: flights) { auto res = db.query(schema) .insert() .into("flight", column_generator::generate(schema, true)) .values(*f) .execute(); REQUIRE(res == 1); } auto f = db.query(schema) .select(column_generator::generate(schema, true)) .from("flight") .fetch_one(); REQUIRE(f.has_value()); REQUIRE(f->at(0).as() == 4); REQUIRE(f->at(1).as() == 1); REQUIRE(f->at(2).as() == "hans"); auto result = db .query(schema) .select({"f.id", "f.airplane_id", "ap.brand", "ap.model", "f.pilot_name"}) .from({"flight", "f"}) .join_left({"airplane", "ap"}) .on("f.airplane_id"_col == "ap.id"_col) .where("f.id"_col == 4) .fetch_one(); auto expected_flight = flights[0]; REQUIRE(result.has_value()); REQUIRE(result->id == expected_flight->id); REQUIRE(result->pilot_name == expected_flight->pilot_name); REQUIRE(result->airplane.get()); REQUIRE(result->airplane->id == 1); REQUIRE(result->airplane->model == "A380"); REQUIRE(result->airplane->brand == "Airbus"); db.query(schema).drop().table("flight").execute(); db.query(schema).drop().table("airplane").execute(); } TEST_CASE_METHOD(QueryFixture, "Select statement with many to many relationship", "[session][join][many_to_many]") { schema.attach("recipes"); schema.attach("ingredients"); schema.attach("recipe_ingredients"); db.query(schema).create() .table("recipes") .execute(); db.query(schema).create() .table("ingredients") .execute(); db.query(schema).create() .table("recipe_ingredients") .execute(); std::vector ingredients { {1, "Apple"}, {2, "Strawberry"}, {3, "Pineapple"}, {4, "Sugar"}, {5, "Flour"}, {6, "Butter"}, {7, "Beans"} }; for (const auto &i: ingredients) { auto res = db .query(schema) .insert() .into("ingredients", column_generator::generate(schema, true)) .values(i) .execute(); REQUIRE(res == 1); } std::vector recipes{ {7, "Apple Crumble"}, {8, "Beans Chili"}, {9, "Fruit Salad"} }; for (const auto &r: recipes) { auto res = db .query(schema) .insert() .into("recipes", column_generator::generate(schema, true)) .values(r) .execute(); REQUIRE(res == 1); } std::vector> recipe_ingredients { { 7, 1 }, { 7, 4 }, { 7, 5 }, { 8, 6 }, { 8, 7 }, { 9, 1 }, { 9, 2 }, { 9, 3 } }; for (const auto &ri: recipe_ingredients) { auto res = db .query(schema) .insert() .into("recipe_ingredients", column_generator::generate(schema, true)) .values({ri.first, ri.second}) .execute(); REQUIRE(res == 1); } auto result = db .query(schema) .select({"r.id", "r.name", "ri.recipe_id"}) .from({"recipes", "r"}) .join_left({"recipe_ingredients", "ri"}) .on("r.id"_col == "ri.recipe_id"_col) .fetch_all(); for (const auto &r: result) { REQUIRE(r.size() == 3); std::cout << "record r.id " << r.at(0).as().value() << " r.name " << r.at(1).as().value() << " ri.id " << r.at(2).as().value() << "\n"; } result = db .query(schema) .select({"r.id", "r.name", "ri.recipe_id", "i.name"}) .from({"recipes", "r"}) .join_left({"recipe_ingredients", "ri"}).on("r.id"_col == "ri.recipe_id"_col) .join_left({"ingredients", "i"}).on("ri.ingredient_id"_col == "i.id"_col) .fetch_all(); for (const auto &r: result) { REQUIRE(r.size() == 4); std::cout << "record r.id " << r.at(0).as().value() << " r.name " << r.at(1).as().value() << " ri.id " << r.at(2).as().value() << " i.name " << r.at(3).as().value() << "\n"; } result = db .query(schema) .select({"r.id", "r.name", "ri.recipe_id", "i.name"}) .from({"recipes", "r"}) .join_left({"recipe_ingredients", "ri"}).on("r.id"_col == "ri.recipe_id"_col) .join_left({"ingredients", "i"}).on("ri.ingredient_id"_col == "i.id"_col) .where("r.id"_col == 8) .fetch_all(); for (const auto &r: result) { REQUIRE(r.size() == 4); std::cout << "record r.id " << r.at(0).as().value() << " r.name " << r.at(1).as().value() << " ri.id " << r.at(2).as().value() << " i.name " << r.at(3).as().value() << "\n"; } db.query(schema).drop().table("recipe_ingredients").execute(); db.query(schema).drop().table("recipes").execute(); db.query(schema).drop().table("ingredients").execute(); }