diff options
-rw-r--r-- | common/storage_lmdb.cpp | 36 | ||||
-rw-r--r-- | store/kyotodatabase.cpp | 164 | ||||
-rw-r--r-- | store/kyotodatabase.h | 28 | ||||
-rw-r--r-- | store/test/storagetest.cpp | 113 |
4 files changed, 330 insertions, 11 deletions
diff --git a/common/storage_lmdb.cpp b/common/storage_lmdb.cpp index 18a5aa6..283205f 100644 --- a/common/storage_lmdb.cpp +++ b/common/storage_lmdb.cpp | |||
@@ -14,10 +14,12 @@ | |||
14 | class Storage::Private | 14 | class Storage::Private |
15 | { | 15 | { |
16 | public: | 16 | public: |
17 | Private(const QString &p); | 17 | Private(const QString &s, const QString &name); |
18 | ~Private(); | 18 | ~Private(); |
19 | 19 | ||
20 | QString path; | 20 | QString storageRoot; |
21 | QString name; | ||
22 | |||
21 | MDB_dbi dbi; | 23 | MDB_dbi dbi; |
22 | MDB_env *env; | 24 | MDB_env *env; |
23 | MDB_txn *transaction; | 25 | MDB_txn *transaction; |
@@ -25,20 +27,21 @@ public: | |||
25 | bool firstOpen; | 27 | bool firstOpen; |
26 | }; | 28 | }; |
27 | 29 | ||
28 | Storage::Private::Private(const QString &p) | 30 | Storage::Private::Private(const QString &s, const QString &n) |
29 | : path(p), | 31 | : transaction(0), |
30 | transaction(0), | ||
31 | readTransaction(false), | 32 | readTransaction(false), |
32 | firstOpen(true) | 33 | firstOpen(true), |
34 | storageRoot(s), | ||
35 | name(n) | ||
33 | { | 36 | { |
34 | QDir dir; | 37 | QDir dir; |
35 | dir.mkdir(path); | 38 | dir.mkdir(storageRoot); |
36 | 39 | ||
37 | //create file | 40 | //create file |
38 | if (mdb_env_create(&env)) { | 41 | if (mdb_env_create(&env)) { |
39 | // TODO: handle error | 42 | // TODO: handle error |
40 | } else { | 43 | } else { |
41 | int rc = mdb_env_open(env, path.toStdString().data(), 0, 0664); | 44 | int rc = mdb_env_open(env, storageRoot.toStdString().data(), 0, 0664); |
42 | 45 | ||
43 | if (rc) { | 46 | if (rc) { |
44 | std::cerr << "mdb_env_open: " << rc << " " << mdb_strerror(rc) << std::endl; | 47 | std::cerr << "mdb_env_open: " << rc << " " << mdb_strerror(rc) << std::endl; |
@@ -63,7 +66,7 @@ Storage::Private::~Private() | |||
63 | } | 66 | } |
64 | 67 | ||
65 | Storage::Storage(const QString &storageRoot, const QString &name) | 68 | Storage::Storage(const QString &storageRoot, const QString &name) |
66 | : d(new Private(storageRoot + '/' + name)) | 69 | : d(new Private(storageRoot, name)) |
67 | { | 70 | { |
68 | } | 71 | } |
69 | 72 | ||
@@ -143,6 +146,11 @@ void Storage::abortTransaction() | |||
143 | d->transaction = 0; | 146 | d->transaction = 0; |
144 | } | 147 | } |
145 | 148 | ||
149 | bool Storage::write(const char *key, size_t keySize, const char *value, size_t valueSize) | ||
150 | { | ||
151 | write(std::string(key, keySize), std::string(value, valueSize)); | ||
152 | } | ||
153 | |||
146 | bool Storage::write(const std::string &sKey, const std::string &sValue) | 154 | bool Storage::write(const std::string &sKey, const std::string &sValue) |
147 | { | 155 | { |
148 | if (!d->env) { | 156 | if (!d->env) { |
@@ -241,7 +249,7 @@ bool Storage::read(const std::string &sKey, const std::function<void(void *ptr, | |||
241 | 249 | ||
242 | if (rc) { | 250 | if (rc) { |
243 | std::cerr << "mdb_cursor_get: " << rc << " " << mdb_strerror(rc) << std::endl; | 251 | std::cerr << "mdb_cursor_get: " << rc << " " << mdb_strerror(rc) << std::endl; |
244 | return false | 252 | return false; |
245 | } | 253 | } |
246 | 254 | ||
247 | /** | 255 | /** |
@@ -255,7 +263,7 @@ bool Storage::read(const std::string &sKey, const std::function<void(void *ptr, | |||
255 | 263 | ||
256 | qint64 Storage::diskUsage() const | 264 | qint64 Storage::diskUsage() const |
257 | { | 265 | { |
258 | QFileInfo info(d->path, "data.mdb"); | 266 | QFileInfo info(d->storageRoot + "/data.mdb"); |
259 | return info.size(); | 267 | return info.size(); |
260 | } | 268 | } |
261 | 269 | ||
@@ -266,3 +274,9 @@ void Storage::removeFromDisk() const | |||
266 | dir.remove("lock.mdb"); | 274 | dir.remove("lock.mdb"); |
267 | } | 275 | } |
268 | 276 | ||
277 | void Storage::removeFromDisk() const | ||
278 | { | ||
279 | QDir dir(d->storageRoot); | ||
280 | dir.remove("data.mdb"); | ||
281 | dir.remove("lock.mdb"); | ||
282 | } | ||
diff --git a/store/kyotodatabase.cpp b/store/kyotodatabase.cpp new file mode 100644 index 0000000..542667a --- /dev/null +++ b/store/kyotodatabase.cpp | |||
@@ -0,0 +1,164 @@ | |||
1 | #include "database.h" | ||
2 | |||
3 | #include <iostream> | ||
4 | |||
5 | #include <QAtomicInt> | ||
6 | #include <QDebug> | ||
7 | #include <QDir> | ||
8 | #include <QFileInfo> | ||
9 | #include <QReadWriteLock> | ||
10 | #include <QString> | ||
11 | #include <QTime> | ||
12 | |||
13 | #include <kchashdb.h> | ||
14 | |||
15 | class Database::Private | ||
16 | { | ||
17 | public: | ||
18 | Private(const QString &storageRoot, const QString &name); | ||
19 | ~Private(); | ||
20 | |||
21 | kyotocabinet::TreeDB db; | ||
22 | bool dbOpen; | ||
23 | bool inTransaction; | ||
24 | }; | ||
25 | |||
26 | Database::Private::Private(const QString &storageRoot, const QString &name) | ||
27 | : inTransaction(false) | ||
28 | { | ||
29 | QDir dir; | ||
30 | dir.mkdir(storageRoot); | ||
31 | |||
32 | //create file | ||
33 | dbOpen = db.open((storageRoot + "/" + name + ".kch").toStdString(), kyotocabinet::BasicDB::OWRITER | kyotocabinet::BasicDB::OCREATE); | ||
34 | if (!dbOpen) { | ||
35 | // TODO: handle error | ||
36 | } | ||
37 | } | ||
38 | |||
39 | Database::Private::~Private() | ||
40 | { | ||
41 | if (dbOpen && inTransaction) { | ||
42 | db.end_transaction(false); | ||
43 | } | ||
44 | } | ||
45 | |||
46 | Database::Database(const QString &storageRoot, const QString &name) | ||
47 | : d(new Private(storageRoot, name)) | ||
48 | { | ||
49 | } | ||
50 | |||
51 | Database::~Database() | ||
52 | { | ||
53 | delete d; | ||
54 | } | ||
55 | |||
56 | bool Database::isInTransaction() const | ||
57 | { | ||
58 | return d->inTransaction; | ||
59 | } | ||
60 | |||
61 | bool Database::startTransaction(TransactionType type) | ||
62 | { | ||
63 | if (!d->dbOpen) { | ||
64 | return false; | ||
65 | } | ||
66 | |||
67 | if (d->inTransaction) { | ||
68 | return true; | ||
69 | } | ||
70 | |||
71 | //TODO handle errors | ||
72 | d->inTransaction = d->db.begin_transaction(); | ||
73 | return d->inTransaction; | ||
74 | } | ||
75 | |||
76 | bool Database::commitTransaction() | ||
77 | { | ||
78 | if (!d->dbOpen) { | ||
79 | return false; | ||
80 | } | ||
81 | |||
82 | if (!d->inTransaction) { | ||
83 | return false; | ||
84 | } | ||
85 | |||
86 | bool success = d->db.end_transaction(true); | ||
87 | d->inTransaction = false; | ||
88 | return success; | ||
89 | } | ||
90 | |||
91 | void Database::abortTransaction() | ||
92 | { | ||
93 | if (!d->dbOpen || !d->inTransaction) { | ||
94 | return; | ||
95 | } | ||
96 | |||
97 | d->db.end_transaction(false); | ||
98 | d->inTransaction = false; | ||
99 | } | ||
100 | |||
101 | bool Database::write(const char *key, size_t keySize, const char *value, size_t valueSize) | ||
102 | { | ||
103 | if (!d->dbOpen) { | ||
104 | return false; | ||
105 | } | ||
106 | |||
107 | bool success = d->db.set(key, keySize, value, valueSize); | ||
108 | return success; | ||
109 | } | ||
110 | |||
111 | bool Database::write(const std::string &sKey, const std::string &sValue) | ||
112 | { | ||
113 | if (!d->dbOpen) { | ||
114 | return false; | ||
115 | } | ||
116 | |||
117 | bool success = d->db.set(sKey, sValue); | ||
118 | return success; | ||
119 | } | ||
120 | |||
121 | void Database::read(const std::string &sKey, const std::function<void(const std::string &value)> &resultHandler) | ||
122 | { | ||
123 | if (!d->dbOpen) { | ||
124 | return; | ||
125 | } | ||
126 | |||
127 | std::string value; | ||
128 | if (d->db.get(sKey, &value)) { | ||
129 | resultHandler(value); | ||
130 | } | ||
131 | } | ||
132 | |||
133 | void Database::read(const std::string &sKey, const std::function<void(void *ptr, int size)> &resultHandler) | ||
134 | { | ||
135 | if (!d->dbOpen) { | ||
136 | return; | ||
137 | } | ||
138 | |||
139 | size_t valueSize; | ||
140 | char *valueBuffer = d->db.get(sKey.data(), sKey.size(), &valueSize); | ||
141 | resultHandler(valueBuffer, valueSize); | ||
142 | delete[] valueBuffer; | ||
143 | } | ||
144 | |||
145 | qint64 Database::diskUsage() const | ||
146 | { | ||
147 | if (!d->dbOpen) { | ||
148 | return 0; | ||
149 | } | ||
150 | |||
151 | QFileInfo info(QString::fromStdString(d->db.path())); | ||
152 | return info.size(); | ||
153 | } | ||
154 | |||
155 | void Database::removeFromDisk() const | ||
156 | { | ||
157 | if (!d->dbOpen) { | ||
158 | return; | ||
159 | } | ||
160 | |||
161 | QFileInfo info(QString::fromStdString(d->db.path())); | ||
162 | QDir dir = info.dir(); | ||
163 | dir.remove(info.fileName()); | ||
164 | } | ||
diff --git a/store/kyotodatabase.h b/store/kyotodatabase.h new file mode 100644 index 0000000..e752ff5 --- /dev/null +++ b/store/kyotodatabase.h | |||
@@ -0,0 +1,28 @@ | |||
1 | #pragma once | ||
2 | |||
3 | #include <string> | ||
4 | #include <QString> | ||
5 | |||
6 | class Database { | ||
7 | public: | ||
8 | enum TransactionType { ReadOnly, ReadWrite }; | ||
9 | |||
10 | Database(const QString &storageRoot, const QString &name); | ||
11 | ~Database(); | ||
12 | bool isInTransaction() const; | ||
13 | bool startTransaction(TransactionType type = ReadWrite); | ||
14 | bool commitTransaction(); | ||
15 | void abortTransaction(); | ||
16 | bool write(const char *key, size_t keySize, const char *value, size_t valueSize); | ||
17 | bool write(const std::string &sKey, const std::string &sValue); | ||
18 | //Perhaps prefer iterators (assuming we need to be able to match multiple values | ||
19 | void read(const std::string &sKey, const std::function<void(const std::string &value)> &); | ||
20 | void read(const std::string &sKey, const std::function<void(void *ptr, int size)> &); | ||
21 | |||
22 | qint64 diskUsage() const; | ||
23 | void removeFromDisk() const; | ||
24 | private: | ||
25 | class Private; | ||
26 | Private * const d; | ||
27 | }; | ||
28 | |||
diff --git a/store/test/storagetest.cpp b/store/test/storagetest.cpp new file mode 100644 index 0000000..dba4f6c --- /dev/null +++ b/store/test/storagetest.cpp | |||
@@ -0,0 +1,113 @@ | |||
1 | #include <QtTest> | ||
2 | |||
3 | #include <iostream> | ||
4 | |||
5 | #include <QDebug> | ||
6 | #include <QString> | ||
7 | #include <QtConcurrent/QtConcurrentRun> | ||
8 | |||
9 | #include "store/database.h" | ||
10 | |||
11 | class StorageTest : public QObject | ||
12 | { | ||
13 | Q_OBJECT | ||
14 | private: | ||
15 | //This should point to a directory on disk and not a ramdisk (since we're measuring performance) | ||
16 | QString testDataPath; | ||
17 | QString dbName; | ||
18 | const char *keyPrefix = "key"; | ||
19 | |||
20 | void populate(int count) | ||
21 | { | ||
22 | Database db(testDataPath, dbName); | ||
23 | for (int i = 0; i < count; i++) { | ||
24 | //This should perhaps become an implementation detail of the db? | ||
25 | if (i % 10000 == 0) { | ||
26 | if (i > 0) { | ||
27 | db.commitTransaction(); | ||
28 | } | ||
29 | db.startTransaction(); | ||
30 | } | ||
31 | db.write(keyPrefix + std::to_string(i), keyPrefix + std::to_string(i)); | ||
32 | } | ||
33 | db.commitTransaction(); | ||
34 | } | ||
35 | |||
36 | bool verify(Database &db, int i) | ||
37 | { | ||
38 | bool error = false; | ||
39 | const auto reference = keyPrefix + std::to_string(i); | ||
40 | if(!db.read(keyPrefix + std::to_string(i), [&error, &reference](const std::string &value) { | ||
41 | if (value != reference) { | ||
42 | qDebug() << "Mismatch while reading"; | ||
43 | error = true; | ||
44 | } | ||
45 | })) { | ||
46 | return false; | ||
47 | } | ||
48 | return !error; | ||
49 | } | ||
50 | |||
51 | private Q_SLOTS: | ||
52 | void initTestCase() | ||
53 | { | ||
54 | testDataPath = "./testdb"; | ||
55 | dbName = "test"; | ||
56 | } | ||
57 | |||
58 | void cleanupTestCase() | ||
59 | { | ||
60 | Database db(testDataPath, dbName); | ||
61 | db.removeFromDisk(); | ||
62 | } | ||
63 | |||
64 | |||
65 | void testRead() | ||
66 | { | ||
67 | const int count = 100; | ||
68 | |||
69 | populate(count); | ||
70 | |||
71 | //ensure we can read everything back correctly | ||
72 | { | ||
73 | Database db(testDataPath, dbName); | ||
74 | for (int i = 0; i < count; i++) { | ||
75 | QVERIFY(verify(db, i)); | ||
76 | } | ||
77 | } | ||
78 | |||
79 | Database db(testDataPath, dbName); | ||
80 | db.removeFromDisk(); | ||
81 | } | ||
82 | |||
83 | void testConcurrentRead() | ||
84 | { | ||
85 | const int count = 10000; | ||
86 | |||
87 | populate(count); | ||
88 | |||
89 | //Try to concurrently read | ||
90 | QList<QFuture<void> > futures; | ||
91 | const int concurrencyLevel = 4; | ||
92 | for (int num = 0; num < concurrencyLevel; num++) { | ||
93 | futures << QtConcurrent::run([this, count](){ | ||
94 | Database db(testDataPath, dbName); | ||
95 | for (int i = 0; i < count; i++) { | ||
96 | if (!verify(db, i)) { | ||
97 | qWarning() << "invalid value"; | ||
98 | break; | ||
99 | } | ||
100 | } | ||
101 | }); | ||
102 | } | ||
103 | for(auto future : futures) { | ||
104 | future.waitForFinished(); | ||
105 | } | ||
106 | |||
107 | Database db(testDataPath, dbName); | ||
108 | db.removeFromDisk(); | ||
109 | } | ||
110 | }; | ||
111 | |||
112 | QTEST_MAIN(StorageTest) | ||
113 | #include "storagetest.moc" | ||