Database System 0.1.0
Advanced C++20 Database System with Multi-Backend Support
Loading...
Searching...
No Matches
query_dialect.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
5#include "query_dialect.h"
6#include "query_builder.h"
7
8#include <sstream>
9#include <stdexcept>
10
11namespace database
12{
13 std::unique_ptr<query_dialect> create_dialect(database_types type)
14 {
15 switch (type) {
18 return std::make_unique<detail::sql_dialect>(type);
19#ifdef USE_MONGODB
21 return std::make_unique<detail::mongodb_dialect>();
22#endif
23#ifdef USE_REDIS
25 return std::make_unique<detail::redis_dialect>();
26#endif
27 default:
28 throw std::invalid_argument("Unsupported database type");
29 }
30 }
31
32 namespace detail
33 {
34 // sql_dialect implementation
36 : db_type_(db_type)
37 , type_(query_type::none)
38 , limit_count_(0)
39 , offset_count_(0)
40 {
41 }
42
44 {
45 type_ = type;
46 }
47
52
53 void sql_dialect::set_select_columns(const std::vector<std::string>& columns)
54 {
56 select_columns_ = columns;
57 }
58
59 void sql_dialect::set_from_table(const std::string& table)
60 {
61 from_table_ = table;
62 }
63
65 {
66 where_conditions_.push_back(condition);
67 }
68
69 void sql_dialect::add_where_condition(const std::string& field, const std::string& op, const core::database_value& value)
70 {
71 where_conditions_.emplace_back(field, op, value);
72 }
73
74 void sql_dialect::add_join(const std::string& table, const std::string& condition, join_type type)
75 {
76 std::ostringstream oss;
77 oss << join_type_to_string(type) << " JOIN " << table << " ON " << condition;
78 joins_.push_back(oss.str());
79 }
80
81 void sql_dialect::set_group_by(const std::vector<std::string>& columns)
82 {
83 group_by_columns_ = columns;
84 }
85
86 void sql_dialect::set_having(const std::string& condition)
87 {
88 having_clause_ = condition;
89 }
90
91 void sql_dialect::add_order_by(const std::string& column, sort_order order)
92 {
93 std::ostringstream oss;
94 oss << column << " " << (order == sort_order::asc ? "ASC" : "DESC");
95 order_by_clauses_.push_back(oss.str());
96 }
97
98 void sql_dialect::set_limit(size_t count)
99 {
100 limit_count_ = count;
101 }
102
103 void sql_dialect::set_offset(size_t count)
104 {
105 offset_count_ = count;
106 }
107
108 void sql_dialect::set_insert_table(const std::string& table)
109 {
111 target_table_ = table;
112 }
113
114 void sql_dialect::set_insert_data(const std::map<std::string, core::database_value>& data)
115 {
116 set_data_ = data;
117 }
118
119 void sql_dialect::set_insert_rows(const std::vector<std::map<std::string, core::database_value>>& rows)
120 {
121 insert_rows_ = rows;
122 }
123
124 void sql_dialect::set_update_table(const std::string& table)
125 {
127 target_table_ = table;
128 }
129
130 void sql_dialect::set_update_data(const std::map<std::string, core::database_value>& data)
131 {
132 set_data_ = data;
133 }
134
135 void sql_dialect::set_delete_table(const std::string& table)
136 {
138 target_table_ = table;
139 }
140
141 void sql_dialect::set_collection(const std::string& /*name*/)
142 {
143 // No-op for SQL
144 }
145
146 void sql_dialect::set_key(const std::string& /*key*/)
147 {
148 // No-op for SQL
149 }
150
151 std::string sql_dialect::build() const
152 {
153 std::ostringstream oss;
154
155 switch (type_) {
157 oss << "SELECT ";
158 if (select_columns_.empty()) {
159 oss << "*";
160 } else {
161 for (size_t i = 0; i < select_columns_.size(); ++i) {
162 if (i > 0) oss << ", ";
164 }
165 }
166 if (!from_table_.empty()) {
167 oss << " FROM " << escape_identifier(from_table_);
168 }
169 break;
170
172 oss << "INSERT INTO " << escape_identifier(target_table_);
173 if (!insert_rows_.empty()) {
174 auto& first_row = insert_rows_[0];
175 oss << " (";
176 bool first = true;
177 for (const auto& pair : first_row) {
178 if (!first) oss << ", ";
179 oss << escape_identifier(pair.first);
180 first = false;
181 }
182 oss << ") VALUES ";
183 for (size_t i = 0; i < insert_rows_.size(); ++i) {
184 if (i > 0) oss << ", ";
185 oss << "(";
186 bool first_val = true;
187 for (const auto& pair : insert_rows_[i]) {
188 if (!first_val) oss << ", ";
189 oss << format_value(pair.second);
190 first_val = false;
191 }
192 oss << ")";
193 }
194 } else if (!set_data_.empty()) {
195 oss << " (";
196 bool first = true;
197 for (const auto& pair : set_data_) {
198 if (!first) oss << ", ";
199 oss << escape_identifier(pair.first);
200 first = false;
201 }
202 oss << ") VALUES (";
203 first = true;
204 for (const auto& pair : set_data_) {
205 if (!first) oss << ", ";
206 oss << format_value(pair.second);
207 first = false;
208 }
209 oss << ")";
210 }
211 break;
212
214 oss << "UPDATE " << escape_identifier(target_table_) << " SET ";
215 {
216 bool first = true;
217 for (const auto& pair : set_data_) {
218 if (!first) oss << ", ";
219 oss << escape_identifier(pair.first) << " = " << format_value(pair.second);
220 first = false;
221 }
222 }
223 break;
224
226 oss << "DELETE FROM " << escape_identifier(target_table_);
227 break;
228
229 default:
230 throw std::runtime_error("Invalid query type");
231 }
232
233 // Add JOINs
234 for (const auto& join : joins_) {
235 oss << " " << join;
236 }
237
238 // Add WHERE clause
239 if (!where_conditions_.empty()) {
240 oss << " WHERE ";
241 for (size_t i = 0; i < where_conditions_.size(); ++i) {
242 if (i > 0) oss << " AND ";
243 oss << where_conditions_[i].to_sql();
244 }
245 }
246
247 // Add GROUP BY
248 if (!group_by_columns_.empty()) {
249 oss << " GROUP BY ";
250 for (size_t i = 0; i < group_by_columns_.size(); ++i) {
251 if (i > 0) oss << ", ";
253 }
254 }
255
256 // Add HAVING
257 if (!having_clause_.empty()) {
258 oss << " HAVING " << having_clause_;
259 }
260
261 // Add ORDER BY
262 if (!order_by_clauses_.empty()) {
263 oss << " ORDER BY ";
264 for (size_t i = 0; i < order_by_clauses_.size(); ++i) {
265 if (i > 0) oss << ", ";
266 oss << order_by_clauses_[i];
267 }
268 }
269
270 // Add LIMIT and OFFSET
271 if (limit_count_ > 0) {
272 oss << " LIMIT " << limit_count_;
273 }
274 if (offset_count_ > 0) {
275 oss << " OFFSET " << offset_count_;
276 }
277
278 return oss.str();
279 }
280
282 {
284 select_columns_.clear();
285 from_table_.clear();
286 where_conditions_.clear();
287 joins_.clear();
288 group_by_columns_.clear();
289 having_clause_.clear();
290 order_by_clauses_.clear();
291 limit_count_ = 0;
292 offset_count_ = 0;
293 target_table_.clear();
294 set_data_.clear();
295 insert_rows_.clear();
296 }
297
302
303 std::string sql_dialect::escape_identifier(const std::string& identifier) const
304 {
305 switch (db_type_) {
307 return "\"" + identifier + "\"";
309 return "[" + identifier + "]";
310 default:
311 return identifier;
312 }
313 }
314
315 std::string sql_dialect::format_value(const core::database_value& value) const
316 {
317 std::ostringstream oss;
318 std::visit([&oss](const auto& val) {
319 using T = std::decay_t<decltype(val)>;
320 if constexpr (std::is_same_v<T, std::string>) {
321 // Escape single quotes by doubling them (SQL standard)
322 std::string escaped;
323 escaped.reserve(val.size() + 10);
324 for (char c : val) {
325 if (c == '\'') {
326 escaped += "''";
327 } else {
328 escaped += c;
329 }
330 }
331 oss << "'" << escaped << "'";
332 } else if constexpr (std::is_same_v<T, int64_t>) {
333 oss << val;
334 } else if constexpr (std::is_same_v<T, double>) {
335 oss << val;
336 } else if constexpr (std::is_same_v<T, bool>) {
337 oss << (val ? "TRUE" : "FALSE");
338 } else if constexpr (std::is_same_v<T, std::monostate> || std::is_same_v<T, std::nullptr_t>) {
339 oss << "NULL";
340 }
341 }, value);
342 return oss.str();
343 }
344
346 {
347 switch (type) {
348 case join_type::inner: return "INNER";
349 case join_type::left: return "LEFT";
350 case join_type::right: return "RIGHT";
351 case join_type::full_outer: return "FULL OUTER";
352 case join_type::cross: return "CROSS";
353 default: return "INNER";
354 }
355 }
356
357#ifdef USE_MONGODB
358 // mongodb_dialect implementation
359 mongodb_dialect::mongodb_dialect()
360 : op_type_(operation_type::none)
361 , limit_count_(0)
362 , skip_count_(0)
363 {
364 }
365
366 void mongodb_dialect::set_query_type(query_type type)
367 {
368 switch (type) {
369 case query_type::select:
370 op_type_ = operation_type::find;
371 break;
372 case query_type::insert:
373 op_type_ = operation_type::insert;
374 break;
375 case query_type::update:
376 op_type_ = operation_type::update;
377 break;
378 case query_type::delete_query:
379 op_type_ = operation_type::delete_op;
380 break;
381 default:
382 op_type_ = operation_type::none;
383 break;
384 }
385 }
386
387 query_dialect::query_type mongodb_dialect::get_query_type() const
388 {
389 switch (op_type_) {
390 case operation_type::find:
391 return query_type::select;
392 case operation_type::insert:
393 return query_type::insert;
394 case operation_type::update:
395 return query_type::update;
396 case operation_type::delete_op:
397 return query_type::delete_query;
398 default:
399 return query_type::none;
400 }
401 }
402
403 void mongodb_dialect::set_select_columns(const std::vector<std::string>& columns)
404 {
405 op_type_ = operation_type::find;
406 projection_.clear();
407 for (const auto& field : columns) {
408 projection_[field] = core::database_value{int64_t(1)};
409 }
410 }
411
412 void mongodb_dialect::set_from_table(const std::string& table)
413 {
414 collection_name_ = table;
415 }
416
417 void mongodb_dialect::add_where_condition(const query_condition& /*condition*/)
418 {
419 // MongoDB uses filter_ directly
420 }
421
422 void mongodb_dialect::add_where_condition(const std::string& field, const std::string& op, const core::database_value& value)
423 {
424 if (op == "=") {
425 filter_[field] = value;
426 } else {
427 // For other operators, we need to build a nested structure
428 // This is a simplified implementation
429 filter_[field] = value;
430 }
431 }
432
433 void mongodb_dialect::add_join(const std::string& /*table*/, const std::string& /*condition*/, join_type /*type*/)
434 {
435 // MongoDB doesn't support JOINs in the same way as SQL
436 // Use aggregation pipeline with $lookup instead
437 }
438
439 void mongodb_dialect::set_group_by(const std::vector<std::string>& /*columns*/)
440 {
441 // Use aggregation pipeline with $group
442 op_type_ = operation_type::aggregate;
443 }
444
445 void mongodb_dialect::set_having(const std::string& /*condition*/)
446 {
447 // Part of aggregation pipeline
448 }
449
450 void mongodb_dialect::add_order_by(const std::string& column, sort_order order)
451 {
452 sort_spec_[column] = (order == sort_order::asc) ? 1 : -1;
453 }
454
455 void mongodb_dialect::set_limit(size_t count)
456 {
457 limit_count_ = count;
458 }
459
460 void mongodb_dialect::set_offset(size_t count)
461 {
462 skip_count_ = count;
463 }
464
465 void mongodb_dialect::set_insert_table(const std::string& table)
466 {
467 op_type_ = operation_type::insert;
468 collection_name_ = table;
469 }
470
471 void mongodb_dialect::set_insert_data(const std::map<std::string, core::database_value>& data)
472 {
473 op_type_ = operation_type::insert;
474 document_ = data;
475 }
476
477 void mongodb_dialect::set_insert_rows(const std::vector<std::map<std::string, core::database_value>>& rows)
478 {
479 op_type_ = operation_type::insert;
480 documents_ = rows;
481 }
482
483 void mongodb_dialect::set_update_table(const std::string& table)
484 {
485 op_type_ = operation_type::update;
486 collection_name_ = table;
487 }
488
489 void mongodb_dialect::set_update_data(const std::map<std::string, core::database_value>& data)
490 {
491 op_type_ = operation_type::update;
492 update_spec_ = data;
493 }
494
495 void mongodb_dialect::set_delete_table(const std::string& table)
496 {
497 op_type_ = operation_type::delete_op;
498 collection_name_ = table;
499 }
500
501 void mongodb_dialect::set_collection(const std::string& name)
502 {
503 collection_name_ = name;
504 }
505
506 void mongodb_dialect::set_key(const std::string& /*key*/)
507 {
508 // No-op for MongoDB
509 }
510
511 std::string mongodb_dialect::build() const
512 {
513 std::ostringstream oss;
514
515 switch (op_type_) {
516 case operation_type::find:
517 oss << "db." << collection_name_ << ".find(";
518 oss << to_json(filter_);
519 if (!projection_.empty()) {
520 oss << ", " << to_json(projection_);
521 }
522 oss << ")";
523 if (!sort_spec_.empty()) {
524 oss << ".sort({";
525 bool first = true;
526 for (const auto& pair : sort_spec_) {
527 if (!first) oss << ", ";
528 oss << "\"" << pair.first << "\": " << pair.second;
529 first = false;
530 }
531 oss << "})";
532 }
533 if (skip_count_ > 0) {
534 oss << ".skip(" << skip_count_ << ")";
535 }
536 if (limit_count_ > 0) {
537 oss << ".limit(" << limit_count_ << ")";
538 }
539 break;
540
541 case operation_type::insert:
542 if (documents_.empty()) {
543 oss << "db." << collection_name_ << ".insertOne(" << to_json(document_) << ")";
544 } else {
545 oss << "db." << collection_name_ << ".insertMany([";
546 for (size_t i = 0; i < documents_.size(); ++i) {
547 if (i > 0) oss << ", ";
548 oss << to_json(documents_[i]);
549 }
550 oss << "])";
551 }
552 break;
553
554 case operation_type::update:
555 oss << "db." << collection_name_ << ".updateOne(";
556 oss << to_json(filter_) << ", { \"$set\": " << to_json(update_spec_) << " })";
557 break;
558
559 case operation_type::delete_op:
560 if (limit_count_ == 1) {
561 oss << "db." << collection_name_ << ".deleteOne(" << to_json(filter_) << ")";
562 } else {
563 oss << "db." << collection_name_ << ".deleteMany(" << to_json(filter_) << ")";
564 }
565 break;
566
567 case operation_type::aggregate:
568 oss << "db." << collection_name_ << ".aggregate([";
569 for (size_t i = 0; i < pipeline_.size(); ++i) {
570 if (i > 0) oss << ", ";
571 oss << to_json(pipeline_[i]);
572 }
573 oss << "])";
574 break;
575
576 default:
577 throw std::runtime_error("Invalid MongoDB operation type");
578 }
579
580 return oss.str();
581 }
582
583 void mongodb_dialect::reset()
584 {
585 op_type_ = operation_type::none;
586 collection_name_.clear();
587 filter_.clear();
588 projection_.clear();
589 sort_spec_.clear();
590 limit_count_ = 0;
591 skip_count_ = 0;
592 document_.clear();
593 documents_.clear();
594 update_spec_.clear();
595 pipeline_.clear();
596 }
597
598 database_types mongodb_dialect::get_database_type() const
599 {
601 }
602
603 std::string mongodb_dialect::to_json(const std::map<std::string, core::database_value>& data) const
604 {
605 if (data.empty()) {
606 return "{}";
607 }
608
609 std::ostringstream oss;
610 oss << "{ ";
611 bool first = true;
612 for (const auto& pair : data) {
613 if (!first) oss << ", ";
614 oss << "\"" << pair.first << "\": " << value_to_json(pair.second);
615 first = false;
616 }
617 oss << " }";
618 return oss.str();
619 }
620
621 std::string mongodb_dialect::value_to_json(const core::database_value& value) const
622 {
623 std::ostringstream oss;
624 std::visit([&oss](const auto& val) {
625 using T = std::decay_t<decltype(val)>;
626 if constexpr (std::is_same_v<T, std::string>) {
627 oss << "\"" << val << "\"";
628 } else if constexpr (std::is_same_v<T, int64_t>) {
629 oss << val;
630 } else if constexpr (std::is_same_v<T, double>) {
631 oss << val;
632 } else if constexpr (std::is_same_v<T, bool>) {
633 oss << (val ? "true" : "false");
634 } else if constexpr (std::is_same_v<T, std::monostate> || std::is_same_v<T, std::nullptr_t>) {
635 oss << "null";
636 }
637 }, value);
638 return oss.str();
639 }
640#endif // USE_MONGODB
641
642#ifdef USE_REDIS
643 // redis_dialect implementation
644 redis_dialect::redis_dialect()
645 {
646 }
647
648 void redis_dialect::set_query_type(query_type /*type*/)
649 {
650 // Redis commands don't map directly to query types
651 }
652
653 query_dialect::query_type redis_dialect::get_query_type() const
654 {
655 return query_type::none;
656 }
657
658 void redis_dialect::set_select_columns(const std::vector<std::string>& /*columns*/)
659 {
660 // No-op for Redis
661 }
662
663 void redis_dialect::set_from_table(const std::string& /*table*/)
664 {
665 // No-op for Redis
666 }
667
668 void redis_dialect::add_where_condition(const query_condition& /*condition*/)
669 {
670 // No-op for Redis
671 }
672
673 void redis_dialect::add_where_condition(const std::string& /*field*/, const std::string& /*op*/, const core::database_value& /*value*/)
674 {
675 // No-op for Redis
676 }
677
678 void redis_dialect::add_join(const std::string& /*table*/, const std::string& /*condition*/, join_type /*type*/)
679 {
680 // No-op for Redis
681 }
682
683 void redis_dialect::set_group_by(const std::vector<std::string>& /*columns*/)
684 {
685 // No-op for Redis
686 }
687
688 void redis_dialect::set_having(const std::string& /*condition*/)
689 {
690 // No-op for Redis
691 }
692
693 void redis_dialect::add_order_by(const std::string& /*column*/, sort_order /*order*/)
694 {
695 // No-op for Redis
696 }
697
698 void redis_dialect::set_limit(size_t /*count*/)
699 {
700 // No-op for Redis
701 }
702
703 void redis_dialect::set_offset(size_t /*count*/)
704 {
705 // No-op for Redis
706 }
707
708 void redis_dialect::set_insert_table(const std::string& /*table*/)
709 {
710 // No-op for Redis
711 }
712
713 void redis_dialect::set_insert_data(const std::map<std::string, core::database_value>& data)
714 {
715 // For Redis, we can use HSET to insert hash data
716 if (!data.empty()) {
717 command_ = "HSET";
718 args_.clear();
719 args_.push_back(key_);
720 for (const auto& pair : data) {
721 args_.push_back(pair.first);
722 std::visit([this](const auto& val) {
723 using T = std::decay_t<decltype(val)>;
724 if constexpr (std::is_same_v<T, std::string>) {
725 args_.push_back(val);
726 } else if constexpr (std::is_same_v<T, int64_t>) {
727 args_.push_back(std::to_string(val));
728 } else if constexpr (std::is_same_v<T, double>) {
729 args_.push_back(std::to_string(val));
730 } else if constexpr (std::is_same_v<T, bool>) {
731 args_.push_back(val ? "1" : "0");
732 } else {
733 args_.push_back("");
734 }
735 }, pair.second);
736 }
737 }
738 }
739
740 void redis_dialect::set_insert_rows(const std::vector<std::map<std::string, core::database_value>>& /*rows*/)
741 {
742 // Redis doesn't support bulk insert in the same way
743 }
744
745 void redis_dialect::set_update_table(const std::string& /*table*/)
746 {
747 // No-op for Redis
748 }
749
750 void redis_dialect::set_update_data(const std::map<std::string, core::database_value>& data)
751 {
752 set_insert_data(data); // Same as insert for Redis HSET
753 }
754
755 void redis_dialect::set_delete_table(const std::string& /*table*/)
756 {
757 command_ = "DEL";
758 args_.clear();
759 args_.push_back(key_);
760 }
761
762 void redis_dialect::set_collection(const std::string& /*name*/)
763 {
764 // No-op for Redis
765 }
766
767 void redis_dialect::set_key(const std::string& key)
768 {
769 key_ = key;
770 command_ = "GET";
771 args_.clear();
772 args_.push_back(key);
773 }
774
775 std::string redis_dialect::build() const
776 {
777 std::ostringstream oss;
778 oss << command_;
779 for (const auto& arg : args_) {
780 oss << " " << arg;
781 }
782 return oss.str();
783 }
784
785 void redis_dialect::reset()
786 {
787 command_.clear();
788 args_.clear();
789 key_.clear();
790 value_.clear();
791 }
792
793 database_types redis_dialect::get_database_type() const
794 {
796 }
797
798 std::vector<std::string> redis_dialect::build_args() const
799 {
800 std::vector<std::string> result;
801 result.push_back(command_);
802 result.insert(result.end(), args_.begin(), args_.end());
803 return result;
804 }
805#endif // USE_REDIS
806
807 } // namespace detail
808
809} // namespace database
void add_where_condition(const query_condition &condition) override
std::vector< query_condition > where_conditions_
void set_query_type(query_type type) override
void set_collection(const std::string &name) override
query_type get_query_type() const override
void set_offset(size_t count) override
std::string build() const override
void add_order_by(const std::string &column, sort_order order) override
void set_select_columns(const std::vector< std::string > &columns) override
std::string escape_identifier(const std::string &identifier) const
std::vector< std::string > select_columns_
std::string join_type_to_string(join_type type) const
void set_update_data(const std::map< std::string, core::database_value > &data) override
void set_group_by(const std::vector< std::string > &columns) override
std::vector< std::map< std::string, core::database_value > > insert_rows_
std::vector< std::string > order_by_clauses_
std::vector< std::string > joins_
void set_delete_table(const std::string &table) override
void set_insert_table(const std::string &table) override
void set_having(const std::string &condition) override
void set_update_table(const std::string &table) override
void set_insert_data(const std::map< std::string, core::database_value > &data) override
void add_join(const std::string &table, const std::string &condition, join_type type) override
void set_from_table(const std::string &table) override
void set_limit(size_t count) override
std::map< std::string, core::database_value > set_data_
std::string format_value(const core::database_value &value) const
void set_key(const std::string &key) override
sql_dialect(database_types db_type)
database_types get_database_type() const override
void set_insert_rows(const std::vector< std::map< std::string, core::database_value > > &rows) override
std::vector< std::string > group_by_columns_
Represents a WHERE condition in a query.
std::variant< std::string, int64_t, double, bool, std::nullptr_t > database_value
std::unique_ptr< query_dialect > create_dialect(database_types type)
Factory function to create appropriate dialect for database type.
database_types
Represents various database backends or modes.
@ none
No specific database type is set.
@ mongodb
Indicates a MongoDB database (future implementation).
@ sqlite
Indicates a SQLite database.
@ redis
Indicates a Redis database (future implementation).
@ postgres
Indicates a PostgreSQL database.
sort_order
Sort order for ORDER BY clauses.
join_type
Types of SQL joins.