mdbxmou 0.3.3 → 0.3.5

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/package.json CHANGED
@@ -65,7 +65,7 @@
65
65
  },
66
66
  "gypfile": true,
67
67
  "name": "mdbxmou",
68
- "version": "0.3.3",
68
+ "version": "0.3.5",
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
 
@@ -29,7 +30,7 @@ static Napi::Value write_row(Napi::Env env, const keys_line& row)
29
30
  for (std::uint32_t j = 0; j < param.size(); ++j) {
30
31
  const auto& item = param[j];
31
32
  Napi::Value key_value;
32
- if (key_mode.val & key_mode::ordinal) {
33
+ if (mdbx::is_ordinal(key_mode)) {
33
34
  if (key_flag.val & base_flag::number) {
34
35
  key_value = Napi::Number::New(env, static_cast<double>(item.id_buf));
35
36
  } else {
@@ -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
+ auto is_ordinal = mdbx::is_ordinal(arg0.key_mod);
109
+
110
+ auto cursor = dbi::open_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();
@@ -39,7 +39,7 @@ static Napi::Value write_row(Napi::Env env, const query_line& row)
39
39
  const auto& item = param[j];
40
40
  Napi::Value key_value;
41
41
  Napi::Object js_item = Napi::Object::New(env);
42
- if (key_mode.val & key_mode::ordinal) {
42
+ if (mdbx::is_ordinal(key_mode)) {
43
43
  if (key_flag.val & base_flag::number) {
44
44
  key_value = Napi::Number::New(env, static_cast<double>(item.id_buf));
45
45
  } else {
@@ -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
@@ -80,7 +80,7 @@ namespace mdbxmou
80
80
  auto value_flag = dbi_->get_value_flag();
81
81
 
82
82
  // Ключ
83
- if (key_mode.val & key_mode::ordinal) {
83
+ if (mdbx::is_ordinal(key_mode)) {
84
84
  if (key_flag.val & base_flag::bigint) {
85
85
  result.Set("key", key.to_bigint(env));
86
86
  } else {
@@ -210,7 +210,7 @@ namespace mdbxmou
210
210
 
211
211
  auto key_mode = dbi_->get_key_mode();
212
212
 
213
- keymou key = (key_mode.val & key_mode::ordinal) ?
213
+ keymou key = (mdbx::is_ordinal(key_mode)) ?
214
214
  keymou::from(info[0], env, key_num_) :
215
215
  keymou::from(info[0], env, key_buf_);
216
216
 
@@ -231,7 +231,7 @@ namespace mdbxmou
231
231
  auto value_flag = dbi_->get_value_flag();
232
232
 
233
233
  // Ключ
234
- if (key_mode.val & key_mode::ordinal) {
234
+ if (mdbx::is_ordinal(key_mode)) {
235
235
  if (key_flag.val & base_flag::bigint) {
236
236
  result.Set("key", key.to_bigint(env));
237
237
  } else {
@@ -275,7 +275,7 @@ namespace mdbxmou
275
275
 
276
276
  auto key_mode = dbi_->get_key_mode();
277
277
 
278
- keymou key = (key_mode.val & key_mode::ordinal) ?
278
+ keymou key = (mdbx::is_ordinal(key_mode)) ?
279
279
  keymou::from(info[0], env, key_num_) :
280
280
  keymou::from(info[0], env, key_buf_);
281
281
 
@@ -354,7 +354,7 @@ namespace mdbxmou
354
354
  auto result = Napi::Object::New(env);
355
355
 
356
356
  // Ключ
357
- if (key_mode.val & key_mode::ordinal) {
357
+ if (mdbx::is_ordinal(key_mode)) {
358
358
  if (key_flag.val & base_flag::bigint) {
359
359
  result.Set("key", key.to_bigint(env));
360
360
  } else {
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 open_cursor(txn, mdbx::map_handle{id_});
51
+ }
52
+
53
+ cursormou_managed dbi::open_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 open_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
@@ -48,7 +48,7 @@ Napi::Value dbimou::put(const Napi::CallbackInfo& info)
48
48
  auto txn = Napi::ObjectWrap<txnmou>::Unwrap(arg0);
49
49
  try {
50
50
  std::uint64_t t;
51
- auto key = (key_mode_.val & key_mode::ordinal) ?
51
+ auto key = mdbx::is_ordinal(key_mode_) ?
52
52
  keymou::from(info[1], env, t) :
53
53
  keymou::from(info[1], env, key_buf_);
54
54
 
@@ -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) {
@@ -76,7 +77,7 @@ Napi::Value dbimou::get(const Napi::CallbackInfo& info) {
76
77
 
77
78
  try {
78
79
  std::uint64_t t;
79
- auto key = (key_mode_.val & key_mode::ordinal) ?
80
+ auto key = mdbx::is_ordinal(key_mode_) ?
80
81
  keymou::from(info[1], env, t) :
81
82
  keymou::from(info[1], env, key_buf_);
82
83
 
@@ -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) {
@@ -109,7 +111,7 @@ Napi::Value dbimou::del(const Napi::CallbackInfo& info) {
109
111
 
110
112
  try {
111
113
  std::uint64_t t;
112
- auto key = (key_mode_.val & key_mode::ordinal) ?
114
+ auto key = mdbx::is_ordinal(key_mode_) ?
113
115
  keymou::from(info[1], env, t) :
114
116
  keymou::from(info[1], env, key_buf_);
115
117
 
@@ -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) {
@@ -137,7 +140,7 @@ Napi::Value dbimou::has(const Napi::CallbackInfo& info) {
137
140
 
138
141
  try {
139
142
  std::uint64_t t;
140
- auto key = (key_mode_.val & key_mode::ordinal) ?
143
+ auto key = mdbx::is_ordinal(key_mode_) ?
141
144
  keymou::from(info[1], env, t) :
142
145
  keymou::from(info[1], env, key_buf_);
143
146
 
@@ -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) {
@@ -169,16 +173,16 @@ Napi::Value dbimou::for_each(const Napi::CallbackInfo& info) {
169
173
  auto fn = info[1].As<Napi::Function>();
170
174
 
171
175
  try {
172
- auto cursor = dbi::open_cursor(*txn);
173
- auto stat = dbi::get_stat(*txn);
176
+ auto stat = get_stat(*txn);
174
177
 
175
178
  // Проверяем, есть ли записи в базе данных
176
179
  if (stat.ms_entries == 0) {
177
180
  return Napi::Number::New(env, 0);
178
181
  }
179
-
182
+
183
+ auto cursor = open_cursor(*txn);
180
184
  uint32_t index{};
181
- if (key_mode_.val & key_mode::ordinal) {
185
+ if (mdbx::is_ordinal(key_mode_)) {
182
186
  cursor.scan([&](const mdbx::pair& f) {
183
187
  keymou key{f.key};
184
188
  valuemou val{f.value};
@@ -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
 
@@ -234,7 +238,8 @@ Napi::Value dbimou::for_each(const Napi::CallbackInfo& info) {
234
238
  }
235
239
  }
236
240
 
237
- Napi::Value dbimou::for_each_from(const Napi::CallbackInfo& info) {
241
+ Napi::Value dbimou::for_each_from(const Napi::CallbackInfo& info)
242
+ {
238
243
  Napi::Env env = info.Env();
239
244
  auto arg_len = info.Length();
240
245
 
@@ -270,7 +275,7 @@ Napi::Value dbimou::for_each_from(const Napi::CallbackInfo& info) {
270
275
 
271
276
  // Парсим начальный ключ
272
277
  std::uint64_t t;
273
- keymou from_key = (key_mode_.val & key_mode::ordinal) ?
278
+ keymou from_key = (mdbx::is_ordinal(key_mode_)) ?
274
279
  keymou::from(info[1], env, t) :
275
280
  keymou::from(info[1], env, key_buf_);
276
281
 
@@ -304,7 +309,7 @@ Napi::Value dbimou::for_each_from(const Napi::CallbackInfo& info) {
304
309
  std::size_t index{};
305
310
  bool is_key_equal_mode = (cursor_mode == move_operation::key_equal ||
306
311
  cursor_mode == move_operation::multi_exactkey_value_equal);
307
- if (key_mode_.val & key_mode::ordinal) {
312
+ if (mdbx::is_ordinal(key_mode_)) {
308
313
  cursor.scan_from([&](const mdbx::pair& f) {
309
314
  keymou key{f.key};
310
315
  valuemou val{f.value};
@@ -320,7 +325,7 @@ Napi::Value dbimou::for_each_from(const Napi::CallbackInfo& info) {
320
325
  // Конвертируем значение
321
326
  Napi::Value rc_val = (value_flag_.val & base_flag::string) ?
322
327
  val.to_string(env) : val.to_buffer(env);
323
-
328
+ // Формируем результат
324
329
  Napi::Value result = fn.Call({ rc_key, rc_val,
325
330
  Napi::Number::New(env, static_cast<double>(index)) });
326
331
 
@@ -346,7 +351,7 @@ Napi::Value dbimou::for_each_from(const Napi::CallbackInfo& info) {
346
351
  // Конвертируем значение
347
352
  Napi::Value rc_val = (value_flag_.val & base_flag::string) ?
348
353
  val.to_string(env) : val.to_buffer(env);
349
-
354
+ // Формируем результат
350
355
  Napi::Value result = fn.Call({ rc_key, rc_val,
351
356
  Napi::Number::New(env, static_cast<double>(index)) });
352
357
 
@@ -363,7 +368,8 @@ Napi::Value dbimou::for_each_from(const Napi::CallbackInfo& info) {
363
368
  }
364
369
  }
365
370
 
366
- Napi::Value dbimou::stat(const Napi::CallbackInfo& info) {
371
+ Napi::Value dbimou::stat(const Napi::CallbackInfo& info)
372
+ {
367
373
  Napi::Env env = info.Env();
368
374
  auto arg_len = info.Length();
369
375
  if (arg_len < 1) {
@@ -401,7 +407,8 @@ Napi::Value dbimou::stat(const Napi::CallbackInfo& info) {
401
407
  return env.Undefined();
402
408
  }
403
409
 
404
- Napi::Value dbimou::keys(const Napi::CallbackInfo& info) {
410
+ Napi::Value dbimou::keys(const Napi::CallbackInfo& info)
411
+ {
405
412
  Napi::Env env = info.Env();
406
413
  auto arg_len = info.Length();
407
414
  if (arg_len < 1) {
@@ -415,8 +422,7 @@ Napi::Value dbimou::keys(const Napi::CallbackInfo& info) {
415
422
  auto txn = Napi::ObjectWrap<txnmou>::Unwrap(arg0);
416
423
 
417
424
  try {
418
- auto cursor = dbi::open_cursor(*txn);
419
- auto stat = dbi::get_stat(*txn);
425
+ auto stat = get_stat(*txn);
420
426
 
421
427
  // Создаем массив для ключей
422
428
  Napi::Array keys = Napi::Array::New(env, stat.ms_entries);
@@ -424,27 +430,38 @@ Napi::Value dbimou::keys(const Napi::CallbackInfo& info) {
424
430
  return keys;
425
431
  }
426
432
 
433
+ // Используем batch версию для лучшей производительности
434
+ auto cursor = open_cursor(*txn);
435
+
436
+ // Буфер для batch - MDBXMOU_BATCH_LIMIT/2 пар (key, value)
437
+ #ifndef MDBXMOU_BATCH_LIMIT
438
+ #define MDBXMOU_BATCH_LIMIT 512
439
+ #endif // MDBXMOU_BATCH_LIMIT
440
+ std::array<mdbx::slice, MDBXMOU_BATCH_LIMIT> pairs;
441
+
427
442
  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
-
443
+ auto is_ordinal = mdbx::is_ordinal(key_mode_);
444
+ auto is_bigint = key_flag_.val & base_flag::bigint;
445
+ auto is_string = key_flag_.val & base_flag::string;
446
+
447
+ // Первый вызов с MDBX_FIRST
448
+ size_t count = cursor.get_batch(pairs, MDBX_FIRST);
449
+
450
+ while (count > 0) {
451
+ // pairs[0] = key1, pairs[1] = value1, pairs[2] = key2, ...
452
+ for (size_t i = 0; i < count; i += 2) {
453
+ keymou key{pairs[i]};
454
+ Napi::Value rc_key;
455
+ if (is_ordinal) {
456
+ rc_key = is_bigint ? key.to_bigint(env) : key.to_number(env);
457
+ } else {
458
+ rc_key = is_string ? key.to_string(env) : key.to_buffer(env);
459
+ }
445
460
  keys.Set(index++, rc_key);
446
- return false; // продолжаем сканирование
447
- });
461
+ }
462
+
463
+ // Следующий batch
464
+ count = cursor.get_batch(pairs, MDBX_NEXT);
448
465
  }
449
466
 
450
467
  return keys;
@@ -455,7 +472,8 @@ Napi::Value dbimou::keys(const Napi::CallbackInfo& info) {
455
472
  return env.Undefined();
456
473
  }
457
474
 
458
- Napi::Value dbimou::keys_from(const Napi::CallbackInfo& info) {
475
+ Napi::Value dbimou::keys_from(const Napi::CallbackInfo& info)
476
+ {
459
477
  Napi::Env env = info.Env();
460
478
  auto arg_len = info.Length();
461
479
  if (arg_len < 2) {
@@ -473,7 +491,7 @@ Napi::Value dbimou::keys_from(const Napi::CallbackInfo& info) {
473
491
 
474
492
  // Парсим аргументы: txn, from, limit, cursorMode
475
493
  std::uint64_t t;
476
- keymou from_key = (key_mode_.val & key_mode::ordinal) ?
494
+ keymou from_key = (mdbx::is_ordinal(key_mode_)) ?
477
495
  keymou::from(info[1], env, t) :
478
496
  keymou::from(info[1], env, key_buf_);
479
497
 
@@ -514,7 +532,7 @@ Napi::Value dbimou::keys_from(const Napi::CallbackInfo& info) {
514
532
  bool is_key_equal_mode = (cursor_mode == move_operation::key_equal ||
515
533
  cursor_mode == move_operation::multi_exactkey_value_equal);
516
534
 
517
- if (key_mode_.val & key_mode::ordinal) {
535
+ if (mdbx::is_ordinal(key_mode_)) {
518
536
  cursor.scan_from([&](const mdbx::pair& f) {
519
537
  if (index >= count) {
520
538
  return true; // останавливаем сканирование
@@ -566,7 +584,8 @@ Napi::Value dbimou::keys_from(const Napi::CallbackInfo& info) {
566
584
  return env.Undefined();
567
585
  }
568
586
 
569
- Napi::Value dbimou::drop(const Napi::CallbackInfo& info) {
587
+ Napi::Value dbimou::drop(const Napi::CallbackInfo& info)
588
+ {
570
589
  Napi::Env env = info.Env();
571
590
  if (info.Length() < 1) {
572
591
  throw Napi::TypeError::New(env, "First argument must be a transaction");
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);
@@ -27,7 +27,7 @@ void async_key::parse(const async_common& common, const Napi::Value& item)
27
27
  auto key_flag = common.key_flag;
28
28
 
29
29
  keymou key{};
30
- if (common.key_mod.val & key_mode::ordinal) {
30
+ if (mdbx::is_ordinal(common.key_mod)) {
31
31
  if (item.IsBigInt()) {
32
32
  key = keymou{item.As<Napi::BigInt>(), id_buf};
33
33
  } else if (item.IsNumber()) {
package/src/txnmou.cpp CHANGED
@@ -70,21 +70,102 @@ void txnmou::dec_counter() noexcept
70
70
  }
71
71
  }
72
72
 
73
- Napi::Value txnmou::get_dbi(const Napi::CallbackInfo& info, db_mode db_mode)
73
+ Napi::Value txnmou::get_dbi(const char* name, base_flag key_flag,
74
+ base_flag value_flag, key_mode key_mode, value_mode value_mode,
75
+ db_mode db_mode)
74
76
  {
75
- Napi::Env env = info.Env();
77
+ Napi::Env env = Env();
76
78
 
77
79
  if ((mode_.val & txn_mode::ro) && (db_mode.val & db_mode::create)) {
78
80
  throw Napi::Error::New(env, "dbi: cannot open DB in read-only transaction");
79
81
  }
80
82
 
83
+ if (!txn_) {
84
+ throw Napi::Error::New(env, "txn already completed");
85
+ }
86
+
87
+ MDBX_dbi dbi{};
88
+ auto flags = static_cast<MDBX_db_flags_t>(db_mode.val|key_mode.val|value_mode.val);
89
+ auto rc = mdbx_dbi_open(*this, (name && name[0]) ? name : nullptr, flags, &dbi);
90
+ if (rc != MDBX_SUCCESS) {
91
+ throw Napi::Error::New(env, std::string("mdbx_dbi_open: ") + mdbx_strerror(rc));
92
+ }
93
+ // создаем новый объект dbi
94
+ auto obj = dbimou::ctor.New({});
95
+ auto ptr = dbimou::Unwrap(obj);
96
+ ptr->attach(dbi, db_mode, key_mode,
97
+ value_mode, key_flag, value_flag);
98
+ return obj;
99
+ }
100
+
101
+ Napi::Value txnmou::get_dbi(const Napi::Object& arg0, db_mode db_mode)
102
+ {
103
+ auto env = arg0.Env();
104
+ auto conf = get_env_userctx(*env_);
105
+ auto key_flag = conf->key_flag;
106
+ auto value_flag = conf->value_flag;
81
107
  key_mode key_mode{};
82
108
  value_mode value_mode{};
83
109
  std::string db_name{};
110
+
111
+ if (arg0.Has("name")) {
112
+ auto value = arg0.Get("name");
113
+ if (!value.IsUndefined() && !value.IsNull()) {
114
+ if (!value.IsString()) {
115
+ throw Napi::Error::New(env, "dbi: name must be string");
116
+ }
117
+ db_name = value.As<Napi::String>().Utf8Value();
118
+ }
119
+ }
120
+
121
+ if (arg0.Has("keyFlag")) {
122
+ auto value = arg0.Get("keyFlag");
123
+ if (!value.IsUndefined() && !value.IsNull()) {
124
+ key_flag = base_flag::parse_key(value);
125
+ }
126
+ }
127
+
128
+ if (arg0.Has("valueFlag")) {
129
+ auto value = arg0.Get("valueFlag");
130
+ if (!value.IsUndefined() && !value.IsNull()) {
131
+ value_flag = base_flag::parse_value(value);
132
+ }
133
+ }
134
+
135
+ if (arg0.Has("keyMode")) {
136
+ auto value = arg0.Get("keyMode");
137
+ if (!value.IsUndefined() && !value.IsNull()) {
138
+ key_mode = parse_key_mode(env, value, key_flag);
139
+ }
140
+ }
141
+
142
+ if (arg0.Has("valueMode")) {
143
+ auto value = arg0.Get("valueMode");
144
+ if (!value.IsUndefined() && !value.IsNull()) {
145
+ value_mode = value_mode::parse(value);
146
+ }
147
+ }
148
+
149
+ return get_dbi(db_name.empty() ? nullptr : db_name.c_str(),
150
+ key_flag, value_flag, key_mode, value_mode, db_mode);
151
+ }
152
+
153
+ Napi::Value txnmou::get_dbi(const Napi::CallbackInfo& info, db_mode db_mode)
154
+ {
155
+ Napi::Env env = info.Env();
156
+
84
157
  auto conf = get_env_userctx(*env_);
85
158
  auto key_flag = conf->key_flag;
86
159
  auto value_flag = conf->value_flag;
160
+ key_mode key_mode{};
161
+ value_mode value_mode{};
162
+ std::string db_name{};
87
163
  auto arg_count = info.Length();
164
+
165
+ if (arg_count == 1 && info[0].IsObject()) {
166
+ return get_dbi(info[0].As<Napi::Object>(), db_mode);
167
+ }
168
+
88
169
  if (arg_count == 3) {
89
170
  auto arg0 = info[0]; // db_name
90
171
  auto arg1 = info[1]; // key_mode
@@ -94,8 +175,8 @@ Napi::Value txnmou::get_dbi(const Napi::CallbackInfo& info, db_mode db_mode)
94
175
  value_mode = value_mode::parse(arg2);
95
176
  } else if (arg_count == 2) {
96
177
  // db_name + key_mode || key_mode + value_mode
97
- auto arg0 = info[0];
98
- auto arg1 = info[1];
178
+ auto arg0 = info[0];
179
+ auto arg1 = info[1];
99
180
  if (arg0.IsString()) {
100
181
  db_name = arg0.As<Napi::String>().Utf8Value();
101
182
  key_mode = parse_key_mode(env, arg1, key_flag);
@@ -116,24 +197,10 @@ Napi::Value txnmou::get_dbi(const Napi::CallbackInfo& info, db_mode db_mode)
116
197
  throw Napi::Error::New(env, "Invalid argument type: expected string (db_name) or number (key_mode)");
117
198
  }
118
199
  }
119
- // arg_count == 0: используем значения по умолчанию (строковый ключ, default db)
120
-
121
- if (!txn_) {
122
- throw Napi::Error::New(env, "txn already completed");
123
- }
200
+ // arg_count == 0: значения по умолчанию (key/value = buffer, если env не задавал флаги)
124
201
 
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));
130
- }
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;
202
+ return get_dbi(db_name.empty() ? nullptr : db_name.c_str(),
203
+ key_flag, value_flag, key_mode, value_mode, db_mode);
137
204
  }
138
205
 
139
206
  Napi::Value txnmou::open_cursor(const Napi::CallbackInfo& info) {
@@ -178,4 +245,4 @@ void txnmou::attach(envmou& env, MDBX_txn* txn, txn_mode mode)
178
245
  txn_.reset(txn);
179
246
  }
180
247
 
181
- } // namespace mdbxmou
248
+ } // namespace mdbxmou
package/src/txnmou.hpp CHANGED
@@ -27,7 +27,10 @@ private:
27
27
  // Уменьшает счетчик транзакций
28
28
  void dec_counter() noexcept;
29
29
 
30
- Napi::Value get_dbi(const Napi::CallbackInfo&, db_mode);
30
+ Napi::Value get_dbi(const char* name, base_flag key_flag, base_flag value_flag,
31
+ key_mode key_mode, value_mode value_mode, db_mode db_mode);
32
+ Napi::Value get_dbi(const Napi::Object& arg0, db_mode db_mode);
33
+ Napi::Value get_dbi(const Napi::CallbackInfo& info, db_mode db_mode);
31
34
 
32
35
  public:
33
36
  static Napi::FunctionReference ctor;
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 {
@@ -218,7 +246,7 @@ static inline key_mode parse_key_mode(Napi::Env env, const Napi::Value& arg0, ba
218
246
  throw Napi::Error::New(env, "BigInt value lossless conversion failed");
219
247
  }
220
248
  mode.val = static_cast<int>(value);
221
- if (mode.val & key_mode::ordinal) {
249
+ if (mdbx::is_ordinal(mode)) {
222
250
  // только если цифровой key_flag не был задан
223
251
  if (key_flag.val <= base_flag::string) {
224
252
  key_flag.val = base_flag::bigint;
@@ -226,7 +254,7 @@ static inline key_mode parse_key_mode(Napi::Env env, const Napi::Value& arg0, ba
226
254
  }
227
255
  } else if (arg0.IsNumber()) {
228
256
  mode = key_mode::parse(arg0);
229
- if (mode.val & key_mode::ordinal) {
257
+ if (mdbx::is_ordinal(mode)) {
230
258
  // только если цифровой key_flag не был задан
231
259
  if (key_flag.val <= base_flag::string) {
232
260
  key_flag.val = base_flag::number;