From 141d798a41a0afc2ef9711c15215bb7c958a2f8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sascha=20K=C3=BChl?= Date: Sat, 23 May 2026 20:48:21 +0200 Subject: [PATCH] small fixes and renames --- include/matador/query/builder.hpp | 53 +++++++++--------- .../matador/query/insert_query_builder.hpp | 8 +-- .../query/intermediates/executable_query.hpp | 2 +- .../query/intermediates/fetchable_query.hpp | 1 - .../query_alter_intermediate.hpp | 1 - .../intermediates/query_intermediate.hpp | 4 +- .../query/intermediates/executable_query.cpp | 5 +- .../query/intermediates/fetchable_query.cpp | 3 -- test/orm/query/QueryBuilderTest.cpp | 54 +++++++++---------- test/orm/query/SessionQueryBuilderTest.cpp | 18 +++---- 10 files changed, 73 insertions(+), 76 deletions(-) diff --git a/include/matador/query/builder.hpp b/include/matador/query/builder.hpp index 2f81364..2759842 100644 --- a/include/matador/query/builder.hpp +++ b/include/matador/query/builder.hpp @@ -8,36 +8,39 @@ #include "matador/utils/basic_types.hpp" #include "matador/utils/constraints.hpp" #include "matador/utils/data_type_traits.hpp" +#include "matador/utils/types.hpp" #include namespace matador::object { - class data_type { public: - explicit data_type(const utils::basic_type type, const size_t size = 0) - : type_(type), size_(size) {} + explicit data_type(const utils::basic_type type, const size_t size = 0) + : type_(type), size_(size) { + } - [[nodiscard]] const utils::basic_type& type() const { return type_; } - [[nodiscard]] size_t size() const { return size_; } + [[nodiscard]] const utils::basic_type &type() const { return type_; } + [[nodiscard]] size_t size() const { return size_; } private: - utils::basic_type type_{}; - size_t size_{0}; + utils::basic_type type_{}; + size_t size_{0}; }; template class typed_data_type final : public data_type { public: - typed_data_type() - : data_type(utils::data_type_traits::type()) {} + typed_data_type() + : data_type(utils::data_type_traits::type()) { + } }; template class sized_typed_data_type final : public data_type { public: - explicit sized_typed_data_type(size_t size) - : data_type(utils::data_type_traits::type(size), size) {} + explicit sized_typed_data_type(size_t size) + : data_type(utils::data_type_traits::type(size), size) { + } }; using TinyInt = typed_data_type; @@ -55,11 +58,14 @@ using Double = typed_data_type; using Text = typed_data_type; using Boolean = typed_data_type; using Varchar = sized_typed_data_type; -using Blob = sized_typed_data_type>; +using Blob = sized_typed_data_type >; +using Time = typed_data_type; +using Date = typed_data_type; +using Timestamp = typed_data_type; } -namespace matador::query { +namespace matador::query { class column_builder { public: explicit column_builder(std::string column_name, utils::basic_type type, size_t size = 0); @@ -67,10 +73,10 @@ public: // ReSharper disable once CppNonExplicitConversionOperator operator table_column() const; // NOLINT(*-explicit-constructor) - column_builder& not_null(); - column_builder& primary_key(); - column_builder& unique(); - column_builder& identity(); + column_builder ¬_null(); + column_builder &primary_key(); + column_builder &unique(); + column_builder &identity(); private: std::string column_name_; @@ -87,15 +93,15 @@ public: operator table() const; // NOLINT(*-explicit-constructor) private: - std::string table_name; + std::string table_name; }; class constraint_builder { public: - constraint_builder& constraint(std::string name); - constraint_builder& primary_key(std::string name); - constraint_builder& foreign_key(std::string name); - constraint_builder& references(std::string table, std::string column); + constraint_builder &constraint(std::string name); + constraint_builder &primary_key(std::string name); + constraint_builder &foreign_key(std::string name); + constraint_builder &references(std::string table, std::string column); // ReSharper disable once CppNonExplicitConversionOperator operator table_constraint() const; // NOLINT(*-explicit-constructor) @@ -111,6 +117,5 @@ private: constraint_builder constraint(std::string name); // table_builder table(std::string name); column_builder column(std::string name, utils::basic_type type, size_t size = 0); - } -#endif //MATADOR_BUILDER_HPP \ No newline at end of file +#endif //MATADOR_BUILDER_HPP diff --git a/include/matador/query/insert_query_builder.hpp b/include/matador/query/insert_query_builder.hpp index 0f8acd9..f4c363d 100644 --- a/include/matador/query/insert_query_builder.hpp +++ b/include/matador/query/insert_query_builder.hpp @@ -100,10 +100,10 @@ struct insert_context { const basic_schema &schema_; const std::unordered_map &contexts_by_type_; - std::vector> steps_; - std::vector> relation_steps_; - std::unordered_set, visit_key_hash> visited_; - std::unordered_set processing_many_to_many_relations_; + std::vector> steps_{}; + std::vector> relation_steps_{}; + std::unordered_set, visit_key_hash> visited_{}; + std::unordered_set processing_many_to_many_relations_{}; }; diff --git a/include/matador/query/intermediates/executable_query.hpp b/include/matador/query/intermediates/executable_query.hpp index 2ed5224..7809b13 100644 --- a/include/matador/query/intermediates/executable_query.hpp +++ b/include/matador/query/intermediates/executable_query.hpp @@ -23,7 +23,7 @@ public: [[nodiscard]] utils::result execute(const sql::executor &exec) const; [[nodiscard]] utils::result prepare(sql::executor &exec) const; [[nodiscard]] sql::query_context compile(const sql::dialect &d) const; - [[nodiscard]] std::string str(const sql::executor &exec) const; + [[nodiscard]] std::string str(const sql::dialect &d) const; }; } diff --git a/include/matador/query/intermediates/fetchable_query.hpp b/include/matador/query/intermediates/fetchable_query.hpp index 807ff69..a5c8b2e 100644 --- a/include/matador/query/intermediates/fetchable_query.hpp +++ b/include/matador/query/intermediates/fetchable_query.hpp @@ -75,7 +75,6 @@ public: [[nodiscard]] utils::result prepare(sql::executor &exec) const; - [[nodiscard]] std::string str(const sql::executor &exec) const; [[nodiscard]] std::string str(const sql::dialect &d) const; [[nodiscard]] sql::query_context compile(const sql::dialect &d) const; diff --git a/include/matador/query/intermediates/query_alter_intermediate.hpp b/include/matador/query/intermediates/query_alter_intermediate.hpp index 7d98ba4..07d5807 100644 --- a/include/matador/query/intermediates/query_alter_intermediate.hpp +++ b/include/matador/query/intermediates/query_alter_intermediate.hpp @@ -11,6 +11,5 @@ public: [[nodiscard]] query_alter_table_intermediate table(const table &tab) const; }; - } #endif //MATADOR_QUERY_ALTER_INTERMEDIATE_HPP \ No newline at end of file diff --git a/include/matador/query/intermediates/query_intermediate.hpp b/include/matador/query/intermediates/query_intermediate.hpp index 29cfe08..cb2b0c9 100644 --- a/include/matador/query/intermediates/query_intermediate.hpp +++ b/include/matador/query/intermediates/query_intermediate.hpp @@ -1,8 +1,6 @@ #ifndef QUERY_INTERMEDIATE_HPP #define QUERY_INTERMEDIATE_HPP -// #include "matador/query/query_data.hpp" - #include namespace matador::query { @@ -11,7 +9,7 @@ struct query_data; class query_intermediate { public: query_intermediate(); - query_intermediate(const std::shared_ptr &context); // NOLINT(*-explicit-constructor) + query_intermediate(const std::shared_ptr &context); // NOLINT(*-explicit-constructor) protected: std::shared_ptr context_; diff --git a/source/orm/query/intermediates/executable_query.cpp b/source/orm/query/intermediates/executable_query.cpp index 4d1ed33..ddf2006 100644 --- a/source/orm/query/intermediates/executable_query.cpp +++ b/source/orm/query/intermediates/executable_query.cpp @@ -22,9 +22,8 @@ sql::query_context executable_query::compile(const sql::dialect& d) const { return compiler.build(*context_, d, std::nullopt); } -std::string executable_query::str(const sql::executor &exec) const { - query_builder compiler; - return exec.str(compiler.build(*context_, exec.dialect(), std::nullopt)); +std::string executable_query::str(const sql::dialect& d) const { + return compile(d).sql; } } diff --git a/source/orm/query/intermediates/fetchable_query.cpp b/source/orm/query/intermediates/fetchable_query.cpp index 17af54f..6b25061 100644 --- a/source/orm/query/intermediates/fetchable_query.cpp +++ b/source/orm/query/intermediates/fetchable_query.cpp @@ -55,9 +55,6 @@ utils::result, utils::error> fetchable_query::fetch_o return utils::ok(std::optional{first.release()}); } -std::string fetchable_query::str(const sql::executor &exec) const { - return str(exec.dialect()); -} std::string fetchable_query::str(const sql::dialect &d) const { return compile(d).sql; } diff --git a/test/orm/query/QueryBuilderTest.cpp b/test/orm/query/QueryBuilderTest.cpp index 64b7423..50bb904 100644 --- a/test/orm/query/QueryBuilderTest.cpp +++ b/test/orm/query/QueryBuilderTest.cpp @@ -24,13 +24,13 @@ TEST_CASE_METHOD(QueryFixture, "Test alter table sql statement", "[query][alter] .add_constraint("FK_employees_dep_id") .foreign_key("dep_id"_col) .references("departments", {"id"_col}) - .str(*db); + .str(db->dialect()); REQUIRE(result == R"(ALTER TABLE "employees" ADD CONSTRAINT FK_employees_dep_id FOREIGN KEY ("dep_id") REFERENCES departments ("id"))"); result = alter() .table("employees") .drop_constraint("FK_employees_dep_id") - .str(*db); + .str(db->dialect()); REQUIRE(result == R"(ALTER TABLE "employees" DROP CONSTRAINT FK_employees_dep_id)"); } @@ -46,7 +46,7 @@ TEST_CASE_METHOD(QueryFixture, "Test create table sql statement string", "[query .constraints({ constraint("PK_person").primary_key({"id"}) }) - .str(*db); + .str(db->dialect()); REQUIRE(result == R"##(CREATE TABLE "person" ("id" BIGINT NOT NULL, "name" VARCHAR(255) NOT NULL, "age" INTEGER NOT NULL, CONSTRAINT PK_person PRIMARY KEY (id)))##"); @@ -57,7 +57,7 @@ TEST_CASE_METHOD(QueryFixture, "Test create table sql statement string", "[query column("name", basic_type::Varchar, 255), column("age", basic_type::UInt16) }) - .str(*db); + .str(db->dialect()); REQUIRE(result == R"##(CREATE TABLE "person" ("id" BIGINT NOT NULL AUTO INCREMENT PRIMARY KEY, "name" VARCHAR(255) NOT NULL, "age" INTEGER NOT NULL))##"); @@ -81,7 +81,7 @@ TEST_CASE_METHOD(QueryFixture, "Test create table sql statement string", "[query TEST_CASE_METHOD(QueryFixture, "Test drop table sql statement string", "[query]") { const auto result = drop() .table("person") - .str(*db); + .str(db->dialect()); REQUIRE(result == R"(DROP TABLE "person")"); } @@ -89,7 +89,7 @@ TEST_CASE_METHOD(QueryFixture, "Test drop table sql statement string", "[query]" TEST_CASE_METHOD(QueryFixture, "Test select all columns with asterisk", "[query][select][asterisk]") { const auto result = select() .from("person") - .str(*db); + .str(db->dialect()); REQUIRE(result == R"(SELECT * FROM "person")"); } @@ -97,7 +97,7 @@ TEST_CASE_METHOD(QueryFixture, "Test select all columns with asterisk", "[query] TEST_CASE_METHOD(QueryFixture, "Test select sql statement string", "[query]") { const auto result = select({"id", "name", "age"}) .from("person") - .str(*db); + .str(db->dialect()); REQUIRE(result == R"(SELECT "id", "name", "age" FROM "person")"); } @@ -108,7 +108,7 @@ TEST_CASE_METHOD(QueryFixture, "Test insert sql statement string", "[query]") { "id", "name", "age" }) .values({7U, "george", 65U}) - .str(*db); + .str(db->dialect()); REQUIRE(result == R"(INSERT INTO "person" ("id", "name", "age") VALUES (7, 'george', 65))"); } @@ -118,7 +118,7 @@ TEST_CASE_METHOD(QueryFixture, "Test update sql statement string", "[query]") { .set("id", 7U) .set("name", "george") .set("age", 65U) - .str(*db); + .str(db->dialect()); REQUIRE(result == R"(UPDATE "person" SET "id"=7, "name"='george', "age"=65)"); @@ -130,7 +130,7 @@ TEST_CASE_METHOD(QueryFixture, "Test update sql statement string", "[query]") { .order_by("id").asc() .limit(3) .offset(2) - .str(*db); + .str(db->dialect()); REQUIRE(result == R"(UPDATE "person" SET "id"=7, "name"='george', "age"=65 WHERE "id" > 9 ORDER BY "id" ASC LIMIT 3 OFFSET 2)"); } @@ -142,7 +142,7 @@ TEST_CASE_METHOD(QueryFixture, "Test update returning statement", "[query][updat .set("age", 65U) .where("name"_col == "george") .returning("id"_col, "name"_col, "age"_col) - .str(*db); + .str(db->dialect()); REQUIRE(result == R"(UPDATE "person" SET "id"=7, "name"='george', "age"=65 WHERE "name" = 'george' RETURNING "id", "name", "age")"); } @@ -155,13 +155,13 @@ TEST_CASE_METHOD(QueryFixture, "Test update limit sql statement", "[query][updat .where("name"_col == "george") .order_by("id"_col).asc() .limit(2) - .str(*db); + .str(db->dialect()); REQUIRE(result == R"(UPDATE "person" SET "id"=7, "name"='george', "age"=65 WHERE "name" = 'george' ORDER BY "id" ASC LIMIT 2)"); } TEST_CASE_METHOD(QueryFixture, "Test delete sql statement string", "[query]") { - const auto result = remove().from("person").str(*db); + const auto result = remove().from("person").str(db->dialect()); REQUIRE(result == R"(DELETE FROM "person")"); } @@ -172,7 +172,7 @@ TEST_CASE_METHOD(QueryFixture, "Test delete limit sql statement", "[query][delet .where("name"_col == "george") .order_by("id"_col).asc() .limit(2) - .str(*db); + .str(db->dialect()); REQUIRE(result == R"(DELETE FROM "person" WHERE "name" = 'george' ORDER BY "id" ASC LIMIT 2)"); } @@ -181,14 +181,14 @@ TEST_CASE_METHOD(QueryFixture, "Test select sql statement string with where clau auto result = select({"id", "name", "age"}) .from("person") .where("id"_col == 8 && "age"_col > 50) - .str(*db); + .str(db->dialect()); REQUIRE(result == R"(SELECT "id", "name", "age" FROM "person" WHERE ("id" = 8 AND "age" > 50))"); result = select({"id", "name", "age"}) .from("person") .where("id"_col == _ && "age"_col > 50) - .str(*db); + .str(db->dialect()); REQUIRE(result == R"(SELECT "id", "name", "age" FROM "person" WHERE ("id" = ? AND "age" > 50))"); } @@ -197,14 +197,14 @@ TEST_CASE_METHOD(QueryFixture, "Test insert sql statement with placeholder", "[q auto result = insert() .into("person", {"id", "name", "age"}) .values({_, _, _}) - .str(*db); + .str(db->dialect()); REQUIRE(result == R"(INSERT INTO "person" ("id", "name", "age") VALUES (?, ?, ?))"); result = insert() .into("person", {"id", "name", "age"}) .values({9, "george", _}) - .str(*db); + .str(db->dialect()); REQUIRE(result == R"(INSERT INTO "person" ("id", "name", "age") VALUES (9, 'george', ?))"); } @@ -214,7 +214,7 @@ TEST_CASE_METHOD(QueryFixture, "Test insert sql statement with returning", "[que .into("person", {"id", "name", "age"}) .values({9, "george", _}) .returning("id"_col, "name"_col, "age"_col) - .str(*db); + .str(db->dialect()); REQUIRE(result == R"(INSERT INTO "person" ("id", "name", "age") VALUES (9, 'george', ?) RETURNING "id", "name", "age")"); } @@ -223,7 +223,7 @@ TEST_CASE_METHOD(QueryFixture, "Test select sql statement string with order by", const auto result = select({"id", "name", "age"}) .from("person") .order_by("name"_col).asc() - .str(*db); + .str(db->dialect()); REQUIRE(result == R"(SELECT "id", "name", "age" FROM "person" ORDER BY "name" ASC)"); } @@ -232,7 +232,7 @@ TEST_CASE_METHOD(QueryFixture, "Test select sql statement string with group by", const auto result = select({"id", "name", "age"}) .from("person") .group_by("age"_col) - .str(*db); + .str(db->dialect()); REQUIRE(result == R"(SELECT "id", "name", "age" FROM "person" GROUP BY "age")"); } @@ -243,7 +243,7 @@ TEST_CASE_METHOD(QueryFixture, "Test select sql statement string with offset and .order_by("id"_col).asc() .limit(20) .offset(10) - .str(*db); + .str(db->dialect()); REQUIRE(result == R"(SELECT "id", "name", "age" FROM "person" ORDER BY "id" ASC LIMIT 20 OFFSET 10)"); } @@ -259,20 +259,20 @@ TEST_CASE_METHOD(QueryFixture, "Test create, insert and select a blob column", " .constraints({ constraint("PK_person").primary_key({"id"}) }) - .str(*db); + .str(db->dialect()); REQUIRE(result == R"##(CREATE TABLE "person" ("id" BIGINT NOT NULL, "name" VARCHAR(255) NOT NULL, "data" BLOB NOT NULL, CONSTRAINT PK_person PRIMARY KEY (id)))##"); result = insert() .into("person", {"id", "name", "data"}) .values({7U, "george", blob_type_t{1, 'A', 3, 4}}) - .str(*db); + .str(db->dialect()); REQUIRE(result == R"(INSERT INTO "person" ("id", "name", "data") VALUES (7, 'george', X'01410304'))"); result = select({"id", "name", "data"}) .from("person") - .str(*db); + .str(db->dialect()); REQUIRE(result == R"(SELECT "id", "name", "data" FROM "person")"); } @@ -286,7 +286,7 @@ TEST_CASE_METHOD(QueryFixture, "Test select statement with join_left", "[query][ .from(table{"flight"}.as("f")) .join_left(table{"airplane"}.as("ap")) .on(col1 == col2) - .str(*db); + .str(db->dialect()); REQUIRE(result == R"(SELECT "f"."id", "ap"."brand", "f"."pilot_name" FROM "flight" "f" LEFT JOIN "airplane" "ap" ON "f"."airplane_id" = "ap"."id")"); } @@ -301,7 +301,7 @@ TEST_CASE_METHOD(QueryFixture, "Test select statement with join_left", "[query][ // // const auto result = select(scm) // .from("authors"_tab.as("T01")) -// .str(*db); +// .str(db->dialect()); // // const auto expected_sql = R"(SELECT "T01"."id", "T01"."first_name", "T01"."last_name", "T01"."date_of_birth", "T01"."year_of_birth", "T01"."distinguished" FROM "authors" "T01")"; // diff --git a/test/orm/query/SessionQueryBuilderTest.cpp b/test/orm/query/SessionQueryBuilderTest.cpp index 61c0baf..5ab6d0a 100644 --- a/test/orm/query/SessionQueryBuilderTest.cpp +++ b/test/orm/query/SessionQueryBuilderTest.cpp @@ -53,7 +53,7 @@ TEST_CASE("Create sql query data for entity with eager has one", "[query][entity auto q = eqb.build(*col == _); REQUIRE(q.is_ok()); - const auto sql = q->str(db); + const auto sql = q->str(db.dialect()); const std::string expected_sql = R"(SELECT "t01"."id", "t02"."id", "t02"."brand", "t02"."model", "t01"."pilot_name" FROM "flights" "t01" LEFT JOIN "airplanes" "t02" ON "t01"."airplane_id" = "t02"."id" WHERE "t01"."id" = ? ORDER BY "t01"."id" ASC)"; REQUIRE(expected_sql == sql); @@ -111,7 +111,7 @@ TEST_CASE("Create sql query data for entity with eager belongs to", "[query][ent auto q = eqb.build(*col == _); REQUIRE(q.is_ok()); - const auto sql = q->str(db); + const auto sql = q->str(db.dialect()); const std::string expected_sql = R"(SELECT "t01"."id", "t01"."title", "t02"."id", "t02"."first_name", "t02"."last_name", "t02"."date_of_birth", "t02"."year_of_birth", "t02"."distinguished", "t01"."published_in" FROM "books" "t01" LEFT JOIN "authors" "t02" ON "t01"."author_id" = "t02"."id" WHERE "t01"."id" = ? ORDER BY "t01"."id" ASC)"; REQUIRE(expected_sql == sql); @@ -188,7 +188,7 @@ TEST_CASE("Create sql query data for entity with eager has many belongs to", "[q auto q = eqb.build(*col == _); REQUIRE(q.is_ok()); - const auto sql = q->str(db); + const auto sql = q->str(db.dialect()); const std::string expected_sql = R"(SELECT "t01"."order_id", "t01"."order_date", "t01"."required_date", "t01"."shipped_date", "t01"."ship_via", "t01"."freight", "t01"."ship_name", "t01"."ship_address", "t01"."ship_city", "t01"."ship_region", "t01"."ship_postal_code", "t01"."ship_country", "t02"."order_details_id", "t02"."order_id" FROM "orders" "t01" LEFT JOIN "order_details" "t02" ON "t01"."order_id" = "t02"."order_id" WHERE "t01"."order_id" = ? ORDER BY "t01"."order_id" ASC)"; REQUIRE(expected_sql == sql); @@ -256,7 +256,7 @@ TEST_CASE("Create sql query data for entity with eager many to many", "[query][e auto q = eqb.build(*col == _); REQUIRE(q.is_ok()); - const auto sql = q->str(db); + const auto sql = q->str(db.dialect()); const std::string expected_sql = R"(SELECT "t01"."id", "t01"."name", "t03"."id", "t03"."name" FROM "ingredients" "t01" LEFT JOIN "recipe_ingredients" "t02" ON "t01"."id" = "t02"."ingredient_id" LEFT JOIN "recipes" "t03" ON "t02"."recipe_id" = "t03"."id" WHERE "t01"."id" = ? ORDER BY "t01"."id" ASC)"; REQUIRE(expected_sql == sql); @@ -315,7 +315,7 @@ TEST_CASE("Create sql query data for entity with eager many to many (inverse par auto q = eqb.build(*col == _); REQUIRE(q.is_ok()); - const auto sql = q->str(db); + const auto sql = q->str(db.dialect()); const std::string expected_sql = R"(SELECT "t01"."id", "t01"."title", "t03"."id", "t03"."name" FROM "courses" "t01" LEFT JOIN "student_courses" "t02" ON "t01"."id" = "t02"."course_id" LEFT JOIN "students" "t03" ON "t02"."student_id" = "t03"."id" WHERE "t01"."id" = ? ORDER BY "t01"."id" ASC)"; REQUIRE(expected_sql == sql); @@ -369,11 +369,11 @@ TEST_CASE("Test eager relationship", "[query][entity][builder]") { auto q = eqb.build(); REQUIRE(q.is_ok()); - const auto sql = q->str(db); + const auto sql = q->str(db.dialect()); const std::string expected_sql = R"(SELECT "t01"."id", "t01"."name", "t02"."id", "t02"."first_name", "t02"."last_name", "t02"."dep_id" FROM "departments" "t01" LEFT JOIN "employees" "t02" ON "t01"."id" = "t02"."dep_id" ORDER BY "t01"."id" ASC)"; REQUIRE(expected_sql == sql); - const auto& data = eqb.query_data(); + // const auto& data = eqb.query_data(); // auto ctx = query::select(data.columns) @@ -401,7 +401,7 @@ TEST_CASE("Test has one lazy relationship", "[query][entity][builder][has_one][l auto q = eqb.build(); REQUIRE(q.is_ok()); - const auto sql = q->str(db); + const auto sql = q->str(db.dialect()); const std::string expected_sql = R"(SELECT "t01"."id", "t01"."name", "t01"."password" FROM "users" "t01" ORDER BY "t01"."id" ASC)"; REQUIRE(expected_sql == sql); @@ -421,7 +421,7 @@ TEST_CASE("Test has one eager relationship", "[query][entity][builder][has_one][ auto q = eqb.build(); REQUIRE(q.is_ok()); - const auto sql = q->str(db); + const auto sql = q->str(db.dialect()); const std::string expected_sql = R"(SELECT "t01"."id", "t01"."name", "t02"."id", "t02"."name", "t02"."country_id" FROM "countries" "t01" LEFT JOIN "capitals" "t02" ON "t01"."id" = "t02"."country_id" ORDER BY "t01"."id" ASC)"; REQUIRE(expected_sql == sql);