SQLite Containers
Loading...
Searching...
No Matches
KeyMultiValueDB.hpp
Go to the documentation of this file.
1#pragma once
2
5
6#include "parts/BaseDB.hpp"
7#include <algorithm>
8
9namespace sqlite_containers {
10
21 template<class KeyT, class ValueT>
22 class KeyMultiValueDB final : public BaseDB {
23 public:
24
27
30 KeyMultiValueDB(const Config& config) : BaseDB() {
31 set_config(config);
32 }
33
35 ~KeyMultiValueDB() override final = default;
36
37 // --- Operators ---
38
45 template<template <class...> class ContainerT>
46 KeyMultiValueDB& operator=(const ContainerT<KeyT, ValueT>& container) {
47 // Get the default transaction mode from the configuration
48 auto txn_mode = get_config().default_txn_mode;
49
50 execute_in_transaction([this, &container]() {
51 db_reconcile(container);
52 }, txn_mode); // Use transaction mode from the configuration
53 return *this;
54 }
55
63 template<template <class...> class ContainerT, template <class...> class ValueContainerT>
64 KeyMultiValueDB& operator=(const ContainerT<KeyT, ValueContainerT<ValueT>>& container) {
65 // Get the default transaction mode from the configuration
66 auto txn_mode = get_config().default_txn_mode;
67
68 execute_in_transaction([this, &container]() {
69 db_reconcile(container);
70 }, txn_mode); // Use transaction mode from the configuration
71 return *this;
72 }
73
79 template<template <class...> class ContainerT = std::multimap>
80 ContainerT<KeyT, ValueT> operator()() {
81 ContainerT<KeyT, ValueT> container;
82 // Get the default transaction mode from the configuration
83 auto txn_mode = get_config().default_txn_mode;
84
85 execute_in_transaction([this, &container]() {
86 db_load(container);
87 }, txn_mode); // Use transaction mode from the configuration
88 return container;
89 }
90
97 template<template <class...> class ContainerT, template <class...> class ValueContainerT>
98 ContainerT<KeyT, ValueContainerT<ValueT>> operator()() {
99 ContainerT<KeyT, ValueContainerT<ValueT>> container;
100 // Get the default transaction mode from the configuration
101 auto txn_mode = get_config().default_txn_mode;
102
103 execute_in_transaction([this, &container]() {
104 db_load(container);
105 }, txn_mode); // Use transaction mode from the configuration
106 return container;
107 }
108
109 // --- Existing methods ---
110
116 template<template <class...> class ContainerT>
117 void load(
118 ContainerT<KeyT, ValueT>& container,
119 const TransactionMode& mode) {
120 execute_in_transaction([this, &container]() {
121 db_load(container);
122 }, mode);
123 }
124
129 template<template <class...> class ContainerT>
130 void load(
131 ContainerT<KeyT, ValueT>& container) {
132 std::lock_guard<std::mutex> locker(m_sqlite_mutex);
133 db_load(container);
134 }
135
142 template<template <class...> class ContainerT, template <class...> class ValueContainerT>
143 void load(
144 ContainerT<KeyT, ValueContainerT<ValueT>>& container,
145 const TransactionMode& mode) {
146 execute_in_transaction([this, &container]() {
147 db_load(container);
148 }, mode);
149 }
150
156 template<template <class...> class ContainerT, template <class...> class ValueContainerT>
157 void load(
158 ContainerT<KeyT, ValueContainerT<ValueT>>& container) {
159 std::lock_guard<std::mutex> locker(m_sqlite_mutex);
160 db_load(container);
161 }
162
168 template<template <class...> class ContainerT>
169 ContainerT<KeyT, ValueT> retrieve_all(
170 const TransactionMode& mode) {
171 ContainerT<KeyT, ValueT> container;
172 execute_in_transaction([this, &container]() {
173 db_load(container);
174 }, mode);
175 return container;
176 }
177
182 template<template <class...> class ContainerT>
183 ContainerT<KeyT, ValueT> retrieve_all() {
184 ContainerT<KeyT, ValueT> container;
185 std::lock_guard<std::mutex> locker(m_sqlite_mutex);
186 db_load(container);
187 return container;
188 }
189
196 template<template <class...> class ContainerT, template <class...> class ValueContainerT>
197 ContainerT<KeyT, ValueContainerT<ValueT>> retrieve_all(
198 const TransactionMode& mode) {
199 ContainerT<KeyT, ValueContainerT<ValueT>> container;
200 execute_in_transaction([this, &container]() {
201 db_load(container);
202 }, mode);
203 return container;
204 }
205
211 template<template <class...> class ContainerT, template <class...> class ValueContainerT>
212 ContainerT<KeyT, ValueContainerT<ValueT>> retrieve_all() {
213 ContainerT<KeyT, ValueContainerT<ValueT>> container;
214 std::lock_guard<std::mutex> locker(m_sqlite_mutex);
215 db_load(container);
216 return container;
217 }
218
224 template<template <class...> class ContainerT>
225 void append(
226 const ContainerT<KeyT, ValueT>& container,
227 const TransactionMode& mode) {
228 execute_in_transaction([this, &container]() {
229 db_append(container);
230 }, mode);
231 }
232
237 template<template <class...> class ContainerT>
238 void append(
239 const ContainerT<KeyT, ValueT>& container) {
240 std::lock_guard<std::mutex> locker(m_sqlite_mutex);
241 db_append(container);
242 }
243
250 template<template <class...> class ContainerT, template <class...> class ValueContainerT>
251 void append(
252 const ContainerT<KeyT, ValueContainerT<ValueT>>& container,
253 const TransactionMode& mode) {
254 execute_in_transaction([this, &container]() {
255 db_append(container);
256 }, mode);
257 }
258
264 template<template <class...> class ContainerT, template <class...> class ValueContainerT>
265 void append(
266 const ContainerT<KeyT, ValueContainerT<ValueT>>& container) {
267 std::lock_guard<std::mutex> locker(m_sqlite_mutex);
268 db_append(container);
269 }
270
276 template<template <class...> class ContainerT>
278 const ContainerT<KeyT, ValueT>& container,
279 const TransactionMode& mode) {
280 execute_in_transaction([this, &container]() {
281 db_reconcile(container);
282 }, mode);
283 }
284
289 template<template <class...> class ContainerT>
291 const ContainerT<KeyT, ValueT>& container) {
292 std::lock_guard<std::mutex> locker(m_sqlite_mutex);
293 db_reconcile(container);
294 }
295
302 template<template <class...> class ContainerT, template <class...> class ValueContainerT>
304 const ContainerT<KeyT, ValueContainerT<ValueT>>& container,
305 const TransactionMode& mode) {
306 execute_in_transaction([this, &container]() {
307 db_reconcile(container);
308 }, mode);
309 }
310
316 template<template <class...> class ContainerT, template <class...> class ValueContainerT>
317 void reconcile(const ContainerT<KeyT, ValueContainerT<ValueT>>& container) {
318 std::lock_guard<std::mutex> locker(m_sqlite_mutex);
319 db_reconcile(container);
320 }
321
327 void insert(
328 const KeyT &key,
329 const ValueT &value,
330 const TransactionMode& mode) {
331 execute_in_transaction([this, &key, &value]() {
332 db_insert(key, value);
333 }, mode);
334 }
335
341 void insert(
342 const KeyT &key,
343 const ValueT &value) {
344 std::lock_guard<std::mutex> locker(m_sqlite_mutex);
345 db_insert(key, value);
346 }
347
352 void insert(
353 const std::pair<KeyT, ValueT> &pair,
354 const TransactionMode& mode) {
355 execute_in_transaction([this, &pair]() {
356 db_insert(pair.first, pair.second);
357 }, mode);
358 }
359
363 void insert(
364 const std::pair<KeyT, ValueT> &pair) {
365 std::lock_guard<std::mutex> locker(m_sqlite_mutex);
366 db_insert(pair.first, pair.second);
367 }
368
375 const KeyT& key,
376 const ValueT& value,
377 const std::size_t& value_count) {
378 std::lock_guard<std::mutex> locker(m_sqlite_mutex);
379 db_set_value_count_key_value(key, value, value_count);
380 }
381
389 const KeyT& key,
390 const ValueT& value,
391 const std::size_t& value_count) {
392 std::lock_guard<std::mutex> locker(m_sqlite_mutex);
393 db_set_value_count_key_value(key, value, value_count);
394 }
395
402 std::size_t get_value_count(const KeyT& key, const ValueT& value) const {
403 std::lock_guard<std::mutex> locker(m_sqlite_mutex);
404 return db_get_value_count_key_value(key, value);
405 }
406
413 std::size_t count(const KeyT& key, const ValueT& value) const {
414 std::lock_guard<std::mutex> locker(m_sqlite_mutex);
415 return db_get_value_count_key_value(key, value);
416 }
417
424 template<template <class...> class ContainerT>
425 bool find(const KeyT &key, ContainerT<ValueT>& values) {
426 std::lock_guard<std::mutex> locker(m_sqlite_mutex);
427 return db_find(key, values);
428 }
429
434 std::size_t count() const {
435 std::lock_guard<std::mutex> locker(m_sqlite_mutex);
436 return db_count_key();
437 }
438
443 bool empty() const {
444 return (db_count_key() == 0);
445 }
446
451 void remove(const KeyT &key, const ValueT &value) {
452 std::lock_guard<std::mutex> locker(m_sqlite_mutex);
453 db_remove_key_value(key, value);
454 }
455
459 void remove(const KeyT &key) {
460 std::lock_guard<std::mutex> locker(m_sqlite_mutex);
462 }
463
467 void clear(const TransactionMode& mode) {
468 execute_in_transaction([this]() {
469 db_clear();
470 }, mode);
471 }
472
476 void clear() {
477 std::lock_guard<std::mutex> locker(m_sqlite_mutex);
478 db_clear();
479 }
480
481 private:
482
483 // Statements for loading and managing keys, values, and key-value pairs
485
489
494
496
497 // Statements for temporary tables
500
501 // Statements for purging old keys and values
504
505 // Statements for clearing temporary tables
508
509 // Statements for managing value counts
512
514
515 // Statements for removing key-value pairs and clearing tables
518
522
526 void db_create_table(const Config &config) override final {
527 const std::string keys_table = config.table_name.empty() ? "keys_store" : config.table_name + "_keys";
528 const std::string values_table = config.table_name.empty() ? "values_store" : config.table_name + "_values";
529 const std::string key_value_table = config.table_name.empty() ? "key_value_store" : config.table_name + "_key_value";
530
531 const std::string keys_temp_table = config.table_name.empty() ? "keys_temp_store" : config.table_name + "_temp_keys";
532 const std::string values_temp_table = config.table_name.empty() ? "values_temp_store" : config.table_name + "_temp_values";
533
534 // Create main tables if they do not exist
535 const std::string create_keys_table_sql =
536 "CREATE TABLE IF NOT EXISTS " + keys_table + " ("
537 "id INTEGER PRIMARY KEY AUTOINCREMENT, "
538 "key " + get_sqlite_type<KeyT>() + " NOT NULL UNIQUE);";
539 execute(m_sqlite_db, create_keys_table_sql);
540
541 const std::string create_values_table_sql =
542 "CREATE TABLE IF NOT EXISTS " + values_table + " ("
543 "id INTEGER PRIMARY KEY AUTOINCREMENT, "
544 "value " + get_sqlite_type<ValueT>() + " NOT NULL UNIQUE);";
545 execute(m_sqlite_db, create_values_table_sql);
546
547 const std::string create_key_value_table_sql =
548 "CREATE TABLE IF NOT EXISTS " + key_value_table + " ("
549 "key_id INTEGER NOT NULL, "
550 "value_id INTEGER NOT NULL, "
551 "value_count INTEGER DEFAULT 1, "
552 "FOREIGN KEY(key_id) REFERENCES " + keys_table + "(id) ON DELETE CASCADE, "
553 "FOREIGN KEY(value_id) REFERENCES " + values_table + "(id) ON DELETE CASCADE, "
554 "PRIMARY KEY (key_id, value_id));";
555 execute(m_sqlite_db, create_key_value_table_sql);
556
557 // Create temporary tables for synchronization
558 const std::string create_keys_temp_table_sql =
559 "CREATE TEMPORARY TABLE IF NOT EXISTS " + keys_temp_table + " ("
560 "id INTEGER PRIMARY KEY AUTOINCREMENT, "
561 "key " + get_sqlite_type<KeyT>() + " NOT NULL UNIQUE);";
562 execute(m_sqlite_db, create_keys_temp_table_sql);
563
564 const std::string create_values_temp_table_sql =
565 "CREATE TEMPORARY TABLE IF NOT EXISTS " + values_temp_table + " ("
566 "id INTEGER PRIMARY KEY AUTOINCREMENT, "
567 "value " + get_sqlite_type<ValueT>() + " NOT NULL UNIQUE);";
568 execute(m_sqlite_db, create_values_temp_table_sql);
569
570 // Enable foreign keys
571 execute(m_sqlite_db, "PRAGMA foreign_keys = ON;");
572
573 // Initialize prepared statements for main tables
575 "SELECT " + keys_table + ".key, " + values_table + ".value, " + key_value_table + ".value_count "
576 "FROM " + keys_table + " "
577 "JOIN " + key_value_table + " ON " + keys_table + ".id = " + key_value_table + ".key_id "
578 "JOIN " + values_table + " ON " + key_value_table + ".value_id = " + values_table + ".id;");
579
580 m_stmt_insert_key.init(m_sqlite_db, "INSERT OR IGNORE INTO " + keys_table + " (key) VALUES (?);");
581 m_stmt_insert_value.init(m_sqlite_db, "INSERT OR IGNORE INTO " + values_table + " (value) VALUES (?);");
582 m_stmt_insert_key_value.init(m_sqlite_db, "INSERT INTO " + key_value_table + " (key_id, value_id) VALUES (?, ?);");
583
584 m_stmt_get_key_id.init(m_sqlite_db, "SELECT id FROM " + keys_table + " WHERE key = ?;");
585 m_stmt_get_value_id.init(m_sqlite_db, "SELECT id FROM " + values_table + " WHERE value = ?;");
586 m_stmt_get_value_count.init(m_sqlite_db, "SELECT value_count FROM " + key_value_table + " WHERE key_id = ? AND value_id = ?;");
588 "SELECT value_count FROM " + key_value_table +
589 " WHERE key_id = (SELECT id FROM " + keys_table +
590 " WHERE key = ?) AND value_id = (SELECT id FROM " + values_table +
591 " WHERE value = ?);");
592
593 m_stmt_count_key.init(m_sqlite_db, "SELECT COUNT(*) FROM " + keys_table + ";");
594
595 // Initialize prepared statements for temporary tables
596 m_stmt_insert_key_temp.init(m_sqlite_db, "INSERT OR IGNORE INTO " + keys_temp_table + " (key) VALUES (?);");
597 m_stmt_insert_value_temp.init(m_sqlite_db, "INSERT OR IGNORE INTO " + values_temp_table + " (value) VALUES (?);");
598
599 // Statements for purging old data and clearing temporary tables
600 m_stmt_purge_keys.init(m_sqlite_db, "DELETE FROM " + keys_table + " WHERE key NOT IN (SELECT key FROM " + keys_temp_table + ");");
601 m_stmt_purge_values.init(m_sqlite_db, "DELETE FROM " + values_table + " WHERE value NOT IN (SELECT value FROM " + values_temp_table + ");");
602
603 m_stmt_clear_keys_temp.init(m_sqlite_db, "DELETE FROM " + keys_temp_table + ";");
604 m_stmt_clear_values_temp.init(m_sqlite_db, "DELETE FROM " + values_temp_table + ";");
605
606 // Statements for setting value counts
607 m_stmt_set_value_count.init(m_sqlite_db, "UPDATE " + key_value_table + " SET value_count = ? WHERE key_id = ? AND value_id = ?;");
609 "UPDATE " + key_value_table +
610 " SET value_count = ? WHERE key_id = (SELECT id FROM " + keys_table +
611 " WHERE key = ?) AND value_id = (SELECT id FROM " + values_table +
612 " WHERE value = ?);");
613
614 // Statement for finding key-value pairs
616 "SELECT v.value, kv.value_count "
617 "FROM " + values_table + " v "
618 "JOIN " + key_value_table + " kv ON v.id = kv.value_id "
619 "JOIN " + keys_table + " k ON kv.key_id = k.id "
620 "WHERE k.key = ?;");
621
622 // Statements for removing key-value pairs and clearing tables
623 m_stmt_remove_key_value.init(m_sqlite_db, "DELETE FROM " + key_value_table + " WHERE key_id = (SELECT id FROM " + keys_table + " WHERE key = ?) AND value_id = (SELECT id FROM " + values_table + " WHERE value = ?);");
624 m_stmt_remove_all_values.init(m_sqlite_db, "DELETE FROM " + keys_table + " WHERE key = ?");
625
626 m_stmt_clear_keys.init(m_sqlite_db, "DELETE FROM " + keys_table + ";");
627 m_stmt_clear_values.init(m_sqlite_db, "DELETE FROM " + values_table + ";");
628 m_stmt_clear_key_values.init(m_sqlite_db, "DELETE FROM " + key_value_table + ";");
629 }
630
635 template<template <class...> class ContainerT>
636 void db_load(ContainerT<KeyT, ValueT>& container) {
637 int err;
638 try {
639 for (;;) {
640 while ((err = m_stmt_load.step()) == SQLITE_ROW) {
641 KeyT key = m_stmt_load.extract_column<KeyT>(0);
642 ValueT value = m_stmt_load.extract_column<ValueT>(1);
643 size_t value_count = m_stmt_load.extract_column<size_t>(2);
644 add_value(container, key, value, value_count);
645 }
646 if (err == SQLITE_DONE) {
648 return;
649 }
650 if (err == SQLITE_BUSY) {
651 // Handle busy database, retry reading
654 continue;
655 }
656 // Handle SQLite errors
657 std::string err_msg = "SQLite error: ";
658 err_msg += std::to_string(err);
659 err_msg += ", ";
660 err_msg += sqlite3_errmsg(m_sqlite_db);
661 throw sqlite_exception(err_msg, err);
662 }
663 } catch (...) {
665 std::current_exception(), {&m_stmt_load},
666 "Unknown error occurred while loading data from database.");
667 }
668 }
669
675 template<template <class...> class ContainerT, template <class...> class ValueContainerT>
676 void db_load(ContainerT<KeyT, ValueContainerT<ValueT>>& container) {
677 int err;
678 try {
679 for (;;) {
680 while ((err = m_stmt_load.step()) == SQLITE_ROW) {
681 KeyT key = m_stmt_load.extract_column<KeyT>(0);
682 ValueT value = m_stmt_load.extract_column<ValueT>(1);
683 const size_t value_count = m_stmt_load.extract_column<size_t>(2);
684 add_value(container[key], value, value_count);
685 }
686 if (err == SQLITE_DONE) {
688 return;
689 }
690 if (err == SQLITE_BUSY) {
691 // Handle busy database, retry reading
694 continue;
695 }
696 // Handle SQLite errors
697 std::string err_msg = "SQLite error: ";
698 err_msg += std::to_string(err);
699 err_msg += ", ";
700 err_msg += sqlite3_errmsg(m_sqlite_db);
701 throw sqlite_exception(err_msg, err);
702 }
703 } catch (...) {
705 std::current_exception(), {&m_stmt_load},
706 "Unknown error occurred while loading data from database.");
707 }
708 }
709
714 template<template <class...> class ContainerT>
715 void db_append(const ContainerT<KeyT, ValueT>& container) {
716 try {
717 for (const auto& pair : container) {
718 // Insert the key if it doesn't already exist
719 db_insert_key(pair.first);
720 // Insert the value if it doesn't already exist
721 db_insert_value(pair.second);
722 // Get the key_id
723 const int64_t key_id = db_get_key_id(pair.first);
724 if (key_id == -1) continue;
725 // Get the value_id
726 const int64_t value_id = db_get_value_id(pair.second);
727 if (value_id == -1) continue;
728 // Get the value_count
729 const size_t value_count = db_get_value_count(key_id, value_id);
730 if (value_count) {
731 // Update the value_count with the key_id and value_id
732 db_set_value_count(key_id, value_id, value_count + 1);
733 } else {
734 // Insert the key-value pair
735 db_insert_key_value(key_id, value_id);
736 }
737 }
738 } catch (...) {
740 std::current_exception(), {
743 },
744 "Unknown error occurred while appending key-value pairs to the database.");
745 }
746 }
747
753 template<template <class...> class ContainerT, template <class...> class ValueContainerT>
755 const ContainerT<KeyT, ValueContainerT<ValueT>>& container) {
756 try {
757 for (const auto& pair : container) {
758 // Insert the key if it doesn't already exist
759 db_insert_key(pair.first);
760 // Get the key_id
761 const int64_t key_id = db_get_key_id(pair.first);
762 if (key_id == -1) continue;
763 for (const auto& value : pair.second) {
764 // Insert the value if it doesn't already exist
765 db_insert_value(value);
766 // Get the value_id
767 const int64_t value_id = db_get_value_id(value);
768 if (value_id == -1) continue;
769 // Get the value_count
770 const size_t value_count = db_get_value_count(key_id, value_id);
771 if (value_count) {
772 // Update the value_count with the key_id and value_id
773 db_set_value_count(key_id, value_id, value_count + 1);
774 } else {
775 // Insert the key-value pair
776 db_insert_key_value(key_id, value_id);
777 }
778 }
779 }
780 } catch (...) {
782 std::current_exception(), {
785 },
786 "Unknown error occurred while appending key-value pairs to the database.");
787 }
788 }
789
795 const KeyT &key,
796 const ValueT &value) {
797 try {
798 // Insert the key if it doesn't already exist
799 db_insert_key(key);
800 // Insert the value if it doesn't already exist
801 db_insert_value(value);
802 // Get the key_id
803 const int64_t key_id = db_get_key_id(key);
804 if (key_id == -1) throw sqlite_exception("Key ID not found.");
805 // Get the value_id
806 const int64_t value_id = db_get_value_id(value);
807 if (value_id == -1) throw sqlite_exception("Value ID not found.");
808 // Get the value_count
809 const size_t value_count = db_get_value_count(key_id, value_id);
810 if (value_count) {
811 // Update the value_count with the key_id and value_id
812 db_set_value_count(key_id, value_id, value_count + 1);
813 } else {
814 // Insert the key-value pair
815 db_insert_key_value(key_id, value_id);
816 }
817 } catch (...) {
819 std::current_exception(), {
822 },
823 "Unknown error occurred while inserting key-value pair.");
824 }
825 }
826
834 template<template <class...> class ContainerT>
836 const ContainerT<KeyT, ValueT>& container) {
837 try {
838 // Collect value repetitions
839 std::unordered_map<KeyT, std::vector<std::pair<ValueT, int>>> temp_container;
840 for (const auto& pair : container) {
841 auto& vec = temp_container[pair.first];
842 auto it = find_or_insert(vec, pair.second);
843 it->second++;
844 }
845
847 for (const auto& item : temp_container) {
848 db_insert_key(item.first);
849 db_insert_key_temp(item.first);
850 for (const auto& pair : item.second) {
851 db_insert_value(pair.first);
852 db_insert_value_temp(pair.first);
853 }
854 }
855 for (const auto& pair : container) {
856 const int64_t key_id = db_get_key_id(pair.first);
857 if (key_id == -1) throw sqlite_exception("Failed to retrieve key ID for the provided key during reconciliation.");
858 const int64_t value_id = db_get_value_id(pair.second);
859 if (value_id == -1) throw sqlite_exception("Failed to retrieve value ID for the provided value during reconciliation.");
860 const size_t value_count = db_get_value_count(key_id, value_id);
861 if (!value_count) {
862 db_insert_key_value(key_id, value_id);
863 }
864 }
865
868 for (const auto& item : temp_container) {
869 const int64_t key_id = db_get_key_id(item.first);
870 if (key_id == -1) continue;
871 for (const auto& pair : item.second) {
872 const int64_t value_id = db_get_value_id(pair.first);
873 if (value_id == -1) continue;
874 db_set_value_count(key_id, value_id, pair.second);
875 }
876 }
877 } catch (...) {
879 std::current_exception(), {
884 },
885 "Unknown error occurred while reconciling data.");
886 }
887 }
888
889 // Helper function to find or insert value in a sorted vector
890
898 template<typename T>
899 typename std::vector<std::pair<T, int>>::iterator find_or_insert(std::vector<std::pair<T, int>>& vec, const T& value,
900 typename std::enable_if<
901 !std::is_integral<T>::value &&
902 !std::is_floating_point<T>::value &&
903 !std::is_same<T, std::string>::value &&
904 !std::is_same<T, std::vector<char>>::value &&
905 !std::is_same<T, std::vector<uint8_t>>::value &&
906 std::is_trivially_copyable<T>::value
907 >::type* = 0) {
908 auto it = std::find_if(vec.begin(), vec.end(), [&value](const std::pair<T, int>& element) {
909 return byte_compare(element.first, value);
910 });
911 if (it == vec.end()) {
912 //it = vec.insert(it, {value, 0});
913 it = vec.emplace(it, std::make_pair(value, 0));
914 }
915 return it;
916 }
917
925 template<typename T>
926 typename std::vector<std::pair<T, int>>::iterator find_or_insert(std::vector<std::pair<T, int>>& vec, const T& value,
927 typename std::enable_if<
928 std::is_integral<T>::value ||
929 std::is_floating_point<T>::value ||
930 std::is_same<T, std::string>::value ||
931 std::is_same<T, std::vector<char>>::value ||
932 std::is_same<T, std::vector<uint8_t>>::value>::type* = 0) {
933 auto it = std::find_if(vec.begin(), vec.end(), [&value](const std::pair<T, int>& element) {
934 return element.first == value;
935 });
936 if (it == vec.end()) {
937 it = vec.insert(it, {value, 0});
938 }
939 return it;
940 }
941
947 template<template <class...> class ContainerT, template <class...> class ValueContainerT>
949 const ContainerT<KeyT, ValueContainerT<ValueT>>& container) {
950 try {
951 // Collect value repetitions
952 std::unordered_map<KeyT, std::unordered_map<ValueT, int>> temp_container;
953 for (const auto& pair : container) {
954 auto it_key = temp_container.find(pair.first);
955 for (const ValueT& value : pair.second) {
956 if (it_key == temp_container.end()) {
957 temp_container[pair.first][value] = 1;
958 } else {
959 auto it_value = it_key->second.find(value);
960 if (it_value == it_key->second.end()) {
961 it_key->second[value] = 1;
962 } else {
963 it_value->second++;
964 }
965 }
966 }
967 }
968
970 for (const auto& item : temp_container) {
971 db_insert_key(item.first);
972 db_insert_key_temp(item.first);
973 for (const auto& pair : item.second) {
974 db_insert_value(pair.first);
975 db_insert_value_temp(pair.first);
976 }
977 }
978
979 for (const auto& pair : container) {
980 const int64_t key_id = db_get_key_id(pair.first);
981 if (key_id == -1) continue;
982 for (const ValueT& value : pair.second) {
983 const int64_t value_id = db_get_value_id(value);
984 if (value_id == -1) continue;
985 const size_t value_count = db_get_value_count(key_id, value_id);
986 if (value_count) continue;
987 db_insert_key_value(key_id, value_id);
988 }
989 }
990
993 for (const auto& item : temp_container) {
994 const int64_t key_id = db_get_key_id(item.first);
995 if (key_id == -1) continue;
996 for (const auto& pair : item.second) {
997 const int64_t value_id = db_get_value_id(pair.first);
998 if (value_id == -1) continue;
999 db_set_value_count(key_id, value_id, pair.second);
1000 }
1001 }
1002 } catch (...) {
1004 std::current_exception(), {
1009 },
1010 "Unknown error occurred while reconciling data.");
1011 }
1012 }
1013
1020 template<template <class...> class ContainerT>
1021 bool db_find(const KeyT &key, ContainerT<ValueT>& container) {
1022 int err;
1023 try {
1024 m_stmt_find.bind_value<KeyT>(1, key);
1025 while ((err = m_stmt_find.step()) == SQLITE_ROW) {
1026 ValueT value = m_stmt_find.extract_column<ValueT>(0);
1027 const size_t value_count = m_stmt_find.extract_column<size_t>(1);
1028 add_value(container, value, value_count);
1029 }
1031 } catch (...) {
1033 std::current_exception(),
1034 {&m_stmt_find},
1035 "Unknown error occurred while finding key-value pairs.");
1036 }
1037 return !container.empty();
1038 }
1039
1045 void db_set_value_count_key_value(const KeyT& key, const ValueT& value, const size_t& value_count) {
1046 try {
1047 m_stmt_set_value_count_kv.bind_value<size_t>(1, value_count);
1049 m_stmt_set_value_count_kv.bind_value<ValueT>(3, value);
1053 } catch (...) {
1055 std::current_exception(),
1057 "Unknown error occurred while setting value count for key-value pair.");
1058 }
1059 }
1060
1065 void db_remove_key_value(const KeyT &key, const ValueT &value) {
1066 try {
1067 m_stmt_remove_key_value.bind_value<KeyT>(1, key);
1068 m_stmt_remove_key_value.bind_value<ValueT>(2, value);
1072 } catch (...) {
1074 std::current_exception(),
1076 "Unknown error occurred while removing key-value pair.");
1077 }
1078 }
1079
1083 void db_remove_all_values(const KeyT &key) {
1084 try {
1089 } catch (...) {
1091 std::current_exception(),
1093 "Unknown error occurred while removing values by key.");
1094 }
1095 }
1096
1102 std::size_t db_get_value_count_key_value(const KeyT &key, const ValueT &value) const {
1103 int err;
1104 size_t value_count = 0;
1105 try {
1107 m_stmt_get_value_count_kv.bind_value<ValueT>(2, value);
1108 while ((err = m_stmt_get_value_count_kv.step()) == SQLITE_ROW) {
1109 value_count = m_stmt_get_value_count_kv.extract_column<size_t>(0);
1110 }
1113 return value_count;
1114 } catch (...) {
1116 std::current_exception(),
1118 "Unknown error occurred while retrieving value count for key-value pair.");
1119 }
1120 }
1121
1125 std::size_t db_count_key() const {
1126 std::size_t count = 0;
1127 int err;
1128 try {
1129 for (;;) {
1130 while ((err = m_stmt_count_key.step()) == SQLITE_ROW) {
1131 count = m_stmt_count_key.extract_column<std::size_t>(0);
1132 }
1133 if (err == SQLITE_DONE) {
1135 break;
1136 }
1137 if (err == SQLITE_BUSY) {
1138 // Handle busy database, retry reading
1141 continue;
1142 }
1143 // Handle SQLite errors
1144 std::string err_msg = "SQLite error: ";
1145 err_msg += std::to_string(err);
1146 err_msg += ", ";
1147 err_msg += sqlite3_errmsg(m_sqlite_db);
1148 throw sqlite_exception(err_msg, err);
1149 }
1150 } catch (...) {
1151 db_handle_exception(std::current_exception(),
1152 {&m_stmt_count_key}, "Unknown error occurred while counting the number of unique keys.");
1153 }
1154 return count;
1155 }
1156
1159 void db_clear() {
1160 try {
1161 std::vector<SqliteStmt*> stmts = {&m_stmt_clear_keys, &m_stmt_clear_values, &m_stmt_clear_key_values};
1162 for (auto* stmt : stmts) {
1163 stmt->execute();
1164 stmt->reset();
1165 }
1166 } catch (...) {
1168 std::current_exception(),
1171 "Unknown error occurred while clearing the database tables.");
1172 }
1173 }
1174
1178 void db_insert_key(const KeyT& key) {
1179 m_stmt_insert_key.bind_value<KeyT>(1, key);
1183 }
1184
1189 int64_t db_get_key_id(const KeyT& key) {
1190 m_stmt_get_key_id.bind_value<KeyT>(1, key);
1191 int64_t key_id = -1;
1192 if (m_stmt_get_key_id.step() == SQLITE_ROW) {
1193 key_id = m_stmt_get_key_id.extract_column<int64_t>(0);
1194 }
1197 return key_id;
1198 }
1199
1203 void db_insert_value(const ValueT& value) {
1204 m_stmt_insert_value.bind_value<ValueT>(1, value);
1208 }
1209
1214 int64_t db_get_value_id(const ValueT& value) {
1215 m_stmt_get_value_id.bind_value<ValueT>(1, value);
1216 int64_t value_id = -1;
1217 if (m_stmt_get_value_id.step() == SQLITE_ROW) {
1218 value_id = m_stmt_get_value_id.extract_column<int64_t>(0);
1219 }
1222 return value_id;
1223 }
1224
1234
1244
1249 void db_insert_key_value(const int64_t& key_id, const int64_t& value_id) {
1250 m_stmt_insert_key_value.bind_value<int64_t>(1, key_id);
1251 m_stmt_insert_key_value.bind_value<int64_t>(2, value_id);
1255 }
1256
1262 std::size_t db_get_value_count(const int64_t &key_id, const int64_t& value_id) {
1263 m_stmt_get_value_count.bind_value<int64_t>(1, key_id);
1264 m_stmt_get_value_count.bind_value<int64_t>(2, value_id);
1265 std::size_t value_count = 0;
1266 if (m_stmt_get_value_count.step() == SQLITE_ROW) {
1267 value_count = m_stmt_get_value_count.extract_column<std::size_t>(0);
1268 }
1271 return value_count;
1272 }
1273
1279 void db_set_value_count(const int64_t& key_id, const int64_t& value_id, const size_t& value_count) {
1280 m_stmt_set_value_count.bind_value<size_t>(1, value_count);
1281 m_stmt_set_value_count.bind_value<int64_t>(2, key_id);
1282 m_stmt_set_value_count.bind_value<int64_t>(3, value_id);
1286 }
1287
1296
1305
1306 }; // KeyMultiValueDB
1307
1308}; // namespace sqlite_containers
Base class for SQLite database management in sqlite_containers.
#define SQLITE_CONTAINERS_BUSY_RETRY_DELAY_MS
Definition Utils.hpp:22
Base class for SQLite database management.
Definition BaseDB.hpp:23
Config get_config() const
Gets the current configuration of the database.
Definition BaseDB.hpp:45
void execute_in_transaction(Func operation, const TransactionMode &mode)
Executes an operation inside a transaction.
Definition BaseDB.hpp:148
void set_config(const Config &config)
Sets the configuration for the database.
Definition BaseDB.hpp:37
void db_handle_exception(std::exception_ptr ex, const std::vector< SqliteStmt * > &stmts, const std::string &message="Unknown error occurred.") const
Handles an exception by resetting and clearing bindings of prepared SQL statements.
Definition BaseDB.hpp:197
Configuration class for SQLite database settings.
Definition Config.hpp:11
TransactionMode default_txn_mode
Definition Config.hpp:29
Template class for managing key-value pairs in a SQLite database, where each key can map to multiple ...
std::size_t db_get_value_count(const int64_t &key_id, const int64_t &value_id)
Retrieves the count of values associated with a specific key-value pair.
void insert(const std::pair< KeyT, ValueT > &pair)
Inserts a key-value pair into the database.
SqliteStmt m_stmt_set_value_count
Statement for setting value count for a key-value pair.
SqliteStmt m_stmt_remove_key_value
Statement for removing a specific key-value pair.
ContainerT< KeyT, ValueContainerT< ValueT > > retrieve_all(const TransactionMode &mode)
Retrieves all key-value pairs from the database with a transaction.
void load(ContainerT< KeyT, ValueT > &container, const TransactionMode &mode)
Loads data from the database into the container.
SqliteStmt m_stmt_clear_values_temp
Statement for clearing the temporary values table.
void reconcile(const ContainerT< KeyT, ValueT > &container)
Reconciles the content of the container with the database.
void db_reconcile(const ContainerT< KeyT, ValueContainerT< ValueT > > &container)
Reconciles the database with the provided container, ensuring data integrity.
ContainerT< KeyT, ValueContainerT< ValueT > > retrieve_all()
Retrieves all key-value pairs from the database.
SqliteStmt m_stmt_clear_keys
Statement for clearing the keys table.
void append(const ContainerT< KeyT, ValueT > &container, const TransactionMode &mode)
Appends the content of the container to the database with a transaction.
void set_value_count(const KeyT &key, const ValueT &value, const std::size_t &value_count)
Sets the count of values associated with a specific key-value pair in the database.
SqliteStmt m_stmt_get_value_id
Statement for retrieving value ID.
void db_insert_value_temp(const ValueT &value)
Inserts a value into the temporary values table.
std::size_t db_count_key() const
Returns the number of elements in the database.
void db_insert_key_temp(const KeyT &key)
Inserts a key into the temporary keys table.
int64_t db_get_key_id(const KeyT &key)
Retrieves the ID of a key from the database.
void db_insert_key_value(const int64_t &key_id, const int64_t &value_id)
Inserts a key-value pair into the key-value table.
ContainerT< KeyT, ValueT > retrieve_all()
Retrieves all key-value pairs from the database.
~KeyMultiValueDB() override final=default
Destructor.
std::size_t count() const
Returns the number of elements in the database. This method returns the number of unique keys stored ...
void db_clear()
Clears all key-value pairs from the database.
void db_set_value_count_key_value(const KeyT &key, const ValueT &value, const size_t &value_count)
Sets the value count for a key-value pair in the database.
SqliteStmt m_stmt_remove_all_values
Statement for removing all values associated with a key.
void db_set_value_count(const int64_t &key_id, const int64_t &value_id, const size_t &value_count)
Sets the count of values associated with a specific key-value pair.
void db_purge_old_data()
Removes old keys and values that are no longer in the temporary tables.
int64_t db_get_value_id(const ValueT &value)
Retrieves the ID of a value from the database.
SqliteStmt m_stmt_insert_key
Statement for inserting a key.
ContainerT< KeyT, ValueT > retrieve_all(const TransactionMode &mode)
Retrieves all key-value pairs from the database with a transaction.
void insert(const KeyT &key, const ValueT &value, const TransactionMode &mode)
Inserts a key-value pair into the database with a transaction.
void insert(const std::pair< KeyT, ValueT > &pair, const TransactionMode &mode)
Inserts a key-value pair into the database with a transaction.
std::vector< std::pair< T, int > >::iterator find_or_insert(std::vector< std::pair< T, int > > &vec, const T &value, typename std::enable_if< std::is_integral< T >::value||std::is_floating_point< T >::value||std::is_same< T, std::string >::value||std::is_same< T, std::vector< char > >::value||std::is_same< T, std::vector< uint8_t > >::value >::type *=0)
Finds or inserts a value into a sorted vector. This method finds a value in the vector,...
void reconcile(const ContainerT< KeyT, ValueContainerT< ValueT > > &container, const TransactionMode &mode)
Reconciles the content of the container with the database with a transaction.
SqliteStmt m_stmt_insert_key_temp
Statement for inserting keys into the temporary table.
ContainerT< KeyT, ValueT > operator()()
Loads all key-value pairs from the database into a container.
KeyMultiValueDB & operator=(const ContainerT< KeyT, ValueContainerT< ValueT > > &container)
Assigns a container where each key maps to a collection of values to the database.
void db_append(const ContainerT< KeyT, ValueContainerT< ValueT > > &container)
Appends the content of the container to the database.
void db_clear_temp_tables()
Clears the temporary keys and values tables.
void clear(const TransactionMode &mode)
Clears all key-value pairs from the database with a transaction.
SqliteStmt m_stmt_clear_keys_temp
Statement for clearing the temporary keys table.
void insert(const KeyT &key, const ValueT &value)
Inserts a key-value pair into the database.
void append(const ContainerT< KeyT, ValueContainerT< ValueT > > &container, const TransactionMode &mode)
Appends the content of the container to the database with a transaction.
void load(ContainerT< KeyT, ValueT > &container)
Loads data from the database into the container.
void db_load(ContainerT< KeyT, ValueContainerT< ValueT > > &container)
Loads data from the database into the container.
void load(ContainerT< KeyT, ValueContainerT< ValueT > > &container, const TransactionMode &mode)
Loads data from the database into the container.
SqliteStmt m_stmt_insert_value_temp
Statement for inserting values into the temporary table.
SqliteStmt m_stmt_get_key_id
Statement for retrieving key ID.
void load(ContainerT< KeyT, ValueContainerT< ValueT > > &container)
Loads data from the database into the container.
void reconcile(const ContainerT< KeyT, ValueT > &container, const TransactionMode &mode)
Reconciles the content of the container with the database with a transaction.
std::size_t count(const KeyT &key, const ValueT &value) const
Alias for get_value_count(). This method is an alias for get_value_count() and performs the same oper...
void append(const ContainerT< KeyT, ValueT > &container)
Appends the content of the container to the database.
ContainerT< KeyT, ValueContainerT< ValueT > > operator()()
Loads all key-value pairs, where each key maps to a collection of values, from the database.
void db_reconcile(const ContainerT< KeyT, ValueT > &container)
Reconciles the database with the provided container. This method compares the content of the provided...
void set_count(const KeyT &key, const ValueT &value, const std::size_t &value_count)
Alias for set_value_count(). This method is an alias for set_value_count() and performs the same oper...
SqliteStmt m_stmt_insert_value
Statement for inserting a value.
void db_load(ContainerT< KeyT, ValueT > &container)
Loads data from the database into the container.
void db_insert(const KeyT &key, const ValueT &value)
Inserts a key-value pair into the database.
std::vector< std::pair< T, int > >::iterator find_or_insert(std::vector< std::pair< T, int > > &vec, const T &value, typename std::enable_if< !std::is_integral< T >::value &&!std::is_floating_point< T >::value &&!std::is_same< T, std::string >::value &&!std::is_same< T, std::vector< char > >::value &&!std::is_same< T, std::vector< uint8_t > >::value &&std::is_trivially_copyable< T >::value >::type *=0)
Finds or inserts a value into a sorted vector. This method finds a value in the vector,...
void append(const ContainerT< KeyT, ValueContainerT< ValueT > > &container)
Appends the content of the container to the database.
void db_remove_all_values(const KeyT &key)
Removes all values associated with a key from the database.
bool empty() const
Checks if the database is empty. This method checks if there are any keys stored in the database.
SqliteStmt m_stmt_clear_values
Statement for clearing the values table.
SqliteStmt m_stmt_count_key
Statement for counting the number of keys.
SqliteStmt m_stmt_insert_key_value
Statement for inserting a key-value pair.
void db_create_table(const Config &config) override final
Creates the tables in the database. This method creates both the main and temporary tables for keys,...
void db_append(const ContainerT< KeyT, ValueT > &container)
Appends the content of the container to the database.
void reconcile(const ContainerT< KeyT, ValueContainerT< ValueT > > &container)
Reconciles the content of the container with the database.
KeyMultiValueDB(const Config &config)
Constructor with configuration.
std::size_t get_value_count(const KeyT &key, const ValueT &value) const
Retrieves the count of values associated with a specific key-value pair from the database....
SqliteStmt m_stmt_purge_values
Statement for purging old values.
void db_remove_key_value(const KeyT &key, const ValueT &value)
Removes a specific key-value pair from the database.
void clear()
Clears all key-value pairs from the database.
SqliteStmt m_stmt_purge_keys
Statement for purging old keys.
void remove(const KeyT &key)
Removes all values associated with a key from the database.
void db_insert_value(const ValueT &value)
Inserts a value into the database.
SqliteStmt m_stmt_find
Statement for finding values by key.
SqliteStmt m_stmt_get_value_count
Statement for retrieving the count of a value.
SqliteStmt m_stmt_set_value_count_kv
Statement for setting value count by key-value pair.
bool find(const KeyT &key, ContainerT< ValueT > &values)
Finds values by key in the database.
std::size_t db_get_value_count_key_value(const KeyT &key, const ValueT &value) const
Retrieves the count of a specific value associated with a key in the database.
bool db_find(const KeyT &key, ContainerT< ValueT > &container)
Finds values by key in the database.
void remove(const KeyT &key, const ValueT &value)
Removes a specific key-value pair from the database.
SqliteStmt m_stmt_get_value_count_kv
Statement for retrieving the count of a value by key-value pair.
SqliteStmt m_stmt_load
Statement for loading data from the database.
SqliteStmt m_stmt_clear_key_values
Statement for clearing the key-value pairs table.
void db_insert_key(const KeyT &key)
Inserts a key into the database.
Class for managing SQLite prepared statements.
void clear_bindings()
Clears all bindings on the prepared statement.
void execute(sqlite3 *sqlite_db)
Executes the prepared statement.
T extract_column(const int &index, typename std::enable_if< std::is_integral< T >::value >::type *=0)
Extracts a value from a SQLite statement column.
void reset()
Resets the prepared statement.
void init(sqlite3 *sqlite_db, const char *query)
Initializes the statement.
bool bind_value(const int &index, const T &value, typename std::enable_if< std::is_integral< T >::value >::type *=0)
Binds a value to a SQLite statement.
Exception class for SQLite errors.
Definition Utils.hpp:27
void execute(sqlite3_stmt *stmt)
Executes a SQLite statement.
Definition Utils.hpp:47
std::enable_if< std::is_trivially_copyable< typenameT::value_type >::value &&std::is_same< T, std::vector< typenameT::value_type > >::value, std::string >::type get_sqlite_type()
Definition Utils.hpp:162
TransactionMode
Defines SQLite transaction modes.
Definition Enums.hpp:56
void add_value(ContainerT< T > &container, T &value, typename std::enable_if< std::is_same< ContainerT< T >, std::set< T > >::value||std::is_same< ContainerT< T >, std::unordered_set< T > >::value >::type *=0)
Adds a value to a container (set or unordered_set).
Definition Utils.hpp:184