diff options
author | Christian Mollekopf <chrigi_1@fastmail.fm> | 2018-05-21 19:48:09 +0200 |
---|---|---|
committer | Christian Mollekopf <chrigi_1@fastmail.fm> | 2018-05-23 13:55:15 +0200 |
commit | 4cb0d1561cf41551d4ddc418f8666388b90318b9 (patch) | |
tree | 2efb1dbc7a8627893b3b0ddd9f95d08dda6b7e8f | |
parent | fa9e0e2cbbcb0733e86a47f489296f58fbcf34af (diff) | |
download | sink-4cb0d1561cf41551d4ddc418f8666388b90318b9.tar.gz sink-4cb0d1561cf41551d4ddc418f8666388b90318b9.zip |
Fixed use of mdb_dbi_open
There can only ever be one transaction using mdb_dbi_open running,
and that transaction must commit or abort before any other transaction
attempts to use mdb_dbi_open.
Use delayed dbi merging with write transactions and a temporary
transaction for read transactions.
We now protect dbi initialization with a mutex and immediately update
the sDbis hash. This assumes that the created dbis are indeed
We can still violate the only one transaction may use mdb_dbi_open rule
if we start a read-only transaction after the write transaction, before
the write transaction commits.
It does not seem to be something we actually do though.
Opening dbis on environment init is further separated out, so we don't
end up in the regular openDatabase codepath at all.
-rw-r--r-- | common/storage_lmdb.cpp | 340 | ||||
-rw-r--r-- | tests/dbwriter.cpp | 6 | ||||
-rw-r--r-- | tests/storagetest.cpp | 129 |
3 files changed, 322 insertions, 153 deletions
diff --git a/common/storage_lmdb.cpp b/common/storage_lmdb.cpp index 660326a..5fb1d0f 100644 --- a/common/storage_lmdb.cpp +++ b/common/storage_lmdb.cpp | |||
@@ -26,6 +26,8 @@ | |||
26 | #include <QDebug> | 26 | #include <QDebug> |
27 | #include <QDir> | 27 | #include <QDir> |
28 | #include <QReadWriteLock> | 28 | #include <QReadWriteLock> |
29 | #include <QMutex> | ||
30 | #include <QMutexLocker> | ||
29 | #include <QString> | 31 | #include <QString> |
30 | #include <QTime> | 32 | #include <QTime> |
31 | #include <valgrind.h> | 33 | #include <valgrind.h> |
@@ -99,6 +101,96 @@ static QList<QByteArray> getDatabaseNames(MDB_txn *transaction) | |||
99 | 101 | ||
100 | } | 102 | } |
101 | 103 | ||
104 | /* | ||
105 | * To create a dbi we always need a write transaction, | ||
106 | * and we always need to commit the transaction ASAP | ||
107 | * We can only ever enter from one point per process. | ||
108 | */ | ||
109 | |||
110 | QMutex sCreateDbiLock; | ||
111 | |||
112 | static bool createDbi(MDB_txn *transaction, const QByteArray &db, bool readOnly, bool allowDuplicates, MDB_dbi &dbi) | ||
113 | { | ||
114 | |||
115 | unsigned int flags = 0; | ||
116 | if (allowDuplicates) { | ||
117 | flags |= MDB_DUPSORT; | ||
118 | } | ||
119 | |||
120 | MDB_dbi flagtableDbi; | ||
121 | if (const int rc = mdb_dbi_open(transaction, "__flagtable", readOnly ? 0 : MDB_CREATE, &flagtableDbi)) { | ||
122 | if (!readOnly) { | ||
123 | SinkWarning() << "Failed to to open flagdb: " << QByteArray(mdb_strerror(rc)); | ||
124 | } | ||
125 | } else { | ||
126 | MDB_val key, value; | ||
127 | key.mv_data = const_cast<void*>(static_cast<const void*>(db.constData())); | ||
128 | key.mv_size = db.size(); | ||
129 | if (const auto rc = mdb_get(transaction, flagtableDbi, &key, &value)) { | ||
130 | //We expect this to fail for new databases | ||
131 | if (rc != MDB_NOTFOUND) { | ||
132 | SinkWarning() << "Failed to read flags from flag db: " << QByteArray(mdb_strerror(rc)); | ||
133 | } | ||
134 | } else { | ||
135 | //Found the flags | ||
136 | const auto ba = QByteArray::fromRawData((char *)value.mv_data, value.mv_size); | ||
137 | flags = ba.toInt(); | ||
138 | } | ||
139 | } | ||
140 | |||
141 | if (const int rc = mdb_dbi_open(transaction, db.constData(), flags, &dbi)) { | ||
142 | //Create the db if it is not existing already | ||
143 | if (rc == MDB_NOTFOUND && !readOnly) { | ||
144 | //Sanity check db name | ||
145 | { | ||
146 | auto parts = db.split('.'); | ||
147 | for (const auto &p : parts) { | ||
148 | auto containsSpecialCharacter = [] (const QByteArray &p) { | ||
149 | for (int i = 0; i < p.size(); i++) { | ||
150 | const auto c = p.at(i); | ||
151 | //Between 0 and z in the ascii table. Essentially ensures that the name is printable and doesn't contain special chars | ||
152 | if (c < 0x30 || c > 0x7A) { | ||
153 | return true; | ||
154 | } | ||
155 | } | ||
156 | return false; | ||
157 | }; | ||
158 | if (p.isEmpty() || containsSpecialCharacter(p)) { | ||
159 | SinkError() << "Tried to create a db with an invalid name. Hex:" << db.toHex() << " ASCII:" << db; | ||
160 | Q_ASSERT(false); | ||
161 | throw std::runtime_error("Fatal error while creating db."); | ||
162 | } | ||
163 | } | ||
164 | } | ||
165 | if (const int rc = mdb_dbi_open(transaction, db.constData(), flags | MDB_CREATE, &dbi)) { | ||
166 | SinkWarning() << "Failed to create db " << QByteArray(mdb_strerror(rc)); | ||
167 | return false; | ||
168 | } | ||
169 | //Record the db flags | ||
170 | MDB_val key, value; | ||
171 | key.mv_data = const_cast<void*>(static_cast<const void*>(db.constData())); | ||
172 | key.mv_size = db.size(); | ||
173 | //Store the flags without the create option | ||
174 | const auto ba = QByteArray::number(flags); | ||
175 | value.mv_data = const_cast<void*>(static_cast<const void*>(db.constData())); | ||
176 | value.mv_size = db.size(); | ||
177 | if (const int rc = mdb_put(transaction, flagtableDbi, &key, &value, MDB_NOOVERWRITE)) { | ||
178 | //We expect this to fail if we're only creating the dbi but not the db | ||
179 | if (rc != MDB_KEYEXIST) { | ||
180 | SinkWarning() << "Failed to write flags to flag db: " << QByteArray(mdb_strerror(rc)); | ||
181 | } | ||
182 | } | ||
183 | } else { | ||
184 | //It's not an error if we only want to read | ||
185 | if (!readOnly) { | ||
186 | SinkWarning() << "Failed to open db " << QByteArray(mdb_strerror(rc)); | ||
187 | return true; | ||
188 | } | ||
189 | return false; | ||
190 | } | ||
191 | } | ||
192 | return true; | ||
193 | } | ||
102 | 194 | ||
103 | class DataStore::NamedDatabase::Private | 195 | class DataStore::NamedDatabase::Private |
104 | { | 196 | { |
@@ -119,112 +211,91 @@ public: | |||
119 | std::function<void(const DataStore::Error &error)> defaultErrorHandler; | 211 | std::function<void(const DataStore::Error &error)> defaultErrorHandler; |
120 | QString name; | 212 | QString name; |
121 | bool createdNewDbi = false; | 213 | bool createdNewDbi = false; |
122 | QString createdDbName; | 214 | QString createdNewDbiName; |
123 | 215 | ||
124 | bool openDatabase(bool readOnly, std::function<void(const DataStore::Error &error)> errorHandler) | 216 | bool dbiValidForTransaction(MDB_dbi dbi, MDB_txn *transaction) |
125 | { | 217 | { |
126 | unsigned int flags = 0; | 218 | //sDbis can contain dbi's that are not available to this transaction. |
127 | if (allowDuplicates) { | 219 | //We use mdb_dbi_flags to check if the dbi is valid for this transaction. |
128 | flags |= MDB_DUPSORT; | 220 | uint f; |
221 | if (mdb_dbi_flags(transaction, dbi, &f) == EINVAL) { | ||
222 | return false; | ||
129 | } | 223 | } |
224 | return true; | ||
225 | } | ||
130 | 226 | ||
227 | bool openDatabase(bool readOnly, std::function<void(const DataStore::Error &error)> errorHandler) | ||
228 | { | ||
131 | const auto dbiName = name + db; | 229 | const auto dbiName = name + db; |
230 | QReadLocker dbiLocker{&sDbisLock}; | ||
132 | if (sDbis.contains(dbiName)) { | 231 | if (sDbis.contains(dbiName)) { |
133 | dbi = sDbis.value(dbiName); | 232 | dbi = sDbis.value(dbiName); |
134 | //sDbis can contain dbi's that are not available to this transaction. | 233 | Q_ASSERT(dbiValidForTransaction(dbi, transaction)); |
135 | //We use mdb_dbi_flags to check if the dbi is valid for this transaction. | ||
136 | uint f; | ||
137 | if (mdb_dbi_flags(transaction, dbi, &f) == EINVAL) { | ||
138 | //In readonly mode we can just ignore this. In read-write we would have tried to concurrently create a db. | ||
139 | if (!readOnly) { | ||
140 | SinkWarning() << "Tried to create database in second transaction: " << dbiName; | ||
141 | } | ||
142 | dbi = 0; | ||
143 | transaction = 0; | ||
144 | return false; | ||
145 | } | ||
146 | } else { | 234 | } else { |
147 | MDB_dbi flagtableDbi; | 235 | /* |
148 | if (const int rc = mdb_dbi_open(transaction, "__flagtable", readOnly ? 0 : MDB_CREATE, &flagtableDbi)) { | 236 | * Dynamic creation of databases. |
149 | if (!readOnly) { | 237 | * If all databases were defined via the database layout we wouldn't ever end up in here. |
150 | SinkWarning() << "Failed to to open flagdb: " << QByteArray(mdb_strerror(rc)); | 238 | * However, we rely on this codepath for indexes, synchronization databases and in race-conditions |
239 | * where the database is not yet fully created when the client initializes it for reading. | ||
240 | * | ||
241 | * There are a few things to consider: | ||
242 | * * dbi's (DataBase Identifier) should be opened once (ideally), and then be persisted in the environment. | ||
243 | * * To open a dbi we need a transaction and must commit the transaction. From then on any open transaction will have access to the dbi. | ||
244 | * * Already running transactions will not have access to the dbi. | ||
245 | * * There *must* only ever be one active transaction opening dbi's (using mdb_dbi_open), and that transaction *must* | ||
246 | * commit or abort before any other transaction opens a dbi. | ||
247 | * | ||
248 | * We solve this the following way: | ||
249 | * * For read-only transactions we abort the transaction, open the dbi and persist it in the environment, and reopen the transaction (so the dbi is available). This may result in the db content changing unexpectedly and referenced memory becoming unavailable, but isn't a problem as long as we don't rely on memory remaining valid for the duration of the transaction (which is anyways not given since any operation would invalidate the memory region).. | ||
250 | * * For write transactions we open the dbi for future use, and then open it as well in the current transaction. | ||
251 | */ | ||
252 | SinkTrace() << "Creating database dynamically: " << dbiName << readOnly; | ||
253 | //Only one transaction may ever create dbis at a time. | ||
254 | QMutexLocker createDbiLocker(&sCreateDbiLock); | ||
255 | //Double checked locking | ||
256 | if (sDbis.contains(dbiName)) { | ||
257 | dbi = sDbis.value(dbiName); | ||
258 | Q_ASSERT(dbiValidForTransaction(dbi, transaction)); | ||
259 | return true; | ||
260 | } | ||
261 | |||
262 | //Create a transaction to open the dbi | ||
263 | MDB_txn *dbiTransaction; | ||
264 | if (readOnly) { | ||
265 | MDB_env *env = mdb_txn_env(transaction); | ||
266 | Q_ASSERT(env); | ||
267 | mdb_txn_reset(transaction); | ||
268 | if (const int rc = mdb_txn_begin(env, nullptr, MDB_RDONLY, &dbiTransaction)) { | ||
269 | SinkError() << "Failed to open transaction: " << QByteArray(mdb_strerror(rc)) << readOnly << transaction; | ||
270 | return false; | ||
151 | } | 271 | } |
152 | } else { | 272 | } else { |
153 | MDB_val key, value; | 273 | dbiTransaction = transaction; |
154 | key.mv_data = const_cast<void*>(static_cast<const void*>(db.constData())); | ||
155 | key.mv_size = db.size(); | ||
156 | if (const auto rc = mdb_get(transaction, flagtableDbi, &key, &value)) { | ||
157 | //We expect this to fail for new databases | ||
158 | if (rc != MDB_NOTFOUND) { | ||
159 | SinkWarning() << "Failed to read flags from flag db: " << QByteArray(mdb_strerror(rc)); | ||
160 | } | ||
161 | } else { | ||
162 | //Found the flags | ||
163 | const auto ba = QByteArray::fromRawData((char *)value.mv_data, value.mv_size); | ||
164 | flags = ba.toInt(); | ||
165 | } | ||
166 | } | 274 | } |
167 | 275 | if (createDbi(dbiTransaction, db, readOnly, allowDuplicates, dbi)) { | |
168 | Q_ASSERT(transaction); | 276 | if (readOnly) { |
169 | if (const int rc = mdb_dbi_open(transaction, db.constData(), flags, &dbi)) { | 277 | mdb_txn_commit(dbiTransaction); |
170 | //Create the db if it is not existing already | 278 | dbiLocker.unlock(); |
171 | if (rc == MDB_NOTFOUND && !readOnly) { | 279 | QWriteLocker dbiWriteLocker(&sDbisLock); |
172 | //Sanity check db name | 280 | sDbis.insert(dbiName, dbi); |
173 | { | 281 | //We reopen the read-only transaction so the dbi becomes available in it. |
174 | auto parts = db.split('.'); | 282 | mdb_txn_renew(transaction); |
175 | for (const auto &p : parts) { | ||
176 | auto containsSpecialCharacter = [] (const QByteArray &p) { | ||
177 | for (int i = 0; i < p.size(); i++) { | ||
178 | const auto c = p.at(i); | ||
179 | //Between 0 and z in the ascii table. Essentially ensures that the name is printable and doesn't contain special chars | ||
180 | if (c < 0x30 || c > 0x7A) { | ||
181 | return true; | ||
182 | } | ||
183 | } | ||
184 | return false; | ||
185 | }; | ||
186 | if (p.isEmpty() || containsSpecialCharacter(p)) { | ||
187 | SinkError() << "Tried to create a db with an invalid name. Hex:" << db.toHex() << " ASCII:" << db; | ||
188 | Q_ASSERT(false); | ||
189 | throw std::runtime_error("Fatal error while creating db."); | ||
190 | } | ||
191 | } | ||
192 | } | ||
193 | if (const int rc = mdb_dbi_open(transaction, db.constData(), flags | MDB_CREATE, &dbi)) { | ||
194 | SinkWarning() << "Failed to create db " << QByteArray(mdb_strerror(rc)); | ||
195 | Error error(name.toLatin1(), ErrorCodes::GenericError, "Error while creating database: " + QByteArray(mdb_strerror(rc))); | ||
196 | errorHandler ? errorHandler(error) : defaultErrorHandler(error); | ||
197 | return false; | ||
198 | } | ||
199 | //Record the db flags | ||
200 | MDB_val key, value; | ||
201 | key.mv_data = const_cast<void*>(static_cast<const void*>(db.constData())); | ||
202 | key.mv_size = db.size(); | ||
203 | //Store the flags without the create option | ||
204 | const auto ba = QByteArray::number(flags); | ||
205 | value.mv_data = const_cast<void*>(static_cast<const void*>(db.constData())); | ||
206 | value.mv_size = db.size(); | ||
207 | if (const int rc = mdb_put(transaction, flagtableDbi, &key, &value, MDB_NOOVERWRITE)) { | ||
208 | //We expect this to fail if we're only creating the dbi but not the db | ||
209 | if (rc != MDB_KEYEXIST) { | ||
210 | SinkWarning() << "Failed to write flags to flag db: " << QByteArray(mdb_strerror(rc)); | ||
211 | } | ||
212 | } | ||
213 | } else { | 283 | } else { |
214 | dbi = 0; | 284 | createdNewDbi = true; |
215 | transaction = 0; | 285 | createdNewDbiName = dbiName; |
216 | //It's not an error if we only want to read | 286 | } |
217 | if (!readOnly) { | 287 | //Ensure the dbi is valid for the parent transaction |
218 | SinkWarning() << "Failed to open db " << QByteArray(mdb_strerror(rc)); | 288 | Q_ASSERT(dbiValidForTransaction(dbi, transaction)); |
219 | Error error(name.toLatin1(), ErrorCodes::GenericError, "Error while opening database: " + QByteArray(mdb_strerror(rc))); | 289 | } else { |
220 | errorHandler ? errorHandler(error) : defaultErrorHandler(error); | 290 | if (readOnly) { |
221 | } | 291 | mdb_txn_abort(dbiTransaction); |
222 | return false; | 292 | mdb_txn_renew(transaction); |
223 | } | 293 | } |
294 | SinkWarning() << "Failed to create the dbi: " << dbiName; | ||
295 | dbi = 0; | ||
296 | transaction = 0; | ||
297 | return false; | ||
224 | } | 298 | } |
225 | |||
226 | createdNewDbi = true; | ||
227 | createdDbName = dbiName; | ||
228 | } | 299 | } |
229 | return true; | 300 | return true; |
230 | } | 301 | } |
@@ -538,8 +609,8 @@ bool DataStore::NamedDatabase::allowsDuplicates() const | |||
538 | class DataStore::Transaction::Private | 609 | class DataStore::Transaction::Private |
539 | { | 610 | { |
540 | public: | 611 | public: |
541 | Private(bool _requestRead, const std::function<void(const DataStore::Error &error)> &_defaultErrorHandler, const QString &_name, MDB_env *_env, bool _noLock = false) | 612 | Private(bool _requestRead, const std::function<void(const DataStore::Error &error)> &_defaultErrorHandler, const QString &_name, MDB_env *_env) |
542 | : env(_env), transaction(nullptr), requestedRead(_requestRead), defaultErrorHandler(_defaultErrorHandler), name(_name), implicitCommit(false), error(false), modificationCounter(0), noLock(_noLock) | 613 | : env(_env), transaction(nullptr), requestedRead(_requestRead), defaultErrorHandler(_defaultErrorHandler), name(_name), implicitCommit(false), error(false) |
543 | { | 614 | { |
544 | } | 615 | } |
545 | ~Private() | 616 | ~Private() |
@@ -553,9 +624,6 @@ public: | |||
553 | QString name; | 624 | QString name; |
554 | bool implicitCommit; | 625 | bool implicitCommit; |
555 | bool error; | 626 | bool error; |
556 | int modificationCounter; | ||
557 | bool noLock; | ||
558 | |||
559 | QMap<QString, MDB_dbi> createdDbs; | 627 | QMap<QString, MDB_dbi> createdDbs; |
560 | 628 | ||
561 | void startTransaction() | 629 | void startTransaction() |
@@ -641,23 +709,22 @@ bool DataStore::Transaction::commit(const std::function<void(const DataStore::Er | |||
641 | //If transactions start failing we're in an unrecoverable situation (i.e. out of diskspace). So throw an exception that will terminate the application. | 709 | //If transactions start failing we're in an unrecoverable situation (i.e. out of diskspace). So throw an exception that will terminate the application. |
642 | throw std::runtime_error("Fatal error while committing transaction."); | 710 | throw std::runtime_error("Fatal error while committing transaction."); |
643 | } | 711 | } |
644 | d->transaction = nullptr; | ||
645 | 712 | ||
646 | //Add the created dbis to the shared environment | 713 | //Add the created dbis to the shared environment |
647 | if (!d->createdDbs.isEmpty()) { | 714 | if (!d->createdDbs.isEmpty()) { |
648 | if (!d->noLock) { | 715 | sDbisLock.lockForWrite(); |
649 | sDbisLock.lockForWrite(); | ||
650 | } | ||
651 | for (auto it = d->createdDbs.constBegin(); it != d->createdDbs.constEnd(); it++) { | 716 | for (auto it = d->createdDbs.constBegin(); it != d->createdDbs.constEnd(); it++) { |
717 | //This means we opened the dbi again in a read-only transaction while the write transaction was ongoing. | ||
652 | Q_ASSERT(!sDbis.contains(it.key())); | 718 | Q_ASSERT(!sDbis.contains(it.key())); |
653 | sDbis.insert(it.key(), it.value()); | 719 | if (!sDbis.contains(it.key())) { |
720 | sDbis.insert(it.key(), it.value()); | ||
721 | } | ||
654 | } | 722 | } |
655 | d->createdDbs.clear(); | 723 | d->createdDbs.clear(); |
656 | if (!d->noLock) { | 724 | sDbisLock.unlock(); |
657 | sDbisLock.unlock(); | ||
658 | } | ||
659 | } | 725 | } |
660 | 726 | ||
727 | d->transaction = nullptr; | ||
661 | return !rc; | 728 | return !rc; |
662 | } | 729 | } |
663 | 730 | ||
@@ -667,10 +734,10 @@ void DataStore::Transaction::abort() | |||
667 | return; | 734 | return; |
668 | } | 735 | } |
669 | 736 | ||
670 | d->createdDbs.clear(); | ||
671 | // Trace_area("storage." + d->name.toLatin1()) << "Aborting transaction" << mdb_txn_id(d->transaction) << d->transaction; | 737 | // Trace_area("storage." + d->name.toLatin1()) << "Aborting transaction" << mdb_txn_id(d->transaction) << d->transaction; |
672 | Q_ASSERT(sEnvironments.values().contains(d->env)); | 738 | Q_ASSERT(sEnvironments.values().contains(d->env)); |
673 | mdb_txn_abort(d->transaction); | 739 | mdb_txn_abort(d->transaction); |
740 | d->createdDbs.clear(); | ||
674 | d->transaction = nullptr; | 741 | d->transaction = nullptr; |
675 | } | 742 | } |
676 | 743 | ||
@@ -707,22 +774,16 @@ DataStore::NamedDatabase DataStore::Transaction::openDatabase(const QByteArray & | |||
707 | // We don't now if anything changed | 774 | // We don't now if anything changed |
708 | d->implicitCommit = true; | 775 | d->implicitCommit = true; |
709 | auto p = new DataStore::NamedDatabase::Private(db, allowDuplicates, d->defaultErrorHandler, d->name, d->transaction); | 776 | auto p = new DataStore::NamedDatabase::Private(db, allowDuplicates, d->defaultErrorHandler, d->name, d->transaction); |
710 | if (!d->noLock) { | 777 | auto ret = p->openDatabase(d->requestedRead, errorHandler); |
711 | sDbisLock.lockForRead(); | 778 | if (!ret) { |
712 | } | ||
713 | if (!p->openDatabase(d->requestedRead, errorHandler)) { | ||
714 | if (!d->noLock) { | ||
715 | sDbisLock.unlock(); | ||
716 | } | ||
717 | delete p; | 779 | delete p; |
718 | return DataStore::NamedDatabase(); | 780 | return DataStore::NamedDatabase(); |
719 | } | 781 | } |
720 | if (!d->noLock) { | 782 | |
721 | sDbisLock.unlock(); | ||
722 | } | ||
723 | if (p->createdNewDbi) { | 783 | if (p->createdNewDbi) { |
724 | d->createdDbs.insert(p->createdDbName, p->dbi); | 784 | d->createdDbs.insert(p->createdNewDbiName, p->dbi); |
725 | } | 785 | } |
786 | |||
726 | auto database = DataStore::NamedDatabase(p); | 787 | auto database = DataStore::NamedDatabase(p); |
727 | if (!ensureCorrectDb(database, db, d->requestedRead)) { | 788 | if (!ensureCorrectDb(database, db, d->requestedRead)) { |
728 | SinkWarning() << "Failed to open the database correctly" << db; | 789 | SinkWarning() << "Failed to open the database correctly" << db; |
@@ -863,28 +924,41 @@ public: | |||
863 | Q_ASSERT(env); | 924 | Q_ASSERT(env); |
864 | sEnvironments.insert(fullPath, env); | 925 | sEnvironments.insert(fullPath, env); |
865 | //Open all available dbi's | 926 | //Open all available dbi's |
866 | bool noLock = true; | 927 | MDB_txn *transaction; |
867 | auto t = Transaction(new Transaction::Private(readOnly, nullptr, name, env, noLock)); | 928 | if (const int rc = mdb_txn_begin(env, nullptr, readOnly ? MDB_RDONLY : 0, &transaction)) { |
929 | SinkWarning() << "Failed to to open transaction: " << QByteArray(mdb_strerror(rc)) << readOnly << transaction; | ||
930 | return; | ||
931 | } | ||
868 | if (!layout.tables.isEmpty()) { | 932 | if (!layout.tables.isEmpty()) { |
869 | 933 | ||
870 | //TODO upgrade db if the layout has changed: | 934 | //TODO upgrade db if the layout has changed: |
871 | //* read existing layout | 935 | //* read existing layout |
872 | //* if layout is not the same create new layout | 936 | //* if layout is not the same create new layout |
873 | //If the db is read only, abort if the db is not yet existing. | ||
874 | //If the db is not read-only but is not existing, ensure we have a layout and create all tables. | ||
875 | 937 | ||
938 | //Create dbis from the given layout. | ||
876 | for (auto it = layout.tables.constBegin(); it != layout.tables.constEnd(); it++) { | 939 | for (auto it = layout.tables.constBegin(); it != layout.tables.constEnd(); it++) { |
877 | bool allowDuplicates = it.value(); | 940 | const bool allowDuplicates = it.value(); |
878 | t.openDatabase(it.key(), {}, allowDuplicates); | 941 | MDB_dbi dbi = 0; |
942 | const auto db = it.key(); | ||
943 | const auto dbiName = name + db; | ||
944 | if (createDbi(transaction, db, readOnly, allowDuplicates, dbi)) { | ||
945 | sDbis.insert(dbiName, dbi); | ||
946 | } | ||
879 | } | 947 | } |
880 | } else { | 948 | } else { |
881 | for (const auto &db : t.getDatabaseNames()) { | 949 | //Open all available databases |
882 | //Get dbi to store for future use. | 950 | for (const auto &db : getDatabaseNames(transaction)) { |
883 | t.openDatabase(db); | 951 | MDB_dbi dbi = 0; |
952 | const auto dbiName = name + db; | ||
953 | //We're going to load the flags anyways. | ||
954 | bool allowDuplicates = false; | ||
955 | if (createDbi(transaction, db, readOnly, allowDuplicates, dbi)) { | ||
956 | sDbis.insert(dbiName, dbi); | ||
957 | } | ||
884 | } | 958 | } |
885 | } | 959 | } |
886 | //To persist the dbis (this is also necessary for read-only transactions) | 960 | //To persist the dbis (this is also necessary for read-only transactions) |
887 | t.commit(); | 961 | mdb_txn_commit(transaction); |
888 | } | 962 | } |
889 | } | 963 | } |
890 | } | 964 | } |
@@ -990,8 +1064,18 @@ void DataStore::removeFromDisk() const | |||
990 | 1064 | ||
991 | void DataStore::clearEnv() | 1065 | void DataStore::clearEnv() |
992 | { | 1066 | { |
1067 | SinkTrace() << "Clearing environment"; | ||
993 | QWriteLocker locker(&sEnvironmentsLock); | 1068 | QWriteLocker locker(&sEnvironmentsLock); |
994 | for (auto env : sEnvironments) { | 1069 | QWriteLocker dbiLocker(&sDbisLock); |
1070 | for (const auto &envName : sEnvironments.keys()) { | ||
1071 | auto env = sEnvironments.value(envName); | ||
1072 | mdb_env_sync(env, true); | ||
1073 | for (const auto &k : sDbis.keys()) { | ||
1074 | if (k.startsWith(envName)) { | ||
1075 | auto dbi = sDbis.value(k); | ||
1076 | mdb_dbi_close(env, dbi); | ||
1077 | } | ||
1078 | } | ||
995 | mdb_env_close(env); | 1079 | mdb_env_close(env); |
996 | } | 1080 | } |
997 | sDbis.clear(); | 1081 | sDbis.clear(); |
diff --git a/tests/dbwriter.cpp b/tests/dbwriter.cpp index 902a607..3045eac 100644 --- a/tests/dbwriter.cpp +++ b/tests/dbwriter.cpp | |||
@@ -18,7 +18,11 @@ int main(int argc, char *argv[]) | |||
18 | } | 18 | } |
19 | 19 | ||
20 | qWarning() << "Creating db: " << testDataPath << dbName << count; | 20 | qWarning() << "Creating db: " << testDataPath << dbName << count; |
21 | Sink::Storage::DataStore store(testDataPath, dbName, Sink::Storage::DataStore::ReadWrite); | 21 | QMap<QByteArray, int> dbs = {{"a", 0}, {"b", 0}, {"c", 0}, {"p", 0}, {"q", 0}, {"db", 0}}; |
22 | for (int d = 0; d < 40; d++) { | ||
23 | dbs.insert("db" + QByteArray::number(d), 0); | ||
24 | } | ||
25 | Sink::Storage::DataStore store(testDataPath, {dbName, dbs}, Sink::Storage::DataStore::ReadWrite); | ||
22 | auto transaction = store.createTransaction(Sink::Storage::DataStore::ReadWrite); | 26 | auto transaction = store.createTransaction(Sink::Storage::DataStore::ReadWrite); |
23 | for (int i = 0; i < count; i++) { | 27 | for (int i = 0; i < count; i++) { |
24 | if (!transaction) { | 28 | if (!transaction) { |
diff --git a/tests/storagetest.cpp b/tests/storagetest.cpp index 802947f..618f9d0 100644 --- a/tests/storagetest.cpp +++ b/tests/storagetest.cpp | |||
@@ -16,12 +16,12 @@ class StorageTest : public QObject | |||
16 | Q_OBJECT | 16 | Q_OBJECT |
17 | private: | 17 | private: |
18 | QString testDataPath; | 18 | QString testDataPath; |
19 | QString dbName; | 19 | QByteArray dbName; |
20 | const char *keyPrefix = "key"; | 20 | const char *keyPrefix = "key"; |
21 | 21 | ||
22 | void populate(int count) | 22 | void populate(int count) |
23 | { | 23 | { |
24 | Sink::Storage::DataStore storage(testDataPath, dbName, Sink::Storage::DataStore::ReadWrite); | 24 | Sink::Storage::DataStore storage(testDataPath, {dbName, {{"default", 0}}}, Sink::Storage::DataStore::ReadWrite); |
25 | auto transaction = storage.createTransaction(Sink::Storage::DataStore::ReadWrite); | 25 | auto transaction = storage.createTransaction(Sink::Storage::DataStore::ReadWrite); |
26 | for (int i = 0; i < count; i++) { | 26 | for (int i = 0; i < count; i++) { |
27 | // This should perhaps become an implementation detail of the db? | 27 | // This should perhaps become an implementation detail of the db? |
@@ -63,20 +63,20 @@ private slots: | |||
63 | { | 63 | { |
64 | testDataPath = "./testdb"; | 64 | testDataPath = "./testdb"; |
65 | dbName = "test"; | 65 | dbName = "test"; |
66 | Sink::Storage::DataStore storage(testDataPath, dbName); | 66 | Sink::Storage::DataStore storage(testDataPath, {dbName, {{"default", 0}}}); |
67 | storage.removeFromDisk(); | 67 | storage.removeFromDisk(); |
68 | } | 68 | } |
69 | 69 | ||
70 | void cleanup() | 70 | void cleanup() |
71 | { | 71 | { |
72 | Sink::Storage::DataStore storage(testDataPath, dbName); | 72 | Sink::Storage::DataStore storage(testDataPath, {dbName, {{"default", 0}}}); |
73 | storage.removeFromDisk(); | 73 | storage.removeFromDisk(); |
74 | } | 74 | } |
75 | 75 | ||
76 | void testCleanup() | 76 | void testCleanup() |
77 | { | 77 | { |
78 | populate(1); | 78 | populate(1); |
79 | Sink::Storage::DataStore storage(testDataPath, dbName); | 79 | Sink::Storage::DataStore storage(testDataPath, {dbName, {{"default", 0}}}); |
80 | storage.removeFromDisk(); | 80 | storage.removeFromDisk(); |
81 | QFileInfo info(testDataPath + "/" + dbName); | 81 | QFileInfo info(testDataPath + "/" + dbName); |
82 | QVERIFY(!info.exists()); | 82 | QVERIFY(!info.exists()); |
@@ -163,7 +163,7 @@ private slots: | |||
163 | { | 163 | { |
164 | bool gotResult = false; | 164 | bool gotResult = false; |
165 | bool gotError = false; | 165 | bool gotError = false; |
166 | Sink::Storage::DataStore store(testDataPath, dbName, Sink::Storage::DataStore::ReadWrite); | 166 | Sink::Storage::DataStore store(testDataPath, {dbName, {{"default", 0}}}, Sink::Storage::DataStore::ReadWrite); |
167 | auto transaction = store.createTransaction(Sink::Storage::DataStore::ReadOnly); | 167 | auto transaction = store.createTransaction(Sink::Storage::DataStore::ReadOnly); |
168 | auto db = transaction.openDatabase("default", [&](const Sink::Storage::DataStore::Error &error) { | 168 | auto db = transaction.openDatabase("default", [&](const Sink::Storage::DataStore::Error &error) { |
169 | qDebug() << error.message; | 169 | qDebug() << error.message; |
@@ -227,7 +227,7 @@ private slots: | |||
227 | { | 227 | { |
228 | bool gotResult = false; | 228 | bool gotResult = false; |
229 | bool gotError = false; | 229 | bool gotError = false; |
230 | Sink::Storage::DataStore store(testDataPath, dbName, Sink::Storage::DataStore::ReadWrite); | 230 | Sink::Storage::DataStore store(testDataPath, {dbName, {{"default", 0}}}, Sink::Storage::DataStore::ReadWrite); |
231 | auto transaction = store.createTransaction(Sink::Storage::DataStore::ReadWrite); | 231 | auto transaction = store.createTransaction(Sink::Storage::DataStore::ReadWrite); |
232 | auto db = transaction.openDatabase("default", nullptr, false); | 232 | auto db = transaction.openDatabase("default", nullptr, false); |
233 | db.write("key", "value"); | 233 | db.write("key", "value"); |
@@ -252,7 +252,7 @@ private slots: | |||
252 | { | 252 | { |
253 | bool gotResult = false; | 253 | bool gotResult = false; |
254 | bool gotError = false; | 254 | bool gotError = false; |
255 | Sink::Storage::DataStore store(testDataPath, dbName, Sink::Storage::DataStore::ReadWrite); | 255 | Sink::Storage::DataStore store(testDataPath, {dbName, {{"default", 0x04}}}, Sink::Storage::DataStore::ReadWrite); |
256 | auto transaction = store.createTransaction(Sink::Storage::DataStore::ReadWrite); | 256 | auto transaction = store.createTransaction(Sink::Storage::DataStore::ReadWrite); |
257 | auto db = transaction.openDatabase("default", nullptr, true); | 257 | auto db = transaction.openDatabase("default", nullptr, true); |
258 | db.write("key", "value1"); | 258 | db.write("key", "value1"); |
@@ -295,7 +295,7 @@ private slots: | |||
295 | void testWriteToNamedDb() | 295 | void testWriteToNamedDb() |
296 | { | 296 | { |
297 | bool gotError = false; | 297 | bool gotError = false; |
298 | Sink::Storage::DataStore store(testDataPath, dbName, Sink::Storage::DataStore::ReadWrite); | 298 | Sink::Storage::DataStore store(testDataPath, {dbName, {{"test", 0}}}, Sink::Storage::DataStore::ReadWrite); |
299 | store.createTransaction(Sink::Storage::DataStore::ReadWrite) | 299 | store.createTransaction(Sink::Storage::DataStore::ReadWrite) |
300 | .openDatabase("test") | 300 | .openDatabase("test") |
301 | .write("key1", "value1", [&](const Sink::Storage::DataStore::Error &error) { | 301 | .write("key1", "value1", [&](const Sink::Storage::DataStore::Error &error) { |
@@ -308,7 +308,8 @@ private slots: | |||
308 | void testWriteDuplicatesToNamedDb() | 308 | void testWriteDuplicatesToNamedDb() |
309 | { | 309 | { |
310 | bool gotError = false; | 310 | bool gotError = false; |
311 | Sink::Storage::DataStore store(testDataPath, dbName, Sink::Storage::DataStore::ReadWrite); | 311 | |
312 | Sink::Storage::DataStore store(testDataPath, {dbName, {{"test", 0}}}, Sink::Storage::DataStore::ReadWrite); | ||
312 | store.createTransaction(Sink::Storage::DataStore::ReadWrite) | 313 | store.createTransaction(Sink::Storage::DataStore::ReadWrite) |
313 | .openDatabase("test", nullptr, true) | 314 | .openDatabase("test", nullptr, true) |
314 | .write("key1", "value1", [&](const Sink::Storage::DataStore::Error &error) { | 315 | .write("key1", "value1", [&](const Sink::Storage::DataStore::Error &error) { |
@@ -321,7 +322,7 @@ private slots: | |||
321 | // By default we want only exact matches | 322 | // By default we want only exact matches |
322 | void testSubstringKeys() | 323 | void testSubstringKeys() |
323 | { | 324 | { |
324 | Sink::Storage::DataStore store(testDataPath, dbName, Sink::Storage::DataStore::ReadWrite); | 325 | Sink::Storage::DataStore store(testDataPath, {dbName, {{"test", 0x04}}}, Sink::Storage::DataStore::ReadWrite); |
325 | auto transaction = store.createTransaction(Sink::Storage::DataStore::ReadWrite); | 326 | auto transaction = store.createTransaction(Sink::Storage::DataStore::ReadWrite); |
326 | auto db = transaction.openDatabase("test", nullptr, true); | 327 | auto db = transaction.openDatabase("test", nullptr, true); |
327 | db.write("sub", "value1"); | 328 | db.write("sub", "value1"); |
@@ -333,7 +334,7 @@ private slots: | |||
333 | 334 | ||
334 | void testFindSubstringKeys() | 335 | void testFindSubstringKeys() |
335 | { | 336 | { |
336 | Sink::Storage::DataStore store(testDataPath, dbName, Sink::Storage::DataStore::ReadWrite); | 337 | Sink::Storage::DataStore store(testDataPath, {dbName, {{"test", 0}}}, Sink::Storage::DataStore::ReadWrite); |
337 | auto transaction = store.createTransaction(Sink::Storage::DataStore::ReadWrite); | 338 | auto transaction = store.createTransaction(Sink::Storage::DataStore::ReadWrite); |
338 | auto db = transaction.openDatabase("test", nullptr, false); | 339 | auto db = transaction.openDatabase("test", nullptr, false); |
339 | db.write("sub", "value1"); | 340 | db.write("sub", "value1"); |
@@ -346,7 +347,7 @@ private slots: | |||
346 | 347 | ||
347 | void testFindSubstringKeysWithDuplicatesEnabled() | 348 | void testFindSubstringKeysWithDuplicatesEnabled() |
348 | { | 349 | { |
349 | Sink::Storage::DataStore store(testDataPath, dbName, Sink::Storage::DataStore::ReadWrite); | 350 | Sink::Storage::DataStore store(testDataPath, {dbName, {{"test", 0}}}, Sink::Storage::DataStore::ReadWrite); |
350 | auto transaction = store.createTransaction(Sink::Storage::DataStore::ReadWrite); | 351 | auto transaction = store.createTransaction(Sink::Storage::DataStore::ReadWrite); |
351 | auto db = transaction.openDatabase("test", nullptr, true); | 352 | auto db = transaction.openDatabase("test", nullptr, true); |
352 | db.write("sub", "value1"); | 353 | db.write("sub", "value1"); |
@@ -359,7 +360,7 @@ private slots: | |||
359 | 360 | ||
360 | void testKeySorting() | 361 | void testKeySorting() |
361 | { | 362 | { |
362 | Sink::Storage::DataStore store(testDataPath, dbName, Sink::Storage::DataStore::ReadWrite); | 363 | Sink::Storage::DataStore store(testDataPath, {dbName, {{"test", 0}}}, Sink::Storage::DataStore::ReadWrite); |
363 | auto transaction = store.createTransaction(Sink::Storage::DataStore::ReadWrite); | 364 | auto transaction = store.createTransaction(Sink::Storage::DataStore::ReadWrite); |
364 | auto db = transaction.openDatabase("test", nullptr, false); | 365 | auto db = transaction.openDatabase("test", nullptr, false); |
365 | db.write("sub_2", "value2"); | 366 | db.write("sub_2", "value2"); |
@@ -380,7 +381,7 @@ private slots: | |||
380 | // Ensure we don't retrieve a key that is greater than the current key. We only want equal keys. | 381 | // Ensure we don't retrieve a key that is greater than the current key. We only want equal keys. |
381 | void testKeyRange() | 382 | void testKeyRange() |
382 | { | 383 | { |
383 | Sink::Storage::DataStore store(testDataPath, dbName, Sink::Storage::DataStore::ReadWrite); | 384 | Sink::Storage::DataStore store(testDataPath, {dbName, {{"test", 0}}}, Sink::Storage::DataStore::ReadWrite); |
384 | auto transaction = store.createTransaction(Sink::Storage::DataStore::ReadWrite); | 385 | auto transaction = store.createTransaction(Sink::Storage::DataStore::ReadWrite); |
385 | auto db = transaction.openDatabase("test", nullptr, true); | 386 | auto db = transaction.openDatabase("test", nullptr, true); |
386 | db.write("sub1", "value1"); | 387 | db.write("sub1", "value1"); |
@@ -391,7 +392,7 @@ private slots: | |||
391 | 392 | ||
392 | void testFindLatest() | 393 | void testFindLatest() |
393 | { | 394 | { |
394 | Sink::Storage::DataStore store(testDataPath, dbName, Sink::Storage::DataStore::ReadWrite); | 395 | Sink::Storage::DataStore store(testDataPath, {dbName, {{"test", 0}}}, Sink::Storage::DataStore::ReadWrite); |
395 | auto transaction = store.createTransaction(Sink::Storage::DataStore::ReadWrite); | 396 | auto transaction = store.createTransaction(Sink::Storage::DataStore::ReadWrite); |
396 | auto db = transaction.openDatabase("test", nullptr, false); | 397 | auto db = transaction.openDatabase("test", nullptr, false); |
397 | db.write("sub1", "value1"); | 398 | db.write("sub1", "value1"); |
@@ -406,7 +407,7 @@ private slots: | |||
406 | 407 | ||
407 | void testFindLatestInSingle() | 408 | void testFindLatestInSingle() |
408 | { | 409 | { |
409 | Sink::Storage::DataStore store(testDataPath, dbName, Sink::Storage::DataStore::ReadWrite); | 410 | Sink::Storage::DataStore store(testDataPath, {dbName, {{"test", 0}}}, Sink::Storage::DataStore::ReadWrite); |
410 | auto transaction = store.createTransaction(Sink::Storage::DataStore::ReadWrite); | 411 | auto transaction = store.createTransaction(Sink::Storage::DataStore::ReadWrite); |
411 | auto db = transaction.openDatabase("test", nullptr, false); | 412 | auto db = transaction.openDatabase("test", nullptr, false); |
412 | db.write("sub2", "value2"); | 413 | db.write("sub2", "value2"); |
@@ -418,7 +419,7 @@ private slots: | |||
418 | 419 | ||
419 | void testFindLast() | 420 | void testFindLast() |
420 | { | 421 | { |
421 | Sink::Storage::DataStore store(testDataPath, dbName, Sink::Storage::DataStore::ReadWrite); | 422 | Sink::Storage::DataStore store(testDataPath, {dbName, {{"test", 0}}}, Sink::Storage::DataStore::ReadWrite); |
422 | auto transaction = store.createTransaction(Sink::Storage::DataStore::ReadWrite); | 423 | auto transaction = store.createTransaction(Sink::Storage::DataStore::ReadWrite); |
423 | auto db = transaction.openDatabase("test", nullptr, false); | 424 | auto db = transaction.openDatabase("test", nullptr, false); |
424 | db.write("sub2", "value2"); | 425 | db.write("sub2", "value2"); |
@@ -429,9 +430,18 @@ private slots: | |||
429 | QCOMPARE(result, QByteArray("value3")); | 430 | QCOMPARE(result, QByteArray("value3")); |
430 | } | 431 | } |
431 | 432 | ||
433 | static QMap<QByteArray, int> baseDbs() | ||
434 | { | ||
435 | return {{"revisionType", 0}, | ||
436 | {"revisions", 0}, | ||
437 | {"uids", 0}, | ||
438 | {"default", 0}, | ||
439 | {"__flagtable", 0}}; | ||
440 | } | ||
441 | |||
432 | void testRecordRevision() | 442 | void testRecordRevision() |
433 | { | 443 | { |
434 | Sink::Storage::DataStore store(testDataPath, dbName, Sink::Storage::DataStore::ReadWrite); | 444 | Sink::Storage::DataStore store(testDataPath, {dbName, baseDbs()}, Sink::Storage::DataStore::ReadWrite); |
435 | auto transaction = store.createTransaction(Sink::Storage::DataStore::ReadWrite); | 445 | auto transaction = store.createTransaction(Sink::Storage::DataStore::ReadWrite); |
436 | Sink::Storage::DataStore::recordRevision(transaction, 1, "uid", "type"); | 446 | Sink::Storage::DataStore::recordRevision(transaction, 1, "uid", "type"); |
437 | QCOMPARE(Sink::Storage::DataStore::getTypeFromRevision(transaction, 1), QByteArray("type")); | 447 | QCOMPARE(Sink::Storage::DataStore::getTypeFromRevision(transaction, 1), QByteArray("type")); |
@@ -440,7 +450,7 @@ private slots: | |||
440 | 450 | ||
441 | void testRecordRevisionSorting() | 451 | void testRecordRevisionSorting() |
442 | { | 452 | { |
443 | Sink::Storage::DataStore store(testDataPath, dbName, Sink::Storage::DataStore::ReadWrite); | 453 | Sink::Storage::DataStore store(testDataPath, {dbName, {{"test", 0}}}, Sink::Storage::DataStore::ReadWrite); |
444 | auto transaction = store.createTransaction(Sink::Storage::DataStore::ReadWrite); | 454 | auto transaction = store.createTransaction(Sink::Storage::DataStore::ReadWrite); |
445 | QByteArray result; | 455 | QByteArray result; |
446 | auto db = transaction.openDatabase("test", nullptr, false); | 456 | auto db = transaction.openDatabase("test", nullptr, false); |
@@ -463,7 +473,7 @@ private slots: | |||
463 | return result; | 473 | return result; |
464 | }; | 474 | }; |
465 | { | 475 | { |
466 | Sink::Storage::DataStore store(testDataPath, dbName, Sink::Storage::DataStore::ReadWrite); | 476 | Sink::Storage::DataStore store(testDataPath, {dbName, {{"testTransactionVisibility", 0}}}, Sink::Storage::DataStore::ReadWrite); |
467 | auto transaction = store.createTransaction(Sink::Storage::DataStore::ReadWrite); | 477 | auto transaction = store.createTransaction(Sink::Storage::DataStore::ReadWrite); |
468 | 478 | ||
469 | auto db = transaction.openDatabase("testTransactionVisibility", nullptr, false); | 479 | auto db = transaction.openDatabase("testTransactionVisibility", nullptr, false); |
@@ -489,7 +499,7 @@ private slots: | |||
489 | 499 | ||
490 | void testCopyTransaction() | 500 | void testCopyTransaction() |
491 | { | 501 | { |
492 | Sink::Storage::DataStore store(testDataPath, dbName, Sink::Storage::DataStore::ReadWrite); | 502 | Sink::Storage::DataStore store(testDataPath, {dbName, {{"a", 0}, {"b", 0}, {"c", 0}}}, Sink::Storage::DataStore::ReadWrite); |
493 | { | 503 | { |
494 | auto transaction = store.createTransaction(Sink::Storage::DataStore::ReadWrite); | 504 | auto transaction = store.createTransaction(Sink::Storage::DataStore::ReadWrite); |
495 | transaction.openDatabase("a", nullptr, false); | 505 | transaction.openDatabase("a", nullptr, false); |
@@ -516,7 +526,6 @@ private slots: | |||
516 | */ | 526 | */ |
517 | void testReadDuringExternalProcessWrite() | 527 | void testReadDuringExternalProcessWrite() |
518 | { | 528 | { |
519 | QSKIP("Not running multiprocess test"); | ||
520 | 529 | ||
521 | QList<QFuture<void>> futures; | 530 | QList<QFuture<void>> futures; |
522 | for (int i = 0; i < 5; i++) { | 531 | for (int i = 0; i < 5; i++) { |
@@ -545,7 +554,17 @@ private slots: | |||
545 | 554 | ||
546 | void testRecordUid() | 555 | void testRecordUid() |
547 | { | 556 | { |
548 | Sink::Storage::DataStore store(testDataPath, dbName, Sink::Storage::DataStore::ReadWrite); | 557 | |
558 | QMap<QByteArray, int> dbs = {{"revisionType", 0}, | ||
559 | {"revisions", 0}, | ||
560 | {"uids", 0}, | ||
561 | {"default", 0}, | ||
562 | {"__flagtable", 0}, | ||
563 | {"typeuids", 0}, | ||
564 | {"type2uids", 0} | ||
565 | }; | ||
566 | |||
567 | Sink::Storage::DataStore store(testDataPath, {dbName, dbs}, Sink::Storage::DataStore::ReadWrite); | ||
549 | auto transaction = store.createTransaction(Sink::Storage::DataStore::ReadWrite); | 568 | auto transaction = store.createTransaction(Sink::Storage::DataStore::ReadWrite); |
550 | Sink::Storage::DataStore::recordUid(transaction, "uid1", "type"); | 569 | Sink::Storage::DataStore::recordUid(transaction, "uid1", "type"); |
551 | Sink::Storage::DataStore::recordUid(transaction, "uid2", "type"); | 570 | Sink::Storage::DataStore::recordUid(transaction, "uid2", "type"); |
@@ -571,6 +590,68 @@ private slots: | |||
571 | QCOMPARE(uids, expected); | 590 | QCOMPARE(uids, expected); |
572 | } | 591 | } |
573 | } | 592 | } |
593 | |||
594 | void testDbiVisibility() | ||
595 | { | ||
596 | auto readValue = [](const Sink::Storage::DataStore::NamedDatabase &db, const QByteArray) { | ||
597 | QByteArray result; | ||
598 | db.scan("key1", [&](const QByteArray &, const QByteArray &value) { | ||
599 | result = value; | ||
600 | return true; | ||
601 | }); | ||
602 | return result; | ||
603 | }; | ||
604 | { | ||
605 | Sink::Storage::DataStore store(testDataPath, {dbName, {{"testTransactionVisibility", 0}}}, Sink::Storage::DataStore::ReadWrite); | ||
606 | auto transaction = store.createTransaction(Sink::Storage::DataStore::ReadWrite); | ||
607 | |||
608 | auto db = transaction.openDatabase("testTransactionVisibility", nullptr, false); | ||
609 | db.write("key1", "foo"); | ||
610 | QCOMPARE(readValue(db, "key1"), QByteArray("foo")); | ||
611 | transaction.commit(); | ||
612 | } | ||
613 | Sink::Storage::DataStore::clearEnv(); | ||
614 | |||
615 | //Try to read-only dynamic opening of the db. | ||
616 | //This is the case if we don't have all databases available upon initializatoin and we don't (e.g. because the db hasn't been created yet) | ||
617 | { | ||
618 | // Trick the db into not loading all dbs by passing in a bogus layout. | ||
619 | Sink::Storage::DataStore store(testDataPath, {dbName, {{"bogus", 0}}}, Sink::Storage::DataStore::ReadOnly); | ||
620 | |||
621 | //This transaction should open the dbi | ||
622 | auto transaction2 = store.createTransaction(Sink::Storage::DataStore::ReadOnly); | ||
623 | auto db2 = transaction2.openDatabase("testTransactionVisibility", nullptr, false); | ||
624 | QCOMPARE(readValue(db2, "key1"), QByteArray("foo")); | ||
625 | |||
626 | //This transaction should have the dbi available | ||
627 | auto transaction3 = store.createTransaction(Sink::Storage::DataStore::ReadOnly); | ||
628 | auto db3 = transaction3.openDatabase("testTransactionVisibility", nullptr, false); | ||
629 | QCOMPARE(readValue(db3, "key1"), QByteArray("foo")); | ||
630 | } | ||
631 | |||
632 | Sink::Storage::DataStore::clearEnv(); | ||
633 | //Try to read-write dynamic opening of the db. | ||
634 | //This is the case if we don't have all databases available upon initializatoin and we don't (e.g. because the db hasn't been created yet) | ||
635 | { | ||
636 | // Trick the db into not loading all dbs by passing in a bogus layout. | ||
637 | Sink::Storage::DataStore store(testDataPath, {dbName, {{"bogus", 0}}}, Sink::Storage::DataStore::ReadWrite); | ||
638 | |||
639 | //This transaction should open the dbi | ||
640 | auto transaction2 = store.createTransaction(Sink::Storage::DataStore::ReadWrite); | ||
641 | auto db2 = transaction2.openDatabase("testTransactionVisibility", nullptr, false); | ||
642 | QCOMPARE(readValue(db2, "key1"), QByteArray("foo")); | ||
643 | |||
644 | //This transaction should have the dbi available (creating two write transactions obviously doesn't work) | ||
645 | //NOTE: we don't support this scenario. A write transaction must commit or abort before a read transaction opens the same database. | ||
646 | // auto transaction3 = store.createTransaction(Sink::Storage::DataStore::ReadOnly); | ||
647 | // auto db3 = transaction3.openDatabase("testTransactionVisibility", nullptr, false); | ||
648 | // QCOMPARE(readValue(db3, "key1"), QByteArray("foo")); | ||
649 | |||
650 | //Ensure we can still open further dbis in the write transaction | ||
651 | auto db4 = transaction2.openDatabase("anotherDb", nullptr, false); | ||
652 | } | ||
653 | |||
654 | } | ||
574 | }; | 655 | }; |
575 | 656 | ||
576 | QTEST_MAIN(StorageTest) | 657 | QTEST_MAIN(StorageTest) |