mdbxmou 0.2.6 → 0.3.0
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/CMakeLists.txt +1 -0
- package/README.md +195 -2
- package/package.json +3 -2
- package/src/cursormou.cpp +316 -0
- package/src/cursormou.hpp +69 -0
- package/src/dbimou.cpp +2 -2
- package/src/dbimou.hpp +0 -3
- package/src/env_arg0.hpp +1 -4
- package/src/envmou.cpp +6 -6
- package/src/envmou.hpp +3 -2
- package/src/modulemou.cpp +2 -0
- package/src/txnmou.cpp +110 -82
- package/src/txnmou.hpp +19 -12
- package/src/typemou.hpp +33 -64
- package/src/valuemou.hpp +18 -8
package/CMakeLists.txt
CHANGED
package/README.md
CHANGED
|
@@ -9,7 +9,7 @@ High-performance Node.js binding for libmdbx — a fast, lightweight, embedded k
|
|
|
9
9
|
- **Transactions** — ACID transactions with read/write modes
|
|
10
10
|
- **Multiple key/value types** — String, binary, ordinal (integer) keys
|
|
11
11
|
- **Batch operations** — Efficient multi-key read/write
|
|
12
|
-
- **Memory-mapped** —
|
|
12
|
+
- **Memory-mapped** — High-performance memory-mapped I/O
|
|
13
13
|
|
|
14
14
|
## Installation
|
|
15
15
|
|
|
@@ -266,6 +266,199 @@ dbi.drop(txn, true);
|
|
|
266
266
|
dbi.drop(txn);
|
|
267
267
|
```
|
|
268
268
|
|
|
269
|
+
### Cursor (MDBX_Cursor)
|
|
270
|
+
|
|
271
|
+
Cursors provide low-level control for database traversal with positioning and iteration capabilities.
|
|
272
|
+
|
|
273
|
+
#### Creating a Cursor
|
|
274
|
+
```javascript
|
|
275
|
+
const txn = env.startRead();
|
|
276
|
+
const dbi = txn.openMap();
|
|
277
|
+
const cursor = txn.openCursor(dbi);
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
#### Navigation Methods
|
|
281
|
+
|
|
282
|
+
**first() → {key, value} | undefined**
|
|
283
|
+
```javascript
|
|
284
|
+
const item = cursor.first();
|
|
285
|
+
if (item) {
|
|
286
|
+
console.log(item.key, item.value);
|
|
287
|
+
}
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
**last() → {key, value} | undefined**
|
|
291
|
+
```javascript
|
|
292
|
+
const item = cursor.last();
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
**next() → {key, value} | undefined**
|
|
296
|
+
```javascript
|
|
297
|
+
const item = cursor.next();
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
**prev() → {key, value} | undefined**
|
|
301
|
+
```javascript
|
|
302
|
+
const item = cursor.prev();
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
**current() → {key, value} | undefined**
|
|
306
|
+
```javascript
|
|
307
|
+
const item = cursor.current();
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
#### Search Methods
|
|
311
|
+
|
|
312
|
+
**seek(key) → {key, value} | undefined**
|
|
313
|
+
|
|
314
|
+
Exact key match. Returns `undefined` if key not found.
|
|
315
|
+
```javascript
|
|
316
|
+
const item = cursor.seek('user:123');
|
|
317
|
+
if (item) {
|
|
318
|
+
console.log('Found:', item.value);
|
|
319
|
+
}
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
**seekGE(key) → {key, value} | undefined**
|
|
323
|
+
|
|
324
|
+
Find first key greater or equal to given key (lower_bound).
|
|
325
|
+
```javascript
|
|
326
|
+
const item = cursor.seekGE('user:100');
|
|
327
|
+
// Returns first key >= 'user:100'
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
#### Modification Methods
|
|
331
|
+
|
|
332
|
+
**put(key, value, [flags])**
|
|
333
|
+
|
|
334
|
+
Insert or update a record at cursor position.
|
|
335
|
+
```javascript
|
|
336
|
+
cursor.put('newKey', 'newValue');
|
|
337
|
+
|
|
338
|
+
// With flags (MDBX_NOOVERWRITE, etc.)
|
|
339
|
+
cursor.put('key', 'value', MDBX_Param.queryMode.insertUnique);
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
**del([flags]) → boolean**
|
|
343
|
+
|
|
344
|
+
Delete record at current cursor position. Returns `true` if deleted, `false` if not found.
|
|
345
|
+
```javascript
|
|
346
|
+
cursor.seek('keyToDelete');
|
|
347
|
+
const deleted = cursor.del();
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
#### Control Methods
|
|
351
|
+
|
|
352
|
+
**close()**
|
|
353
|
+
```javascript
|
|
354
|
+
cursor.close();
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
#### Cursor Examples
|
|
358
|
+
|
|
359
|
+
**Iterate all records:**
|
|
360
|
+
```javascript
|
|
361
|
+
const cursor = txn.openCursor(dbi);
|
|
362
|
+
|
|
363
|
+
for (let item = cursor.first(); item; item = cursor.next()) {
|
|
364
|
+
console.log(item.key, item.value.toString());
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
cursor.close();
|
|
368
|
+
txn.abort();
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
**Range iteration:**
|
|
372
|
+
```javascript
|
|
373
|
+
const cursor = txn.openCursor(dbi);
|
|
374
|
+
|
|
375
|
+
// Find all keys starting with 'user:'
|
|
376
|
+
for (let item = cursor.seekGE('user:'); item; item = cursor.next()) {
|
|
377
|
+
if (!item.key.startsWith('user:')) break;
|
|
378
|
+
console.log(item.key, item.value.toString());
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
cursor.close();
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
**Pagination:**
|
|
385
|
+
```javascript
|
|
386
|
+
function getPage(cursor, offset, limit) {
|
|
387
|
+
const results = [];
|
|
388
|
+
|
|
389
|
+
let item = cursor.first();
|
|
390
|
+
|
|
391
|
+
// Skip offset
|
|
392
|
+
for (let i = 0; i < offset && item; i++) {
|
|
393
|
+
item = cursor.next();
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
// Collect limit items
|
|
397
|
+
for (let i = 0; i < limit && item; i++) {
|
|
398
|
+
results.push({ key: item.key, value: item.value.toString() });
|
|
399
|
+
item = cursor.next();
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
return results;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
const txn = env.startRead();
|
|
406
|
+
const dbi = txn.openMap();
|
|
407
|
+
const cursor = txn.openCursor(dbi);
|
|
408
|
+
|
|
409
|
+
const page1 = getPage(cursor, 0, 10); // First 10 items
|
|
410
|
+
const page2 = getPage(cursor, 10, 10); // Next 10 items
|
|
411
|
+
|
|
412
|
+
cursor.close();
|
|
413
|
+
txn.abort();
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
**Reverse iteration:**
|
|
417
|
+
```javascript
|
|
418
|
+
const cursor = txn.openCursor(dbi);
|
|
419
|
+
|
|
420
|
+
for (let item = cursor.last(); item; item = cursor.prev()) {
|
|
421
|
+
console.log(item.key, item.value.toString());
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
cursor.close();
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
**Bulk insert with cursor:**
|
|
428
|
+
```javascript
|
|
429
|
+
const txn = env.startWrite();
|
|
430
|
+
const dbi = txn.createMap();
|
|
431
|
+
const cursor = txn.openCursor(dbi);
|
|
432
|
+
|
|
433
|
+
for (let i = 0; i < 1000; i++) {
|
|
434
|
+
cursor.put(`key${i}`, `value${i}`);
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
cursor.close();
|
|
438
|
+
txn.commit();
|
|
439
|
+
```
|
|
440
|
+
|
|
441
|
+
**Delete with cursor:**
|
|
442
|
+
```javascript
|
|
443
|
+
const txn = env.startWrite();
|
|
444
|
+
const dbi = txn.openMap();
|
|
445
|
+
const cursor = txn.openCursor(dbi);
|
|
446
|
+
|
|
447
|
+
// Delete specific key
|
|
448
|
+
if (cursor.seek('keyToDelete')) {
|
|
449
|
+
cursor.del();
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
// Delete range
|
|
453
|
+
for (let item = cursor.seekGE('prefix:'); item; item = cursor.next()) {
|
|
454
|
+
if (!item.key.startsWith('prefix:')) break;
|
|
455
|
+
cursor.del();
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
cursor.close();
|
|
459
|
+
txn.commit();
|
|
460
|
+
```
|
|
461
|
+
|
|
269
462
|
## Key and Value Types
|
|
270
463
|
|
|
271
464
|
### Key Modes (MDBX_Param.keyMode)
|
|
@@ -623,7 +816,7 @@ Note: For ordinal (integer) keys, use keyFlag.number or keyFlag.bigint to specif
|
|
|
623
816
|
1. **Use ordinal keys** for integer data - much faster than string keys
|
|
624
817
|
2. **Batch operations** - Use query API for bulk operations
|
|
625
818
|
3. **Reuse transactions** - Keep read transactions open for multiple operations
|
|
626
|
-
4. **Memory mapping** - MDBX uses memory-mapped files for
|
|
819
|
+
4. **Memory mapping** - MDBX uses memory-mapped files for fast I/O
|
|
627
820
|
5. **Transaction scope** - Always pass transaction object to DBI methods
|
|
628
821
|
|
|
629
822
|
## License
|
package/package.json
CHANGED
|
@@ -46,7 +46,8 @@
|
|
|
46
46
|
],
|
|
47
47
|
"dependencies": {
|
|
48
48
|
"cmake-js": "^7.3.1",
|
|
49
|
-
"node-addon-api": "^8.5.0"
|
|
49
|
+
"node-addon-api": "^8.5.0",
|
|
50
|
+
"uuid": "^13.0.0"
|
|
50
51
|
},
|
|
51
52
|
"devDependencies": {
|
|
52
53
|
"@types/node": "^22.10.2",
|
|
@@ -65,7 +66,7 @@
|
|
|
65
66
|
},
|
|
66
67
|
"gypfile": true,
|
|
67
68
|
"name": "mdbxmou",
|
|
68
|
-
"version": "0.
|
|
69
|
+
"version": "0.3.0",
|
|
69
70
|
"description": "Node bindings for mdbx",
|
|
70
71
|
"repository": {
|
|
71
72
|
"type": "git",
|
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
#include "cursormou.hpp"
|
|
2
|
+
#include "dbimou.hpp"
|
|
3
|
+
#include "txnmou.hpp"
|
|
4
|
+
|
|
5
|
+
namespace mdbxmou {
|
|
6
|
+
|
|
7
|
+
Napi::FunctionReference cursormou::ctor{};
|
|
8
|
+
|
|
9
|
+
void cursormou::init(const char *class_name, Napi::Env env)
|
|
10
|
+
{
|
|
11
|
+
auto func = DefineClass(env, class_name, {
|
|
12
|
+
InstanceMethod("first", &cursormou::first),
|
|
13
|
+
InstanceMethod("last", &cursormou::last),
|
|
14
|
+
InstanceMethod("next", &cursormou::next),
|
|
15
|
+
InstanceMethod("prev", &cursormou::prev),
|
|
16
|
+
InstanceMethod("seek", &cursormou::seek),
|
|
17
|
+
InstanceMethod("seekGE", &cursormou::seek_ge),
|
|
18
|
+
InstanceMethod("current", &cursormou::current),
|
|
19
|
+
InstanceMethod("put", &cursormou::put),
|
|
20
|
+
InstanceMethod("del", &cursormou::del),
|
|
21
|
+
InstanceMethod("forEach", &cursormou::for_each),
|
|
22
|
+
InstanceMethod("close", &cursormou::close),
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
ctor = Napi::Persistent(func);
|
|
26
|
+
ctor.SuppressDestruct();
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
void cursormou::do_close() noexcept
|
|
30
|
+
{
|
|
31
|
+
if (cursor_) {
|
|
32
|
+
::mdbx_cursor_close(std::exchange(cursor_, nullptr));
|
|
33
|
+
if (txn_) {
|
|
34
|
+
--(*txn_);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
cursormou::~cursormou()
|
|
40
|
+
{
|
|
41
|
+
do_close();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
void cursormou::attach(txnmou& txn, dbimou& dbi, MDBX_cursor* cursor)
|
|
45
|
+
{
|
|
46
|
+
txn_ = &txn;
|
|
47
|
+
dbi_ = &dbi;
|
|
48
|
+
cursor_ = cursor;
|
|
49
|
+
++(*txn_);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Внутренний хелпер для навигации
|
|
53
|
+
Napi::Value cursormou::move(const Napi::Env& env, MDBX_cursor_op op)
|
|
54
|
+
{
|
|
55
|
+
if (!cursor_) {
|
|
56
|
+
throw Napi::Error::New(env, "cursor closed");
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
keymou key{};
|
|
60
|
+
valuemou val{};
|
|
61
|
+
auto rc = mdbx_cursor_get(cursor_, key, val, op);
|
|
62
|
+
if (rc == MDBX_NOTFOUND) {
|
|
63
|
+
return env.Undefined();
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (rc != MDBX_SUCCESS) {
|
|
67
|
+
throw Napi::Error::New(env, mdbx_strerror(rc));
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Возвращаем {key, value}
|
|
71
|
+
auto result = Napi::Object::New(env);
|
|
72
|
+
|
|
73
|
+
auto key_mode = dbi_->get_key_mode();
|
|
74
|
+
auto key_flag = dbi_->get_key_flag();
|
|
75
|
+
auto value_flag = dbi_->get_value_flag();
|
|
76
|
+
|
|
77
|
+
// Ключ
|
|
78
|
+
if (key_mode.val & key_mode::ordinal) {
|
|
79
|
+
if (key_flag.val & base_flag::bigint) {
|
|
80
|
+
result.Set("key", key.to_bigint(env));
|
|
81
|
+
} else {
|
|
82
|
+
result.Set("key", key.to_number(env));
|
|
83
|
+
}
|
|
84
|
+
} else {
|
|
85
|
+
result.Set("key", key.to_string(env));
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Значение
|
|
89
|
+
if (value_flag.val & base_flag::string) {
|
|
90
|
+
result.Set("value", val.to_string(env));
|
|
91
|
+
} else {
|
|
92
|
+
result.Set("value", val.to_buffer(env));
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return result;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
Napi::Value cursormou::first(const Napi::CallbackInfo& info) {
|
|
99
|
+
return move(info.Env(), MDBX_FIRST);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
Napi::Value cursormou::last(const Napi::CallbackInfo& info) {
|
|
103
|
+
return move(info.Env(), MDBX_LAST);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
Napi::Value cursormou::next(const Napi::CallbackInfo& info) {
|
|
107
|
+
return move(info.Env(), MDBX_NEXT);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
Napi::Value cursormou::prev(const Napi::CallbackInfo& info) {
|
|
111
|
+
return move(info.Env(), MDBX_PREV);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
Napi::Value cursormou::current(const Napi::CallbackInfo& info) {
|
|
115
|
+
return move(info.Env(), MDBX_GET_CURRENT);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Хелпер для поиска
|
|
119
|
+
Napi::Value cursormou::seek_impl(const Napi::CallbackInfo& info, MDBX_cursor_op op)
|
|
120
|
+
{
|
|
121
|
+
Napi::Env env = info.Env();
|
|
122
|
+
|
|
123
|
+
if (!cursor_) {
|
|
124
|
+
throw Napi::Error::New(env, "cursor closed");
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (info.Length() < 1) {
|
|
128
|
+
throw Napi::Error::New(env, "key required");
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
auto key_mode = dbi_->get_key_mode();
|
|
132
|
+
|
|
133
|
+
keymou key = (key_mode.val & key_mode::ordinal) ?
|
|
134
|
+
keymou::from(info[0], env, key_num_) :
|
|
135
|
+
keymou::from(info[0], env, key_buf_);
|
|
136
|
+
|
|
137
|
+
valuemou val{};
|
|
138
|
+
auto rc = mdbx_cursor_get(cursor_, key, val, op);
|
|
139
|
+
if (MDBX_NOTFOUND == rc) {
|
|
140
|
+
return env.Undefined();
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (MDBX_SUCCESS != rc) {
|
|
144
|
+
throw Napi::Error::New(env, mdbx_strerror(rc));
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Возвращаем {key, value}
|
|
148
|
+
auto result = Napi::Object::New(env);
|
|
149
|
+
|
|
150
|
+
auto key_flag = dbi_->get_key_flag();
|
|
151
|
+
auto value_flag = dbi_->get_value_flag();
|
|
152
|
+
|
|
153
|
+
// Ключ
|
|
154
|
+
if (key_mode.val & key_mode::ordinal) {
|
|
155
|
+
if (key_flag.val & base_flag::bigint) {
|
|
156
|
+
result.Set("key", key.to_bigint(env));
|
|
157
|
+
} else {
|
|
158
|
+
result.Set("key", key.to_number(env));
|
|
159
|
+
}
|
|
160
|
+
} else {
|
|
161
|
+
result.Set("key", key.to_string(env));
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Значение
|
|
165
|
+
if (value_flag.val & base_flag::string) {
|
|
166
|
+
result.Set("value", val.to_string(env));
|
|
167
|
+
} else {
|
|
168
|
+
result.Set("value", val.to_buffer(env));
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return result;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
Napi::Value cursormou::seek(const Napi::CallbackInfo& info) {
|
|
175
|
+
return seek_impl(info, MDBX_SET_KEY);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
Napi::Value cursormou::seek_ge(const Napi::CallbackInfo& info) {
|
|
179
|
+
return seek_impl(info, MDBX_SET_RANGE);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
Napi::Value cursormou::put(const Napi::CallbackInfo& info) {
|
|
183
|
+
Napi::Env env = info.Env();
|
|
184
|
+
|
|
185
|
+
if (!cursor_) {
|
|
186
|
+
throw Napi::Error::New(env, "cursor closed");
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (info.Length() < 2) {
|
|
190
|
+
throw Napi::Error::New(env, "key and value required");
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
auto key_mode = dbi_->get_key_mode();
|
|
194
|
+
|
|
195
|
+
keymou key = (key_mode.val & key_mode::ordinal) ?
|
|
196
|
+
keymou::from(info[0], env, key_num_) :
|
|
197
|
+
keymou::from(info[0], env, key_buf_);
|
|
198
|
+
|
|
199
|
+
valuemou val = valuemou::from(info[1], env, val_buf_);
|
|
200
|
+
|
|
201
|
+
// Опциональный флаг put_mode (по умолчанию MDBX_UPSERT)
|
|
202
|
+
MDBX_put_flags_t flags = MDBX_UPSERT;
|
|
203
|
+
if (info.Length() > 2 && info[2].IsNumber()) {
|
|
204
|
+
flags = static_cast<MDBX_put_flags_t>(info[2].As<Napi::Number>().Int32Value());
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
auto rc = mdbx_cursor_put(cursor_, key, val, flags);
|
|
208
|
+
if (MDBX_SUCCESS != rc) {
|
|
209
|
+
throw Napi::Error::New(env, mdbx_strerror(rc));
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
return env.Undefined();
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
Napi::Value cursormou::del(const Napi::CallbackInfo& info) {
|
|
216
|
+
Napi::Env env = info.Env();
|
|
217
|
+
|
|
218
|
+
if (!cursor_) {
|
|
219
|
+
throw Napi::Error::New(env, "cursor closed");
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Опциональный флаг (MDBX_NODUPDATA для удаления только текущего значения в multi-value)
|
|
223
|
+
MDBX_put_flags_t flags = MDBX_CURRENT;
|
|
224
|
+
if (info.Length() > 0 && info[0].IsNumber()) {
|
|
225
|
+
flags = static_cast<MDBX_put_flags_t>(info[0].As<Napi::Number>().Int32Value());
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
auto rc = mdbx_cursor_del(cursor_, flags);
|
|
229
|
+
if (MDBX_NOTFOUND == rc) {
|
|
230
|
+
return Napi::Boolean::New(env, false);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
if (MDBX_SUCCESS != rc) {
|
|
234
|
+
throw Napi::Error::New(env, mdbx_strerror(rc));
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
return Napi::Boolean::New(env, true);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// forEach(callback, backward = false)
|
|
241
|
+
// callback({key, value}) => true продолжить, false/undefined остановить
|
|
242
|
+
Napi::Value cursormou::for_each(const Napi::CallbackInfo& info) {
|
|
243
|
+
Napi::Env env = info.Env();
|
|
244
|
+
|
|
245
|
+
if (!cursor_) {
|
|
246
|
+
throw Napi::Error::New(env, "cursor closed");
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
if (info.Length() < 1 || !info[0].IsFunction()) {
|
|
250
|
+
throw Napi::TypeError::New(env, "forEach: callback function required");
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
auto callback = info[0].As<Napi::Function>();
|
|
254
|
+
bool backward = info.Length() > 1 && info[1].ToBoolean().Value();
|
|
255
|
+
|
|
256
|
+
auto key_mode = dbi_->get_key_mode();
|
|
257
|
+
auto key_flag = dbi_->get_key_flag();
|
|
258
|
+
auto value_flag = dbi_->get_value_flag();
|
|
259
|
+
|
|
260
|
+
MDBX_cursor_op start_op = backward ? MDBX_LAST : MDBX_FIRST;
|
|
261
|
+
MDBX_cursor_op move_op = backward ? MDBX_PREV : MDBX_NEXT;
|
|
262
|
+
|
|
263
|
+
keymou key{};
|
|
264
|
+
valuemou val{};
|
|
265
|
+
// Первое позиционирование
|
|
266
|
+
auto rc = mdbx_cursor_get(cursor_, key, val, start_op);
|
|
267
|
+
while (MDBX_SUCCESS == rc)
|
|
268
|
+
{
|
|
269
|
+
auto result = Napi::Object::New(env);
|
|
270
|
+
|
|
271
|
+
// Ключ
|
|
272
|
+
if (key_mode.val & key_mode::ordinal) {
|
|
273
|
+
if (key_flag.val & base_flag::bigint) {
|
|
274
|
+
result.Set("key", key.to_bigint(env));
|
|
275
|
+
} else {
|
|
276
|
+
result.Set("key", key.to_number(env));
|
|
277
|
+
}
|
|
278
|
+
} else {
|
|
279
|
+
result.Set("key", key.to_string(env));
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// Значение
|
|
283
|
+
if (value_flag.val & base_flag::string) {
|
|
284
|
+
result.Set("value", val.to_string(env));
|
|
285
|
+
} else {
|
|
286
|
+
result.Set("value", val.to_buffer(env));
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// Вызов callback
|
|
290
|
+
auto ret = callback.Call({result});
|
|
291
|
+
|
|
292
|
+
// Если вернул false или undefined - остановить
|
|
293
|
+
if (ret.IsBoolean() && !ret.As<Napi::Boolean>().Value()) {
|
|
294
|
+
break;
|
|
295
|
+
}
|
|
296
|
+
if (ret.IsUndefined() || ret.IsNull()) {
|
|
297
|
+
// undefined/null = продолжаем (как forEach в JS)
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// Следующий элемент
|
|
301
|
+
rc = mdbx_cursor_get(cursor_, key, val, move_op);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
if (rc != MDBX_SUCCESS && rc != MDBX_NOTFOUND) {
|
|
305
|
+
throw Napi::Error::New(env, mdbx_strerror(rc));
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
return env.Undefined();
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
Napi::Value cursormou::close(const Napi::CallbackInfo& info) {
|
|
312
|
+
do_close();
|
|
313
|
+
return info.Env().Undefined();
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
} // namespace mdbxmou
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#include "typemou.hpp"
|
|
4
|
+
#include "valuemou.hpp"
|
|
5
|
+
|
|
6
|
+
namespace mdbxmou {
|
|
7
|
+
|
|
8
|
+
class dbimou;
|
|
9
|
+
class txnmou;
|
|
10
|
+
|
|
11
|
+
class cursormou final
|
|
12
|
+
: public Napi::ObjectWrap<cursormou>
|
|
13
|
+
{
|
|
14
|
+
private:
|
|
15
|
+
txnmou* txn_{nullptr};
|
|
16
|
+
dbimou* dbi_{nullptr};
|
|
17
|
+
MDBX_cursor* cursor_{nullptr};
|
|
18
|
+
|
|
19
|
+
// Буферы для ключей/значений
|
|
20
|
+
buffer_type key_buf_{};
|
|
21
|
+
buffer_type val_buf_{};
|
|
22
|
+
std::uint64_t key_num_{};
|
|
23
|
+
|
|
24
|
+
// Внутренний хелпер для навигации
|
|
25
|
+
Napi::Value move(const Napi::Env& env, MDBX_cursor_op op);
|
|
26
|
+
|
|
27
|
+
// Хелпер для поиска
|
|
28
|
+
Napi::Value seek_impl(const Napi::CallbackInfo& info, MDBX_cursor_op op);
|
|
29
|
+
|
|
30
|
+
// Закрытие курсора
|
|
31
|
+
void do_close() noexcept;
|
|
32
|
+
|
|
33
|
+
public:
|
|
34
|
+
static Napi::FunctionReference ctor;
|
|
35
|
+
static void init(const char *class_name, Napi::Env env);
|
|
36
|
+
|
|
37
|
+
cursormou(const Napi::CallbackInfo& info)
|
|
38
|
+
: Napi::ObjectWrap<cursormou>(info)
|
|
39
|
+
{ }
|
|
40
|
+
|
|
41
|
+
~cursormou();
|
|
42
|
+
|
|
43
|
+
// Навигация
|
|
44
|
+
Napi::Value first(const Napi::CallbackInfo&);
|
|
45
|
+
Napi::Value last(const Napi::CallbackInfo&);
|
|
46
|
+
Napi::Value next(const Napi::CallbackInfo&);
|
|
47
|
+
Napi::Value prev(const Napi::CallbackInfo&);
|
|
48
|
+
|
|
49
|
+
// Поиск
|
|
50
|
+
Napi::Value seek(const Napi::CallbackInfo&); // exact match
|
|
51
|
+
Napi::Value seek_ge(const Napi::CallbackInfo&); // >= key (lower_bound)
|
|
52
|
+
|
|
53
|
+
// Текущая позиция
|
|
54
|
+
Napi::Value current(const Napi::CallbackInfo&);
|
|
55
|
+
|
|
56
|
+
// Модификация
|
|
57
|
+
Napi::Value put(const Napi::CallbackInfo&);
|
|
58
|
+
Napi::Value del(const Napi::CallbackInfo&);
|
|
59
|
+
|
|
60
|
+
// Итерация
|
|
61
|
+
Napi::Value for_each(const Napi::CallbackInfo&);
|
|
62
|
+
|
|
63
|
+
// Закрытие
|
|
64
|
+
Napi::Value close(const Napi::CallbackInfo&);
|
|
65
|
+
|
|
66
|
+
void attach(txnmou& txn, dbimou& dbi, MDBX_cursor* cursor);
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
} // namespace mdbxmou
|
package/src/dbimou.cpp
CHANGED
|
@@ -51,6 +51,7 @@ Napi::Value dbimou::put(const Napi::CallbackInfo& info)
|
|
|
51
51
|
auto key = (key_mode_.val & key_mode::ordinal) ?
|
|
52
52
|
keymou::from(info[1], env, t) :
|
|
53
53
|
keymou::from(info[1], env, key_buf_);
|
|
54
|
+
|
|
54
55
|
auto val = valuemou::from(info[2], env, val_buf_);
|
|
55
56
|
dbi::put(*txn, key, val, *this);
|
|
56
57
|
} catch (const std::exception& e) {
|
|
@@ -139,7 +140,7 @@ Napi::Value dbimou::has(const Napi::CallbackInfo& info) {
|
|
|
139
140
|
auto key = (key_mode_.val & key_mode::ordinal) ?
|
|
140
141
|
keymou::from(info[1], env, t) :
|
|
141
142
|
keymou::from(info[1], env, key_buf_);
|
|
142
|
-
|
|
143
|
+
|
|
143
144
|
bool result = dbi::has(*txn, key);
|
|
144
145
|
return Napi::Value::From(env, result);
|
|
145
146
|
} catch (const std::exception& e) {
|
|
@@ -177,7 +178,6 @@ Napi::Value dbimou::for_each(const Napi::CallbackInfo& info) {
|
|
|
177
178
|
}
|
|
178
179
|
|
|
179
180
|
uint32_t index{};
|
|
180
|
-
|
|
181
181
|
if (key_mode_.val & key_mode::ordinal) {
|
|
182
182
|
cursor.scan([&](const mdbx::pair& f) {
|
|
183
183
|
keymou key{f.key};
|
package/src/dbimou.hpp
CHANGED
|
@@ -33,9 +33,6 @@ public:
|
|
|
33
33
|
, dbi{}
|
|
34
34
|
{ }
|
|
35
35
|
|
|
36
|
-
//~dbimou() { fprintf(stderr, "dbimou::~dbimou %p key_cap=%zu val_cap=%zu\n",this, key_buf_.capacity(), val_buf_.capacity()); }
|
|
37
|
-
//void Finalize(Napi::Env env) { fprintf(stderr, "~dbimou %p key_cap=%zu val_cap=%zu\n",this, key_buf_.capacity(), val_buf_.capacity()); }
|
|
38
|
-
|
|
39
36
|
Napi::Value get_id(const Napi::CallbackInfo& info) {
|
|
40
37
|
return Napi::BigInt::New(info.Env(), static_cast<int64_t>(id_));
|
|
41
38
|
}
|
package/src/env_arg0.hpp
CHANGED
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
#pragma once
|
|
2
2
|
|
|
3
3
|
#include "valuemou.hpp"
|
|
4
|
-
#ifdef _WIN32
|
|
5
|
-
typedef unsigned short mode_t;
|
|
6
|
-
#endif
|
|
7
4
|
|
|
8
5
|
namespace mdbxmou {
|
|
9
6
|
|
|
@@ -12,7 +9,7 @@ struct env_arg0 {
|
|
|
12
9
|
MDBX_dbi max_dbi{32};
|
|
13
10
|
mdbx::env::geometry geom{};
|
|
14
11
|
env_flag flag{};
|
|
15
|
-
|
|
12
|
+
mdbx_mode_t file_mode{0664};
|
|
16
13
|
std::uint32_t max_readers{128};
|
|
17
14
|
base_flag key_flag{};
|
|
18
15
|
base_flag value_flag{};
|
package/src/envmou.cpp
CHANGED
|
@@ -103,7 +103,7 @@ env_arg0 envmou::parse(const Napi::Value& arg0)
|
|
|
103
103
|
|
|
104
104
|
if (obj.Has("mode")) {
|
|
105
105
|
auto value = obj.Get("mode").As<Napi::Number>();
|
|
106
|
-
rc.file_mode = static_cast<
|
|
106
|
+
rc.file_mode = static_cast<mdbx_mode_t>(value.Int32Value());
|
|
107
107
|
}
|
|
108
108
|
|
|
109
109
|
if (obj.Has("keyFlag")) {
|
|
@@ -234,7 +234,7 @@ Napi::Value envmou::close(const Napi::CallbackInfo& info)
|
|
|
234
234
|
return env.Undefined();
|
|
235
235
|
}
|
|
236
236
|
|
|
237
|
-
if (trx_count_
|
|
237
|
+
if (trx_count_ > 0) {
|
|
238
238
|
throw std::runtime_error("active transactions");
|
|
239
239
|
}
|
|
240
240
|
|
|
@@ -392,10 +392,10 @@ Napi::Value envmou::query(const Napi::CallbackInfo& info)
|
|
|
392
392
|
auto* worker = new async_query(env, *this, mode,
|
|
393
393
|
std::move(query), arg0.IsObject());
|
|
394
394
|
auto promise = worker->GetPromise();
|
|
395
|
-
worker->Queue();
|
|
396
395
|
|
|
397
|
-
// Увеличиваем счетчик
|
|
396
|
+
// Увеличиваем счетчик ДО Queue() — worker гарантированно вызовет --env_
|
|
398
397
|
++(*this);
|
|
398
|
+
worker->Queue();
|
|
399
399
|
|
|
400
400
|
return promise;
|
|
401
401
|
} catch (const std::exception& e) {
|
|
@@ -433,10 +433,10 @@ Napi::Value envmou::keys(const Napi::CallbackInfo& info)
|
|
|
433
433
|
auto* worker = new async_keys(env, *this, mode,
|
|
434
434
|
std::move(query), arg0.IsObject());
|
|
435
435
|
auto promise = worker->GetPromise();
|
|
436
|
-
worker->Queue();
|
|
437
436
|
|
|
438
|
-
// Увеличиваем счетчик
|
|
437
|
+
// Увеличиваем счетчик ДО Queue() — worker гарантированно вызовет --env_
|
|
439
438
|
++(*this);
|
|
439
|
+
worker->Queue();
|
|
440
440
|
|
|
441
441
|
return promise;
|
|
442
442
|
} catch (const std::exception& e) {
|
package/src/envmou.hpp
CHANGED
|
@@ -27,7 +27,8 @@ class envmou final
|
|
|
27
27
|
};
|
|
28
28
|
|
|
29
29
|
std::unique_ptr<MDBX_env, free_env> env_{};
|
|
30
|
-
|
|
30
|
+
// счетчик транзакицй, не требующий атомарности
|
|
31
|
+
std::size_t trx_count_{};
|
|
31
32
|
env_arg0 arg0_{};
|
|
32
33
|
std::mutex lock_{};
|
|
33
34
|
|
|
@@ -106,7 +107,7 @@ public:
|
|
|
106
107
|
}
|
|
107
108
|
|
|
108
109
|
void do_close() {
|
|
109
|
-
if (trx_count_
|
|
110
|
+
if (trx_count_ > 0) {
|
|
110
111
|
throw std::runtime_error("transaction in progress");
|
|
111
112
|
}
|
|
112
113
|
env_.reset();
|
package/src/modulemou.cpp
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
#include "envmou.hpp"
|
|
2
|
+
#include "cursormou.hpp"
|
|
2
3
|
|
|
3
4
|
using namespace Napi;
|
|
4
5
|
|
|
@@ -106,6 +107,7 @@ Napi::Object Init(Napi::Env env, Napi::Object exports)
|
|
|
106
107
|
mdbxmou::envmou::init("MDBX_Env", env, exports);
|
|
107
108
|
mdbxmou::txnmou::init("MDBX_Txn", env);
|
|
108
109
|
mdbxmou::dbimou::init("MDBX_Dbi", env);
|
|
110
|
+
mdbxmou::cursormou::init("MDBX_Cursor", env);
|
|
109
111
|
|
|
110
112
|
return exports;
|
|
111
113
|
}
|
package/src/txnmou.cpp
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#include "txnmou.hpp"
|
|
2
2
|
#include "envmou.hpp"
|
|
3
|
+
#include "cursormou.hpp"
|
|
3
4
|
|
|
4
5
|
namespace mdbxmou {
|
|
5
6
|
|
|
@@ -11,6 +12,7 @@ void txnmou::init(const char *class_name, Napi::Env env) {
|
|
|
11
12
|
InstanceMethod("abort", &txnmou::abort),
|
|
12
13
|
InstanceMethod("openMap", &txnmou::open_map),
|
|
13
14
|
InstanceMethod("createMap", &txnmou::create_map),
|
|
15
|
+
InstanceMethod("openCursor", &txnmou::open_cursor),
|
|
14
16
|
InstanceMethod("isActive", &txnmou::is_active),
|
|
15
17
|
});
|
|
16
18
|
|
|
@@ -21,18 +23,19 @@ void txnmou::init(const char *class_name, Napi::Env env) {
|
|
|
21
23
|
Napi::Value txnmou::commit(const Napi::CallbackInfo& info) {
|
|
22
24
|
Napi::Env env = info.Env();
|
|
23
25
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
26
|
+
if (!txn_) {
|
|
27
|
+
throw Napi::Error::New(env, "txn already completed");
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (cursor_count_ > 0) {
|
|
31
|
+
throw Napi::Error::New(env,
|
|
32
|
+
"txn commit: " + std::to_string(cursor_count_) + " cursor(s) still open");
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
dec_counter();
|
|
36
|
+
auto rc = mdbx_txn_commit(txn_.release());
|
|
37
|
+
if (rc != MDBX_SUCCESS) {
|
|
38
|
+
throw Napi::Error::New(env, std::string("txn commit: ") + mdbx_strerror(rc));
|
|
36
39
|
}
|
|
37
40
|
|
|
38
41
|
return env.Undefined();
|
|
@@ -41,18 +44,19 @@ Napi::Value txnmou::commit(const Napi::CallbackInfo& info) {
|
|
|
41
44
|
Napi::Value txnmou::abort(const Napi::CallbackInfo& info) {
|
|
42
45
|
Napi::Env env = info.Env();
|
|
43
46
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
47
|
+
if (!txn_) {
|
|
48
|
+
throw Napi::Error::New(env, "txn already completed");
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (cursor_count_ > 0) {
|
|
52
|
+
throw Napi::Error::New(env,
|
|
53
|
+
"txn abort: " + std::to_string(cursor_count_) + " cursor(s) still open");
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
dec_counter();
|
|
57
|
+
auto rc = mdbx_txn_abort(txn_.release());
|
|
58
|
+
if (rc != MDBX_SUCCESS) {
|
|
59
|
+
throw Napi::Error::New(env, std::string("txn abort: ") + mdbx_strerror(rc));
|
|
56
60
|
}
|
|
57
61
|
|
|
58
62
|
return env.Undefined();
|
|
@@ -70,73 +74,97 @@ Napi::Value txnmou::get_dbi(const Napi::CallbackInfo& info, db_mode db_mode)
|
|
|
70
74
|
{
|
|
71
75
|
Napi::Env env = info.Env();
|
|
72
76
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
throw std::runtime_error("dbi: cannot open DB in read-only transaction");
|
|
77
|
-
}
|
|
77
|
+
if ((mode_.val & txn_mode::ro) && (db_mode.val & db_mode::create)) {
|
|
78
|
+
throw Napi::Error::New(env, "dbi: cannot open DB in read-only transaction");
|
|
79
|
+
}
|
|
78
80
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
81
|
+
key_mode key_mode{};
|
|
82
|
+
value_mode value_mode{};
|
|
83
|
+
std::string db_name{};
|
|
84
|
+
auto conf = get_env_userctx(*env_);
|
|
85
|
+
auto key_flag = conf->key_flag;
|
|
86
|
+
auto value_flag = conf->value_flag;
|
|
87
|
+
auto arg_count = info.Length();
|
|
88
|
+
if (arg_count == 3) {
|
|
89
|
+
auto arg0 = info[0]; // db_name
|
|
90
|
+
auto arg1 = info[1]; // key_mode
|
|
91
|
+
auto arg2 = info[2]; // value_mode
|
|
92
|
+
db_name = arg0.As<Napi::String>().Utf8Value();
|
|
93
|
+
key_mode = parse_key_mode(env, arg1, key_flag);
|
|
94
|
+
value_mode = value_mode::parse(arg2);
|
|
95
|
+
} else if (arg_count == 2) {
|
|
96
|
+
// db_name + key_mode || key_mode + value_mode
|
|
97
|
+
auto arg0 = info[0];
|
|
98
|
+
auto arg1 = info[1];
|
|
99
|
+
if (arg0.IsString()) {
|
|
90
100
|
db_name = arg0.As<Napi::String>().Utf8Value();
|
|
91
101
|
key_mode = parse_key_mode(env, arg1, key_flag);
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
// db_name || key_mode
|
|
108
|
-
auto arg0 = info[0];
|
|
109
|
-
if (arg0.IsString()) {
|
|
110
|
-
db_name = arg0.As<Napi::String>().Utf8Value();
|
|
111
|
-
} else if (arg0.IsNumber() || arg0.IsBigInt()) {
|
|
112
|
-
key_mode = parse_key_mode(env, arg0, key_flag);
|
|
113
|
-
} else {
|
|
114
|
-
throw Napi::Error::New(env, "Invalid argument type: expected string (db_name) or number (key_mode)");
|
|
115
|
-
}
|
|
102
|
+
} else if (arg0.IsNumber() || arg0.IsBigInt()) {
|
|
103
|
+
key_mode = parse_key_mode(env, arg0, key_flag);
|
|
104
|
+
value_mode = value_mode::parse(arg1);
|
|
105
|
+
} else {
|
|
106
|
+
throw Napi::Error::New(env, "Invalid argument type for db_name or value_mode");
|
|
107
|
+
}
|
|
108
|
+
} else if (arg_count == 1) {
|
|
109
|
+
// db_name || key_mode
|
|
110
|
+
auto arg0 = info[0];
|
|
111
|
+
if (arg0.IsString()) {
|
|
112
|
+
db_name = arg0.As<Napi::String>().Utf8Value();
|
|
113
|
+
} else if (arg0.IsNumber() || arg0.IsBigInt()) {
|
|
114
|
+
key_mode = parse_key_mode(env, arg0, key_flag);
|
|
115
|
+
} else {
|
|
116
|
+
throw Napi::Error::New(env, "Invalid argument type: expected string (db_name) or number (key_mode)");
|
|
116
117
|
}
|
|
117
|
-
|
|
118
|
+
}
|
|
119
|
+
// arg_count == 0: используем значения по умолчанию (строковый ключ, default db)
|
|
118
120
|
|
|
119
|
-
|
|
121
|
+
if (!txn_) {
|
|
122
|
+
throw Napi::Error::New(env, "txn already completed");
|
|
123
|
+
}
|
|
120
124
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
}
|
|
127
|
-
// создаем новый объект dbi
|
|
128
|
-
auto obj = dbimou::ctor.New({});
|
|
129
|
-
auto ptr = dbimou::Unwrap(obj);
|
|
130
|
-
ptr->attach(dbi, db_mode, key_mode,
|
|
131
|
-
value_mode, key_flag, value_flag);
|
|
132
|
-
return obj;
|
|
125
|
+
MDBX_dbi dbi{};
|
|
126
|
+
auto flags = static_cast<MDBX_db_flags_t>(db_mode.val|key_mode.val|value_mode.val);
|
|
127
|
+
auto rc = mdbx_dbi_open(*this, db_name.empty() ? nullptr : db_name.c_str(), flags, &dbi);
|
|
128
|
+
if (rc != MDBX_SUCCESS) {
|
|
129
|
+
throw Napi::Error::New(env, std::string("mdbx_dbi_open: ") + mdbx_strerror(rc));
|
|
133
130
|
}
|
|
134
|
-
|
|
135
|
-
{
|
|
136
|
-
|
|
131
|
+
// создаем новый объект dbi
|
|
132
|
+
auto obj = dbimou::ctor.New({});
|
|
133
|
+
auto ptr = dbimou::Unwrap(obj);
|
|
134
|
+
ptr->attach(dbi, db_mode, key_mode,
|
|
135
|
+
value_mode, key_flag, value_flag);
|
|
136
|
+
return obj;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
Napi::Value txnmou::open_cursor(const Napi::CallbackInfo& info) {
|
|
140
|
+
Napi::Env env = info.Env();
|
|
141
|
+
|
|
142
|
+
if (!txn_) {
|
|
143
|
+
throw Napi::Error::New(env, "txn not active");
|
|
137
144
|
}
|
|
138
145
|
|
|
139
|
-
|
|
146
|
+
if (info.Length() < 1 || !info[0].IsObject()) {
|
|
147
|
+
throw Napi::Error::New(env, "dbi required");
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
auto arg0 = info[0].As<Napi::Object>();
|
|
151
|
+
if (!arg0.InstanceOf(dbimou::ctor.Value())) {
|
|
152
|
+
throw Napi::TypeError::New(env, "openCursor: first argument must be MDBX_Dbi instance");
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
auto* dbi = dbimou::Unwrap(arg0);
|
|
156
|
+
|
|
157
|
+
MDBX_cursor* cursor{};
|
|
158
|
+
int rc = mdbx_cursor_open(txn_.get(), dbi->get_id(), &cursor);
|
|
159
|
+
|
|
160
|
+
if (rc != MDBX_SUCCESS) {
|
|
161
|
+
throw Napi::Error::New(env, std::string("mdbx_cursor_open: ") + mdbx_strerror(rc));
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
auto obj = cursormou::ctor.New({});
|
|
165
|
+
auto ptr = cursormou::Unwrap(obj);
|
|
166
|
+
ptr->attach(*this, *dbi, cursor);
|
|
167
|
+
return obj;
|
|
140
168
|
}
|
|
141
169
|
|
|
142
170
|
Napi::Value txnmou::is_active(const Napi::CallbackInfo& info) {
|
package/src/txnmou.hpp
CHANGED
|
@@ -10,17 +10,19 @@ class txnmou final
|
|
|
10
10
|
: public Napi::ObjectWrap<txnmou>
|
|
11
11
|
{
|
|
12
12
|
private:
|
|
13
|
-
envmou* env_{nullptr};
|
|
13
|
+
envmou* env_{nullptr};
|
|
14
|
+
|
|
15
|
+
// свободу txn
|
|
16
|
+
struct free_txn
|
|
17
|
+
{
|
|
18
|
+
void operator()(MDBX_txn *txn) const noexcept {
|
|
19
|
+
mdbx_txn_abort(txn);
|
|
20
|
+
}
|
|
21
|
+
};
|
|
14
22
|
|
|
15
|
-
std::unique_ptr<MDBX_txn,
|
|
16
|
-
txnmou_managed::free_txn> txn_{};
|
|
23
|
+
std::unique_ptr<MDBX_txn, free_txn> txn_{};
|
|
17
24
|
txn_mode mode_{};
|
|
18
|
-
|
|
19
|
-
void check() const {
|
|
20
|
-
if (!txn_) {
|
|
21
|
-
throw std::runtime_error("txn: inactive");
|
|
22
|
-
}
|
|
23
|
-
}
|
|
25
|
+
std::size_t cursor_count_{};
|
|
24
26
|
|
|
25
27
|
// Уменьшает счетчик транзакций
|
|
26
28
|
void dec_counter() noexcept;
|
|
@@ -40,8 +42,6 @@ public:
|
|
|
40
42
|
}
|
|
41
43
|
}
|
|
42
44
|
|
|
43
|
-
//void Finalize(Napi::Env env) { fprintf(stderr, "txnmou::Finalize %p\n", this); }
|
|
44
|
-
|
|
45
45
|
static void init(const char *class_name, Napi::Env env);
|
|
46
46
|
|
|
47
47
|
Napi::Value commit(const Napi::CallbackInfo&);
|
|
@@ -52,11 +52,18 @@ public:
|
|
|
52
52
|
Napi::Value create_map(const Napi::CallbackInfo& info) {
|
|
53
53
|
return get_dbi(info, {db_mode::create});
|
|
54
54
|
}
|
|
55
|
+
|
|
56
|
+
Napi::Value open_cursor(const Napi::CallbackInfo&);
|
|
55
57
|
|
|
56
|
-
operator MDBX_txn*()
|
|
58
|
+
operator MDBX_txn*() noexcept {
|
|
57
59
|
return txn_.get();
|
|
58
60
|
}
|
|
59
61
|
|
|
62
|
+
// Cursor counting
|
|
63
|
+
txnmou& operator++() noexcept { ++cursor_count_; return *this; }
|
|
64
|
+
txnmou& operator--() noexcept { --cursor_count_; return *this; }
|
|
65
|
+
std::size_t cursor_count() const noexcept { return cursor_count_; }
|
|
66
|
+
|
|
60
67
|
Napi::Value is_active(const Napi::CallbackInfo&);
|
|
61
68
|
|
|
62
69
|
void attach(envmou& env, MDBX_txn* txn, txn_mode mode);
|
package/src/typemou.hpp
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
#include <mdbx.h++>
|
|
5
5
|
#include <cstdint>
|
|
6
6
|
#include <vector>
|
|
7
|
+
#include <utility>
|
|
7
8
|
|
|
8
9
|
namespace mdbxmou {
|
|
9
10
|
|
|
@@ -12,57 +13,39 @@ using buffer_type = std::vector<char>;
|
|
|
12
13
|
struct txnmou_managed final
|
|
13
14
|
: mdbx::txn
|
|
14
15
|
{
|
|
15
|
-
|
|
16
|
-
struct free_txn
|
|
17
|
-
{
|
|
18
|
-
void operator()(MDBX_txn *txn) const noexcept {
|
|
19
|
-
mdbx_txn_abort(txn);
|
|
20
|
-
}
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
std::unique_ptr<MDBX_txn, free_txn> guard_;
|
|
24
|
-
|
|
25
|
-
txnmou_managed(MDBX_txn* txn) noexcept
|
|
16
|
+
txnmou_managed(MDBX_txn *txn) noexcept
|
|
26
17
|
: mdbx::txn(txn)
|
|
27
|
-
, guard_(txn)
|
|
28
18
|
{ }
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
{
|
|
35
|
-
other.txn::handle_ = nullptr;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
// Запрет копирования
|
|
19
|
+
|
|
20
|
+
txnmou_managed(txnmou_managed &&other) noexcept
|
|
21
|
+
: mdbx::txn(std::exchange(other.txn::handle_, nullptr))
|
|
22
|
+
{ }
|
|
23
|
+
|
|
39
24
|
txnmou_managed(const txnmou_managed&) = delete;
|
|
40
25
|
txnmou_managed& operator=(const txnmou_managed&) = delete;
|
|
41
26
|
|
|
42
|
-
~txnmou_managed() noexcept
|
|
43
|
-
|
|
44
|
-
txn::handle_
|
|
27
|
+
~txnmou_managed() noexcept
|
|
28
|
+
{
|
|
29
|
+
if (txn::handle_) {
|
|
30
|
+
::mdbx_txn_abort(txn::handle_);
|
|
31
|
+
}
|
|
45
32
|
}
|
|
46
33
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
throw std::runtime_error(::mdbx_strerror(rc));
|
|
54
|
-
}
|
|
34
|
+
void commit()
|
|
35
|
+
{
|
|
36
|
+
auto rc = ::mdbx_txn_commit(
|
|
37
|
+
std::exchange(txn::handle_, nullptr));
|
|
38
|
+
if (rc != MDBX_SUCCESS) {
|
|
39
|
+
throw std::runtime_error(::mdbx_strerror(rc));
|
|
55
40
|
}
|
|
56
41
|
}
|
|
57
42
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
throw std::runtime_error(::mdbx_strerror(rc));
|
|
65
|
-
}
|
|
43
|
+
void abort()
|
|
44
|
+
{
|
|
45
|
+
auto rc = ::mdbx_txn_abort(
|
|
46
|
+
std::exchange(txn::handle_, nullptr));
|
|
47
|
+
if (rc != MDBX_SUCCESS) {
|
|
48
|
+
throw std::runtime_error(::mdbx_strerror(rc));
|
|
66
49
|
}
|
|
67
50
|
}
|
|
68
51
|
};
|
|
@@ -70,36 +53,22 @@ struct txnmou_managed final
|
|
|
70
53
|
struct cursormou_managed final
|
|
71
54
|
: mdbx::cursor
|
|
72
55
|
{
|
|
73
|
-
// свободу курсору
|
|
74
|
-
struct free_cursor
|
|
75
|
-
{
|
|
76
|
-
void operator()(MDBX_cursor* c) const noexcept {
|
|
77
|
-
if (c) ::mdbx_cursor_close(c);
|
|
78
|
-
}
|
|
79
|
-
};
|
|
80
|
-
|
|
81
|
-
std::unique_ptr<MDBX_cursor, free_cursor> guard_;
|
|
82
|
-
|
|
83
56
|
explicit cursormou_managed(MDBX_cursor* cursor) noexcept
|
|
84
57
|
: mdbx::cursor(cursor)
|
|
85
|
-
|
|
58
|
+
{ }
|
|
59
|
+
|
|
60
|
+
cursormou_managed(cursormou_managed &&other) noexcept
|
|
61
|
+
: mdbx::cursor(std::exchange(other.cursor::handle_, nullptr))
|
|
86
62
|
{ }
|
|
87
63
|
|
|
88
|
-
// Move конструктор
|
|
89
|
-
cursormou_managed(cursormou_managed&& other) noexcept
|
|
90
|
-
: mdbx::cursor(other.cursor::handle_)
|
|
91
|
-
, guard_(std::move(other.guard_))
|
|
92
|
-
{
|
|
93
|
-
other.cursor::handle_ = nullptr;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
// Запрет копирования
|
|
97
64
|
cursormou_managed(const cursormou_managed&) = delete;
|
|
98
65
|
cursormou_managed& operator=(const cursormou_managed&) = delete;
|
|
99
66
|
|
|
100
|
-
~cursormou_managed() noexcept
|
|
101
|
-
|
|
102
|
-
cursor::handle_
|
|
67
|
+
~cursormou_managed() noexcept
|
|
68
|
+
{
|
|
69
|
+
if (cursor::handle_) {
|
|
70
|
+
::mdbx_cursor_close(cursor::handle_);
|
|
71
|
+
}
|
|
103
72
|
}
|
|
104
73
|
};
|
|
105
74
|
|
package/src/valuemou.hpp
CHANGED
|
@@ -4,11 +4,18 @@
|
|
|
4
4
|
|
|
5
5
|
namespace mdbxmou {
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
// Обёртка над mdbx::slice — НЕ ВЛАДЕЕТ памятью.
|
|
8
|
+
// Хранит указатель на внешний буфер (buffer_type или stack).
|
|
9
|
+
// Copy и move эквивалентны — просто копируют указатель и размер.
|
|
10
|
+
// ВАЖНО: не использовать после уничтожения буфера!
|
|
11
|
+
struct valuemou
|
|
12
|
+
: mdbx::slice
|
|
9
13
|
{
|
|
10
|
-
public:
|
|
11
14
|
valuemou() = default;
|
|
15
|
+
valuemou(const valuemou&) = default;
|
|
16
|
+
valuemou& operator=(const valuemou&) = default;
|
|
17
|
+
valuemou(valuemou&&) = default;
|
|
18
|
+
valuemou& operator=(valuemou&&) = default;
|
|
12
19
|
|
|
13
20
|
valuemou(const mdbx::slice& arg0) noexcept
|
|
14
21
|
: mdbx::slice{arg0}
|
|
@@ -39,10 +46,10 @@ public:
|
|
|
39
46
|
throw Napi::Error::New(env, "napi_get_value_string_utf8 length");
|
|
40
47
|
}
|
|
41
48
|
|
|
42
|
-
mem.
|
|
49
|
+
mem.resize(length + 1);
|
|
43
50
|
|
|
44
51
|
status = napi_get_value_string_utf8(
|
|
45
|
-
env, arg0, mem.data(), mem.
|
|
52
|
+
env, arg0, mem.data(), mem.size(), nullptr);
|
|
46
53
|
if (status != napi_ok) {
|
|
47
54
|
throw Napi::Error::New(env, "napi_get_value_string_utf8 copyout");
|
|
48
55
|
}
|
|
@@ -76,11 +83,14 @@ public:
|
|
|
76
83
|
}
|
|
77
84
|
};
|
|
78
85
|
|
|
79
|
-
|
|
80
|
-
:
|
|
86
|
+
struct keymou final
|
|
87
|
+
: valuemou
|
|
81
88
|
{
|
|
82
|
-
public:
|
|
83
89
|
keymou() = default;
|
|
90
|
+
keymou(const keymou&) = default;
|
|
91
|
+
keymou& operator=(const keymou&) = default;
|
|
92
|
+
keymou(keymou&&) = default;
|
|
93
|
+
keymou& operator=(keymou&&) = default;
|
|
84
94
|
|
|
85
95
|
keymou(const valuemou& arg0) noexcept
|
|
86
96
|
: valuemou{arg0}
|