Database System 0.1.0
Advanced C++20 Database System with Multi-Backend Support
Loading...
Searching...
No Matches
sql_query_builder_test.cpp
Go to the documentation of this file.
1// BSD 3-Clause License
2// Copyright (c) 2025, 🍀☀🌕🌥 🌊
3// See the LICENSE file in the project root for full license information.
4
10#include <gtest/gtest.h>
12#include <string>
13#include <map>
14#include <vector>
15
17{
18
19class SQLQueryBuilderTest : public ::testing::Test
20{
21protected:
22 void SetUp() override
23 {
24 // Use unified query_builder with PostgreSQL dialect (default for SQL)
25 builder_ = std::make_unique<query_builder>(database_types::postgres);
26 }
27
28 void TearDown() override
29 {
30 builder_.reset();
31 }
32
33 std::unique_ptr<query_builder> builder_;
34};
35
36//=============================================================================
37// SELECT Tests
38//=============================================================================
39
41{
42 std::vector<std::string> cols = {"id", "name", "email"};
43 auto query = builder_->select(cols)
44 .from("users")
45 .build();
46
47 EXPECT_TRUE(query.find("SELECT") != std::string::npos);
48 EXPECT_TRUE(query.find("FROM") != std::string::npos);
49}
50
52{
53 auto query = builder_->select({"*"}).from("users").build();
54 EXPECT_TRUE(query.find("SELECT") != std::string::npos);
55 EXPECT_TRUE(query.find("*") != std::string::npos);
56}
57
58//=============================================================================
59// JOIN Tests
60//=============================================================================
61
63{
64 std::vector<std::string> cols = {"u.id", "u.name", "o.total"};
65 auto query = builder_->select(cols)
66 .from("users u")
67 .join("orders o", "u.id = o.user_id", join_type::inner)
68 .build();
69
70 EXPECT_TRUE(query.find("INNER JOIN") != std::string::npos);
71 EXPECT_TRUE(query.find("ON u.id = o.user_id") != std::string::npos);
72}
73
75{
76 auto query = builder_->select({"*"})
77 .from("users")
78 .join("profiles", "users.id = profiles.user_id", join_type::left)
79 .build();
80
81 EXPECT_TRUE(query.find("LEFT JOIN") != std::string::npos);
82}
83
85{
86 auto query = builder_->select({"*"})
87 .from("users")
88 .join("orders", "users.id = orders.user_id", join_type::right)
89 .build();
90
91 EXPECT_TRUE(query.find("RIGHT JOIN") != std::string::npos);
92}
93
95{
96 auto query = builder_->select({"*"})
97 .from("users")
98 .join("orders", "users.id = orders.user_id", join_type::full_outer)
99 .build();
100
101 EXPECT_TRUE(query.find("FULL OUTER JOIN") != std::string::npos);
102}
103
105{
106 auto query = builder_->select({"*"})
107 .from("colors")
108 .join("sizes", "1=1", join_type::cross)
109 .build();
110
111 EXPECT_TRUE(query.find("CROSS JOIN") != std::string::npos);
112}
113
115{
116 std::vector<std::string> cols = {"u.id", "u.name", "o.total", "p.name"};
117 auto query = builder_->select(cols)
118 .from("users u")
119 .join("orders o", "u.id = o.user_id")
120 .join("products p", "o.product_id = p.id", join_type::left)
121 .build();
122
123 EXPECT_TRUE(query.find("INNER JOIN") != std::string::npos);
124 EXPECT_TRUE(query.find("LEFT JOIN") != std::string::npos);
125}
126
127//=============================================================================
128// WHERE Tests
129//=============================================================================
130
132{
133 auto query = builder_->select({"*"})
134 .from("users")
135 .where("active", "=", core::database_value{true})
136 .build();
137
138 EXPECT_TRUE(query.find("WHERE") != std::string::npos);
139 EXPECT_TRUE(query.find("active") != std::string::npos);
140 EXPECT_TRUE(query.find("TRUE") != std::string::npos);
141}
142
144{
145 auto query = builder_->select({"*"})
146 .from("users")
147 .where("status", "=", core::database_value{std::string("active")})
148 .build();
149
150 EXPECT_TRUE(query.find("'active'") != std::string::npos);
151}
152
154{
155 auto query = builder_->select({"*"})
156 .from("users")
157 .where("age", ">", core::database_value{int64_t(18)})
158 .build();
159
160 EXPECT_TRUE(query.find("> 18") != std::string::npos);
161}
162
164{
165 auto query = builder_->select({"*"})
166 .from("products")
167 .where("price", "<", core::database_value{99.99})
168 .build();
169
170 EXPECT_TRUE(query.find("price") != std::string::npos);
171 EXPECT_TRUE(query.find("<") != std::string::npos);
172}
173
174TEST_F(SQLQueryBuilderTest, MultipleWhereConditions)
175{
176 auto query = builder_->select({"*"})
177 .from("users")
178 .where("active", "=", core::database_value{true})
179 .where("age", ">", core::database_value{int64_t(18)})
180 .build();
181
182 EXPECT_TRUE(query.find("AND") != std::string::npos);
183}
184
185TEST_F(SQLQueryBuilderTest, NestedConditionsWithAnd)
186{
187 query_condition cond1("age", ">", core::database_value{int64_t(18)});
188 query_condition cond2("status", "=", core::database_value{std::string("active")});
189 auto combined = cond1 && cond2;
190
191 auto query = builder_->select({"*"})
192 .from("users")
193 .where(combined)
194 .build();
195
196 EXPECT_TRUE(query.find("AND") != std::string::npos);
197}
198
199TEST_F(SQLQueryBuilderTest, NestedConditionsWithOr)
200{
201 query_condition cond1("role", "=", core::database_value{std::string("admin")});
202 query_condition cond2("role", "=", core::database_value{std::string("superadmin")});
203 auto combined = cond1 || cond2;
204
205 auto query = builder_->select({"*"})
206 .from("users")
207 .where(combined)
208 .build();
209
210 EXPECT_TRUE(query.find("OR") != std::string::npos);
211}
212
213//=============================================================================
214// GROUP BY & HAVING Tests
215//=============================================================================
216
218{
219 std::vector<std::string> cols = {"department", "COUNT(*)"};
220 auto query = builder_->select(cols)
221 .from("employees")
222 .group_by("department")
223 .build();
224
225 EXPECT_TRUE(query.find("GROUP BY") != std::string::npos);
226}
227
228TEST_F(SQLQueryBuilderTest, GroupByMultipleColumns)
229{
230 std::vector<std::string> cols = {"department", "city", "COUNT(*)"};
231 std::vector<std::string> group_cols = {"department", "city"};
232 auto query = builder_->select(cols)
233 .from("employees")
234 .group_by(group_cols)
235 .build();
236
237 EXPECT_TRUE(query.find("GROUP BY") != std::string::npos);
238}
239
240TEST_F(SQLQueryBuilderTest, GroupByWithHaving)
241{
242 std::vector<std::string> cols = {"department", "COUNT(*) as count"};
243 auto query = builder_->select(cols)
244 .from("employees")
245 .group_by("department")
246 .having("COUNT(*) > 5")
247 .build();
248
249 EXPECT_TRUE(query.find("GROUP BY") != std::string::npos);
250 EXPECT_TRUE(query.find("HAVING") != std::string::npos);
251 EXPECT_TRUE(query.find("COUNT(*) > 5") != std::string::npos);
252}
253
254//=============================================================================
255// ORDER BY Tests
256//=============================================================================
257
259{
260 auto query = builder_->select({"*"})
261 .from("users")
262 .order_by("name", sort_order::asc)
263 .build();
264
265 EXPECT_TRUE(query.find("ORDER BY") != std::string::npos);
266 EXPECT_TRUE(query.find("name ASC") != std::string::npos);
267}
268
270{
271 auto query = builder_->select({"*"})
272 .from("users")
273 .order_by("created_at", sort_order::desc)
274 .build();
275
276 EXPECT_TRUE(query.find("ORDER BY") != std::string::npos);
277 EXPECT_TRUE(query.find("created_at DESC") != std::string::npos);
278}
279
280TEST_F(SQLQueryBuilderTest, OrderByMultipleColumns)
281{
282 auto query = builder_->select({"*"})
283 .from("products")
284 .order_by("category", sort_order::asc)
285 .order_by("price", sort_order::desc)
286 .build();
287
288 EXPECT_TRUE(query.find("ORDER BY") != std::string::npos);
289 EXPECT_TRUE(query.find("category ASC") != std::string::npos);
290 EXPECT_TRUE(query.find("price DESC") != std::string::npos);
291}
292
293//=============================================================================
294// LIMIT & OFFSET Tests
295//=============================================================================
296
298{
299 auto query = builder_->select({"*"})
300 .from("users")
301 .limit(10)
302 .build();
303
304 EXPECT_TRUE(query.find("LIMIT 10") != std::string::npos);
305}
306
308{
309 auto query = builder_->select({"*"})
310 .from("users")
311 .limit(10)
312 .offset(20)
313 .build();
314
315 EXPECT_TRUE(query.find("LIMIT 10") != std::string::npos);
316 EXPECT_TRUE(query.find("OFFSET 20") != std::string::npos);
317}
318
319//=============================================================================
320// INSERT Tests
321//=============================================================================
322
324{
325 std::map<std::string, core::database_value> data;
326 data["name"] = core::database_value{std::string("John")};
327 data["email"] = core::database_value{std::string("john@example.com")};
328
329 auto query = builder_->insert_into("users")
330 .values(data)
331 .build();
332
333 EXPECT_TRUE(query.find("INSERT INTO") != std::string::npos);
334 EXPECT_TRUE(query.find("VALUES") != std::string::npos);
335}
336
337TEST_F(SQLQueryBuilderTest, InsertMultipleRows)
338{
339 std::vector<std::map<std::string, core::database_value>> rows;
340
341 std::map<std::string, core::database_value> row1;
342 row1["name"] = core::database_value{std::string("John")};
343 row1["age"] = core::database_value{int64_t(30)};
344 rows.push_back(row1);
345
346 std::map<std::string, core::database_value> row2;
347 row2["name"] = core::database_value{std::string("Jane")};
348 row2["age"] = core::database_value{int64_t(25)};
349 rows.push_back(row2);
350
351 auto query = builder_->insert_into("users")
352 .values(rows)
353 .build();
354
355 EXPECT_TRUE(query.find("INSERT INTO") != std::string::npos);
356 EXPECT_TRUE(query.find("VALUES") != std::string::npos);
357}
358
359//=============================================================================
360// UPDATE Tests
361//=============================================================================
362
363TEST_F(SQLQueryBuilderTest, UpdateSingleField)
364{
365 auto query = builder_->update("users")
366 .set("status", core::database_value{std::string("active")})
367 .where("id", "=", core::database_value{int64_t(1)})
368 .build();
369
370 EXPECT_TRUE(query.find("UPDATE") != std::string::npos);
371 EXPECT_TRUE(query.find("SET") != std::string::npos);
372 EXPECT_TRUE(query.find("WHERE") != std::string::npos);
373}
374
375TEST_F(SQLQueryBuilderTest, UpdateMultipleFields)
376{
377 std::map<std::string, core::database_value> data;
378 data["status"] = core::database_value{std::string("active")};
379 data["updated_at"] = core::database_value{std::string("2025-01-01")};
380
381 auto query = builder_->update("users")
382 .set(data)
383 .where("id", "=", core::database_value{int64_t(1)})
384 .build();
385
386 EXPECT_TRUE(query.find("UPDATE") != std::string::npos);
387 EXPECT_TRUE(query.find("SET") != std::string::npos);
388}
389
390//=============================================================================
391// DELETE Tests
392//=============================================================================
393
395{
396 auto query = builder_->delete_from("users")
397 .where("id", "=", core::database_value{int64_t(1)})
398 .build();
399
400 EXPECT_TRUE(query.find("DELETE FROM") != std::string::npos);
401 EXPECT_TRUE(query.find("WHERE") != std::string::npos);
402}
403
404TEST_F(SQLQueryBuilderTest, DeleteWithMultipleConditions)
405{
406 auto query = builder_->delete_from("users")
407 .where("status", "=", core::database_value{std::string("inactive")})
408 .where("last_login", "<", core::database_value{std::string("2024-01-01")})
409 .build();
410
411 EXPECT_TRUE(query.find("DELETE FROM") != std::string::npos);
412 EXPECT_TRUE(query.find("AND") != std::string::npos);
413}
414
415//=============================================================================
416// Database-Specific Syntax Tests
417//=============================================================================
418
419TEST_F(SQLQueryBuilderTest, PostgreSQLSyntax)
420{
422 auto query = pg_builder.select({"*"})
423 .from("users")
424 .limit(10)
425 .build();
426
427 EXPECT_TRUE(query.find("\"users\"") != std::string::npos);
428}
429
431{
433 auto query = sqlite_builder.select({"*"})
434 .from("users")
435 .limit(10)
436 .build();
437
438 EXPECT_TRUE(query.find("[users]") != std::string::npos);
439}
440
441//=============================================================================
442// Reset & Reuse Tests
443//=============================================================================
444
446{
447 builder_->select({"*"}).from("users").limit(10);
448 builder_->reset();
449
450 // After reset, builder should be clean - verify by building a new query
451 // Reset clears the state, so next query starts fresh
452 auto query = builder_->select({"id"}).from("test_table").build();
453 EXPECT_TRUE(query.find("test_table") != std::string::npos);
454 EXPECT_TRUE(query.find("users") == std::string::npos); // Previous table not present
455}
456
458{
459 builder_->select({"*"}).from("users").limit(10);
460 builder_->reset();
461
462 auto query = builder_->select({"*"}).from("products").build();
463 EXPECT_TRUE(query.find("products") != std::string::npos);
464 EXPECT_TRUE(query.find("users") == std::string::npos);
465}
466
467//=============================================================================
468// Complex Query Tests
469//=============================================================================
470
471TEST_F(SQLQueryBuilderTest, ComplexSelectQuery)
472{
473 std::vector<std::string> cols = {"u.id", "u.name", "COUNT(o.id) as order_count"};
474 std::vector<std::string> group_cols = {"u.id", "u.name"};
475 auto query = builder_->select(cols)
476 .from("users u")
477 .join("orders o", "u.id = o.user_id", join_type::left)
478 .where("u.status", "=", core::database_value{std::string("active")})
479 .group_by(group_cols)
480 .having("COUNT(o.id) > 5")
481 .order_by("order_count", sort_order::desc)
482 .limit(10)
483 .build();
484
485 EXPECT_TRUE(query.find("SELECT") != std::string::npos);
486 EXPECT_TRUE(query.find("LEFT JOIN") != std::string::npos);
487 EXPECT_TRUE(query.find("WHERE") != std::string::npos);
488 EXPECT_TRUE(query.find("GROUP BY") != std::string::npos);
489 EXPECT_TRUE(query.find("HAVING") != std::string::npos);
490 EXPECT_TRUE(query.find("ORDER BY") != std::string::npos);
491 EXPECT_TRUE(query.find("LIMIT") != std::string::npos);
492}
493
494//=============================================================================
495// Edge Cases
496//=============================================================================
497
499{
500 auto query = builder_->select({"*"}).from("users").build();
501
502 EXPECT_FALSE(query.find("WHERE") != std::string::npos);
503}
504
505//=============================================================================
506// For Database Switch Tests
507//=============================================================================
508
510{
511 builder_->for_database(database_types::sqlite);
512
513 auto query = builder_->select({"*"})
514 .from("users")
515 .build();
516
517 EXPECT_TRUE(query.find("[users]") != std::string::npos); // SQLite syntax
518}
519
520} // namespace database::tests
Universal query builder that adapts to different database types.
query_builder & select(const std::vector< std::string > &columns)
std::string build() const
query_builder & limit(size_t count)
Represents a WHERE condition in a query.
std::unique_ptr< query_builder > builder_
std::variant< std::string, int64_t, double, bool, std::nullptr_t > database_value
TEST_F(SQLQueryBuilderTest, SimpleSelect)
@ sqlite
Indicates a SQLite database.
@ postgres
Indicates a PostgreSQL database.