@nxtedition/rocksdb 5.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +294 -0
- package/LICENSE +21 -0
- package/README.md +102 -0
- package/UPGRADING.md +91 -0
- package/binding.cc +2120 -0
- package/binding.gyp +73 -0
- package/binding.js +1 -0
- package/chained-batch.js +30 -0
- package/deps/rocksdb/build_version.cc +4 -0
- package/deps/rocksdb/rocksdb.gyp +475 -0
- package/deps/snappy/freebsd/config.h +135 -0
- package/deps/snappy/freebsd/snappy-stubs-public.h +100 -0
- package/deps/snappy/linux/config.h +135 -0
- package/deps/snappy/linux/snappy-stubs-public.h +100 -0
- package/deps/snappy/mac/config.h +137 -0
- package/deps/snappy/mac/snappy-stubs-public.h +100 -0
- package/deps/snappy/openbsd/config.h +135 -0
- package/deps/snappy/openbsd/snappy-stubs-public.h +100 -0
- package/deps/snappy/snappy-1.1.7/COPYING +54 -0
- package/deps/snappy/snappy-1.1.7/cmake/SnappyConfig.cmake +1 -0
- package/deps/snappy/snappy-1.1.7/cmake/config.h.in +62 -0
- package/deps/snappy/snappy-1.1.7/snappy-c.cc +90 -0
- package/deps/snappy/snappy-1.1.7/snappy-c.h +138 -0
- package/deps/snappy/snappy-1.1.7/snappy-internal.h +224 -0
- package/deps/snappy/snappy-1.1.7/snappy-sinksource.cc +104 -0
- package/deps/snappy/snappy-1.1.7/snappy-sinksource.h +182 -0
- package/deps/snappy/snappy-1.1.7/snappy-stubs-internal.cc +42 -0
- package/deps/snappy/snappy-1.1.7/snappy-stubs-internal.h +561 -0
- package/deps/snappy/snappy-1.1.7/snappy-stubs-public.h.in +94 -0
- package/deps/snappy/snappy-1.1.7/snappy-test.cc +612 -0
- package/deps/snappy/snappy-1.1.7/snappy-test.h +573 -0
- package/deps/snappy/snappy-1.1.7/snappy.cc +1515 -0
- package/deps/snappy/snappy-1.1.7/snappy.h +203 -0
- package/deps/snappy/snappy-1.1.7/snappy_unittest.cc +1410 -0
- package/deps/snappy/snappy.gyp +90 -0
- package/deps/snappy/solaris/config.h +135 -0
- package/deps/snappy/solaris/snappy-stubs-public.h +100 -0
- package/deps/snappy/win32/config.h +29 -0
- package/deps/snappy/win32/snappy-stubs-public.h +100 -0
- package/iterator.js +50 -0
- package/leveldown.js +164 -0
- package/package.json +70 -0
package/binding.cc
ADDED
|
@@ -0,0 +1,2120 @@
|
|
|
1
|
+
#define NAPI_VERSION 3
|
|
2
|
+
|
|
3
|
+
#include <napi-macros.h>
|
|
4
|
+
#include <node_api.h>
|
|
5
|
+
#include <assert.h>
|
|
6
|
+
|
|
7
|
+
#include <rocksdb/db.h>
|
|
8
|
+
#include <rocksdb/write_batch.h>
|
|
9
|
+
#include <rocksdb/cache.h>
|
|
10
|
+
#include <rocksdb/filter_policy.h>
|
|
11
|
+
#include <rocksdb/cache.h>
|
|
12
|
+
#include <rocksdb/comparator.h>
|
|
13
|
+
#include <rocksdb/env.h>
|
|
14
|
+
#include <rocksdb/options.h>
|
|
15
|
+
#include <rocksdb/table.h>
|
|
16
|
+
|
|
17
|
+
namespace leveldb = rocksdb;
|
|
18
|
+
|
|
19
|
+
#include <map>
|
|
20
|
+
#include <vector>
|
|
21
|
+
|
|
22
|
+
class NullLogger : public rocksdb::Logger {
|
|
23
|
+
public:
|
|
24
|
+
using rocksdb::Logger::Logv;
|
|
25
|
+
virtual void Logv(const char* format, va_list ap) override {}
|
|
26
|
+
virtual size_t GetLogFileSize() const override { return 0; }
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Forward declarations.
|
|
31
|
+
*/
|
|
32
|
+
struct Database;
|
|
33
|
+
struct Iterator;
|
|
34
|
+
static void iterator_end_do (napi_env env, Iterator* iterator, napi_value cb);
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Macros.
|
|
38
|
+
*/
|
|
39
|
+
|
|
40
|
+
#define NAPI_DB_CONTEXT() \
|
|
41
|
+
Database* database = NULL; \
|
|
42
|
+
NAPI_STATUS_THROWS(napi_get_value_external(env, argv[0], (void**)&database));
|
|
43
|
+
|
|
44
|
+
#define NAPI_ITERATOR_CONTEXT() \
|
|
45
|
+
Iterator* iterator = NULL; \
|
|
46
|
+
NAPI_STATUS_THROWS(napi_get_value_external(env, argv[0], (void**)&iterator));
|
|
47
|
+
|
|
48
|
+
#define NAPI_BATCH_CONTEXT() \
|
|
49
|
+
Batch* batch = NULL; \
|
|
50
|
+
NAPI_STATUS_THROWS(napi_get_value_external(env, argv[0], (void**)&batch));
|
|
51
|
+
|
|
52
|
+
#define NAPI_RETURN_UNDEFINED() \
|
|
53
|
+
return 0;
|
|
54
|
+
|
|
55
|
+
#define NAPI_UTF8_NEW(name, val) \
|
|
56
|
+
size_t name##_size = 0; \
|
|
57
|
+
NAPI_STATUS_THROWS(napi_get_value_string_utf8(env, val, NULL, 0, &name##_size)) \
|
|
58
|
+
char* name = new char[name##_size + 1]; \
|
|
59
|
+
NAPI_STATUS_THROWS(napi_get_value_string_utf8(env, val, name, name##_size + 1, &name##_size)) \
|
|
60
|
+
name[name##_size] = '\0';
|
|
61
|
+
|
|
62
|
+
#define NAPI_ARGV_UTF8_NEW(name, i) \
|
|
63
|
+
NAPI_UTF8_NEW(name, argv[i])
|
|
64
|
+
|
|
65
|
+
#define LD_STRING_OR_BUFFER_TO_COPY(env, from, to) \
|
|
66
|
+
char* to##Ch_ = 0; \
|
|
67
|
+
size_t to##Sz_ = 0; \
|
|
68
|
+
if (IsString(env, from)) { \
|
|
69
|
+
napi_get_value_string_utf8(env, from, NULL, 0, &to##Sz_); \
|
|
70
|
+
to##Ch_ = new char[to##Sz_ + 1]; \
|
|
71
|
+
napi_get_value_string_utf8(env, from, to##Ch_, to##Sz_ + 1, &to##Sz_); \
|
|
72
|
+
to##Ch_[to##Sz_] = '\0'; \
|
|
73
|
+
} else if (IsBuffer(env, from)) { \
|
|
74
|
+
char* buf = 0; \
|
|
75
|
+
napi_get_buffer_info(env, from, (void **)&buf, &to##Sz_); \
|
|
76
|
+
to##Ch_ = new char[to##Sz_]; \
|
|
77
|
+
memcpy(to##Ch_, buf, to##Sz_); \
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/*********************************************************************
|
|
81
|
+
* Helpers.
|
|
82
|
+
********************************************************************/
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Returns true if 'value' is a string.
|
|
86
|
+
*/
|
|
87
|
+
static bool IsString (napi_env env, napi_value value) {
|
|
88
|
+
napi_valuetype type;
|
|
89
|
+
napi_typeof(env, value, &type);
|
|
90
|
+
return type == napi_string;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Returns true if 'value' is a buffer.
|
|
95
|
+
*/
|
|
96
|
+
static bool IsBuffer (napi_env env, napi_value value) {
|
|
97
|
+
bool isBuffer;
|
|
98
|
+
napi_is_buffer(env, value, &isBuffer);
|
|
99
|
+
return isBuffer;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Returns true if 'value' is an object.
|
|
104
|
+
*/
|
|
105
|
+
static bool IsObject (napi_env env, napi_value value) {
|
|
106
|
+
napi_valuetype type;
|
|
107
|
+
napi_typeof(env, value, &type);
|
|
108
|
+
return type == napi_object;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Create an error object.
|
|
113
|
+
*/
|
|
114
|
+
static napi_value CreateError (napi_env env, const char* str) {
|
|
115
|
+
napi_value msg;
|
|
116
|
+
napi_create_string_utf8(env, str, strlen(str), &msg);
|
|
117
|
+
napi_value error;
|
|
118
|
+
napi_create_error(env, NULL, msg, &error);
|
|
119
|
+
return error;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Returns true if 'obj' has a property 'key'.
|
|
124
|
+
*/
|
|
125
|
+
static bool HasProperty (napi_env env, napi_value obj, const char* key) {
|
|
126
|
+
bool has = false;
|
|
127
|
+
napi_has_named_property(env, obj, key, &has);
|
|
128
|
+
return has;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Returns a property in napi_value form.
|
|
133
|
+
*/
|
|
134
|
+
static napi_value GetProperty (napi_env env, napi_value obj, const char* key) {
|
|
135
|
+
napi_value value;
|
|
136
|
+
napi_get_named_property(env, obj, key, &value);
|
|
137
|
+
return value;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Returns a boolean property 'key' from 'obj'.
|
|
142
|
+
* Returns 'DEFAULT' if the property doesn't exist.
|
|
143
|
+
*/
|
|
144
|
+
static bool BooleanProperty (napi_env env, napi_value obj, const char* key,
|
|
145
|
+
bool DEFAULT) {
|
|
146
|
+
if (HasProperty(env, obj, key)) {
|
|
147
|
+
napi_value value = GetProperty(env, obj, key);
|
|
148
|
+
bool result;
|
|
149
|
+
napi_get_value_bool(env, value, &result);
|
|
150
|
+
return result;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return DEFAULT;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Returns a uint32 property 'key' from 'obj'.
|
|
158
|
+
* Returns 'DEFAULT' if the property doesn't exist.
|
|
159
|
+
*/
|
|
160
|
+
static uint32_t Uint32Property (napi_env env, napi_value obj, const char* key,
|
|
161
|
+
uint32_t DEFAULT) {
|
|
162
|
+
if (HasProperty(env, obj, key)) {
|
|
163
|
+
napi_value value = GetProperty(env, obj, key);
|
|
164
|
+
uint32_t result;
|
|
165
|
+
napi_get_value_uint32(env, value, &result);
|
|
166
|
+
return result;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return DEFAULT;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Returns a int32 property 'key' from 'obj'.
|
|
174
|
+
* Returns 'DEFAULT' if the property doesn't exist.
|
|
175
|
+
*/
|
|
176
|
+
static int Int32Property (napi_env env, napi_value obj, const char* key,
|
|
177
|
+
int DEFAULT) {
|
|
178
|
+
if (HasProperty(env, obj, key)) {
|
|
179
|
+
napi_value value = GetProperty(env, obj, key);
|
|
180
|
+
int result;
|
|
181
|
+
napi_get_value_int32(env, value, &result);
|
|
182
|
+
return result;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return DEFAULT;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Returns a string property 'key' from 'obj'.
|
|
190
|
+
* Returns empty string if the property doesn't exist.
|
|
191
|
+
*/
|
|
192
|
+
static std::string StringProperty (napi_env env, napi_value obj, const char* key) {
|
|
193
|
+
if (HasProperty(env, obj, key)) {
|
|
194
|
+
napi_value value = GetProperty(env, obj, key);
|
|
195
|
+
if (IsString(env, value)) {
|
|
196
|
+
size_t size = 0;
|
|
197
|
+
napi_get_value_string_utf8(env, value, NULL, 0, &size);
|
|
198
|
+
|
|
199
|
+
char* buf = new char[size + 1];
|
|
200
|
+
napi_get_value_string_utf8(env, value, buf, size + 1, &size);
|
|
201
|
+
buf[size] = '\0';
|
|
202
|
+
|
|
203
|
+
std::string result = buf;
|
|
204
|
+
delete [] buf;
|
|
205
|
+
return result;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
return "";
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
static void DisposeSliceBuffer (leveldb::Slice slice) {
|
|
213
|
+
if (!slice.empty()) delete [] slice.data();
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Convert a napi_value to a leveldb::Slice.
|
|
218
|
+
*/
|
|
219
|
+
static leveldb::Slice ToSlice (napi_env env, napi_value from) {
|
|
220
|
+
LD_STRING_OR_BUFFER_TO_COPY(env, from, to);
|
|
221
|
+
return leveldb::Slice(toCh_, toSz_);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Returns length of string or buffer
|
|
226
|
+
*/
|
|
227
|
+
static size_t StringOrBufferLength (napi_env env, napi_value value) {
|
|
228
|
+
size_t size = 0;
|
|
229
|
+
|
|
230
|
+
if (IsString(env, value)) {
|
|
231
|
+
napi_get_value_string_utf8(env, value, NULL, 0, &size);
|
|
232
|
+
} else if (IsBuffer(env, value)) {
|
|
233
|
+
char* buf;
|
|
234
|
+
napi_get_buffer_info(env, value, (void **)&buf, &size);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
return size;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Takes a Buffer or string property 'name' from 'opts'.
|
|
242
|
+
* Returns null if the property does not exist or is zero-length.
|
|
243
|
+
*/
|
|
244
|
+
static std::string* RangeOption (napi_env env, napi_value opts, const char* name) {
|
|
245
|
+
if (HasProperty(env, opts, name)) {
|
|
246
|
+
napi_value value = GetProperty(env, opts, name);
|
|
247
|
+
|
|
248
|
+
if (StringOrBufferLength(env, value) > 0) {
|
|
249
|
+
LD_STRING_OR_BUFFER_TO_COPY(env, value, to);
|
|
250
|
+
std::string* result = new std::string(toCh_, toSz_);
|
|
251
|
+
delete [] toCh_;
|
|
252
|
+
return result;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
return NULL;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Converts an array containing Buffer or string keys to a vector.
|
|
261
|
+
* Empty elements are skipped.
|
|
262
|
+
*/
|
|
263
|
+
static std::vector<std::string>* KeyArray (napi_env env, napi_value arr) {
|
|
264
|
+
uint32_t length;
|
|
265
|
+
std::vector<std::string>* result = new std::vector<std::string>();
|
|
266
|
+
|
|
267
|
+
if (napi_get_array_length(env, arr, &length) == napi_ok) {
|
|
268
|
+
result->reserve(length);
|
|
269
|
+
|
|
270
|
+
for (uint32_t i = 0; i < length; i++) {
|
|
271
|
+
napi_value element;
|
|
272
|
+
|
|
273
|
+
if (napi_get_element(env, arr, i, &element) == napi_ok &&
|
|
274
|
+
StringOrBufferLength(env, element) > 0) {
|
|
275
|
+
LD_STRING_OR_BUFFER_TO_COPY(env, element, to);
|
|
276
|
+
result->emplace_back(toCh_, toSz_);
|
|
277
|
+
delete [] toCh_;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
return result;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Calls a function.
|
|
287
|
+
*/
|
|
288
|
+
static napi_status CallFunction (napi_env env,
|
|
289
|
+
napi_value callback,
|
|
290
|
+
const int argc,
|
|
291
|
+
napi_value* argv) {
|
|
292
|
+
napi_value global;
|
|
293
|
+
napi_get_global(env, &global);
|
|
294
|
+
return napi_call_function(env, global, callback, argc, argv, NULL);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Whether to yield entries, keys or values.
|
|
299
|
+
*/
|
|
300
|
+
enum Mode {
|
|
301
|
+
entries,
|
|
302
|
+
keys,
|
|
303
|
+
values
|
|
304
|
+
};
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Helper struct for caching and converting a key-value pair to napi_values.
|
|
308
|
+
*/
|
|
309
|
+
struct Entry {
|
|
310
|
+
Entry (const leveldb::Slice* key, const leveldb::Slice* value) {
|
|
311
|
+
key_ = key != NULL ? new std::string(key->data(), key->size()) : NULL;
|
|
312
|
+
value_ = value != NULL ? new std::string(value->data(), value->size()) : NULL;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
~Entry () {
|
|
316
|
+
if (key_ != NULL) delete key_;
|
|
317
|
+
if (value_ != NULL) delete value_;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// Not used yet.
|
|
321
|
+
void ConvertXX (napi_env env, Mode mode, bool keyAsBuffer, bool valueAsBuffer, napi_value* result) {
|
|
322
|
+
if (mode == Mode::entries) {
|
|
323
|
+
napi_create_array_with_length(env, 2, result);
|
|
324
|
+
|
|
325
|
+
napi_value valueElement;
|
|
326
|
+
napi_value keyElement;
|
|
327
|
+
|
|
328
|
+
Convert(env, key_, keyAsBuffer, &keyElement);
|
|
329
|
+
Convert(env, value_, valueAsBuffer, &valueElement);
|
|
330
|
+
|
|
331
|
+
napi_set_element(env, *result, 0, keyElement);
|
|
332
|
+
napi_set_element(env, *result, 1, valueElement);
|
|
333
|
+
} else if (mode == Mode::keys) {
|
|
334
|
+
Convert(env, key_, keyAsBuffer, result);
|
|
335
|
+
} else {
|
|
336
|
+
Convert(env, value_, valueAsBuffer, result);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
static void Convert (napi_env env, const std::string* s, bool asBuffer, napi_value* result) {
|
|
341
|
+
if (s == NULL) {
|
|
342
|
+
napi_get_undefined(env, result);
|
|
343
|
+
} else if (asBuffer) {
|
|
344
|
+
napi_create_buffer_copy(env, s->size(), s->data(), NULL, result);
|
|
345
|
+
} else {
|
|
346
|
+
napi_create_string_utf8(env, s->data(), s->size(), result);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
private:
|
|
351
|
+
std::string* key_;
|
|
352
|
+
std::string* value_;
|
|
353
|
+
};
|
|
354
|
+
|
|
355
|
+
/**
|
|
356
|
+
* Base worker class. Handles the async work. Derived classes can override the
|
|
357
|
+
* following virtual methods (listed in the order in which they're called):
|
|
358
|
+
*
|
|
359
|
+
* - DoExecute (abstract, worker pool thread): main work
|
|
360
|
+
* - HandleOKCallback (main thread): call JS callback on success
|
|
361
|
+
* - HandleErrorCallback (main thread): call JS callback on error
|
|
362
|
+
* - DoFinally (main thread): do cleanup regardless of success
|
|
363
|
+
*/
|
|
364
|
+
struct BaseWorker {
|
|
365
|
+
// Note: storing env is discouraged as we'd end up using it in unsafe places.
|
|
366
|
+
BaseWorker (napi_env env,
|
|
367
|
+
Database* database,
|
|
368
|
+
napi_value callback,
|
|
369
|
+
const char* resourceName)
|
|
370
|
+
: database_(database), errMsg_(NULL) {
|
|
371
|
+
NAPI_STATUS_THROWS_VOID(napi_create_reference(env, callback, 1, &callbackRef_));
|
|
372
|
+
napi_value asyncResourceName;
|
|
373
|
+
NAPI_STATUS_THROWS_VOID(napi_create_string_utf8(env, resourceName,
|
|
374
|
+
NAPI_AUTO_LENGTH,
|
|
375
|
+
&asyncResourceName));
|
|
376
|
+
NAPI_STATUS_THROWS_VOID(napi_create_async_work(env, callback,
|
|
377
|
+
asyncResourceName,
|
|
378
|
+
BaseWorker::Execute,
|
|
379
|
+
BaseWorker::Complete,
|
|
380
|
+
this, &asyncWork_));
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
virtual ~BaseWorker () {
|
|
384
|
+
delete [] errMsg_;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
static void Execute (napi_env env, void* data) {
|
|
388
|
+
BaseWorker* self = (BaseWorker*)data;
|
|
389
|
+
|
|
390
|
+
// Don't pass env to DoExecute() because use of Node-API
|
|
391
|
+
// methods should generally be avoided in async work.
|
|
392
|
+
self->DoExecute();
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
bool SetStatus (leveldb::Status status) {
|
|
396
|
+
status_ = status;
|
|
397
|
+
if (!status.ok()) {
|
|
398
|
+
SetErrorMessage(status.ToString().c_str());
|
|
399
|
+
return false;
|
|
400
|
+
}
|
|
401
|
+
return true;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
void SetErrorMessage(const char *msg) {
|
|
405
|
+
delete [] errMsg_;
|
|
406
|
+
size_t size = strlen(msg) + 1;
|
|
407
|
+
errMsg_ = new char[size];
|
|
408
|
+
memcpy(errMsg_, msg, size);
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
virtual void DoExecute () = 0;
|
|
412
|
+
|
|
413
|
+
static void Complete (napi_env env, napi_status status, void* data) {
|
|
414
|
+
BaseWorker* self = (BaseWorker*)data;
|
|
415
|
+
|
|
416
|
+
self->DoComplete(env);
|
|
417
|
+
self->DoFinally(env);
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
void DoComplete (napi_env env) {
|
|
421
|
+
napi_value callback;
|
|
422
|
+
napi_get_reference_value(env, callbackRef_, &callback);
|
|
423
|
+
|
|
424
|
+
if (status_.ok()) {
|
|
425
|
+
HandleOKCallback(env, callback);
|
|
426
|
+
} else {
|
|
427
|
+
HandleErrorCallback(env, callback);
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
virtual void HandleOKCallback (napi_env env, napi_value callback) {
|
|
432
|
+
napi_value argv;
|
|
433
|
+
napi_get_null(env, &argv);
|
|
434
|
+
CallFunction(env, callback, 1, &argv);
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
virtual void HandleErrorCallback (napi_env env, napi_value callback) {
|
|
438
|
+
napi_value argv = CreateError(env, errMsg_);
|
|
439
|
+
CallFunction(env, callback, 1, &argv);
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
virtual void DoFinally (napi_env env) {
|
|
443
|
+
napi_delete_reference(env, callbackRef_);
|
|
444
|
+
napi_delete_async_work(env, asyncWork_);
|
|
445
|
+
|
|
446
|
+
delete this;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
void Queue (napi_env env) {
|
|
450
|
+
napi_queue_async_work(env, asyncWork_);
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
Database* database_;
|
|
454
|
+
|
|
455
|
+
private:
|
|
456
|
+
napi_ref callbackRef_;
|
|
457
|
+
napi_async_work asyncWork_;
|
|
458
|
+
leveldb::Status status_;
|
|
459
|
+
char *errMsg_;
|
|
460
|
+
};
|
|
461
|
+
|
|
462
|
+
/**
|
|
463
|
+
* Owns the LevelDB storage, cache, filter policy and iterators.
|
|
464
|
+
*/
|
|
465
|
+
struct Database {
|
|
466
|
+
Database ()
|
|
467
|
+
: db_(NULL),
|
|
468
|
+
currentIteratorId_(0),
|
|
469
|
+
pendingCloseWorker_(NULL),
|
|
470
|
+
ref_(NULL),
|
|
471
|
+
priorityWork_(0) {}
|
|
472
|
+
|
|
473
|
+
~Database () {
|
|
474
|
+
if (db_ != NULL) {
|
|
475
|
+
delete db_;
|
|
476
|
+
db_ = NULL;
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
leveldb::Status Open (const leveldb::Options& options,
|
|
481
|
+
bool readOnly,
|
|
482
|
+
const char* location) {
|
|
483
|
+
if (readOnly) {
|
|
484
|
+
return rocksdb::DB::OpenForReadOnly(options, location, &db_);
|
|
485
|
+
} else {
|
|
486
|
+
return leveldb::DB::Open(options, location, &db_);
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
void CloseDatabase () {
|
|
491
|
+
delete db_;
|
|
492
|
+
db_ = NULL;
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
leveldb::Status Put (const leveldb::WriteOptions& options,
|
|
496
|
+
leveldb::Slice key,
|
|
497
|
+
leveldb::Slice value) {
|
|
498
|
+
return db_->Put(options, key, value);
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
leveldb::Status Get (const leveldb::ReadOptions& options,
|
|
502
|
+
leveldb::Slice key,
|
|
503
|
+
std::string& value) {
|
|
504
|
+
return db_->Get(options, key, &value);
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
leveldb::Status Del (const leveldb::WriteOptions& options,
|
|
508
|
+
leveldb::Slice key) {
|
|
509
|
+
return db_->Delete(options, key);
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
leveldb::Status WriteBatch (const leveldb::WriteOptions& options,
|
|
513
|
+
leveldb::WriteBatch* batch) {
|
|
514
|
+
return db_->Write(options, batch);
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
uint64_t ApproximateSize (const leveldb::Range* range) {
|
|
518
|
+
uint64_t size = 0;
|
|
519
|
+
db_->GetApproximateSizes(range, 1, &size);
|
|
520
|
+
return size;
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
void CompactRange (const leveldb::Slice* start,
|
|
524
|
+
const leveldb::Slice* end) {
|
|
525
|
+
rocksdb::CompactRangeOptions options;
|
|
526
|
+
db_->CompactRange(options, start, end);
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
void GetProperty (const leveldb::Slice& property, std::string* value) {
|
|
530
|
+
db_->GetProperty(property, value);
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
const leveldb::Snapshot* NewSnapshot () {
|
|
534
|
+
return db_->GetSnapshot();
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
leveldb::Iterator* NewIterator (leveldb::ReadOptions* options) {
|
|
538
|
+
return db_->NewIterator(*options);
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
void ReleaseSnapshot (const leveldb::Snapshot* snapshot) {
|
|
542
|
+
return db_->ReleaseSnapshot(snapshot);
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
void AttachIterator (napi_env env, uint32_t id, Iterator* iterator) {
|
|
546
|
+
iterators_[id] = iterator;
|
|
547
|
+
IncrementPriorityWork(env);
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
void DetachIterator (napi_env env, uint32_t id) {
|
|
551
|
+
iterators_.erase(id);
|
|
552
|
+
DecrementPriorityWork(env);
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
void IncrementPriorityWork (napi_env env) {
|
|
556
|
+
napi_reference_ref(env, ref_, &priorityWork_);
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
void DecrementPriorityWork (napi_env env) {
|
|
560
|
+
napi_reference_unref(env, ref_, &priorityWork_);
|
|
561
|
+
|
|
562
|
+
if (priorityWork_ == 0 && pendingCloseWorker_ != NULL) {
|
|
563
|
+
pendingCloseWorker_->Queue(env);
|
|
564
|
+
pendingCloseWorker_ = NULL;
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
bool HasPriorityWork () const {
|
|
569
|
+
return priorityWork_ > 0;
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
leveldb::DB* db_;
|
|
573
|
+
uint32_t currentIteratorId_;
|
|
574
|
+
BaseWorker *pendingCloseWorker_;
|
|
575
|
+
std::map< uint32_t, Iterator * > iterators_;
|
|
576
|
+
napi_ref ref_;
|
|
577
|
+
|
|
578
|
+
private:
|
|
579
|
+
uint32_t priorityWork_;
|
|
580
|
+
};
|
|
581
|
+
|
|
582
|
+
/**
|
|
583
|
+
* Base worker class for doing async work that defers closing the database.
|
|
584
|
+
*/
|
|
585
|
+
struct PriorityWorker : public BaseWorker {
|
|
586
|
+
PriorityWorker (napi_env env, Database* database, napi_value callback, const char* resourceName)
|
|
587
|
+
: BaseWorker(env, database, callback, resourceName) {
|
|
588
|
+
database_->IncrementPriorityWork(env);
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
virtual ~PriorityWorker () {}
|
|
592
|
+
|
|
593
|
+
void DoFinally (napi_env env) override {
|
|
594
|
+
database_->DecrementPriorityWork(env);
|
|
595
|
+
BaseWorker::DoFinally(env);
|
|
596
|
+
}
|
|
597
|
+
};
|
|
598
|
+
|
|
599
|
+
/**
|
|
600
|
+
* Owns a leveldb iterator.
|
|
601
|
+
*/
|
|
602
|
+
struct BaseIterator {
|
|
603
|
+
BaseIterator(Database* database,
|
|
604
|
+
const bool reverse,
|
|
605
|
+
std::string* lt,
|
|
606
|
+
std::string* lte,
|
|
607
|
+
std::string* gt,
|
|
608
|
+
std::string* gte,
|
|
609
|
+
const int limit,
|
|
610
|
+
const bool fillCache)
|
|
611
|
+
: database_(database),
|
|
612
|
+
hasEnded_(false),
|
|
613
|
+
didSeek_(false),
|
|
614
|
+
reverse_(reverse),
|
|
615
|
+
lt_(lt),
|
|
616
|
+
lte_(lte),
|
|
617
|
+
gt_(gt),
|
|
618
|
+
gte_(gte),
|
|
619
|
+
limit_(limit),
|
|
620
|
+
count_(0) {
|
|
621
|
+
options_ = new leveldb::ReadOptions();
|
|
622
|
+
options_->fill_cache = fillCache;
|
|
623
|
+
options_->verify_checksums = false;
|
|
624
|
+
options_->snapshot = database->NewSnapshot();
|
|
625
|
+
dbIterator_ = database_->NewIterator(options_);
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
virtual ~BaseIterator () {
|
|
629
|
+
assert(hasEnded_);
|
|
630
|
+
|
|
631
|
+
if (lt_ != NULL) delete lt_;
|
|
632
|
+
if (gt_ != NULL) delete gt_;
|
|
633
|
+
if (lte_ != NULL) delete lte_;
|
|
634
|
+
if (gte_ != NULL) delete gte_;
|
|
635
|
+
|
|
636
|
+
delete options_;
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
bool DidSeek () const {
|
|
640
|
+
return didSeek_;
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
/**
|
|
644
|
+
* Seek to the first relevant key based on range options.
|
|
645
|
+
*/
|
|
646
|
+
void SeekToRange () {
|
|
647
|
+
didSeek_ = true;
|
|
648
|
+
|
|
649
|
+
if (!reverse_ && gte_ != NULL) {
|
|
650
|
+
dbIterator_->Seek(*gte_);
|
|
651
|
+
} else if (!reverse_ && gt_ != NULL) {
|
|
652
|
+
dbIterator_->Seek(*gt_);
|
|
653
|
+
|
|
654
|
+
if (dbIterator_->Valid() && dbIterator_->key().compare(*gt_) == 0) {
|
|
655
|
+
dbIterator_->Next();
|
|
656
|
+
}
|
|
657
|
+
} else if (reverse_ && lte_ != NULL) {
|
|
658
|
+
dbIterator_->Seek(*lte_);
|
|
659
|
+
|
|
660
|
+
if (!dbIterator_->Valid()) {
|
|
661
|
+
dbIterator_->SeekToLast();
|
|
662
|
+
} else if (dbIterator_->key().compare(*lte_) > 0) {
|
|
663
|
+
dbIterator_->Prev();
|
|
664
|
+
}
|
|
665
|
+
} else if (reverse_ && lt_ != NULL) {
|
|
666
|
+
dbIterator_->Seek(*lt_);
|
|
667
|
+
|
|
668
|
+
if (!dbIterator_->Valid()) {
|
|
669
|
+
dbIterator_->SeekToLast();
|
|
670
|
+
} else if (dbIterator_->key().compare(*lt_) >= 0) {
|
|
671
|
+
dbIterator_->Prev();
|
|
672
|
+
}
|
|
673
|
+
} else if (reverse_) {
|
|
674
|
+
dbIterator_->SeekToLast();
|
|
675
|
+
} else {
|
|
676
|
+
dbIterator_->SeekToFirst();
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
/**
|
|
681
|
+
* Seek manually (during iteration).
|
|
682
|
+
*/
|
|
683
|
+
void Seek (leveldb::Slice& target) {
|
|
684
|
+
didSeek_ = true;
|
|
685
|
+
|
|
686
|
+
if (OutOfRange(target)) {
|
|
687
|
+
return SeekToEnd();
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
dbIterator_->Seek(target);
|
|
691
|
+
|
|
692
|
+
if (dbIterator_->Valid()) {
|
|
693
|
+
int cmp = dbIterator_->key().compare(target);
|
|
694
|
+
if (reverse_ ? cmp > 0 : cmp < 0) {
|
|
695
|
+
Next();
|
|
696
|
+
}
|
|
697
|
+
} else {
|
|
698
|
+
SeekToFirst();
|
|
699
|
+
if (dbIterator_->Valid()) {
|
|
700
|
+
int cmp = dbIterator_->key().compare(target);
|
|
701
|
+
if (reverse_ ? cmp > 0 : cmp < 0) {
|
|
702
|
+
SeekToEnd();
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
void End () {
|
|
709
|
+
if (!hasEnded_) {
|
|
710
|
+
hasEnded_ = true;
|
|
711
|
+
delete dbIterator_;
|
|
712
|
+
dbIterator_ = NULL;
|
|
713
|
+
database_->ReleaseSnapshot(options_->snapshot);
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
bool Valid () const {
|
|
718
|
+
return dbIterator_->Valid() && !OutOfRange(dbIterator_->key());
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
bool Increment () {
|
|
722
|
+
return limit_ < 0 || ++count_ <= limit_;
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
void Next () {
|
|
726
|
+
if (reverse_) dbIterator_->Prev();
|
|
727
|
+
else dbIterator_->Next();
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
void SeekToFirst () {
|
|
731
|
+
if (reverse_) dbIterator_->SeekToLast();
|
|
732
|
+
else dbIterator_->SeekToFirst();
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
void SeekToLast () {
|
|
736
|
+
if (reverse_) dbIterator_->SeekToFirst();
|
|
737
|
+
else dbIterator_->SeekToLast();
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
void SeekToEnd () {
|
|
741
|
+
SeekToLast();
|
|
742
|
+
Next();
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
leveldb::Slice CurrentKey () const {
|
|
746
|
+
return dbIterator_->key();
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
leveldb::Slice CurrentValue () const {
|
|
750
|
+
return dbIterator_->value();
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
leveldb::Status Status () const {
|
|
754
|
+
return dbIterator_->status();
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
bool OutOfRange (const leveldb::Slice& target) const {
|
|
758
|
+
// TODO: benchmark to see if this is worth it
|
|
759
|
+
// if (upperBoundOnly && !reverse_) {
|
|
760
|
+
// return ((lt_ != NULL && target.compare(*lt_) >= 0) ||
|
|
761
|
+
// (lte_ != NULL && target.compare(*lte_) > 0));
|
|
762
|
+
// }
|
|
763
|
+
|
|
764
|
+
return ((lt_ != NULL && target.compare(*lt_) >= 0) ||
|
|
765
|
+
(lte_ != NULL && target.compare(*lte_) > 0) ||
|
|
766
|
+
(gt_ != NULL && target.compare(*gt_) <= 0) ||
|
|
767
|
+
(gte_ != NULL && target.compare(*gte_) < 0));
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
Database* database_;
|
|
771
|
+
bool hasEnded_;
|
|
772
|
+
|
|
773
|
+
private:
|
|
774
|
+
leveldb::Iterator* dbIterator_;
|
|
775
|
+
bool didSeek_;
|
|
776
|
+
const bool reverse_;
|
|
777
|
+
std::string* lt_;
|
|
778
|
+
std::string* lte_;
|
|
779
|
+
std::string* gt_;
|
|
780
|
+
std::string* gte_;
|
|
781
|
+
const int limit_;
|
|
782
|
+
int count_;
|
|
783
|
+
leveldb::ReadOptions* options_;
|
|
784
|
+
};
|
|
785
|
+
|
|
786
|
+
/**
|
|
787
|
+
* Extends BaseIterator for reading it from JS land.
|
|
788
|
+
*/
|
|
789
|
+
struct Iterator final : public BaseIterator {
|
|
790
|
+
Iterator (Database* database,
|
|
791
|
+
const uint32_t id,
|
|
792
|
+
const bool reverse,
|
|
793
|
+
const bool keys,
|
|
794
|
+
const bool values,
|
|
795
|
+
const int limit,
|
|
796
|
+
std::string* lt,
|
|
797
|
+
std::string* lte,
|
|
798
|
+
std::string* gt,
|
|
799
|
+
std::string* gte,
|
|
800
|
+
const bool fillCache,
|
|
801
|
+
const bool keyAsBuffer,
|
|
802
|
+
const bool valueAsBuffer,
|
|
803
|
+
const uint32_t highWaterMark)
|
|
804
|
+
: BaseIterator(database, reverse, lt, lte, gt, gte, limit, fillCache),
|
|
805
|
+
id_(id),
|
|
806
|
+
keys_(keys),
|
|
807
|
+
values_(values),
|
|
808
|
+
keyAsBuffer_(keyAsBuffer),
|
|
809
|
+
valueAsBuffer_(valueAsBuffer),
|
|
810
|
+
highWaterMark_(highWaterMark),
|
|
811
|
+
landed_(false),
|
|
812
|
+
nexting_(false),
|
|
813
|
+
isEnding_(false),
|
|
814
|
+
endWorker_(NULL),
|
|
815
|
+
ref_(NULL) {
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
~Iterator () {}
|
|
819
|
+
|
|
820
|
+
void Attach (napi_env env, napi_value context) {
|
|
821
|
+
napi_create_reference(env, context, 1, &ref_);
|
|
822
|
+
database_->AttachIterator(env, id_, this);
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
void Detach (napi_env env) {
|
|
826
|
+
database_->DetachIterator(env, id_);
|
|
827
|
+
if (ref_ != NULL) napi_delete_reference(env, ref_);
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
bool ReadMany (uint32_t size) {
|
|
831
|
+
cache_.clear();
|
|
832
|
+
size_t bytesRead = 0;
|
|
833
|
+
|
|
834
|
+
while (true) {
|
|
835
|
+
if (landed_) Next();
|
|
836
|
+
if (!Valid() || !Increment()) break;
|
|
837
|
+
|
|
838
|
+
if (keys_) {
|
|
839
|
+
leveldb::Slice slice = CurrentKey();
|
|
840
|
+
cache_.emplace_back(slice.data(), slice.size());
|
|
841
|
+
bytesRead += slice.size();
|
|
842
|
+
} else {
|
|
843
|
+
cache_.emplace_back("");
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
if (values_) {
|
|
847
|
+
leveldb::Slice slice = CurrentValue();
|
|
848
|
+
cache_.emplace_back(slice.data(), slice.size());
|
|
849
|
+
bytesRead += slice.size();
|
|
850
|
+
} else {
|
|
851
|
+
cache_.emplace_back("");
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
if (!landed_) {
|
|
855
|
+
landed_ = true;
|
|
856
|
+
return true;
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
if (bytesRead > highWaterMark_ || cache_.size() >= size * 2) {
|
|
860
|
+
return true;
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
return false;
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
const uint32_t id_;
|
|
868
|
+
const bool keys_;
|
|
869
|
+
const bool values_;
|
|
870
|
+
const bool keyAsBuffer_;
|
|
871
|
+
const bool valueAsBuffer_;
|
|
872
|
+
const uint32_t highWaterMark_;
|
|
873
|
+
bool landed_;
|
|
874
|
+
bool nexting_;
|
|
875
|
+
bool isEnding_;
|
|
876
|
+
BaseWorker* endWorker_;
|
|
877
|
+
std::vector<std::string> cache_;
|
|
878
|
+
|
|
879
|
+
private:
|
|
880
|
+
napi_ref ref_;
|
|
881
|
+
};
|
|
882
|
+
|
|
883
|
+
/**
|
|
884
|
+
* Hook for when the environment exits. This hook will be called after
|
|
885
|
+
* already-scheduled napi_async_work items have finished, which gives us
|
|
886
|
+
* the guarantee that no db operations will be in-flight at this time.
|
|
887
|
+
*/
|
|
888
|
+
static void env_cleanup_hook (void* arg) {
|
|
889
|
+
Database* database = (Database*)arg;
|
|
890
|
+
|
|
891
|
+
// Do everything that db_close() does but synchronously. We're expecting that GC
|
|
892
|
+
// did not (yet) collect the database because that would be a user mistake (not
|
|
893
|
+
// closing their db) made during the lifetime of the environment. That's different
|
|
894
|
+
// from an environment being torn down (like the main process or a worker thread)
|
|
895
|
+
// where it's our responsibility to clean up. Note also, the following code must
|
|
896
|
+
// be a safe noop if called before db_open() or after db_close().
|
|
897
|
+
if (database && database->db_ != NULL) {
|
|
898
|
+
std::map<uint32_t, Iterator*> iterators = database->iterators_;
|
|
899
|
+
std::map<uint32_t, Iterator*>::iterator it;
|
|
900
|
+
|
|
901
|
+
// TODO: does not do `napi_delete_reference(env, iterator->ref_)`. Problem?
|
|
902
|
+
for (it = iterators.begin(); it != iterators.end(); ++it) {
|
|
903
|
+
it->second->End();
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
// Having ended the iterators (and released snapshots) we can safely close.
|
|
907
|
+
database->CloseDatabase();
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
/**
|
|
912
|
+
* Runs when a Database is garbage collected.
|
|
913
|
+
*/
|
|
914
|
+
static void FinalizeDatabase (napi_env env, void* data, void* hint) {
|
|
915
|
+
if (data) {
|
|
916
|
+
Database* database = (Database*)data;
|
|
917
|
+
napi_remove_env_cleanup_hook(env, env_cleanup_hook, database);
|
|
918
|
+
if (database->ref_ != NULL) napi_delete_reference(env, database->ref_);
|
|
919
|
+
delete database;
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
/**
|
|
924
|
+
* Returns a context object for a database.
|
|
925
|
+
*/
|
|
926
|
+
NAPI_METHOD(db_init) {
|
|
927
|
+
Database* database = new Database();
|
|
928
|
+
napi_add_env_cleanup_hook(env, env_cleanup_hook, database);
|
|
929
|
+
|
|
930
|
+
napi_value result;
|
|
931
|
+
NAPI_STATUS_THROWS(napi_create_external(env, database,
|
|
932
|
+
FinalizeDatabase,
|
|
933
|
+
NULL, &result));
|
|
934
|
+
|
|
935
|
+
// Reference counter to prevent GC of database while priority workers are active
|
|
936
|
+
NAPI_STATUS_THROWS(napi_create_reference(env, result, 0, &database->ref_));
|
|
937
|
+
|
|
938
|
+
return result;
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
/**
|
|
942
|
+
* Worker class for opening a database.
|
|
943
|
+
* TODO: shouldn't this be a PriorityWorker?
|
|
944
|
+
*/
|
|
945
|
+
struct OpenWorker final : public BaseWorker {
|
|
946
|
+
OpenWorker (napi_env env,
|
|
947
|
+
Database* database,
|
|
948
|
+
napi_value callback,
|
|
949
|
+
const std::string& location,
|
|
950
|
+
const bool createIfMissing,
|
|
951
|
+
const bool errorIfExists,
|
|
952
|
+
const bool compression,
|
|
953
|
+
const uint32_t writeBufferSize,
|
|
954
|
+
const uint32_t blockSize,
|
|
955
|
+
const uint32_t maxOpenFiles,
|
|
956
|
+
const uint32_t blockRestartInterval,
|
|
957
|
+
const uint32_t maxFileSize,
|
|
958
|
+
const uint32_t cacheSize,
|
|
959
|
+
const std::string& infoLogLevel,
|
|
960
|
+
const bool readOnly)
|
|
961
|
+
: BaseWorker(env, database, callback, "leveldown.db.open"),
|
|
962
|
+
readOnly_(readOnly),
|
|
963
|
+
location_(location) {
|
|
964
|
+
options_.create_if_missing = createIfMissing;
|
|
965
|
+
options_.error_if_exists = errorIfExists;
|
|
966
|
+
options_.compression = compression
|
|
967
|
+
? leveldb::kSnappyCompression
|
|
968
|
+
: leveldb::kNoCompression;
|
|
969
|
+
options_.write_buffer_size = writeBufferSize;
|
|
970
|
+
options_.max_open_files = maxOpenFiles;
|
|
971
|
+
options_.max_log_file_size = maxFileSize;
|
|
972
|
+
options_.paranoid_checks = false;
|
|
973
|
+
|
|
974
|
+
if (infoLogLevel.size() > 0) {
|
|
975
|
+
rocksdb::InfoLogLevel lvl;
|
|
976
|
+
|
|
977
|
+
if (infoLogLevel == "debug") lvl = rocksdb::InfoLogLevel::DEBUG_LEVEL;
|
|
978
|
+
else if (infoLogLevel == "info") lvl = rocksdb::InfoLogLevel::INFO_LEVEL;
|
|
979
|
+
else if (infoLogLevel == "warn") lvl = rocksdb::InfoLogLevel::WARN_LEVEL;
|
|
980
|
+
else if (infoLogLevel == "error") lvl = rocksdb::InfoLogLevel::ERROR_LEVEL;
|
|
981
|
+
else if (infoLogLevel == "fatal") lvl = rocksdb::InfoLogLevel::FATAL_LEVEL;
|
|
982
|
+
else if (infoLogLevel == "header") lvl = rocksdb::InfoLogLevel::HEADER_LEVEL;
|
|
983
|
+
else napi_throw_error(env, NULL, "invalid log level");
|
|
984
|
+
|
|
985
|
+
options_.info_log_level = lvl;
|
|
986
|
+
} else {
|
|
987
|
+
// In some places RocksDB checks this option to see if it should prepare
|
|
988
|
+
// debug information (ahead of logging), so set it to the highest level.
|
|
989
|
+
options_.info_log_level = rocksdb::InfoLogLevel::HEADER_LEVEL;
|
|
990
|
+
options_.info_log.reset(new NullLogger());
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
rocksdb::BlockBasedTableOptions tableOptions;
|
|
994
|
+
|
|
995
|
+
if (cacheSize) {
|
|
996
|
+
tableOptions.block_cache = rocksdb::NewLRUCache(cacheSize);
|
|
997
|
+
} else {
|
|
998
|
+
tableOptions.no_block_cache = true;
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
tableOptions.block_size = blockSize;
|
|
1002
|
+
tableOptions.block_restart_interval = blockRestartInterval;
|
|
1003
|
+
tableOptions.filter_policy.reset(rocksdb::NewBloomFilterPolicy(10));
|
|
1004
|
+
|
|
1005
|
+
options_.table_factory.reset(
|
|
1006
|
+
rocksdb::NewBlockBasedTableFactory(tableOptions)
|
|
1007
|
+
);
|
|
1008
|
+
}
|
|
1009
|
+
|
|
1010
|
+
~OpenWorker () {}
|
|
1011
|
+
|
|
1012
|
+
void DoExecute () override {
|
|
1013
|
+
SetStatus(database_->Open(options_, readOnly_, location_.c_str()));
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
leveldb::Options options_;
|
|
1017
|
+
bool readOnly_;
|
|
1018
|
+
std::string location_;
|
|
1019
|
+
};
|
|
1020
|
+
|
|
1021
|
+
/**
|
|
1022
|
+
* Open a database.
|
|
1023
|
+
*/
|
|
1024
|
+
NAPI_METHOD(db_open) {
|
|
1025
|
+
NAPI_ARGV(4);
|
|
1026
|
+
NAPI_DB_CONTEXT();
|
|
1027
|
+
NAPI_ARGV_UTF8_NEW(location, 1);
|
|
1028
|
+
|
|
1029
|
+
napi_value options = argv[2];
|
|
1030
|
+
const bool createIfMissing = BooleanProperty(env, options, "createIfMissing", true);
|
|
1031
|
+
const bool errorIfExists = BooleanProperty(env, options, "errorIfExists", false);
|
|
1032
|
+
const bool compression = BooleanProperty(env, options, "compression", true);
|
|
1033
|
+
bool readOnly = BooleanProperty(env, options, "readOnly", false);
|
|
1034
|
+
|
|
1035
|
+
const std::string infoLogLevel = StringProperty(env, options, "infoLogLevel");
|
|
1036
|
+
|
|
1037
|
+
const uint32_t cacheSize = Uint32Property(env, options, "cacheSize", 8 << 20);
|
|
1038
|
+
const uint32_t writeBufferSize = Uint32Property(env, options , "writeBufferSize" , 4 << 20);
|
|
1039
|
+
const uint32_t blockSize = Uint32Property(env, options, "blockSize", 4096);
|
|
1040
|
+
const uint32_t maxOpenFiles = Uint32Property(env, options, "maxOpenFiles", 1000);
|
|
1041
|
+
const uint32_t blockRestartInterval = Uint32Property(env, options,
|
|
1042
|
+
"blockRestartInterval", 16);
|
|
1043
|
+
const uint32_t maxFileSize = Uint32Property(env, options, "maxFileSize", 2 << 20);
|
|
1044
|
+
|
|
1045
|
+
napi_value callback = argv[3];
|
|
1046
|
+
OpenWorker* worker = new OpenWorker(env, database, callback, location,
|
|
1047
|
+
createIfMissing, errorIfExists,
|
|
1048
|
+
compression, writeBufferSize, blockSize,
|
|
1049
|
+
maxOpenFiles, blockRestartInterval,
|
|
1050
|
+
maxFileSize, cacheSize,
|
|
1051
|
+
infoLogLevel, readOnly);
|
|
1052
|
+
worker->Queue(env);
|
|
1053
|
+
delete [] location;
|
|
1054
|
+
|
|
1055
|
+
NAPI_RETURN_UNDEFINED();
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1058
|
+
/**
|
|
1059
|
+
* Worker class for closing a database
|
|
1060
|
+
*/
|
|
1061
|
+
struct CloseWorker final : public BaseWorker {
|
|
1062
|
+
CloseWorker (napi_env env,
|
|
1063
|
+
Database* database,
|
|
1064
|
+
napi_value callback)
|
|
1065
|
+
: BaseWorker(env, database, callback, "leveldown.db.close") {}
|
|
1066
|
+
|
|
1067
|
+
~CloseWorker () {}
|
|
1068
|
+
|
|
1069
|
+
void DoExecute () override {
|
|
1070
|
+
database_->CloseDatabase();
|
|
1071
|
+
}
|
|
1072
|
+
};
|
|
1073
|
+
|
|
1074
|
+
napi_value noop_callback (napi_env env, napi_callback_info info) {
|
|
1075
|
+
return 0;
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
/**
|
|
1079
|
+
* Close a database.
|
|
1080
|
+
*/
|
|
1081
|
+
NAPI_METHOD(db_close) {
|
|
1082
|
+
NAPI_ARGV(2);
|
|
1083
|
+
NAPI_DB_CONTEXT();
|
|
1084
|
+
|
|
1085
|
+
napi_value callback = argv[1];
|
|
1086
|
+
CloseWorker* worker = new CloseWorker(env, database, callback);
|
|
1087
|
+
|
|
1088
|
+
if (!database->HasPriorityWork()) {
|
|
1089
|
+
worker->Queue(env);
|
|
1090
|
+
NAPI_RETURN_UNDEFINED();
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1093
|
+
database->pendingCloseWorker_ = worker;
|
|
1094
|
+
|
|
1095
|
+
napi_value noop;
|
|
1096
|
+
napi_create_function(env, NULL, 0, noop_callback, NULL, &noop);
|
|
1097
|
+
|
|
1098
|
+
std::map<uint32_t, Iterator*> iterators = database->iterators_;
|
|
1099
|
+
std::map<uint32_t, Iterator*>::iterator it;
|
|
1100
|
+
|
|
1101
|
+
for (it = iterators.begin(); it != iterators.end(); ++it) {
|
|
1102
|
+
iterator_end_do(env, it->second, noop);
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1105
|
+
NAPI_RETURN_UNDEFINED();
|
|
1106
|
+
}
|
|
1107
|
+
|
|
1108
|
+
/**
|
|
1109
|
+
* Worker class for putting key/value to the database
|
|
1110
|
+
*/
|
|
1111
|
+
struct PutWorker final : public PriorityWorker {
|
|
1112
|
+
PutWorker (napi_env env,
|
|
1113
|
+
Database* database,
|
|
1114
|
+
napi_value callback,
|
|
1115
|
+
leveldb::Slice key,
|
|
1116
|
+
leveldb::Slice value,
|
|
1117
|
+
bool sync)
|
|
1118
|
+
: PriorityWorker(env, database, callback, "leveldown.db.put"),
|
|
1119
|
+
key_(key), value_(value) {
|
|
1120
|
+
options_.sync = sync;
|
|
1121
|
+
}
|
|
1122
|
+
|
|
1123
|
+
~PutWorker () {
|
|
1124
|
+
DisposeSliceBuffer(key_);
|
|
1125
|
+
DisposeSliceBuffer(value_);
|
|
1126
|
+
}
|
|
1127
|
+
|
|
1128
|
+
void DoExecute () override {
|
|
1129
|
+
SetStatus(database_->Put(options_, key_, value_));
|
|
1130
|
+
}
|
|
1131
|
+
|
|
1132
|
+
leveldb::WriteOptions options_;
|
|
1133
|
+
leveldb::Slice key_;
|
|
1134
|
+
leveldb::Slice value_;
|
|
1135
|
+
};
|
|
1136
|
+
|
|
1137
|
+
/**
|
|
1138
|
+
* Puts a key and a value to a database.
|
|
1139
|
+
*/
|
|
1140
|
+
NAPI_METHOD(db_put) {
|
|
1141
|
+
NAPI_ARGV(5);
|
|
1142
|
+
NAPI_DB_CONTEXT();
|
|
1143
|
+
|
|
1144
|
+
leveldb::Slice key = ToSlice(env, argv[1]);
|
|
1145
|
+
leveldb::Slice value = ToSlice(env, argv[2]);
|
|
1146
|
+
bool sync = BooleanProperty(env, argv[3], "sync", false);
|
|
1147
|
+
napi_value callback = argv[4];
|
|
1148
|
+
|
|
1149
|
+
PutWorker* worker = new PutWorker(env, database, callback, key, value, sync);
|
|
1150
|
+
worker->Queue(env);
|
|
1151
|
+
|
|
1152
|
+
NAPI_RETURN_UNDEFINED();
|
|
1153
|
+
}
|
|
1154
|
+
|
|
1155
|
+
/**
|
|
1156
|
+
* Worker class for getting a value from a database.
|
|
1157
|
+
*/
|
|
1158
|
+
struct GetWorker final : public PriorityWorker {
|
|
1159
|
+
GetWorker (napi_env env,
|
|
1160
|
+
Database* database,
|
|
1161
|
+
napi_value callback,
|
|
1162
|
+
leveldb::Slice key,
|
|
1163
|
+
const bool asBuffer,
|
|
1164
|
+
const bool fillCache)
|
|
1165
|
+
: PriorityWorker(env, database, callback, "leveldown.db.get"),
|
|
1166
|
+
key_(key),
|
|
1167
|
+
asBuffer_(asBuffer) {
|
|
1168
|
+
options_.fill_cache = fillCache;
|
|
1169
|
+
}
|
|
1170
|
+
|
|
1171
|
+
~GetWorker () {
|
|
1172
|
+
DisposeSliceBuffer(key_);
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
void DoExecute () override {
|
|
1176
|
+
SetStatus(database_->Get(options_, key_, value_));
|
|
1177
|
+
}
|
|
1178
|
+
|
|
1179
|
+
void HandleOKCallback (napi_env env, napi_value callback) override {
|
|
1180
|
+
napi_value argv[2];
|
|
1181
|
+
napi_get_null(env, &argv[0]);
|
|
1182
|
+
Entry::Convert(env, &value_, asBuffer_, &argv[1]);
|
|
1183
|
+
CallFunction(env, callback, 2, argv);
|
|
1184
|
+
}
|
|
1185
|
+
|
|
1186
|
+
private:
|
|
1187
|
+
leveldb::ReadOptions options_;
|
|
1188
|
+
leveldb::Slice key_;
|
|
1189
|
+
std::string value_;
|
|
1190
|
+
const bool asBuffer_;
|
|
1191
|
+
};
|
|
1192
|
+
|
|
1193
|
+
/**
|
|
1194
|
+
* Gets a value from a database.
|
|
1195
|
+
*/
|
|
1196
|
+
NAPI_METHOD(db_get) {
|
|
1197
|
+
NAPI_ARGV(4);
|
|
1198
|
+
NAPI_DB_CONTEXT();
|
|
1199
|
+
|
|
1200
|
+
leveldb::Slice key = ToSlice(env, argv[1]);
|
|
1201
|
+
napi_value options = argv[2];
|
|
1202
|
+
const bool asBuffer = BooleanProperty(env, options, "asBuffer", true);
|
|
1203
|
+
const bool fillCache = BooleanProperty(env, options, "fillCache", true);
|
|
1204
|
+
napi_value callback = argv[3];
|
|
1205
|
+
|
|
1206
|
+
GetWorker* worker = new GetWorker(env, database, callback, key, asBuffer,
|
|
1207
|
+
fillCache);
|
|
1208
|
+
worker->Queue(env);
|
|
1209
|
+
|
|
1210
|
+
NAPI_RETURN_UNDEFINED();
|
|
1211
|
+
}
|
|
1212
|
+
|
|
1213
|
+
/**
|
|
1214
|
+
* Worker class for getting many values.
|
|
1215
|
+
*/
|
|
1216
|
+
struct GetManyWorker final : public PriorityWorker {
|
|
1217
|
+
GetManyWorker (napi_env env,
|
|
1218
|
+
Database* database,
|
|
1219
|
+
const std::vector<std::string>* keys,
|
|
1220
|
+
napi_value callback,
|
|
1221
|
+
const bool valueAsBuffer,
|
|
1222
|
+
const bool fillCache)
|
|
1223
|
+
: PriorityWorker(env, database, callback, "leveldown.get.many"),
|
|
1224
|
+
keys_(keys), valueAsBuffer_(valueAsBuffer) {
|
|
1225
|
+
options_.fill_cache = fillCache;
|
|
1226
|
+
options_.snapshot = database->NewSnapshot();
|
|
1227
|
+
}
|
|
1228
|
+
|
|
1229
|
+
~GetManyWorker() {
|
|
1230
|
+
delete keys_;
|
|
1231
|
+
}
|
|
1232
|
+
|
|
1233
|
+
void DoExecute () override {
|
|
1234
|
+
cache_.reserve(keys_->size());
|
|
1235
|
+
|
|
1236
|
+
for (const std::string& key: *keys_) {
|
|
1237
|
+
std::string* value = new std::string();
|
|
1238
|
+
leveldb::Status status = database_->Get(options_, key, *value);
|
|
1239
|
+
|
|
1240
|
+
if (status.ok()) {
|
|
1241
|
+
cache_.push_back(value);
|
|
1242
|
+
} else if (status.IsNotFound()) {
|
|
1243
|
+
delete value;
|
|
1244
|
+
cache_.push_back(NULL);
|
|
1245
|
+
} else {
|
|
1246
|
+
delete value;
|
|
1247
|
+
for (const std::string* value: cache_) {
|
|
1248
|
+
if (value != NULL) delete value;
|
|
1249
|
+
}
|
|
1250
|
+
SetStatus(status);
|
|
1251
|
+
break;
|
|
1252
|
+
}
|
|
1253
|
+
}
|
|
1254
|
+
|
|
1255
|
+
database_->ReleaseSnapshot(options_.snapshot);
|
|
1256
|
+
}
|
|
1257
|
+
|
|
1258
|
+
void HandleOKCallback (napi_env env, napi_value callback) override {
|
|
1259
|
+
size_t size = cache_.size();
|
|
1260
|
+
napi_value array;
|
|
1261
|
+
napi_create_array_with_length(env, size, &array);
|
|
1262
|
+
|
|
1263
|
+
for (size_t idx = 0; idx < size; idx++) {
|
|
1264
|
+
std::string* value = cache_[idx];
|
|
1265
|
+
napi_value element;
|
|
1266
|
+
Entry::Convert(env, value, valueAsBuffer_, &element);
|
|
1267
|
+
napi_set_element(env, array, static_cast<uint32_t>(idx), element);
|
|
1268
|
+
if (value != NULL) delete value;
|
|
1269
|
+
}
|
|
1270
|
+
|
|
1271
|
+
napi_value argv[2];
|
|
1272
|
+
napi_get_null(env, &argv[0]);
|
|
1273
|
+
argv[1] = array;
|
|
1274
|
+
CallFunction(env, callback, 2, argv);
|
|
1275
|
+
}
|
|
1276
|
+
|
|
1277
|
+
private:
|
|
1278
|
+
leveldb::ReadOptions options_;
|
|
1279
|
+
const std::vector<std::string>* keys_;
|
|
1280
|
+
const bool valueAsBuffer_;
|
|
1281
|
+
std::vector<std::string*> cache_;
|
|
1282
|
+
};
|
|
1283
|
+
|
|
1284
|
+
/**
|
|
1285
|
+
* Gets many values from a database.
|
|
1286
|
+
*/
|
|
1287
|
+
NAPI_METHOD(db_get_many) {
|
|
1288
|
+
NAPI_ARGV(4);
|
|
1289
|
+
NAPI_DB_CONTEXT();
|
|
1290
|
+
|
|
1291
|
+
const std::vector<std::string>* keys = KeyArray(env, argv[1]);
|
|
1292
|
+
napi_value options = argv[2];
|
|
1293
|
+
const bool asBuffer = BooleanProperty(env, options, "asBuffer", true);
|
|
1294
|
+
const bool fillCache = BooleanProperty(env, options, "fillCache", true);
|
|
1295
|
+
napi_value callback = argv[3];
|
|
1296
|
+
|
|
1297
|
+
GetManyWorker* worker = new GetManyWorker(
|
|
1298
|
+
env, database, keys, callback, asBuffer, fillCache
|
|
1299
|
+
);
|
|
1300
|
+
|
|
1301
|
+
worker->Queue(env);
|
|
1302
|
+
NAPI_RETURN_UNDEFINED();
|
|
1303
|
+
}
|
|
1304
|
+
|
|
1305
|
+
/**
|
|
1306
|
+
* Worker class for deleting a value from a database.
|
|
1307
|
+
*/
|
|
1308
|
+
struct DelWorker final : public PriorityWorker {
|
|
1309
|
+
DelWorker (napi_env env,
|
|
1310
|
+
Database* database,
|
|
1311
|
+
napi_value callback,
|
|
1312
|
+
leveldb::Slice key,
|
|
1313
|
+
bool sync)
|
|
1314
|
+
: PriorityWorker(env, database, callback, "leveldown.db.del"),
|
|
1315
|
+
key_(key) {
|
|
1316
|
+
options_.sync = sync;
|
|
1317
|
+
}
|
|
1318
|
+
|
|
1319
|
+
~DelWorker () {
|
|
1320
|
+
DisposeSliceBuffer(key_);
|
|
1321
|
+
}
|
|
1322
|
+
|
|
1323
|
+
void DoExecute () override {
|
|
1324
|
+
SetStatus(database_->Del(options_, key_));
|
|
1325
|
+
}
|
|
1326
|
+
|
|
1327
|
+
leveldb::WriteOptions options_;
|
|
1328
|
+
leveldb::Slice key_;
|
|
1329
|
+
};
|
|
1330
|
+
|
|
1331
|
+
/**
|
|
1332
|
+
* Delete a value from a database.
|
|
1333
|
+
*/
|
|
1334
|
+
NAPI_METHOD(db_del) {
|
|
1335
|
+
NAPI_ARGV(4);
|
|
1336
|
+
NAPI_DB_CONTEXT();
|
|
1337
|
+
|
|
1338
|
+
leveldb::Slice key = ToSlice(env, argv[1]);
|
|
1339
|
+
bool sync = BooleanProperty(env, argv[2], "sync", false);
|
|
1340
|
+
napi_value callback = argv[3];
|
|
1341
|
+
|
|
1342
|
+
DelWorker* worker = new DelWorker(env, database, callback, key, sync);
|
|
1343
|
+
worker->Queue(env);
|
|
1344
|
+
|
|
1345
|
+
NAPI_RETURN_UNDEFINED();
|
|
1346
|
+
}
|
|
1347
|
+
|
|
1348
|
+
/**
|
|
1349
|
+
* Worker class for deleting a range from a database.
|
|
1350
|
+
*/
|
|
1351
|
+
struct ClearWorker final : public PriorityWorker {
|
|
1352
|
+
ClearWorker (napi_env env,
|
|
1353
|
+
Database* database,
|
|
1354
|
+
napi_value callback,
|
|
1355
|
+
const bool reverse,
|
|
1356
|
+
const int limit,
|
|
1357
|
+
std::string* lt,
|
|
1358
|
+
std::string* lte,
|
|
1359
|
+
std::string* gt,
|
|
1360
|
+
std::string* gte)
|
|
1361
|
+
: PriorityWorker(env, database, callback, "leveldown.db.clear") {
|
|
1362
|
+
iterator_ = new BaseIterator(database, reverse, lt, lte, gt, gte, limit, false);
|
|
1363
|
+
writeOptions_ = new leveldb::WriteOptions();
|
|
1364
|
+
writeOptions_->sync = false;
|
|
1365
|
+
}
|
|
1366
|
+
|
|
1367
|
+
~ClearWorker () {
|
|
1368
|
+
delete iterator_;
|
|
1369
|
+
delete writeOptions_;
|
|
1370
|
+
}
|
|
1371
|
+
|
|
1372
|
+
void DoExecute () override {
|
|
1373
|
+
iterator_->SeekToRange();
|
|
1374
|
+
|
|
1375
|
+
// TODO: add option
|
|
1376
|
+
uint32_t hwm = 16 * 1024;
|
|
1377
|
+
leveldb::WriteBatch batch;
|
|
1378
|
+
|
|
1379
|
+
while (true) {
|
|
1380
|
+
size_t bytesRead = 0;
|
|
1381
|
+
|
|
1382
|
+
while (bytesRead <= hwm && iterator_->Valid() && iterator_->Increment()) {
|
|
1383
|
+
leveldb::Slice key = iterator_->CurrentKey();
|
|
1384
|
+
batch.Delete(key);
|
|
1385
|
+
bytesRead += key.size();
|
|
1386
|
+
iterator_->Next();
|
|
1387
|
+
}
|
|
1388
|
+
|
|
1389
|
+
if (!SetStatus(iterator_->Status()) || bytesRead == 0) {
|
|
1390
|
+
break;
|
|
1391
|
+
}
|
|
1392
|
+
|
|
1393
|
+
if (!SetStatus(database_->WriteBatch(*writeOptions_, &batch))) {
|
|
1394
|
+
break;
|
|
1395
|
+
}
|
|
1396
|
+
|
|
1397
|
+
batch.Clear();
|
|
1398
|
+
}
|
|
1399
|
+
|
|
1400
|
+
iterator_->End();
|
|
1401
|
+
}
|
|
1402
|
+
|
|
1403
|
+
private:
|
|
1404
|
+
BaseIterator* iterator_;
|
|
1405
|
+
leveldb::WriteOptions* writeOptions_;
|
|
1406
|
+
};
|
|
1407
|
+
|
|
1408
|
+
/**
|
|
1409
|
+
* Delete a range from a database.
|
|
1410
|
+
*/
|
|
1411
|
+
NAPI_METHOD(db_clear) {
|
|
1412
|
+
NAPI_ARGV(3);
|
|
1413
|
+
NAPI_DB_CONTEXT();
|
|
1414
|
+
|
|
1415
|
+
napi_value options = argv[1];
|
|
1416
|
+
napi_value callback = argv[2];
|
|
1417
|
+
|
|
1418
|
+
const bool reverse = BooleanProperty(env, options, "reverse", false);
|
|
1419
|
+
const int limit = Int32Property(env, options, "limit", -1);
|
|
1420
|
+
|
|
1421
|
+
std::string* lt = RangeOption(env, options, "lt");
|
|
1422
|
+
std::string* lte = RangeOption(env, options, "lte");
|
|
1423
|
+
std::string* gt = RangeOption(env, options, "gt");
|
|
1424
|
+
std::string* gte = RangeOption(env, options, "gte");
|
|
1425
|
+
|
|
1426
|
+
ClearWorker* worker = new ClearWorker(env, database, callback, reverse, limit, lt, lte, gt, gte);
|
|
1427
|
+
worker->Queue(env);
|
|
1428
|
+
|
|
1429
|
+
NAPI_RETURN_UNDEFINED();
|
|
1430
|
+
}
|
|
1431
|
+
|
|
1432
|
+
/**
|
|
1433
|
+
* Worker class for calculating the size of a range.
|
|
1434
|
+
*/
|
|
1435
|
+
struct ApproximateSizeWorker final : public PriorityWorker {
|
|
1436
|
+
ApproximateSizeWorker (napi_env env,
|
|
1437
|
+
Database* database,
|
|
1438
|
+
napi_value callback,
|
|
1439
|
+
leveldb::Slice start,
|
|
1440
|
+
leveldb::Slice end)
|
|
1441
|
+
: PriorityWorker(env, database, callback, "leveldown.db.approximate_size"),
|
|
1442
|
+
start_(start), end_(end) {}
|
|
1443
|
+
|
|
1444
|
+
~ApproximateSizeWorker () {
|
|
1445
|
+
DisposeSliceBuffer(start_);
|
|
1446
|
+
DisposeSliceBuffer(end_);
|
|
1447
|
+
}
|
|
1448
|
+
|
|
1449
|
+
void DoExecute () override {
|
|
1450
|
+
leveldb::Range range(start_, end_);
|
|
1451
|
+
size_ = database_->ApproximateSize(&range);
|
|
1452
|
+
}
|
|
1453
|
+
|
|
1454
|
+
void HandleOKCallback (napi_env env, napi_value callback) override {
|
|
1455
|
+
napi_value argv[2];
|
|
1456
|
+
napi_get_null(env, &argv[0]);
|
|
1457
|
+
napi_create_int64(env, (uint64_t)size_, &argv[1]);
|
|
1458
|
+
CallFunction(env, callback, 2, argv);
|
|
1459
|
+
}
|
|
1460
|
+
|
|
1461
|
+
leveldb::Slice start_;
|
|
1462
|
+
leveldb::Slice end_;
|
|
1463
|
+
uint64_t size_;
|
|
1464
|
+
};
|
|
1465
|
+
|
|
1466
|
+
/**
|
|
1467
|
+
* Calculates the approximate size of a range in a database.
|
|
1468
|
+
*/
|
|
1469
|
+
NAPI_METHOD(db_approximate_size) {
|
|
1470
|
+
NAPI_ARGV(4);
|
|
1471
|
+
NAPI_DB_CONTEXT();
|
|
1472
|
+
|
|
1473
|
+
leveldb::Slice start = ToSlice(env, argv[1]);
|
|
1474
|
+
leveldb::Slice end = ToSlice(env, argv[2]);
|
|
1475
|
+
|
|
1476
|
+
napi_value callback = argv[3];
|
|
1477
|
+
|
|
1478
|
+
ApproximateSizeWorker* worker = new ApproximateSizeWorker(env, database,
|
|
1479
|
+
callback, start,
|
|
1480
|
+
end);
|
|
1481
|
+
worker->Queue(env);
|
|
1482
|
+
|
|
1483
|
+
NAPI_RETURN_UNDEFINED();
|
|
1484
|
+
}
|
|
1485
|
+
|
|
1486
|
+
/**
|
|
1487
|
+
* Worker class for compacting a range in a database.
|
|
1488
|
+
*/
|
|
1489
|
+
struct CompactRangeWorker final : public PriorityWorker {
|
|
1490
|
+
CompactRangeWorker (napi_env env,
|
|
1491
|
+
Database* database,
|
|
1492
|
+
napi_value callback,
|
|
1493
|
+
leveldb::Slice start,
|
|
1494
|
+
leveldb::Slice end)
|
|
1495
|
+
: PriorityWorker(env, database, callback, "leveldown.db.compact_range"),
|
|
1496
|
+
start_(start), end_(end) {}
|
|
1497
|
+
|
|
1498
|
+
~CompactRangeWorker () {
|
|
1499
|
+
DisposeSliceBuffer(start_);
|
|
1500
|
+
DisposeSliceBuffer(end_);
|
|
1501
|
+
}
|
|
1502
|
+
|
|
1503
|
+
void DoExecute () override {
|
|
1504
|
+
database_->CompactRange(&start_, &end_);
|
|
1505
|
+
}
|
|
1506
|
+
|
|
1507
|
+
leveldb::Slice start_;
|
|
1508
|
+
leveldb::Slice end_;
|
|
1509
|
+
};
|
|
1510
|
+
|
|
1511
|
+
/**
|
|
1512
|
+
* Compacts a range in a database.
|
|
1513
|
+
*/
|
|
1514
|
+
NAPI_METHOD(db_compact_range) {
|
|
1515
|
+
NAPI_ARGV(4);
|
|
1516
|
+
NAPI_DB_CONTEXT();
|
|
1517
|
+
|
|
1518
|
+
leveldb::Slice start = ToSlice(env, argv[1]);
|
|
1519
|
+
leveldb::Slice end = ToSlice(env, argv[2]);
|
|
1520
|
+
napi_value callback = argv[3];
|
|
1521
|
+
|
|
1522
|
+
CompactRangeWorker* worker = new CompactRangeWorker(env, database, callback,
|
|
1523
|
+
start, end);
|
|
1524
|
+
worker->Queue(env);
|
|
1525
|
+
|
|
1526
|
+
NAPI_RETURN_UNDEFINED();
|
|
1527
|
+
}
|
|
1528
|
+
|
|
1529
|
+
/**
|
|
1530
|
+
* Get a property from a database.
|
|
1531
|
+
*/
|
|
1532
|
+
NAPI_METHOD(db_get_property) {
|
|
1533
|
+
NAPI_ARGV(2);
|
|
1534
|
+
NAPI_DB_CONTEXT();
|
|
1535
|
+
|
|
1536
|
+
leveldb::Slice property = ToSlice(env, argv[1]);
|
|
1537
|
+
|
|
1538
|
+
std::string value;
|
|
1539
|
+
database->GetProperty(property, &value);
|
|
1540
|
+
|
|
1541
|
+
napi_value result;
|
|
1542
|
+
napi_create_string_utf8(env, value.data(), value.size(), &result);
|
|
1543
|
+
|
|
1544
|
+
DisposeSliceBuffer(property);
|
|
1545
|
+
|
|
1546
|
+
return result;
|
|
1547
|
+
}
|
|
1548
|
+
|
|
1549
|
+
/**
|
|
1550
|
+
* Worker class for destroying a database.
|
|
1551
|
+
*/
|
|
1552
|
+
struct DestroyWorker final : public BaseWorker {
|
|
1553
|
+
DestroyWorker (napi_env env,
|
|
1554
|
+
const std::string& location,
|
|
1555
|
+
napi_value callback)
|
|
1556
|
+
: BaseWorker(env, NULL, callback, "leveldown.destroy_db"),
|
|
1557
|
+
location_(location) {}
|
|
1558
|
+
|
|
1559
|
+
~DestroyWorker () {}
|
|
1560
|
+
|
|
1561
|
+
void DoExecute () override {
|
|
1562
|
+
leveldb::Options options;
|
|
1563
|
+
|
|
1564
|
+
// TODO: support overriding infoLogLevel the same as db.open(options)
|
|
1565
|
+
options.info_log_level = rocksdb::InfoLogLevel::HEADER_LEVEL;
|
|
1566
|
+
options.info_log.reset(new NullLogger());
|
|
1567
|
+
|
|
1568
|
+
SetStatus(leveldb::DestroyDB(location_, options));
|
|
1569
|
+
}
|
|
1570
|
+
|
|
1571
|
+
std::string location_;
|
|
1572
|
+
};
|
|
1573
|
+
|
|
1574
|
+
/**
|
|
1575
|
+
* Destroys a database.
|
|
1576
|
+
*/
|
|
1577
|
+
NAPI_METHOD(destroy_db) {
|
|
1578
|
+
NAPI_ARGV(2);
|
|
1579
|
+
NAPI_ARGV_UTF8_NEW(location, 0);
|
|
1580
|
+
napi_value callback = argv[1];
|
|
1581
|
+
|
|
1582
|
+
DestroyWorker* worker = new DestroyWorker(env, location, callback);
|
|
1583
|
+
worker->Queue(env);
|
|
1584
|
+
|
|
1585
|
+
delete [] location;
|
|
1586
|
+
|
|
1587
|
+
NAPI_RETURN_UNDEFINED();
|
|
1588
|
+
}
|
|
1589
|
+
|
|
1590
|
+
/**
|
|
1591
|
+
* Worker class for repairing a database.
|
|
1592
|
+
*/
|
|
1593
|
+
struct RepairWorker final : public BaseWorker {
|
|
1594
|
+
RepairWorker (napi_env env,
|
|
1595
|
+
const std::string& location,
|
|
1596
|
+
napi_value callback)
|
|
1597
|
+
: BaseWorker(env, NULL, callback, "leveldown.repair_db"),
|
|
1598
|
+
location_(location) {}
|
|
1599
|
+
|
|
1600
|
+
~RepairWorker () {}
|
|
1601
|
+
|
|
1602
|
+
void DoExecute () override {
|
|
1603
|
+
leveldb::Options options;
|
|
1604
|
+
|
|
1605
|
+
// TODO: support overriding infoLogLevel the same as db.open(options)
|
|
1606
|
+
options.info_log_level = rocksdb::InfoLogLevel::HEADER_LEVEL;
|
|
1607
|
+
options.info_log.reset(new NullLogger());
|
|
1608
|
+
|
|
1609
|
+
SetStatus(leveldb::RepairDB(location_, options));
|
|
1610
|
+
}
|
|
1611
|
+
|
|
1612
|
+
std::string location_;
|
|
1613
|
+
};
|
|
1614
|
+
|
|
1615
|
+
/**
|
|
1616
|
+
* Repairs a database.
|
|
1617
|
+
*/
|
|
1618
|
+
NAPI_METHOD(repair_db) {
|
|
1619
|
+
NAPI_ARGV(2);
|
|
1620
|
+
NAPI_ARGV_UTF8_NEW(location, 0);
|
|
1621
|
+
napi_value callback = argv[1];
|
|
1622
|
+
|
|
1623
|
+
RepairWorker* worker = new RepairWorker(env, location, callback);
|
|
1624
|
+
worker->Queue(env);
|
|
1625
|
+
|
|
1626
|
+
delete [] location;
|
|
1627
|
+
|
|
1628
|
+
NAPI_RETURN_UNDEFINED();
|
|
1629
|
+
}
|
|
1630
|
+
|
|
1631
|
+
/**
|
|
1632
|
+
* Runs when an Iterator is garbage collected.
|
|
1633
|
+
*/
|
|
1634
|
+
static void FinalizeIterator (napi_env env, void* data, void* hint) {
|
|
1635
|
+
if (data) {
|
|
1636
|
+
delete (Iterator*)data;
|
|
1637
|
+
}
|
|
1638
|
+
}
|
|
1639
|
+
|
|
1640
|
+
/**
|
|
1641
|
+
* Create an iterator.
|
|
1642
|
+
*/
|
|
1643
|
+
NAPI_METHOD(iterator_init) {
|
|
1644
|
+
NAPI_ARGV(2);
|
|
1645
|
+
NAPI_DB_CONTEXT();
|
|
1646
|
+
|
|
1647
|
+
napi_value options = argv[1];
|
|
1648
|
+
const bool reverse = BooleanProperty(env, options, "reverse", false);
|
|
1649
|
+
const bool keys = BooleanProperty(env, options, "keys", true);
|
|
1650
|
+
const bool values = BooleanProperty(env, options, "values", true);
|
|
1651
|
+
const bool fillCache = BooleanProperty(env, options, "fillCache", false);
|
|
1652
|
+
const bool keyAsBuffer = BooleanProperty(env, options, "keyAsBuffer", true);
|
|
1653
|
+
const bool valueAsBuffer = BooleanProperty(env, options, "valueAsBuffer", true);
|
|
1654
|
+
const int limit = Int32Property(env, options, "limit", -1);
|
|
1655
|
+
const uint32_t highWaterMark = Uint32Property(env, options, "highWaterMark",
|
|
1656
|
+
16 * 1024);
|
|
1657
|
+
|
|
1658
|
+
std::string* lt = RangeOption(env, options, "lt");
|
|
1659
|
+
std::string* lte = RangeOption(env, options, "lte");
|
|
1660
|
+
std::string* gt = RangeOption(env, options, "gt");
|
|
1661
|
+
std::string* gte = RangeOption(env, options, "gte");
|
|
1662
|
+
|
|
1663
|
+
const uint32_t id = database->currentIteratorId_++;
|
|
1664
|
+
Iterator* iterator = new Iterator(database, id, reverse, keys,
|
|
1665
|
+
values, limit, lt, lte, gt, gte, fillCache,
|
|
1666
|
+
keyAsBuffer, valueAsBuffer, highWaterMark);
|
|
1667
|
+
napi_value result;
|
|
1668
|
+
|
|
1669
|
+
NAPI_STATUS_THROWS(napi_create_external(env, iterator,
|
|
1670
|
+
FinalizeIterator,
|
|
1671
|
+
NULL, &result));
|
|
1672
|
+
|
|
1673
|
+
// Prevent GC of JS object before the iterator is ended (explicitly or on
|
|
1674
|
+
// db close) and keep track of non-ended iterators to end them on db close.
|
|
1675
|
+
iterator->Attach(env, result);
|
|
1676
|
+
|
|
1677
|
+
return result;
|
|
1678
|
+
}
|
|
1679
|
+
|
|
1680
|
+
/**
|
|
1681
|
+
* Seeks an iterator.
|
|
1682
|
+
*/
|
|
1683
|
+
NAPI_METHOD(iterator_seek) {
|
|
1684
|
+
NAPI_ARGV(2);
|
|
1685
|
+
NAPI_ITERATOR_CONTEXT();
|
|
1686
|
+
|
|
1687
|
+
if (iterator->isEnding_ || iterator->hasEnded_) {
|
|
1688
|
+
napi_throw_error(env, NULL, "iterator has ended");
|
|
1689
|
+
}
|
|
1690
|
+
|
|
1691
|
+
leveldb::Slice target = ToSlice(env, argv[1]);
|
|
1692
|
+
iterator->landed_ = false;
|
|
1693
|
+
iterator->Seek(target);
|
|
1694
|
+
|
|
1695
|
+
DisposeSliceBuffer(target);
|
|
1696
|
+
NAPI_RETURN_UNDEFINED();
|
|
1697
|
+
}
|
|
1698
|
+
|
|
1699
|
+
/**
|
|
1700
|
+
* Worker class for ending an iterator
|
|
1701
|
+
*/
|
|
1702
|
+
struct EndWorker final : public BaseWorker {
|
|
1703
|
+
EndWorker (napi_env env,
|
|
1704
|
+
Iterator* iterator,
|
|
1705
|
+
napi_value callback)
|
|
1706
|
+
: BaseWorker(env, iterator->database_, callback, "leveldown.iterator.end"),
|
|
1707
|
+
iterator_(iterator) {}
|
|
1708
|
+
|
|
1709
|
+
~EndWorker () {}
|
|
1710
|
+
|
|
1711
|
+
void DoExecute () override {
|
|
1712
|
+
iterator_->End();
|
|
1713
|
+
}
|
|
1714
|
+
|
|
1715
|
+
void DoFinally (napi_env env) override {
|
|
1716
|
+
iterator_->Detach(env);
|
|
1717
|
+
BaseWorker::DoFinally(env);
|
|
1718
|
+
}
|
|
1719
|
+
|
|
1720
|
+
private:
|
|
1721
|
+
Iterator* iterator_;
|
|
1722
|
+
};
|
|
1723
|
+
|
|
1724
|
+
/**
|
|
1725
|
+
* Called by NAPI_METHOD(iterator_end) and also when closing
|
|
1726
|
+
* open iterators during NAPI_METHOD(db_close).
|
|
1727
|
+
*/
|
|
1728
|
+
static void iterator_end_do (napi_env env, Iterator* iterator, napi_value cb) {
|
|
1729
|
+
if (!iterator->isEnding_ && !iterator->hasEnded_) {
|
|
1730
|
+
EndWorker* worker = new EndWorker(env, iterator, cb);
|
|
1731
|
+
iterator->isEnding_ = true;
|
|
1732
|
+
|
|
1733
|
+
if (iterator->nexting_) {
|
|
1734
|
+
iterator->endWorker_ = worker;
|
|
1735
|
+
} else {
|
|
1736
|
+
worker->Queue(env);
|
|
1737
|
+
}
|
|
1738
|
+
}
|
|
1739
|
+
}
|
|
1740
|
+
|
|
1741
|
+
/**
|
|
1742
|
+
* Ends an iterator.
|
|
1743
|
+
*/
|
|
1744
|
+
NAPI_METHOD(iterator_end) {
|
|
1745
|
+
NAPI_ARGV(2);
|
|
1746
|
+
NAPI_ITERATOR_CONTEXT();
|
|
1747
|
+
|
|
1748
|
+
iterator_end_do(env, iterator, argv[1]);
|
|
1749
|
+
|
|
1750
|
+
NAPI_RETURN_UNDEFINED();
|
|
1751
|
+
}
|
|
1752
|
+
|
|
1753
|
+
/**
|
|
1754
|
+
* Worker class for nexting an iterator.
|
|
1755
|
+
*/
|
|
1756
|
+
struct NextWorker final : public BaseWorker {
|
|
1757
|
+
NextWorker (napi_env env,
|
|
1758
|
+
Iterator* iterator,
|
|
1759
|
+
napi_value callback)
|
|
1760
|
+
: BaseWorker(env, iterator->database_, callback,
|
|
1761
|
+
"leveldown.iterator.next"),
|
|
1762
|
+
iterator_(iterator), ok_() {}
|
|
1763
|
+
|
|
1764
|
+
~NextWorker () {}
|
|
1765
|
+
|
|
1766
|
+
void DoExecute () override {
|
|
1767
|
+
if (!iterator_->DidSeek()) {
|
|
1768
|
+
iterator_->SeekToRange();
|
|
1769
|
+
}
|
|
1770
|
+
|
|
1771
|
+
// Limit the size of the cache to prevent starving the event loop
|
|
1772
|
+
// in JS-land while we're recursively calling process.nextTick().
|
|
1773
|
+
ok_ = iterator_->ReadMany(1000);
|
|
1774
|
+
|
|
1775
|
+
if (!ok_) {
|
|
1776
|
+
SetStatus(iterator_->Status());
|
|
1777
|
+
}
|
|
1778
|
+
}
|
|
1779
|
+
|
|
1780
|
+
void HandleOKCallback (napi_env env, napi_value callback) override {
|
|
1781
|
+
size_t arraySize = iterator_->cache_.size();
|
|
1782
|
+
napi_value jsArray;
|
|
1783
|
+
napi_create_array_with_length(env, arraySize, &jsArray);
|
|
1784
|
+
|
|
1785
|
+
for (size_t idx = 0; idx < iterator_->cache_.size(); idx += 2) {
|
|
1786
|
+
std::string key = iterator_->cache_[idx];
|
|
1787
|
+
std::string value = iterator_->cache_[idx + 1];
|
|
1788
|
+
|
|
1789
|
+
napi_value returnKey;
|
|
1790
|
+
napi_value returnValue;
|
|
1791
|
+
|
|
1792
|
+
Entry::Convert(env, &key, iterator_->keyAsBuffer_, &returnKey);
|
|
1793
|
+
Entry::Convert(env, &value, iterator_->valueAsBuffer_, &returnValue);
|
|
1794
|
+
|
|
1795
|
+
// put the key & value in a descending order, so that they can be .pop:ed in javascript-land
|
|
1796
|
+
napi_set_element(env, jsArray, static_cast<int>(arraySize - idx - 1), returnKey);
|
|
1797
|
+
napi_set_element(env, jsArray, static_cast<int>(arraySize - idx - 2), returnValue);
|
|
1798
|
+
}
|
|
1799
|
+
|
|
1800
|
+
napi_value argv[3];
|
|
1801
|
+
napi_get_null(env, &argv[0]);
|
|
1802
|
+
argv[1] = jsArray;
|
|
1803
|
+
napi_get_boolean(env, !ok_, &argv[2]);
|
|
1804
|
+
CallFunction(env, callback, 3, argv);
|
|
1805
|
+
}
|
|
1806
|
+
|
|
1807
|
+
void DoFinally (napi_env env) override {
|
|
1808
|
+
// clean up & handle the next/end state
|
|
1809
|
+
iterator_->nexting_ = false;
|
|
1810
|
+
|
|
1811
|
+
if (iterator_->endWorker_ != NULL) {
|
|
1812
|
+
iterator_->endWorker_->Queue(env);
|
|
1813
|
+
iterator_->endWorker_ = NULL;
|
|
1814
|
+
}
|
|
1815
|
+
|
|
1816
|
+
BaseWorker::DoFinally(env);
|
|
1817
|
+
}
|
|
1818
|
+
|
|
1819
|
+
private:
|
|
1820
|
+
Iterator* iterator_;
|
|
1821
|
+
bool ok_;
|
|
1822
|
+
};
|
|
1823
|
+
|
|
1824
|
+
/**
|
|
1825
|
+
* Moves an iterator to next element.
|
|
1826
|
+
*/
|
|
1827
|
+
NAPI_METHOD(iterator_next) {
|
|
1828
|
+
NAPI_ARGV(2);
|
|
1829
|
+
NAPI_ITERATOR_CONTEXT();
|
|
1830
|
+
|
|
1831
|
+
napi_value callback = argv[1];
|
|
1832
|
+
|
|
1833
|
+
if (iterator->isEnding_ || iterator->hasEnded_) {
|
|
1834
|
+
napi_value argv = CreateError(env, "iterator has ended");
|
|
1835
|
+
CallFunction(env, callback, 1, &argv);
|
|
1836
|
+
|
|
1837
|
+
NAPI_RETURN_UNDEFINED();
|
|
1838
|
+
}
|
|
1839
|
+
|
|
1840
|
+
NextWorker* worker = new NextWorker(env, iterator, callback);
|
|
1841
|
+
iterator->nexting_ = true;
|
|
1842
|
+
worker->Queue(env);
|
|
1843
|
+
|
|
1844
|
+
NAPI_RETURN_UNDEFINED();
|
|
1845
|
+
}
|
|
1846
|
+
|
|
1847
|
+
/**
|
|
1848
|
+
* Worker class for batch write operation.
|
|
1849
|
+
*/
|
|
1850
|
+
struct BatchWorker final : public PriorityWorker {
|
|
1851
|
+
BatchWorker (napi_env env,
|
|
1852
|
+
Database* database,
|
|
1853
|
+
napi_value callback,
|
|
1854
|
+
leveldb::WriteBatch* batch,
|
|
1855
|
+
const bool sync,
|
|
1856
|
+
const bool hasData)
|
|
1857
|
+
: PriorityWorker(env, database, callback, "leveldown.batch.do"),
|
|
1858
|
+
batch_(batch), hasData_(hasData) {
|
|
1859
|
+
options_.sync = sync;
|
|
1860
|
+
}
|
|
1861
|
+
|
|
1862
|
+
~BatchWorker () {
|
|
1863
|
+
delete batch_;
|
|
1864
|
+
}
|
|
1865
|
+
|
|
1866
|
+
void DoExecute () override {
|
|
1867
|
+
if (hasData_) {
|
|
1868
|
+
SetStatus(database_->WriteBatch(options_, batch_));
|
|
1869
|
+
}
|
|
1870
|
+
}
|
|
1871
|
+
|
|
1872
|
+
private:
|
|
1873
|
+
leveldb::WriteOptions options_;
|
|
1874
|
+
leveldb::WriteBatch* batch_;
|
|
1875
|
+
const bool hasData_;
|
|
1876
|
+
};
|
|
1877
|
+
|
|
1878
|
+
/**
|
|
1879
|
+
* Does a batch write operation on a database.
|
|
1880
|
+
*/
|
|
1881
|
+
NAPI_METHOD(batch_do) {
|
|
1882
|
+
NAPI_ARGV(4);
|
|
1883
|
+
NAPI_DB_CONTEXT();
|
|
1884
|
+
|
|
1885
|
+
napi_value array = argv[1];
|
|
1886
|
+
const bool sync = BooleanProperty(env, argv[2], "sync", false);
|
|
1887
|
+
napi_value callback = argv[3];
|
|
1888
|
+
|
|
1889
|
+
uint32_t length;
|
|
1890
|
+
napi_get_array_length(env, array, &length);
|
|
1891
|
+
|
|
1892
|
+
leveldb::WriteBatch* batch = new leveldb::WriteBatch();
|
|
1893
|
+
bool hasData = false;
|
|
1894
|
+
|
|
1895
|
+
for (uint32_t i = 0; i < length; i++) {
|
|
1896
|
+
napi_value element;
|
|
1897
|
+
napi_get_element(env, array, i, &element);
|
|
1898
|
+
|
|
1899
|
+
if (!IsObject(env, element)) continue;
|
|
1900
|
+
|
|
1901
|
+
std::string type = StringProperty(env, element, "type");
|
|
1902
|
+
|
|
1903
|
+
if (type == "del") {
|
|
1904
|
+
if (!HasProperty(env, element, "key")) continue;
|
|
1905
|
+
leveldb::Slice key = ToSlice(env, GetProperty(env, element, "key"));
|
|
1906
|
+
|
|
1907
|
+
batch->Delete(key);
|
|
1908
|
+
if (!hasData) hasData = true;
|
|
1909
|
+
|
|
1910
|
+
DisposeSliceBuffer(key);
|
|
1911
|
+
} else if (type == "put") {
|
|
1912
|
+
if (!HasProperty(env, element, "key")) continue;
|
|
1913
|
+
if (!HasProperty(env, element, "value")) continue;
|
|
1914
|
+
|
|
1915
|
+
leveldb::Slice key = ToSlice(env, GetProperty(env, element, "key"));
|
|
1916
|
+
leveldb::Slice value = ToSlice(env, GetProperty(env, element, "value"));
|
|
1917
|
+
|
|
1918
|
+
batch->Put(key, value);
|
|
1919
|
+
if (!hasData) hasData = true;
|
|
1920
|
+
|
|
1921
|
+
DisposeSliceBuffer(key);
|
|
1922
|
+
DisposeSliceBuffer(value);
|
|
1923
|
+
}
|
|
1924
|
+
}
|
|
1925
|
+
|
|
1926
|
+
BatchWorker* worker = new BatchWorker(env, database, callback, batch, sync, hasData);
|
|
1927
|
+
worker->Queue(env);
|
|
1928
|
+
|
|
1929
|
+
NAPI_RETURN_UNDEFINED();
|
|
1930
|
+
}
|
|
1931
|
+
|
|
1932
|
+
/**
|
|
1933
|
+
* Owns a WriteBatch.
|
|
1934
|
+
*/
|
|
1935
|
+
struct Batch {
|
|
1936
|
+
Batch (Database* database)
|
|
1937
|
+
: database_(database),
|
|
1938
|
+
batch_(new leveldb::WriteBatch()),
|
|
1939
|
+
hasData_(false) {}
|
|
1940
|
+
|
|
1941
|
+
~Batch () {
|
|
1942
|
+
delete batch_;
|
|
1943
|
+
}
|
|
1944
|
+
|
|
1945
|
+
void Put (leveldb::Slice key, leveldb::Slice value) {
|
|
1946
|
+
batch_->Put(key, value);
|
|
1947
|
+
hasData_ = true;
|
|
1948
|
+
}
|
|
1949
|
+
|
|
1950
|
+
void Del (leveldb::Slice key) {
|
|
1951
|
+
batch_->Delete(key);
|
|
1952
|
+
hasData_ = true;
|
|
1953
|
+
}
|
|
1954
|
+
|
|
1955
|
+
void Clear () {
|
|
1956
|
+
batch_->Clear();
|
|
1957
|
+
hasData_ = false;
|
|
1958
|
+
}
|
|
1959
|
+
|
|
1960
|
+
leveldb::Status Write (bool sync) {
|
|
1961
|
+
leveldb::WriteOptions options;
|
|
1962
|
+
options.sync = sync;
|
|
1963
|
+
return database_->WriteBatch(options, batch_);
|
|
1964
|
+
}
|
|
1965
|
+
|
|
1966
|
+
Database* database_;
|
|
1967
|
+
leveldb::WriteBatch* batch_;
|
|
1968
|
+
bool hasData_;
|
|
1969
|
+
};
|
|
1970
|
+
|
|
1971
|
+
/**
|
|
1972
|
+
* Runs when a Batch is garbage collected.
|
|
1973
|
+
*/
|
|
1974
|
+
static void FinalizeBatch (napi_env env, void* data, void* hint) {
|
|
1975
|
+
if (data) {
|
|
1976
|
+
delete (Batch*)data;
|
|
1977
|
+
}
|
|
1978
|
+
}
|
|
1979
|
+
|
|
1980
|
+
/**
|
|
1981
|
+
* Return a batch object.
|
|
1982
|
+
*/
|
|
1983
|
+
NAPI_METHOD(batch_init) {
|
|
1984
|
+
NAPI_ARGV(1);
|
|
1985
|
+
NAPI_DB_CONTEXT();
|
|
1986
|
+
|
|
1987
|
+
Batch* batch = new Batch(database);
|
|
1988
|
+
|
|
1989
|
+
napi_value result;
|
|
1990
|
+
NAPI_STATUS_THROWS(napi_create_external(env, batch,
|
|
1991
|
+
FinalizeBatch,
|
|
1992
|
+
NULL, &result));
|
|
1993
|
+
return result;
|
|
1994
|
+
}
|
|
1995
|
+
|
|
1996
|
+
/**
|
|
1997
|
+
* Adds a put instruction to a batch object.
|
|
1998
|
+
*/
|
|
1999
|
+
NAPI_METHOD(batch_put) {
|
|
2000
|
+
NAPI_ARGV(3);
|
|
2001
|
+
NAPI_BATCH_CONTEXT();
|
|
2002
|
+
|
|
2003
|
+
leveldb::Slice key = ToSlice(env, argv[1]);
|
|
2004
|
+
leveldb::Slice value = ToSlice(env, argv[2]);
|
|
2005
|
+
batch->Put(key, value);
|
|
2006
|
+
DisposeSliceBuffer(key);
|
|
2007
|
+
DisposeSliceBuffer(value);
|
|
2008
|
+
|
|
2009
|
+
NAPI_RETURN_UNDEFINED();
|
|
2010
|
+
}
|
|
2011
|
+
|
|
2012
|
+
/**
|
|
2013
|
+
* Adds a delete instruction to a batch object.
|
|
2014
|
+
*/
|
|
2015
|
+
NAPI_METHOD(batch_del) {
|
|
2016
|
+
NAPI_ARGV(2);
|
|
2017
|
+
NAPI_BATCH_CONTEXT();
|
|
2018
|
+
|
|
2019
|
+
leveldb::Slice key = ToSlice(env, argv[1]);
|
|
2020
|
+
batch->Del(key);
|
|
2021
|
+
DisposeSliceBuffer(key);
|
|
2022
|
+
|
|
2023
|
+
NAPI_RETURN_UNDEFINED();
|
|
2024
|
+
}
|
|
2025
|
+
|
|
2026
|
+
/**
|
|
2027
|
+
* Clears a batch object.
|
|
2028
|
+
*/
|
|
2029
|
+
NAPI_METHOD(batch_clear) {
|
|
2030
|
+
NAPI_ARGV(1);
|
|
2031
|
+
NAPI_BATCH_CONTEXT();
|
|
2032
|
+
|
|
2033
|
+
batch->Clear();
|
|
2034
|
+
|
|
2035
|
+
NAPI_RETURN_UNDEFINED();
|
|
2036
|
+
}
|
|
2037
|
+
|
|
2038
|
+
/**
|
|
2039
|
+
* Worker class for batch write operation.
|
|
2040
|
+
*/
|
|
2041
|
+
struct BatchWriteWorker final : public PriorityWorker {
|
|
2042
|
+
BatchWriteWorker (napi_env env,
|
|
2043
|
+
napi_value context,
|
|
2044
|
+
Batch* batch,
|
|
2045
|
+
napi_value callback,
|
|
2046
|
+
const bool sync)
|
|
2047
|
+
: PriorityWorker(env, batch->database_, callback, "leveldown.batch.write"),
|
|
2048
|
+
batch_(batch),
|
|
2049
|
+
sync_(sync) {
|
|
2050
|
+
// Prevent GC of batch object before we execute
|
|
2051
|
+
NAPI_STATUS_THROWS_VOID(napi_create_reference(env, context, 1, &contextRef_));
|
|
2052
|
+
}
|
|
2053
|
+
|
|
2054
|
+
~BatchWriteWorker () {}
|
|
2055
|
+
|
|
2056
|
+
void DoExecute () override {
|
|
2057
|
+
if (batch_->hasData_) {
|
|
2058
|
+
SetStatus(batch_->Write(sync_));
|
|
2059
|
+
}
|
|
2060
|
+
}
|
|
2061
|
+
|
|
2062
|
+
void DoFinally (napi_env env) override {
|
|
2063
|
+
napi_delete_reference(env, contextRef_);
|
|
2064
|
+
PriorityWorker::DoFinally(env);
|
|
2065
|
+
}
|
|
2066
|
+
|
|
2067
|
+
private:
|
|
2068
|
+
Batch* batch_;
|
|
2069
|
+
const bool sync_;
|
|
2070
|
+
napi_ref contextRef_;
|
|
2071
|
+
};
|
|
2072
|
+
|
|
2073
|
+
/**
|
|
2074
|
+
* Writes a batch object.
|
|
2075
|
+
*/
|
|
2076
|
+
NAPI_METHOD(batch_write) {
|
|
2077
|
+
NAPI_ARGV(3);
|
|
2078
|
+
NAPI_BATCH_CONTEXT();
|
|
2079
|
+
|
|
2080
|
+
napi_value options = argv[1];
|
|
2081
|
+
const bool sync = BooleanProperty(env, options, "sync", false);
|
|
2082
|
+
napi_value callback = argv[2];
|
|
2083
|
+
|
|
2084
|
+
BatchWriteWorker* worker = new BatchWriteWorker(env, argv[0], batch, callback, sync);
|
|
2085
|
+
worker->Queue(env);
|
|
2086
|
+
|
|
2087
|
+
NAPI_RETURN_UNDEFINED();
|
|
2088
|
+
}
|
|
2089
|
+
|
|
2090
|
+
/**
|
|
2091
|
+
* All exported functions.
|
|
2092
|
+
*/
|
|
2093
|
+
NAPI_INIT() {
|
|
2094
|
+
NAPI_EXPORT_FUNCTION(db_init);
|
|
2095
|
+
NAPI_EXPORT_FUNCTION(db_open);
|
|
2096
|
+
NAPI_EXPORT_FUNCTION(db_close);
|
|
2097
|
+
NAPI_EXPORT_FUNCTION(db_put);
|
|
2098
|
+
NAPI_EXPORT_FUNCTION(db_get);
|
|
2099
|
+
NAPI_EXPORT_FUNCTION(db_get_many);
|
|
2100
|
+
NAPI_EXPORT_FUNCTION(db_del);
|
|
2101
|
+
NAPI_EXPORT_FUNCTION(db_clear);
|
|
2102
|
+
NAPI_EXPORT_FUNCTION(db_approximate_size);
|
|
2103
|
+
NAPI_EXPORT_FUNCTION(db_compact_range);
|
|
2104
|
+
NAPI_EXPORT_FUNCTION(db_get_property);
|
|
2105
|
+
|
|
2106
|
+
NAPI_EXPORT_FUNCTION(destroy_db);
|
|
2107
|
+
NAPI_EXPORT_FUNCTION(repair_db);
|
|
2108
|
+
|
|
2109
|
+
NAPI_EXPORT_FUNCTION(iterator_init);
|
|
2110
|
+
NAPI_EXPORT_FUNCTION(iterator_seek);
|
|
2111
|
+
NAPI_EXPORT_FUNCTION(iterator_end);
|
|
2112
|
+
NAPI_EXPORT_FUNCTION(iterator_next);
|
|
2113
|
+
|
|
2114
|
+
NAPI_EXPORT_FUNCTION(batch_do);
|
|
2115
|
+
NAPI_EXPORT_FUNCTION(batch_init);
|
|
2116
|
+
NAPI_EXPORT_FUNCTION(batch_put);
|
|
2117
|
+
NAPI_EXPORT_FUNCTION(batch_del);
|
|
2118
|
+
NAPI_EXPORT_FUNCTION(batch_clear);
|
|
2119
|
+
NAPI_EXPORT_FUNCTION(batch_write);
|
|
2120
|
+
}
|