LMDBAL 0.6.0
LMDB (Lightning Memory-Mapped Database Manager) Abstraction Layer
Loading...
Searching...
No Matches
storage.hpp
1/*
2 * LMDB Abstraction Layer.
3 * Copyright (C) 2023 Yury Gubich <blue@macaw.me>
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19#pragma once
20
21#include "storage.h"
22#include "exceptions.h"
23
24#define UNUSED(x) (void)(x)
25
47template<class K, class V>
48LMDBAL::Storage<K, V>::Storage(Base* parent, const std::string& name, bool duplicates):
49 iStorage(parent, name, duplicates),
50 keySerializer(),
51 valueSerializer(),
52 cursors()
53{}
54
58template<class K, class V>
60 for (const std::pair<const uint32_t, Cursor<K, V>*>& pair : cursors)
61 pair.second->dropped();
62}
63
78template<class K, class V>
79void LMDBAL::Storage<K, V>::addRecord(const K& key, const V& value) {
80 ensureOpened(addRecordMethodName);
81 TransactionID txn = beginTransaction();
82 try {
83 Storage<K, V>::addRecord(key, value, txn);
84 } catch (...) {
85 abortTransaction(txn);
86 throw;
87 }
88
89 commitTransaction(txn);
90}
91
106template<class K, class V>
107void LMDBAL::Storage<K, V>::addRecord(const K& key, const V& value, TransactionID txn) {
108 MDB_val lmdbKey = keySerializer.setData(key);
109 MDB_val lmdbData = valueSerializer.setData(value);
110
111 unsigned int flags = 0;
112 if (duplicates)
113 flags |= MDB_NODUPDATA;
114 else
115 flags |= MDB_NOOVERWRITE;
116
117 int rc = _mdbPut(txn, lmdbKey, lmdbData, flags);
118 if (rc != MDB_SUCCESS)
119 throwDuplicateOrUnknown(rc, toString(key));
120}
121
141template<class K, class V>
142void LMDBAL::Storage<K, V>::addRecord(const K& key, const V& value, const WriteTransaction& txn) {
143 ensureOpened(addRecordMethodName);
144 addRecord(key, value, extractTransactionId(txn, addRecordMethodName));
145}
146
163template<class K, class V>
164bool LMDBAL::Storage<K, V>::forceRecord(const K& key, const V& value) {
165 ensureOpened(forceRecordMethodName);
166
167 TransactionID txn = beginTransaction();
168 bool added;
169 try {
170 added = Storage<K, V>::forceRecord(key, value, txn);
171 } catch (...) {
172 abortTransaction(txn);
173 throw;
174 }
175
176 commitTransaction(txn);
177 return added;
178}
179
197template<class K, class V>
198bool LMDBAL::Storage<K, V>::forceRecord(const K& key, const V& value, TransactionID txn) {
199 bool added;
200 if (duplicates) {
201 try {
202 addRecord(key, value, txn);
203 added = true;
204 } catch (const LMDBAL::Exist& e) {
205 added = false;
206 }
207 } else {
208 MDB_val lmdbKey = keySerializer.setData(key);
209 MDB_val lmdbData;
210
211 int rc = _mdbGet(txn, lmdbKey, lmdbData);
212 switch (rc) {
213 case MDB_SUCCESS:
214 added = false;
215 break;
216 case MDB_NOTFOUND:
217 added = true;
218 break;
219 default:
220 added = false;
221 throwUnknown(rc);
222 }
223
224 lmdbData = valueSerializer.setData(value);
225 rc = _mdbPut(txn, lmdbKey, lmdbData);
226 if (rc != MDB_SUCCESS)
227 throwUnknown(rc);
228 }
229 return added;
230}
231
253template<class K, class V>
254bool LMDBAL::Storage<K, V>::forceRecord(const K& key, const V& value, const WriteTransaction& txn) {
255 ensureOpened(forceRecordMethodName);
256 return forceRecord(key, value, extractTransactionId(txn, forceRecordMethodName));
257}
258
278template<class K, class V>
279void LMDBAL::Storage<K, V>::changeRecord(const K& key, const V& value) {
280 ensureOpened(changeRecordMethodName);
281
282 TransactionID txn = beginTransaction();
283 try {
284 Storage<K, V>::changeRecord(key, value, txn);
285 } catch (...) {
286 abortTransaction(txn);
287 throw;
288 }
289
290 commitTransaction(txn);
291}
292
313template<class K, class V>
314void LMDBAL::Storage<K, V>::changeRecord(const K& key, const V& value, TransactionID txn) {
315 MDB_cursor* cursor;
316 int rc = _mdbCursorOpen(txn, &cursor);
317 if (rc != MDB_SUCCESS)
318 throwUnknown(rc);
319
320 MDB_val lmdbKey = keySerializer.setData(key);
321 MDB_val lmdbData;
322 rc = _mdbCursorGet(cursor, lmdbKey, lmdbData, MDB_SET);
323 if (rc != MDB_SUCCESS)
324 throwNotFoundOrUnknown(rc, toString(key));
325
326 MDB_val lmdbNewData = valueSerializer.setData(value);
327 bool sameSize = lmdbData.mv_size == lmdbNewData.mv_size;
328 int firstDifferentByte = 0;
329 if (sameSize) { //can compare only if they are the same size
330 firstDifferentByte = memcmp(lmdbData.mv_data, lmdbNewData.mv_data, lmdbData.mv_size);
331 if (firstDifferentByte == 0) { //old and new is the same, nothing to do
332 _mdbCursorClose(cursor);
333 return;
334 }
335 }
336
337 unsigned int flags = MDB_CURRENT;
338 if (duplicates && (!sameSize || firstDifferentByte < 0)) { //if new value is greater than the old one
339 rc = _mdbCursorDel(cursor); //we need to initiate duplicates sort, for it to be in the correct place
340 flags = MDB_NODUPDATA;
341 }
342
343 if (rc == MDB_SUCCESS)
344 rc = _mdbCursorPut(cursor, lmdbKey, lmdbNewData, flags);
345
346 _mdbCursorClose(cursor);
347 if (rc != MDB_SUCCESS)
348 throwDuplicateOrUnknown(rc, toString(key));
349}
350
351
376template<class K, class V>
377void LMDBAL::Storage<K, V>::changeRecord(const K& key, const V& value, const WriteTransaction& txn) {
378 ensureOpened(changeRecordMethodName);
379 changeRecord(key, value, extractTransactionId(txn, changeRecordMethodName));
380}
381
400template<class K, class V>
401V LMDBAL::Storage<K, V>::getRecord(const K& key) const {
402 ensureOpened(getRecordMethodName);
403
404 V value;
405 Storage<K, V>::getRecord(key, value);
406 return value;
407}
408
427template<class K, class V>
428void LMDBAL::Storage<K, V>::getRecord(const K& key, V& value) const {
429 ensureOpened(getRecordMethodName);
430
431 TransactionID txn = beginReadOnlyTransaction();
432 try {
433 Storage<K, V>::getRecord(key, value, txn);
434 } catch (...) {
435 abortTransaction(txn);
436 throw;
437 }
438
439 abortTransaction(txn);
440}
441
460template<class K, class V>
461V LMDBAL::Storage<K, V>::getRecord(const K& key, TransactionID txn) const {
462 V value;
463 Storage<K, V>::getRecord(key, value, txn);
464 return value;
465}
466
490template<class K, class V>
491V LMDBAL::Storage<K, V>::getRecord(const K& key, const Transaction& txn) const {
492 ensureOpened(getRecordMethodName);
493 return getRecord(key, extractTransactionId(txn, getRecordMethodName));
494}
495
514template<class K, class V>
515void LMDBAL::Storage<K, V>::getRecord(const K& key, V& value, TransactionID txn) const {
516 MDB_val lmdbKey = keySerializer.setData(key);
517 MDB_val lmdbData;
518
519 int rc = _mdbGet(txn, lmdbKey, lmdbData);
520 if (rc != MDB_SUCCESS)
521 throwNotFoundOrUnknown(rc, toString(key));
522
523 valueSerializer.deserialize(lmdbData, value);
524}
525
526
550template<class K, class V>
551void LMDBAL::Storage<K, V>::getRecord(const K& key, V& value, const Transaction& txn) const {
552 ensureOpened(getRecordMethodName);
553 getRecord(key, value, extractTransactionId(txn, getRecordMethodName));
554}
555
565template<class K, class V>
566bool LMDBAL::Storage<K, V>::checkRecord(const K& key) const {
567 ensureOpened(checkRecordMethodName);
568
569 TransactionID txn = beginReadOnlyTransaction();
570 bool result;
571 try {
572 result = Storage<K, V>::checkRecord(key, txn);
573 } catch (...) {
574 abortTransaction(txn);
575 throw;
576 }
577
578 abortTransaction(txn);
579 return result;
580}
581
591template<class K, class V>
592bool LMDBAL::Storage<K, V>::checkRecord(const K& key, TransactionID txn) const {
593 MDB_val lmdbKey = keySerializer.setData(key);
594 MDB_val lmdbData;
595
596 int rc = _mdbGet(txn, lmdbKey, lmdbData);
597 if (rc == MDB_SUCCESS)
598 return true;
599
600 if (rc != MDB_NOTFOUND)
601 throwUnknown(rc);
602
603 return false;
604}
605
620template<class K, class V>
621bool LMDBAL::Storage<K, V>::checkRecord(const K& key, const Transaction& txn) const {
622 ensureOpened(checkRecordMethodName);
623 return checkRecord(key, extractTransactionId(txn, checkRecordMethodName));
624}
625
637template<class K, class V>
638std::map<K, V> LMDBAL::Storage<K, V>::readAll() const {
639 ensureOpened(readAllMethodName);
640
641 std::map<K, V> result;
643 return result;
644}
645
659template<class K, class V>
660void LMDBAL::Storage<K, V>::readAll(std::map<K, V>& result) const {
661 ensureOpened(readAllMethodName);
662
663 TransactionID txn = beginReadOnlyTransaction();
664 try {
665 Storage<K, V>::readAll(result, txn);
666 } catch (...) {
667 abortTransaction(txn);
668 throw;
669 }
670
671 abortTransaction(txn);
672}
673
685template<class K, class V>
686std::map<K, V> LMDBAL::Storage<K, V>::readAll(TransactionID txn) const {
687 std::map<K, V> result;
688 Storage<K, V>::readAll(result, txn);
689 return result;
690}
691
708template<class K, class V>
709std::map<K, V> LMDBAL::Storage<K, V>::readAll(const Transaction& txn) const {
710 ensureOpened(readAllMethodName);
711 return readAll(extractTransactionId(txn, readAllMethodName));
712}
713
726template<class K, class V>
727void LMDBAL::Storage<K, V>::readAll(std::map<K, V>& result, TransactionID txn) const {
728 MDB_cursor* cursor;
729 MDB_val lmdbKey, lmdbData;
730
731 int rc = _mdbCursorOpen(txn, &cursor);
732 if (rc != MDB_SUCCESS)
733 throwUnknown(rc);
734
735 rc = _mdbCursorGet(cursor, lmdbKey, lmdbData, MDB_FIRST);
736 while (rc == MDB_SUCCESS) {
737 K key;
738 keySerializer.deserialize(lmdbKey, key);
739 std::pair<typename std::map<K, V>::iterator, bool> probe = result.emplace(key, V{});
740 if (probe.second) //I do this to avoid overwrites in case duplicates are enabled
741 valueSerializer.deserialize(lmdbData, probe.first->second);
742
743 rc = _mdbCursorGet(cursor, lmdbKey, lmdbData, MDB_NEXT);
744 }
745 _mdbCursorClose(cursor);
746 if (rc != MDB_NOTFOUND)
747 throwUnknown(rc);
748}
749
767template<class K, class V>
768void LMDBAL::Storage<K, V>::readAll(std::map<K, V>& result, const Transaction& txn) const {
769 ensureOpened(readAllMethodName);
770 readAll(result, extractTransactionId(txn, readAllMethodName));
771}
772
783template<class K, class V>
784void LMDBAL::Storage<K, V>::replaceAll(const std::map<K, V>& data) {
785 ensureOpened(replaceAllMethodName);
786
787 TransactionID txn = beginTransaction();
788 try {
789 Storage<K, V>::replaceAll(data, txn);
790 } catch (...) {
791 abortTransaction(txn);
792 throw;
793 }
794
795 commitTransaction(txn);
796}
797
808template<class K, class V>
809void LMDBAL::Storage<K, V>::replaceAll(const std::map<K, V>& data, TransactionID txn) {
810 int rc = drop(txn);
811 if (rc != MDB_SUCCESS)
812 throwUnknown(rc);
813
814 MDB_val lmdbKey, lmdbData;
815 for (const std::pair<const K, V>& pair : data) {
816 lmdbKey = keySerializer.setData(pair.first);
817 lmdbData = valueSerializer.setData(pair.second);
818
819 rc = _mdbPut(txn, lmdbKey, lmdbData, MDB_NOOVERWRITE); //TODO may be appending with cursor makes sence here?
820 if (rc != MDB_SUCCESS)
821 throwUnknown(rc);
822 }
823}
824
839template<class K, class V>
840void LMDBAL::Storage<K, V>::replaceAll(const std::map<K, V>& data, const WriteTransaction& txn) {
841 ensureOpened(replaceAllMethodName);
842 replaceAll(data, extractTransactionId(txn, replaceAllMethodName));
843}
844
856template<class K, class V>
857uint32_t LMDBAL::Storage<K, V>::addRecords(const std::map<K, V>& data, bool overwrite) {
858 ensureOpened(addRecordsMethodName);
859
860 TransactionID txn = beginTransaction();
861 uint32_t amount;
862 try {
863 amount = Storage<K, V>::addRecords(data, txn, overwrite);
864 } catch (...) {
865 abortTransaction(txn);
866 throw;
867 }
868
869 commitTransaction(txn);
870 return amount;
871}
872
886template<class K, class V>
887uint32_t LMDBAL::Storage<K, V>::addRecords(const std::map<K, V>& data, TransactionID txn, bool overwrite) {
888 MDB_val lmdbKey, lmdbData;
889 int rc;
890 for (const std::pair<const K, V>& pair : data) {
891 lmdbKey = keySerializer.setData(pair.first);
892 lmdbData = valueSerializer.setData(pair.second);
893
894 rc = _mdbPut(txn, lmdbKey, lmdbData, overwrite ? 0 : MDB_NOOVERWRITE);
895 if (rc == MDB_KEYEXIST)
896 throwDuplicate(toString(pair.first));
897
898 if (rc != MDB_SUCCESS)
899 throwUnknown(rc);
900 }
901
902 MDB_stat stat;
903 rc = _mdbStat(txn, stat);
904 if (rc != MDB_SUCCESS)
905 throwUnknown(rc);
906
907 return stat.ms_entries;
908}
909
926template<class K, class V>
927uint32_t LMDBAL::Storage<K, V>::addRecords(const std::map<K, V>& data, const WriteTransaction& txn, bool overwrite) {
928 ensureOpened(addRecordsMethodName);
929 return addRecords(data, extractTransactionId(txn, addRecordsMethodName), overwrite);
930}
931
943template<class K, class V>
945 ensureOpened(removeRecordMethodName);
946
947 TransactionID txn = beginTransaction();
948 try {
950 } catch (...) {
951 abortTransaction(txn);
952 throw;
953 }
954
955 commitTransaction(txn);
956}
957
970template<class K, class V>
971void LMDBAL::Storage<K, V>::removeRecord(const K& key, TransactionID txn) {
972 MDB_val lmdbKey = keySerializer.setData(key);
973 int rc = _mdbDel(txn, lmdbKey);
974 if (rc != MDB_SUCCESS)
975 throwNotFoundOrUnknown(rc, toString(key));
976}
977
993template<class K, class V>
995 ensureOpened(removeRecordMethodName);
996 removeRecord(key, extractTransactionId(txn, removeRecordMethodName));
997}
998
1005template<class K, class V>
1006int LMDBAL::Storage<K, V>::open(MDB_txn* transaction) {
1007 return makeStorage<K, V>(transaction, duplicates);
1008}
1009
1013template<class K, class V>
1015 for (const std::pair<const uint32_t, Cursor<K, V>*>& pair : cursors)
1016 pair.second->terminated();
1017
1019}
1020
1029template<class K, class V>
1033
1046template<class K, class V>
1048 typename std::map<uint32_t, Cursor<K, V>*>::iterator itr = cursors.find(cursor.id);
1049 if (itr == cursors.end())
1050 throwUnknown("An attempt to destroy a cursor the storage doesn't own");
1051
1052 cursor.close();
1053 cursors.erase(itr);
1054 cursor.freed();
1055}
1056
1067template<class K, class V>
1069 ensureOpened(flagsMethodName);
1070 uint32_t result;
1071 TransactionID txn = beginReadOnlyTransaction();
1072
1073 int res = _mdbFlags(txn, result);
1074 abortTransaction(txn);
1075 if (res != MDB_SUCCESS)
1076 throwUnknown(res);
1077
1078 return result;
1079}
1080
1087template<class K, class V>
1088void LMDBAL::Storage<K, V>::discoveredRecord(const K& key, const V& value) const {
1089 UNUSED(key);
1090 UNUSED(value);
1091}
1092
1100template<class K, class V>
1101void LMDBAL::Storage<K, V>::discoveredRecord(const K& key, const V& value, TransactionID txn) const {
1102 UNUSED(key);
1103 UNUSED(value);
1104 UNUSED(txn);
1105}
1106
1121template<class K, class V>
1122inline int LMDBAL::iStorage::makeStorage(MDB_txn* transaction, bool duplicates) {
1123 unsigned int flags = MDB_CREATE;
1124 if constexpr (std::is_integral<K>::value)
1125 flags |= MDB_INTEGERKEY;
1126
1127 if (duplicates) {
1128 flags |= MDB_DUPSORT;
1129
1130 if constexpr (std::is_scalar<V>::value)
1131 flags |= MDB_DUPFIXED;
1132
1133 if constexpr (
1134 std::is_same<V, uint32_t>::value ||
1135 std::is_same<V, int32_t>::value ||
1136 std::is_same<V, uint64_t>::value ||
1137 std::is_same<V, int64_t>::value
1138 ) //for some reason lmdb breaks if it's not one of these types in MDB_DUPFIXED mode
1139 flags |= MDB_INTEGERDUP;
1140 }
1141
1142 return _mdbOpen(transaction, flags);
1143}
1144
1154template<class T>
1155inline std::string LMDBAL::iStorage::toString(const T& value) {
1156 return std::to_string(value);
1157}
1158
1167template<>
1168inline std::string LMDBAL::iStorage::toString(const QString& value) {
1169 return value.toStdString();
1170}
1171
1180template<>
1181inline std::string LMDBAL::iStorage::toString(const std::string& value) {
1182 return value;
1183}
Database abstraction.
Definition base.h:53
An object to iterate storages.
Definition cursor.h:31
void close()
Termiates a sequence of operations with the cursor.
Definition cursor.hpp:361
Thrown if there was a key conflict in one of the storages.
Definition exceptions.h:178
virtual bool checkRecord(const K &key, TransactionID txn) const
Chechs if storage has value (private transaction variant)
Definition storage.hpp:592
virtual void addRecord(const K &key, const V &value, TransactionID txn)
Adds a key-value record to the storage (private transaction variant)
Definition storage.hpp:107
virtual std::map< K, V > readAll() const
Reads whole storage into a map.
Definition storage.hpp:638
~Storage() override
Destroys a storage.
Definition storage.hpp:59
virtual void discoveredRecord(const K &key, const V &value) const
A private virtual method that cursor calls when he reads a record, does nothing here but populates th...
Definition storage.hpp:1088
virtual void replaceAll(const std::map< K, V > &data, TransactionID txn)
Replaces the content of the whole storage with the given (private transaction variant)
Definition storage.hpp:809
void destroyCursor(Cursor< K, V > &cursor)
Frees cursor.
Definition storage.hpp:1047
uint32_t flags() const
Reads current storage flags it was opened with.
Definition storage.hpp:1068
virtual void removeRecord(const K &key, TransactionID txn)
Removes one of the records (private transaction variant)
Definition storage.hpp:971
void close() override
A private virtual method I need to close each storage in the database.
Definition storage.hpp:1014
Cursor< K, V > createCursor()
Creates cursor.
Definition storage.hpp:1030
virtual bool forceRecord(const K &key, const V &value, TransactionID txn)
Adds a key-value record to the storage, overwrites if it already exists (private transaction variant)
Definition storage.hpp:198
virtual void changeRecord(const K &key, const V &value, TransactionID txn)
Changes key-value record to the storage (private transaction variant)
Definition storage.hpp:314
virtual void getRecord(const K &key, V &value, TransactionID txn) const
Gets the record from the database (private transaction, reference variant)
Definition storage.hpp:515
Storage(Base *parent, const std::string &name, bool duplicates=false)
Creates a storage.
Definition storage.hpp:48
virtual uint32_t addRecords(const std::map< K, V > &data, TransactionID txn, bool overwrite=false)
Adds records in bulk (private transaction variant)
Definition storage.hpp:887
int open(MDB_txn *transaction) override
A private virtual method I need to open each storage in the database.
Definition storage.hpp:1006
Public read only transaction.
Definition transaction.h:26
Public writable transaction.
Definition transaction.h:50
Storage interface.
Definition storage.h:36
int makeStorage(MDB_txn *transaction, bool duplicates=false)
A functiion to actually open MDB_dbi storage.
Definition storage.hpp:1122
virtual void close()
A private virtual function I need to close each storage in the database.
Definition storage.cpp:55
static std::string toString(const T &value)
A method to cast a value (which can be a value or a key) to string.
Definition storage.hpp:1155