mdbxmou 0.3.2 → 0.3.4

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 CHANGED
@@ -47,6 +47,13 @@ if(MSVC)
47
47
  target_link_libraries(${PROJECT_NAME} ntdll.lib)
48
48
  endif()
49
49
 
50
+ # Batch read limit for mdbx_cursor_get_batch (pairs count = limit / 2)
51
+ if(NOT DEFINED MDBXMOU_BATCH_LIMIT)
52
+ set(MDBXMOU_BATCH_LIMIT 512)
53
+ endif()
54
+ add_definitions(-DMDBXMOU_BATCH_LIMIT=${MDBXMOU_BATCH_LIMIT})
55
+ message(STATUS "MDBXMOU_BATCH_LIMIT: ${MDBXMOU_BATCH_LIMIT}")
56
+
50
57
  # Include N-API wrappers
51
58
  execute_process(COMMAND node -p "require('node-addon-api').include"
52
59
  WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
package/README.md CHANGED
@@ -88,6 +88,58 @@ Options:
88
88
 
89
89
  Note: When `keyFlag` or `valueFlag` are set at environment level, they become defaults for all subsequent operations unless explicitly overridden.
90
90
 
91
+ ### Key Type Configuration
92
+
93
+ The library uses a two-level system for configuring key types:
94
+
95
+ #### Level 1: Environment (`keyFlag`)
96
+
97
+ When opening the environment, `keyFlag` controls how **string/binary keys** are returned:
98
+
99
+ | `keyFlag` value | String/Binary keys returned as |
100
+ |-----------------|-------------------------------|
101
+ | `0` (default) | `Buffer` |
102
+ | `keyFlag.string` (2) | `String` |
103
+
104
+ ```javascript
105
+ // Keys returned as Buffer (default)
106
+ await env.open({ path: './data' });
107
+
108
+ // Keys returned as String
109
+ await env.open({
110
+ path: './data',
111
+ keyFlag: MDBX_Param.keyFlag.string
112
+ });
113
+ ```
114
+
115
+ #### Level 2: Database (`keyMode`)
116
+
117
+ When opening/creating a database, the **argument type** determines how **ordinal (integer) keys** are returned:
118
+
119
+ | Argument type | Ordinal keys returned as |
120
+ |---------------|-------------------------|
121
+ | `Number` | `Number` |
122
+ | `BigInt` | `BigInt` |
123
+
124
+ ```javascript
125
+ // Ordinal keys as Number
126
+ const dbi = txn.openMap(MDBX_Param.keyMode.ordinal); // keyMode.ordinal = 8
127
+ dbi.keys(txn); // [0, 1, 2, 3, ...]
128
+
129
+ // Ordinal keys as BigInt
130
+ const dbi = txn.openMap(BigInt(MDBX_Param.keyMode.ordinal)); // BigInt(8)
131
+ dbi.keys(txn); // [0n, 1n, 2n, 3n, ...]
132
+ ```
133
+
134
+ #### Summary
135
+
136
+ | Key type | Configuration level | Option | Result type |
137
+ |----------|--------------------|--------------------|-------------|
138
+ | String/Binary | env.open() | `keyFlag: 0` | `Buffer` |
139
+ | String/Binary | env.open() | `keyFlag: keyFlag.string` | `String` |
140
+ | Ordinal | openMap/createMap | `keyMode.ordinal` (Number) | `Number` |
141
+ | Ordinal | openMap/createMap | `BigInt(keyMode.ordinal)` | `BigInt` |
142
+
91
143
  **close() → Promise**
92
144
  ```javascript
93
145
  await env.close();
package/lib/types.d.ts CHANGED
@@ -147,6 +147,36 @@ export interface MDBX_Cursor<K extends MDBXKey = MDBXKey, V extends MDBXValue =
147
147
  */
148
148
  forEach(callback: (item: MDBXCursorResult<K, V>) => boolean | void, backward?: boolean): void;
149
149
 
150
+ /**
151
+ * Check if cursor is at end-of-data (beyond first or last record).
152
+ * @returns true if cursor has no valid position
153
+ */
154
+ eof(): boolean;
155
+
156
+ /**
157
+ * Check if cursor is on first record in database.
158
+ * @returns true if on first record
159
+ */
160
+ onFirst(): boolean;
161
+
162
+ /**
163
+ * Check if cursor is on last record in database.
164
+ * @returns true if on last record
165
+ */
166
+ onLast(): boolean;
167
+
168
+ /**
169
+ * Check if cursor is on first value of current key (DUPSORT databases).
170
+ * @returns true if on first value for current key
171
+ */
172
+ onFirstMultival(): boolean;
173
+
174
+ /**
175
+ * Check if cursor is on last value of current key (DUPSORT databases).
176
+ * @returns true if on last value for current key
177
+ */
178
+ onLastMultival(): boolean;
179
+
150
180
  /**
151
181
  * Close cursor. Must be called before transaction commit/abort.
152
182
  * Safe to call multiple times.
package/package.json CHANGED
@@ -65,7 +65,7 @@
65
65
  },
66
66
  "gypfile": true,
67
67
  "name": "mdbxmou",
68
- "version": "0.3.2",
68
+ "version": "0.3.4",
69
69
  "description": "Node bindings for mdbx",
70
70
  "repository": {
71
71
  "type": "git",
@@ -5,9 +5,6 @@ namespace mdbxmou {
5
5
 
6
6
  void async_close::Execute()
7
7
  {
8
- // выдадим идентфикатор потока для лога (thread_id)
9
- // fprintf(stderr, "TRACE: async_close id=%d\n", gettid());
10
-
11
8
  try {
12
9
  that_.do_close();
13
10
  } catch (const std::exception& e) {
@@ -5,9 +5,6 @@ namespace mdbxmou {
5
5
 
6
6
  void async_copy::Execute()
7
7
  {
8
- // выдадим идентфикатор потока для лога (thread_id)
9
- // fprintf(stderr, "TRACE: async_copy id=%d\n", gettid());
10
-
11
8
  auto rc = mdbx_env_copy(that_, dest_.c_str(), flags_);
12
9
  if (rc != MDBX_SUCCESS) {
13
10
  SetError(mdbx_strerror(rc));
@@ -1,6 +1,7 @@
1
1
  #include "envmou_keys.hpp"
2
2
  #include "envmou.hpp"
3
3
  #include "dbimou.hpp"
4
+ #include "../valuemou.hpp"
4
5
 
5
6
  namespace mdbxmou {
6
7
 
@@ -92,27 +93,50 @@ void async_keys::do_keys(txnmou_managed& txn,
92
93
  auto stat = dbimou::get_stat(txn, dbi);
93
94
  item.reserve(stat.ms_entries);
94
95
 
95
- auto cursor = txn.open_cursor(dbi);
96
- if (mdbx::is_ordinal(arg0.key_mod)) {
97
- cursor.scan([&](const mdbx::pair& f) {
98
- async_key rc{};
99
- rc.id_buf = f.key.as_uint64();
100
- item.push_back(std::move(rc));
101
- return false;
102
- });
103
- } else {
104
- cursor.scan([&](const mdbx::pair& f) {
105
- async_key rc{};
106
- rc.key_buf.assign(f.key.char_ptr(), f.key.end_char_ptr());
107
- item.push_back(std::move(rc));
108
- return false;
109
- });
110
- }
96
+ // Используем batch версию для лучшей производительности
97
+ do_keys_batch(txn, dbi, arg0);
111
98
  } else {
112
99
  do_keys_from(txn, dbi, arg0);
113
100
  }
114
101
  }
115
102
 
103
+ // Batch версия - читает ключи блоками через cursormou_managed::get_batch
104
+ void async_keys::do_keys_batch(txnmou_managed& txn,
105
+ mdbx::map_handle dbi, keys_line& arg0)
106
+ {
107
+ auto& item = arg0.item;
108
+ const bool is_ordinal = mdbx::is_ordinal(arg0.key_mod);
109
+
110
+ auto cursor = dbi::get_cursor(txn, dbi);
111
+
112
+ // Буфер для batch - MDBXMOU_BATCH_LIMIT/2 пар (key, value)
113
+ #ifndef MDBXMOU_BATCH_LIMIT
114
+ #define MDBXMOU_BATCH_LIMIT 512
115
+ #endif
116
+ std::array<mdbx::slice, MDBXMOU_BATCH_LIMIT> pairs;
117
+
118
+ // Первый вызов с MDBX_FIRST
119
+ size_t count = cursor.get_batch(pairs, MDBX_FIRST);
120
+
121
+ while (count > 0) {
122
+ // pairs[0] = key1, pairs[1] = value1, pairs[2] = key2, ...
123
+ for (size_t i = 0; i < count; i += 2) {
124
+ keymou key{pairs[i]};
125
+ async_key key_item{};
126
+ if (is_ordinal) {
127
+ key_item.id_buf = key.as_uint64();
128
+ } else {
129
+ // Ключ - строка/буфер
130
+ key_item.key_buf.assign(key.char_ptr(), key.end_char_ptr());
131
+ }
132
+ item.push_back(std::move(key_item));
133
+ }
134
+
135
+ // Следующий batch
136
+ count = cursor.get_batch(pairs, MDBX_NEXT);
137
+ }
138
+ }
139
+
116
140
  void async_keys::do_keys_from(txnmou_managed& txn,
117
141
  mdbx::map_handle dbi, keys_line& arg0)
118
142
  {
@@ -43,6 +43,9 @@ public:
43
43
  void do_keys(txnmou_managed& txn,
44
44
  mdbx::map_handle dbi, keys_line& arg0);
45
45
 
46
+ void do_keys_batch(txnmou_managed& txn,
47
+ mdbx::map_handle dbi, keys_line& arg0);
48
+
46
49
  void do_keys_from(txnmou_managed& txn,
47
50
  mdbx::map_handle dbi, keys_line& arg0);
48
51
  };
@@ -5,9 +5,6 @@ namespace mdbxmou {
5
5
 
6
6
  void async_open::Execute()
7
7
  {
8
- // выдадим идентфикатор потока для лога (thread_id)
9
- // fprintf(stderr, "TRACE: async_open id=%d\n", gettid());
10
-
11
8
  try {
12
9
  auto e = envmou::create_and_open(arg0_);
13
10
  that_.attach(e, arg0_);
@@ -18,9 +15,6 @@ void async_open::Execute()
18
15
 
19
16
  void async_open::OnOK()
20
17
  {
21
- // выдадим идентфикатор потока для лога (thread_id)
22
- // fprintf(stderr, "TRACE: async_open OnOK id=%d\n", gettid());
23
-
24
18
  auto env = Env();
25
19
 
26
20
  that_.unlock();
@@ -141,7 +141,7 @@ void async_query::do_get(const txnmou_managed& txn,
141
141
  {
142
142
  auto key = mdbx::is_ordinal(key_mode) ?
143
143
  keymou{q.id_buf} : keymou{q.key_buf};
144
- mdbx::slice abs{};
144
+ mdbx::slice abs;
145
145
  valuemou val{txn.get(dbi, key, abs)};
146
146
  q.set(val);
147
147
  }
package/src/cursormou.cpp CHANGED
@@ -2,272 +2,234 @@
2
2
  #include "dbimou.hpp"
3
3
  #include "txnmou.hpp"
4
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
5
+ namespace mdbxmou
30
6
  {
31
- if (cursor_) {
32
- ::mdbx_cursor_close(std::exchange(cursor_, nullptr));
33
- if (txn_) {
34
- --(*txn_);
35
- }
36
- }
37
- }
38
7
 
39
- cursormou::~cursormou()
40
- {
41
- do_close();
42
- }
8
+ Napi::FunctionReference cursormou::ctor{};
43
9
 
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));
10
+ void cursormou::init(const char *class_name, Napi::Env env)
11
+ {
12
+ auto func = DefineClass(env, class_name, {
13
+ InstanceMethod("first", &cursormou::first),
14
+ InstanceMethod("last", &cursormou::last),
15
+ InstanceMethod("next", &cursormou::next),
16
+ InstanceMethod("prev", &cursormou::prev),
17
+ InstanceMethod("seek", &cursormou::seek),
18
+ InstanceMethod("seekGE", &cursormou::seek_ge),
19
+ InstanceMethod("current", &cursormou::current),
20
+ InstanceMethod("eof", &cursormou::eof),
21
+ InstanceMethod("onFirst", &cursormou::on_first),
22
+ InstanceMethod("onLast", &cursormou::on_last),
23
+ InstanceMethod("onFirstMultival", &cursormou::on_first_multival),
24
+ InstanceMethod("onLastMultival", &cursormou::on_last_multival),
25
+ InstanceMethod("put", &cursormou::put),
26
+ InstanceMethod("del", &cursormou::del),
27
+ InstanceMethod("forEach", &cursormou::for_each),
28
+ InstanceMethod("close", &cursormou::close),
29
+ });
30
+
31
+ ctor = Napi::Persistent(func);
32
+ ctor.SuppressDestruct();
68
33
  }
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));
34
+
35
+ void cursormou::do_close() noexcept
36
+ {
37
+ if (cursor_) {
38
+ ::mdbx_cursor_close(std::exchange(cursor_, nullptr));
39
+ if (txn_) {
40
+ --(*txn_);
41
+ }
83
42
  }
84
- } else {
85
- result.Set("key", key.to_string(env));
86
43
  }
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));
44
+
45
+ cursormou::~cursormou()
46
+ {
47
+ do_close();
93
48
  }
94
-
95
- return result;
96
- }
97
49
 
98
- Napi::Value cursormou::first(const Napi::CallbackInfo& info) {
99
- return move(info.Env(), MDBX_FIRST);
100
- }
50
+ void cursormou::attach(txnmou &txn, dbimou &dbi, MDBX_cursor *cursor)
51
+ {
52
+ txn_ = &(++txn);
53
+ dbi_ = &dbi;
54
+ cursor_ = cursor;
55
+ }
101
56
 
102
- Napi::Value cursormou::last(const Napi::CallbackInfo& info) {
103
- return move(info.Env(), MDBX_LAST);
104
- }
57
+ // Внутренний хелпер для навигации
58
+ Napi::Value cursormou::move(const Napi::Env &env, MDBX_cursor_op op)
59
+ {
60
+ if (!cursor_) {
61
+ throw Napi::Error::New(env, "cursor closed");
62
+ }
105
63
 
106
- Napi::Value cursormou::next(const Napi::CallbackInfo& info) {
107
- return move(info.Env(), MDBX_NEXT);
108
- }
64
+ keymou key{};
65
+ valuemou val{};
66
+ auto rc = mdbx_cursor_get(cursor_, key, val, op);
67
+ if (rc == MDBX_NOTFOUND) {
68
+ return env.Undefined();
69
+ }
109
70
 
110
- Napi::Value cursormou::prev(const Napi::CallbackInfo& info) {
111
- return move(info.Env(), MDBX_PREV);
112
- }
71
+ if (rc != MDBX_SUCCESS) {
72
+ throw Napi::Error::New(env, mdbx_strerror(rc));
73
+ }
113
74
 
114
- Napi::Value cursormou::current(const Napi::CallbackInfo& info) {
115
- return move(info.Env(), MDBX_GET_CURRENT);
116
- }
75
+ // Возвращаем {key, value}
76
+ auto result = Napi::Object::New(env);
117
77
 
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
- }
78
+ auto key_mode = dbi_->get_key_mode();
79
+ auto key_flag = dbi_->get_key_flag();
80
+ auto value_flag = dbi_->get_value_flag();
142
81
 
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));
82
+ // Ключ
83
+ if (key_mode.val & key_mode::ordinal) {
84
+ if (key_flag.val & base_flag::bigint) {
85
+ result.Set("key", key.to_bigint(env));
86
+ } else {
87
+ result.Set("key", key.to_number(env));
88
+ }
157
89
  } else {
158
- result.Set("key", key.to_number(env));
90
+ result.Set("key", key.to_string(env));
159
91
  }
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));
92
+
93
+ // Значение
94
+ if (value_flag.val & base_flag::string) {
95
+ result.Set("value", val.to_string(env));
96
+ } else {
97
+ result.Set("value", val.to_buffer(env));
98
+ }
99
+
100
+ return result;
169
101
  }
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");
102
+
103
+ Napi::Value cursormou::first(const Napi::CallbackInfo &info)
104
+ {
105
+ return move(info.Env(), MDBX_FIRST);
187
106
  }
188
-
189
- if (info.Length() < 2) {
190
- throw Napi::Error::New(env, "key and value required");
107
+
108
+ Napi::Value cursormou::last(const Napi::CallbackInfo &info)
109
+ {
110
+ return move(info.Env(), MDBX_LAST);
191
111
  }
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());
112
+
113
+ Napi::Value cursormou::next(const Napi::CallbackInfo &info)
114
+ {
115
+ return move(info.Env(), MDBX_NEXT);
205
116
  }
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));
117
+
118
+ Napi::Value cursormou::prev(const Napi::CallbackInfo &info)
119
+ {
120
+ return move(info.Env(), MDBX_PREV);
210
121
  }
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");
122
+
123
+ Napi::Value cursormou::current(const Napi::CallbackInfo &info)
124
+ {
125
+ return move(info.Env(), MDBX_GET_CURRENT);
220
126
  }
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());
127
+
128
+ Napi::Value cursormou::eof(const Napi::CallbackInfo &info)
129
+ {
130
+ Napi::Env env = info.Env();
131
+ if (!cursor_) {
132
+ throw Napi::Error::New(env, "cursor closed");
133
+ }
134
+ auto rc = ::mdbx_cursor_eof(cursor_);
135
+ if (rc == MDBX_RESULT_TRUE)
136
+ return Napi::Boolean::New(env, true);
137
+ if (rc == MDBX_RESULT_FALSE)
138
+ return Napi::Boolean::New(env, false);
139
+ throw Napi::Error::New(env, mdbx_strerror(rc));
226
140
  }
227
-
228
- auto rc = mdbx_cursor_del(cursor_, flags);
229
- if (MDBX_NOTFOUND == rc) {
230
- return Napi::Boolean::New(env, false);
141
+
142
+ Napi::Value cursormou::on_first(const Napi::CallbackInfo &info)
143
+ {
144
+ Napi::Env env = info.Env();
145
+ if (!cursor_) {
146
+ throw Napi::Error::New(env, "cursor closed");
147
+ }
148
+ auto rc = ::mdbx_cursor_on_first(cursor_);
149
+ if (rc == MDBX_RESULT_TRUE)
150
+ return Napi::Boolean::New(env, true);
151
+ if (rc == MDBX_RESULT_FALSE)
152
+ return Napi::Boolean::New(env, false);
153
+ throw Napi::Error::New(env, mdbx_strerror(rc));
231
154
  }
232
-
233
- if (MDBX_SUCCESS != rc) {
155
+
156
+ Napi::Value cursormou::on_last(const Napi::CallbackInfo &info)
157
+ {
158
+ Napi::Env env = info.Env();
159
+ if (!cursor_) {
160
+ throw Napi::Error::New(env, "cursor closed");
161
+ }
162
+ auto rc = ::mdbx_cursor_on_last(cursor_);
163
+ if (rc == MDBX_RESULT_TRUE)
164
+ return Napi::Boolean::New(env, true);
165
+ if (rc == MDBX_RESULT_FALSE)
166
+ return Napi::Boolean::New(env, false);
234
167
  throw Napi::Error::New(env, mdbx_strerror(rc));
235
168
  }
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");
169
+
170
+ Napi::Value cursormou::on_first_multival(const Napi::CallbackInfo &info)
171
+ {
172
+ Napi::Env env = info.Env();
173
+ if (!cursor_) {
174
+ throw Napi::Error::New(env, "cursor closed");
175
+ }
176
+ auto rc = ::mdbx_cursor_on_first_dup(cursor_);
177
+ if (rc == MDBX_RESULT_TRUE)
178
+ return Napi::Boolean::New(env, true);
179
+ if (rc == MDBX_RESULT_FALSE)
180
+ return Napi::Boolean::New(env, false);
181
+ throw Napi::Error::New(env, mdbx_strerror(rc));
247
182
  }
248
-
249
- if (info.Length() < 1 || !info[0].IsFunction()) {
250
- throw Napi::TypeError::New(env, "forEach: callback function required");
183
+
184
+ Napi::Value cursormou::on_last_multival(const Napi::CallbackInfo &info)
185
+ {
186
+ Napi::Env env = info.Env();
187
+ if (!cursor_) {
188
+ throw Napi::Error::New(env, "cursor closed");
189
+ }
190
+ auto rc = ::mdbx_cursor_on_last_dup(cursor_);
191
+ if (rc == MDBX_RESULT_TRUE)
192
+ return Napi::Boolean::New(env, true);
193
+ if (rc == MDBX_RESULT_FALSE)
194
+ return Napi::Boolean::New(env, false);
195
+ throw Napi::Error::New(env, mdbx_strerror(rc));
251
196
  }
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)
197
+
198
+ // Хелпер для поиска
199
+ Napi::Value cursormou::seek_impl(const Napi::CallbackInfo &info, MDBX_cursor_op op)
268
200
  {
201
+ Napi::Env env = info.Env();
202
+
203
+ if (!cursor_) {
204
+ throw Napi::Error::New(env, "cursor closed");
205
+ }
206
+
207
+ if (info.Length() < 1) {
208
+ throw Napi::Error::New(env, "key required");
209
+ }
210
+
211
+ auto key_mode = dbi_->get_key_mode();
212
+
213
+ keymou key = (key_mode.val & key_mode::ordinal) ?
214
+ keymou::from(info[0], env, key_num_) :
215
+ keymou::from(info[0], env, key_buf_);
216
+
217
+ valuemou val{};
218
+ auto rc = mdbx_cursor_get(cursor_, key, val, op);
219
+ if (MDBX_NOTFOUND == rc) {
220
+ return env.Undefined();
221
+ }
222
+
223
+ if (MDBX_SUCCESS != rc) {
224
+ throw Napi::Error::New(env, mdbx_strerror(rc));
225
+ }
226
+
227
+ // Возвращаем {key, value}
269
228
  auto result = Napi::Object::New(env);
270
-
229
+
230
+ auto key_flag = dbi_->get_key_flag();
231
+ auto value_flag = dbi_->get_value_flag();
232
+
271
233
  // Ключ
272
234
  if (key_mode.val & key_mode::ordinal) {
273
235
  if (key_flag.val & base_flag::bigint) {
@@ -278,36 +240,160 @@ Napi::Value cursormou::for_each(const Napi::CallbackInfo& info) {
278
240
  } else {
279
241
  result.Set("key", key.to_string(env));
280
242
  }
281
-
243
+
282
244
  // Значение
283
245
  if (value_flag.val & base_flag::string) {
284
246
  result.Set("value", val.to_string(env));
285
247
  } else {
286
248
  result.Set("value", val.to_buffer(env));
287
249
  }
288
-
289
- // Вызов callback
290
- auto ret = callback.Call({result});
291
-
292
- // true stops the scan, false/undefined continues (same as dbi.forEach)
293
- if (ret.IsBoolean() && ret.As<Napi::Boolean>().Value()) {
294
- break;
250
+
251
+ return result;
252
+ }
253
+
254
+ Napi::Value cursormou::seek(const Napi::CallbackInfo &info)
255
+ {
256
+ return seek_impl(info, MDBX_SET_KEY);
257
+ }
258
+
259
+ Napi::Value cursormou::seek_ge(const Napi::CallbackInfo &info)
260
+ {
261
+ return seek_impl(info, MDBX_SET_RANGE);
262
+ }
263
+
264
+ Napi::Value cursormou::put(const Napi::CallbackInfo &info)
265
+ {
266
+ Napi::Env env = info.Env();
267
+
268
+ if (!cursor_) {
269
+ throw Napi::Error::New(env, "cursor closed");
270
+ }
271
+
272
+ if (info.Length() < 2) {
273
+ throw Napi::Error::New(env, "key and value required");
274
+ }
275
+
276
+ auto key_mode = dbi_->get_key_mode();
277
+
278
+ keymou key = (key_mode.val & key_mode::ordinal) ?
279
+ keymou::from(info[0], env, key_num_) :
280
+ keymou::from(info[0], env, key_buf_);
281
+
282
+ valuemou val = valuemou::from(info[1], env, val_buf_);
283
+
284
+ // Опциональный флаг put_mode (по умолчанию MDBX_UPSERT)
285
+ MDBX_put_flags_t flags = MDBX_UPSERT;
286
+ if (info.Length() > 2 && info[2].IsNumber()) {
287
+ flags = static_cast<MDBX_put_flags_t>(info[2].As<Napi::Number>().Int32Value());
288
+ }
289
+
290
+ auto rc = mdbx_cursor_put(cursor_, key, val, flags);
291
+ if (MDBX_SUCCESS != rc) {
292
+ throw Napi::Error::New(env, mdbx_strerror(rc));
295
293
  }
296
-
297
- // Следующий элемент
298
- rc = mdbx_cursor_get(cursor_, key, val, move_op);
294
+
295
+ return env.Undefined();
299
296
  }
300
-
301
- if (rc != MDBX_SUCCESS && rc != MDBX_NOTFOUND) {
302
- throw Napi::Error::New(env, mdbx_strerror(rc));
297
+
298
+ Napi::Value cursormou::del(const Napi::CallbackInfo &info)
299
+ {
300
+ Napi::Env env = info.Env();
301
+
302
+ if (!cursor_) {
303
+ throw Napi::Error::New(env, "cursor closed");
304
+ }
305
+
306
+ // Опциональный флаг (MDBX_NODUPDATA для удаления только текущего значения в multi-value)
307
+ MDBX_put_flags_t flags = MDBX_CURRENT;
308
+ if (info.Length() > 0 && info[0].IsNumber()) {
309
+ flags = static_cast<MDBX_put_flags_t>(info[0].As<Napi::Number>().Int32Value());
310
+ }
311
+
312
+ auto rc = mdbx_cursor_del(cursor_, flags);
313
+ if (MDBX_NOTFOUND == rc) {
314
+ return Napi::Boolean::New(env, false);
315
+ }
316
+
317
+ if (MDBX_SUCCESS != rc) {
318
+ throw Napi::Error::New(env, mdbx_strerror(rc));
319
+ }
320
+
321
+ return Napi::Boolean::New(env, true);
322
+ }
323
+
324
+ // forEach(callback, backward = false)
325
+ // callback({key, value}) => true продолжить, false/undefined остановить
326
+ Napi::Value cursormou::for_each(const Napi::CallbackInfo &info)
327
+ {
328
+ Napi::Env env = info.Env();
329
+
330
+ if (!cursor_) {
331
+ throw Napi::Error::New(env, "cursor closed");
332
+ }
333
+
334
+ if (info.Length() < 1 || !info[0].IsFunction()) {
335
+ throw Napi::TypeError::New(env, "forEach: callback function required");
336
+ }
337
+
338
+ auto callback = info[0].As<Napi::Function>();
339
+ bool backward = info.Length() > 1 && info[1].ToBoolean().Value();
340
+
341
+ auto key_mode = dbi_->get_key_mode();
342
+ auto key_flag = dbi_->get_key_flag();
343
+ auto value_flag = dbi_->get_value_flag();
344
+
345
+ MDBX_cursor_op start_op = backward ? MDBX_LAST : MDBX_FIRST;
346
+ MDBX_cursor_op move_op = backward ? MDBX_PREV : MDBX_NEXT;
347
+
348
+ keymou key{};
349
+ valuemou val{};
350
+ // Первое позиционирование
351
+ auto rc = mdbx_cursor_get(cursor_, key, val, start_op);
352
+ while (MDBX_SUCCESS == rc)
353
+ {
354
+ auto result = Napi::Object::New(env);
355
+
356
+ // Ключ
357
+ if (key_mode.val & key_mode::ordinal) {
358
+ if (key_flag.val & base_flag::bigint) {
359
+ result.Set("key", key.to_bigint(env));
360
+ } else {
361
+ result.Set("key", key.to_number(env));
362
+ }
363
+ } else {
364
+ result.Set("key", key.to_string(env));
365
+ }
366
+
367
+ // Значение
368
+ if (value_flag.val & base_flag::string) {
369
+ result.Set("value", val.to_string(env));
370
+ } else {
371
+ result.Set("value", val.to_buffer(env));
372
+ }
373
+
374
+ // Вызов callback
375
+ auto ret = callback.Call({result});
376
+
377
+ // true stops the scan, false/undefined continues (same as dbi.forEach)
378
+ if (ret.IsBoolean() && ret.ToBoolean()) {
379
+ break;
380
+ }
381
+
382
+ // Следующий элемент
383
+ rc = mdbx_cursor_get(cursor_, key, val, move_op);
384
+ }
385
+
386
+ if (rc != MDBX_SUCCESS && rc != MDBX_NOTFOUND) {
387
+ throw Napi::Error::New(env, mdbx_strerror(rc));
388
+ }
389
+
390
+ return env.Undefined();
391
+ }
392
+
393
+ Napi::Value cursormou::close(const Napi::CallbackInfo &info)
394
+ {
395
+ do_close();
396
+ return info.Env().Undefined();
303
397
  }
304
-
305
- return env.Undefined();
306
- }
307
-
308
- Napi::Value cursormou::close(const Napi::CallbackInfo& info) {
309
- do_close();
310
- return info.Env().Undefined();
311
- }
312
398
 
313
399
  } // namespace mdbxmou
package/src/cursormou.hpp CHANGED
@@ -53,6 +53,13 @@ public:
53
53
  // Текущая позиция
54
54
  Napi::Value current(const Napi::CallbackInfo&);
55
55
 
56
+ // Статус курсора
57
+ Napi::Value eof(const Napi::CallbackInfo&);
58
+ Napi::Value on_first(const Napi::CallbackInfo&);
59
+ Napi::Value on_last(const Napi::CallbackInfo&);
60
+ Napi::Value on_first_multival(const Napi::CallbackInfo&);
61
+ Napi::Value on_last_multival(const Napi::CallbackInfo&);
62
+
56
63
  // Модификация
57
64
  Napi::Value put(const Napi::CallbackInfo&);
58
65
  Napi::Value del(const Napi::CallbackInfo&);
package/src/dbi.cpp CHANGED
@@ -46,11 +46,16 @@ bool dbi::del(MDBX_txn* txn, const keymou& key)
46
46
 
47
47
 
48
48
  cursormou_managed dbi::open_cursor(MDBX_txn* txn) const
49
+ {
50
+ return get_cursor(txn, mdbx::map_handle{ id_ });
51
+ }
52
+
53
+ cursormou_managed dbi::get_cursor(MDBX_txn* txn, mdbx::map_handle dbi)
49
54
  {
50
55
  MDBX_cursor* cursor_ptr;
51
- auto rc = mdbx_cursor_open(txn, id_, &cursor_ptr);
56
+ auto rc = mdbx_cursor_open(txn, dbi.dbi, &cursor_ptr);
52
57
  if (rc != MDBX_SUCCESS) {
53
- throw std::runtime_error(mdbx_strerror(rc));
58
+ mdbx::error::throw_exception(rc);
54
59
  }
55
60
  return cursormou_managed{ cursor_ptr };
56
61
  }
package/src/dbi.hpp CHANGED
@@ -37,6 +37,9 @@ public:
37
37
  bool del(MDBX_txn* txn, const keymou& key);
38
38
 
39
39
  cursormou_managed open_cursor(MDBX_txn* txn) const;
40
+
41
+ // Static version for map_handle
42
+ static cursormou_managed get_cursor(MDBX_txn* txn, mdbx::map_handle dbi);
40
43
 
41
44
  // Drop database or delete it
42
45
  void drop(MDBX_txn* txn, bool delete_db = false);
package/src/dbimou.cpp CHANGED
@@ -61,7 +61,8 @@ Napi::Value dbimou::put(const Napi::CallbackInfo& info)
61
61
  return env.Undefined();
62
62
  }
63
63
 
64
- Napi::Value dbimou::get(const Napi::CallbackInfo& info) {
64
+ Napi::Value dbimou::get(const Napi::CallbackInfo& info)
65
+ {
65
66
  Napi::Env env = info.Env();
66
67
  auto arg_len = info.Length();
67
68
  if (arg_len < 2) {
@@ -94,7 +95,8 @@ Napi::Value dbimou::get(const Napi::CallbackInfo& info) {
94
95
  return env.Undefined();
95
96
  }
96
97
 
97
- Napi::Value dbimou::del(const Napi::CallbackInfo& info) {
98
+ Napi::Value dbimou::del(const Napi::CallbackInfo& info)
99
+ {
98
100
  Napi::Env env = info.Env();
99
101
  auto arg_len = info.Length();
100
102
  if (arg_len < 2) {
@@ -122,7 +124,8 @@ Napi::Value dbimou::del(const Napi::CallbackInfo& info) {
122
124
  return env.Undefined();
123
125
  }
124
126
 
125
- Napi::Value dbimou::has(const Napi::CallbackInfo& info) {
127
+ Napi::Value dbimou::has(const Napi::CallbackInfo& info)
128
+ {
126
129
  Napi::Env env = info.Env();
127
130
  auto arg_len = info.Length();
128
131
  if (arg_len < 2) {
@@ -150,7 +153,8 @@ Napi::Value dbimou::has(const Napi::CallbackInfo& info) {
150
153
  return env.Undefined();
151
154
  }
152
155
 
153
- Napi::Value dbimou::for_each(const Napi::CallbackInfo& info) {
156
+ Napi::Value dbimou::for_each(const Napi::CallbackInfo& info)
157
+ {
154
158
  Napi::Env env = info.Env();
155
159
  auto arg_len = info.Length();
156
160
  if (arg_len < 2) {
@@ -188,7 +192,7 @@ Napi::Value dbimou::for_each(const Napi::CallbackInfo& info) {
188
192
  // Конвертируем значение
189
193
  Napi::Value rc_val = (value_flag_.val & base_flag::string) ?
190
194
  val.to_string(env) : val.to_buffer(env);
191
-
195
+ // Формируем результат
192
196
  Napi::Value result = fn.Call({ rc_key, rc_val,
193
197
  Napi::Number::New(env, static_cast<double>(index)) });
194
198
 
@@ -208,7 +212,7 @@ Napi::Value dbimou::for_each(const Napi::CallbackInfo& info) {
208
212
  // Конвертируем значение
209
213
  Napi::Value rc_val = (value_flag_.val & base_flag::string) ?
210
214
  val.to_string(env) : val.to_buffer(env);
211
-
215
+ // Формируем результат
212
216
  Napi::Value result = fn.Call({ rc_key, rc_val,
213
217
  Napi::Number::New(env, static_cast<double>(index)) });
214
218
 
@@ -320,7 +324,7 @@ Napi::Value dbimou::for_each_from(const Napi::CallbackInfo& info) {
320
324
  // Конвертируем значение
321
325
  Napi::Value rc_val = (value_flag_.val & base_flag::string) ?
322
326
  val.to_string(env) : val.to_buffer(env);
323
-
327
+ // Формируем результат
324
328
  Napi::Value result = fn.Call({ rc_key, rc_val,
325
329
  Napi::Number::New(env, static_cast<double>(index)) });
326
330
 
@@ -346,7 +350,7 @@ Napi::Value dbimou::for_each_from(const Napi::CallbackInfo& info) {
346
350
  // Конвертируем значение
347
351
  Napi::Value rc_val = (value_flag_.val & base_flag::string) ?
348
352
  val.to_string(env) : val.to_buffer(env);
349
-
353
+ // Формируем результат
350
354
  Napi::Value result = fn.Call({ rc_key, rc_val,
351
355
  Napi::Number::New(env, static_cast<double>(index)) });
352
356
 
@@ -415,8 +419,7 @@ Napi::Value dbimou::keys(const Napi::CallbackInfo& info) {
415
419
  auto txn = Napi::ObjectWrap<txnmou>::Unwrap(arg0);
416
420
 
417
421
  try {
418
- auto cursor = dbi::open_cursor(*txn);
419
- auto stat = dbi::get_stat(*txn);
422
+ auto stat = get_stat(*txn);
420
423
 
421
424
  // Создаем массив для ключей
422
425
  Napi::Array keys = Napi::Array::New(env, stat.ms_entries);
@@ -424,27 +427,38 @@ Napi::Value dbimou::keys(const Napi::CallbackInfo& info) {
424
427
  return keys;
425
428
  }
426
429
 
430
+ // Используем batch версию для лучшей производительности
431
+ auto cursor = open_cursor(*txn);
432
+
433
+ // Буфер для batch - MDBXMOU_BATCH_LIMIT/2 пар (key, value)
434
+ #ifndef MDBXMOU_BATCH_LIMIT
435
+ #define MDBXMOU_BATCH_LIMIT 512
436
+ #endif // MDBXMOU_BATCH_LIMIT
437
+ std::array<mdbx::slice, MDBXMOU_BATCH_LIMIT> pairs;
438
+
427
439
  uint32_t index{};
428
- if (key_mode_.val & key_mode::ordinal) {
429
- cursor.scan([&](const mdbx::pair& f) {
430
- keymou key{f.key};
431
- // Конвертируем ключ
432
- Napi::Value rc_key = (key_flag_.val & base_flag::bigint) ?
433
- key.to_bigint(env) : key.to_number(env);
434
-
435
- keys.Set(index++, rc_key);
436
- return false; // продолжаем сканирование
437
- });
438
- } else {
439
- cursor.scan([&](const mdbx::pair& f) {
440
- keymou key{f.key};
441
- // Конвертируем ключ
442
- Napi::Value rc_key = (key_flag_.val & base_flag::string) ?
443
- key.to_string(env) : key.to_buffer(env);
444
-
440
+ const bool is_ordinal = key_mode_.val & key_mode::ordinal;
441
+ const bool is_bigint = key_flag_.val & base_flag::bigint;
442
+ const bool is_string = key_flag_.val & base_flag::string;
443
+
444
+ // Первый вызов с MDBX_FIRST
445
+ size_t count = cursor.get_batch(pairs, MDBX_FIRST);
446
+
447
+ while (count > 0) {
448
+ // pairs[0] = key1, pairs[1] = value1, pairs[2] = key2, ...
449
+ for (size_t i = 0; i < count; i += 2) {
450
+ keymou key{pairs[i]};
451
+ Napi::Value rc_key;
452
+ if (is_ordinal) {
453
+ rc_key = is_bigint ? key.to_bigint(env) : key.to_number(env);
454
+ } else {
455
+ rc_key = is_string ? key.to_string(env) : key.to_buffer(env);
456
+ }
445
457
  keys.Set(index++, rc_key);
446
- return false; // продолжаем сканирование
447
- });
458
+ }
459
+
460
+ // Следующий batch
461
+ count = cursor.get_batch(pairs, MDBX_NEXT);
448
462
  }
449
463
 
450
464
  return keys;
package/src/envmou.cpp CHANGED
@@ -193,11 +193,6 @@ MDBX_env* envmou::create_and_open(const env_arg0& arg0)
193
193
  throw std::runtime_error(mdbx_strerror(rc));
194
194
  }
195
195
 
196
- #ifdef _WIN32
197
- auto id = static_cast<std::uint32_t>(GetCurrentThreadId());
198
- #else
199
- auto id = static_cast<std::uint32_t>(pthread_self());
200
- #endif
201
196
  // выдадим параметры mode, flag и id потока в котором открывается env
202
197
  rc = mdbx_env_open(env, arg0.path.c_str(), arg0.flag, arg0.file_mode);
203
198
  if (rc != MDBX_SUCCESS) {
package/src/querymou.cpp CHANGED
@@ -5,7 +5,7 @@ namespace mdbxmou {
5
5
 
6
6
  dbimou* async_common::parse(const Napi::Object& arg0)
7
7
  {
8
- dbimou* dbi = nullptr;
8
+ dbimou* dbi{};
9
9
  // если просто передали dbi
10
10
  if (arg0.InstanceOf(dbimou::ctor.Value())) {
11
11
  dbi = Napi::ObjectWrap<dbimou>::Unwrap(arg0);
package/src/txnmou.cpp CHANGED
@@ -155,8 +155,7 @@ Napi::Value txnmou::open_cursor(const Napi::CallbackInfo& info) {
155
155
  auto* dbi = dbimou::Unwrap(arg0);
156
156
 
157
157
  MDBX_cursor* cursor{};
158
- int rc = mdbx_cursor_open(txn_.get(), dbi->get_id(), &cursor);
159
-
158
+ auto rc = mdbx_cursor_open(txn_.get(), dbi->get_id(), &cursor);
160
159
  if (rc != MDBX_SUCCESS) {
161
160
  throw Napi::Error::New(env, std::string("mdbx_cursor_open: ") + mdbx_strerror(rc));
162
161
  }
@@ -174,10 +173,8 @@ Napi::Value txnmou::is_active(const Napi::CallbackInfo& info) {
174
173
 
175
174
  void txnmou::attach(envmou& env, MDBX_txn* txn, txn_mode mode)
176
175
  {
177
- env_ = &env;
176
+ env_ = &(++env);
178
177
  mode_ = mode;
179
-
180
- ++(*env_);
181
178
  txn_.reset(txn);
182
179
  }
183
180
 
package/src/txnmou.hpp CHANGED
@@ -46,6 +46,7 @@ public:
46
46
 
47
47
  Napi::Value commit(const Napi::CallbackInfo&);
48
48
  Napi::Value abort(const Napi::CallbackInfo&);
49
+
49
50
  Napi::Value open_map(const Napi::CallbackInfo& info) {
50
51
  return get_dbi(info, db_mode{});
51
52
  }
@@ -60,9 +61,17 @@ public:
60
61
  }
61
62
 
62
63
  // 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_; }
64
+ txnmou& operator++() noexcept {
65
+ ++cursor_count_; return *this;
66
+ }
67
+
68
+ txnmou& operator--() noexcept {
69
+ --cursor_count_; return *this;
70
+ }
71
+
72
+ std::size_t cursor_count() const noexcept {
73
+ return cursor_count_;
74
+ }
66
75
 
67
76
  Napi::Value is_active(const Napi::CallbackInfo&);
68
77
 
package/src/typemou.hpp CHANGED
@@ -4,6 +4,7 @@
4
4
  #include <mdbx.h++>
5
5
  #include <cstdint>
6
6
  #include <vector>
7
+ #include <array>
7
8
  #include <utility>
8
9
 
9
10
  namespace mdbxmou {
@@ -70,6 +71,33 @@ struct cursormou_managed final
70
71
  ::mdbx_cursor_close(cursor::handle_);
71
72
  }
72
73
  }
74
+
75
+ /// \brief Batch read using mdbx_cursor_get_batch
76
+ /// \param[out] pairs Array of mdbx::slice to fill (key0, val0, key1, val1, ...)
77
+ /// \param[in] limit Maximum number of slice elements in pairs array
78
+ /// \param[in] op Cursor operation (MDBX_FIRST or MDBX_NEXT only)
79
+ /// \return Number of items read (key+value count), 0 if end of data
80
+ /// \throws mdbx::exception on error
81
+ size_t get_batch(mdbx::slice* pairs, size_t limit, MDBX_cursor_op op)
82
+ {
83
+ size_t count{};
84
+ auto rc = ::mdbx_cursor_get_batch(cursor::handle_, &count, pairs, limit, op);
85
+ if (rc == MDBX_SUCCESS || rc == MDBX_RESULT_TRUE) {
86
+ return count;
87
+ }
88
+ if (rc == MDBX_NOTFOUND || rc == MDBX_ENODATA) {
89
+ return 0;
90
+ }
91
+ mdbx::error::throw_exception(rc);
92
+ return 0; // unreachable
93
+ }
94
+
95
+ /// \brief Batch read with std::array
96
+ template<size_t N>
97
+ size_t get_batch(std::array<mdbx::slice, N>& pairs, MDBX_cursor_op op)
98
+ {
99
+ return get_batch(pairs.data(), N, op);
100
+ }
73
101
  };
74
102
 
75
103
  struct env_flag {