diff --git a/backends/postgres/test/CMakeLists.txt b/backends/postgres/test/CMakeLists.txt index efa28ee..ed1497f 100644 --- a/backends/postgres/test/CMakeLists.txt +++ b/backends/postgres/test/CMakeLists.txt @@ -12,14 +12,13 @@ list(APPEND CMAKE_MODULE_PATH ${catch2_SOURCE_DIR}/extras) include(CTest) include(Catch) -set(POSTGRES_CONNECTION_STRING "postgres://test:test123@127.0.0.1:5432/matador_test") +set(POSTGRES_CONNECTION_STRING "postgres://test:test123@127.0.0.1:15432/test") configure_file(Connection.hpp.in ${PROJECT_BINARY_DIR}/backends/postgres/test/connection.hpp @ONLY IMMEDIATE) message(STATUS "postgresql connection string: ${POSTGRES_CONNECTION_STRING}") set(TEST_SOURCES - ../../tests/QueryTest.cpp ../../tests/QueryTest.cpp ../../tests/ConnectionTest.cpp ../../tests/QueryRecordTest.cpp diff --git a/backends/tests/QueryRecordTest.cpp b/backends/tests/QueryRecordTest.cpp index 10c6f37..933b30d 100644 --- a/backends/tests/QueryRecordTest.cpp +++ b/backends/tests/QueryRecordTest.cpp @@ -252,7 +252,8 @@ TEST_CASE_METHOD(QueryRecordFixture, "Execute select statement", "[session][reco auto rec = db.query(schema).select({"id", "name", "age"}) .from("person") .fetch_one(); - REQUIRE(rec.at(1).str() == "george"); + REQUIRE(rec.has_value()); + REQUIRE(rec->at(1).str() == "george"); auto name = db.query(schema).select({"name"}) .from("person") @@ -386,15 +387,16 @@ TEST_CASE_METHOD(QueryRecordFixture, "Test quoted identifier", "[session][record auto res = db.query(schema).select({"from", "to"}).from("quotes").fetch_one(); - REQUIRE("Berlin" == res.at("from").str()); - REQUIRE("London" == res.at("to").str()); + REQUIRE(res.has_value()); + REQUIRE("Berlin" == res->at("from").str()); + REQUIRE("London" == res->at("to").str()); db.query(schema).update("quotes").set({{"from", "Hamburg"}, {"to", "New York"}}).where("from"_col == "Berlin").execute(); res = db.query(schema).select({"from", "to"}).from("quotes").fetch_one(); - REQUIRE("Hamburg" == res.at("from").str()); - REQUIRE("New York" == res.at("to").str()); + REQUIRE("Hamburg" == res->at("from").str()); + REQUIRE("New York" == res->at("to").str()); db.query(schema).drop().table("quotes").execute(); } \ No newline at end of file diff --git a/backends/tests/QueryTest.cpp b/backends/tests/QueryTest.cpp index 868ac9a..d5d3674 100644 --- a/backends/tests/QueryTest.cpp +++ b/backends/tests/QueryTest.cpp @@ -188,11 +188,13 @@ TEST_CASE_METHOD(QueryFixture, "Select statement with foreign key", "[session]") auto res = db.query(schema).insert().into("flight").values(f4711).execute(); REQUIRE(res == 1); - auto f = *db.query(schema).select().from("flight").fetch_all().begin(); - REQUIRE(f.id == 4); - REQUIRE(f.pilot_name == "hans"); - REQUIRE(f.airplane.get() != nullptr); - REQUIRE(f.airplane->id == 2); + auto f = *db.query(schema) + .select() + .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(); @@ -236,11 +238,13 @@ TEST_CASE_METHOD(QueryFixture, "Select statement with foreign key and join_left" REQUIRE(res == 1); } - auto f = *db.query(schema).select().from("flight").fetch_all().begin(); - REQUIRE(f.id == 4); - REQUIRE(f.pilot_name == "hans"); - REQUIRE(f.airplane.get() != nullptr); - REQUIRE(f.airplane->id == 1); + auto f = *db.query(schema) + .select() + .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"}) @@ -264,5 +268,81 @@ TEST_CASE_METHOD(QueryFixture, "Select statement with foreign key and join_left" 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") + .values(*plane) + .execute(); + REQUIRE(res == 1); + } + + auto count = db + .query(schema) + .select({count_all()}) + .from("airplane") + .fetch_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").values(*f).execute(); + REQUIRE(res == 1); + } + + auto f = *db.query(schema) + .select() + .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", "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(); } \ No newline at end of file diff --git a/backends/tests/SessionTest.cpp b/backends/tests/SessionTest.cpp index 70e19d2..dba4bc8 100644 --- a/backends/tests/SessionTest.cpp +++ b/backends/tests/SessionTest.cpp @@ -46,10 +46,11 @@ TEST_CASE_METHOD(SessionFixture, "Session relation test", "[session][relation]") } TEST_CASE_METHOD(SessionFixture, "Find object with id", "[session][find]") { - ses.attach("airplane"); + using namespace matador::test; + ses.attach("airplane"); ses.create_schema(); - auto a380 = ses.insert(1, "Boeing", "A380"); + auto a380 = ses.insert(1, "Boeing", "A380"); - ses.find(1); + ses.find(1); } \ No newline at end of file diff --git a/demo/author.json b/demo/author.json new file mode 100644 index 0000000..01fd813 --- /dev/null +++ b/demo/author.json @@ -0,0 +1,27 @@ +{ + "name": "book", + "fields": { + "id": { + "type": "unsigned long", + "constraint": "primary_key" + }, + "first_name": { + "type": "string", + "size": 63 + }, + "last_name": { + "type": "string", + "size": 63 + }, + "date_of_birth": { + "type": "string", + "size": 31 + }, + "year_of_birth": { + "type": "unsigned short" + }, + "distinguished": { + "type": "boolean" + } + } +} diff --git a/demo/book.json b/demo/book.json new file mode 100644 index 0000000..606d655 --- /dev/null +++ b/demo/book.json @@ -0,0 +1,23 @@ +{ + "name": "book", + "fields": { + "id": { + "type": "unsigned long", + "constraint": "primary_key" + }, + "title": { + "type": "string", + "size": 511 + }, + "book_author": { + "type": "author", + "constraint": { + "type": "foreign_key", + "references": "id" + } + }, + "published_in": { + "type": "unsigned_short" + } + } +} diff --git a/include/matador/sql/query_intermediates.hpp b/include/matador/sql/query_intermediates.hpp index bd58a61..fd7b0d0 100644 --- a/include/matador/sql/query_intermediates.hpp +++ b/include/matador/sql/query_intermediates.hpp @@ -61,13 +61,29 @@ public: { return query_result(fetch()); } - query_result fetch_all(); - record fetch_one(); - template - Type fetch_value() + + template < class Type > + std::optional fetch_one() { - return fetch_one().at(0).as().value(); + auto result = query_result(fetch()); + auto first = result.begin(); + if (first == result.end()) { + return std::nullopt; + } + + return *first.get(); + } + std::optional fetch_one(); + + template + std::optional fetch_value() + { + const auto result = fetch_one(); + if (result.has_value()) { + return result.value().at(0).as().value(); + } + return std::nullopt; } statement prepare(); diff --git a/include/matador/sql/query_result_impl.hpp b/include/matador/sql/query_result_impl.hpp index 4c714a0..c301d5b 100644 --- a/include/matador/sql/query_result_impl.hpp +++ b/include/matador/sql/query_result_impl.hpp @@ -73,23 +73,30 @@ public: void on_attribute(const char *id, char *value, const utils::field_attributes &attr = utils::null_attributes); void on_attribute(const char *id, std::string &value, const utils::field_attributes &attr = utils::null_attributes); void on_attribute(const char *id, any_type &value, data_type_t type, const utils::field_attributes &attr = utils::null_attributes); -// void on_attribute(const char *id, any_type &value, data_type_t type, const utils::field_attributes &attr = utils::null_attributes); template < class Pointer > - void on_belongs_to(const char * /*id*/, Pointer &x, const utils::foreign_attributes &/*attr*/) + void on_belongs_to(const char * /*id*/, Pointer &x, const utils::foreign_attributes &attr) { if (!x.get()) { x.reset(new typename Pointer::value_type); } - pk_reader_.read(*x, column_index_++); + if (attr.fetch() == utils::fetch_type::LAZY) { + pk_reader_.read(*x, column_index_++); + } else { + utils::access::process(*this, *x); + } } template < class Pointer > - void on_has_one(const char * /*id*/, Pointer &x, const utils::foreign_attributes &/*attr*/) + void on_has_one(const char * /*id*/, Pointer &x, const utils::foreign_attributes &attr) { if (!x.get()) { x.reset(new typename Pointer::value_type); } - pk_reader_.read(*x, column_index_++); + if (attr.fetch() == utils::fetch_type::LAZY) { + pk_reader_.read(*x, column_index_++); + } else { + utils::access::process(*this, *x); + } } template diff --git a/src/sql/query_intermediates.cpp b/src/sql/query_intermediates.cpp index edb40b6..f73a7a2 100644 --- a/src/sql/query_intermediates.cpp +++ b/src/sql/query_intermediates.cpp @@ -14,10 +14,16 @@ query_result query_select_finish::fetch_all() return connection_.fetch(compiler.compile(&data_)); } -record query_select_finish::fetch_one() +std::optional query_select_finish::fetch_one() { query_compiler compiler(connection_.dialect()); - return *connection_.fetch(compiler.compile(&data_)).begin().get(); + auto result = connection_.fetch(compiler.compile(&data_)); + auto first = result.begin(); + if (first == result.end()) { + return std::nullopt; + } + + return *first.get(); } query_context query_select_finish::build() const