summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--common/storage_lmdb.cpp36
-rw-r--r--store/kyotodatabase.cpp164
-rw-r--r--store/kyotodatabase.h28
-rw-r--r--store/test/storagetest.cpp113
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 @@
14class Storage::Private 14class Storage::Private
15{ 15{
16public: 16public:
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
28Storage::Private::Private(const QString &p) 30Storage::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
65Storage::Storage(const QString &storageRoot, const QString &name) 68Storage::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
149bool 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
146bool Storage::write(const std::string &sKey, const std::string &sValue) 154bool 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
256qint64 Storage::diskUsage() const 264qint64 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
277void 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
15class Database::Private
16{
17public:
18 Private(const QString &storageRoot, const QString &name);
19 ~Private();
20
21 kyotocabinet::TreeDB db;
22 bool dbOpen;
23 bool inTransaction;
24};
25
26Database::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
39Database::Private::~Private()
40{
41 if (dbOpen && inTransaction) {
42 db.end_transaction(false);
43 }
44}
45
46Database::Database(const QString &storageRoot, const QString &name)
47 : d(new Private(storageRoot, name))
48{
49}
50
51Database::~Database()
52{
53 delete d;
54}
55
56bool Database::isInTransaction() const
57{
58 return d->inTransaction;
59}
60
61bool 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
76bool 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
91void 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
101bool 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
111bool 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
121void 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
133void 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
145qint64 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
155void 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
6class Database {
7public:
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;
24private:
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
11class StorageTest : public QObject
12{
13 Q_OBJECT
14private:
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
51private 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
112QTEST_MAIN(StorageTest)
113#include "storagetest.moc"