@ohos-ports/lmdb 2.5.3 → 2.8.5-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (124) hide show
  1. package/LICENSE +20 -20
  2. package/README.md +72 -28
  3. package/SECURITY.md +12 -12
  4. package/bin/download-prebuilds.js +15 -0
  5. package/binding.gyp +115 -111
  6. package/build/Release/lmdb.node +0 -0
  7. package/caching.js +212 -156
  8. package/dependencies/lmdb/libraries/liblmdb/COPYRIGHT +20 -20
  9. package/dependencies/lmdb/libraries/liblmdb/Doxyfile +1631 -1631
  10. package/dependencies/lmdb/libraries/liblmdb/LICENSE +47 -47
  11. package/dependencies/lmdb/libraries/liblmdb/Makefile +136 -136
  12. package/dependencies/lmdb/libraries/liblmdb/chacha8.c +183 -183
  13. package/dependencies/lmdb/libraries/liblmdb/chacha8.h +14 -14
  14. package/dependencies/lmdb/libraries/liblmdb/crypto.c +121 -121
  15. package/dependencies/lmdb/libraries/liblmdb/intro.doc +192 -192
  16. package/dependencies/lmdb/libraries/liblmdb/lmdb.h +28 -3
  17. package/dependencies/lmdb/libraries/liblmdb/mdb.c +157 -31
  18. package/dependencies/lmdb/libraries/liblmdb/mdb_copy.1 +74 -74
  19. package/dependencies/lmdb/libraries/liblmdb/mdb_copy.c +106 -106
  20. package/dependencies/lmdb/libraries/liblmdb/mdb_drop.1 +53 -53
  21. package/dependencies/lmdb/libraries/liblmdb/mdb_drop.c +154 -154
  22. package/dependencies/lmdb/libraries/liblmdb/mdb_dump.1 +94 -94
  23. package/dependencies/lmdb/libraries/liblmdb/mdb_dump.c +333 -333
  24. package/dependencies/lmdb/libraries/liblmdb/mdb_load.1 +97 -97
  25. package/dependencies/lmdb/libraries/liblmdb/mdb_load.c +530 -530
  26. package/dependencies/lmdb/libraries/liblmdb/mdb_stat.1 +83 -83
  27. package/dependencies/lmdb/libraries/liblmdb/mdb_stat.c +276 -276
  28. package/dependencies/lmdb/libraries/liblmdb/midl-sparse.c +452 -0
  29. package/dependencies/lmdb/libraries/liblmdb/midl.c +452 -452
  30. package/dependencies/lmdb/libraries/liblmdb/midl.h +208 -208
  31. package/dependencies/lmdb/libraries/liblmdb/module.c +101 -101
  32. package/dependencies/lmdb/libraries/liblmdb/module.h +16 -16
  33. package/dependencies/lmdb/libraries/liblmdb/mtest.c +178 -178
  34. package/dependencies/lmdb/libraries/liblmdb/mtest2.c +124 -124
  35. package/dependencies/lmdb/libraries/liblmdb/mtest3.c +133 -133
  36. package/dependencies/lmdb/libraries/liblmdb/mtest4.c +168 -168
  37. package/dependencies/lmdb/libraries/liblmdb/mtest5.c +135 -135
  38. package/dependencies/lmdb/libraries/liblmdb/mtest6.c +141 -141
  39. package/dependencies/lmdb/libraries/liblmdb/mtest_enc.c +190 -190
  40. package/dependencies/lmdb/libraries/liblmdb/mtest_enc2.c +189 -189
  41. package/dependencies/lmdb/libraries/liblmdb/mtest_remap.c +177 -177
  42. package/dependencies/lmdb/libraries/liblmdb/sample-bdb.txt +73 -73
  43. package/dependencies/lmdb/libraries/liblmdb/sample-mdb.txt +62 -62
  44. package/dependencies/lmdb/libraries/liblmdb/tooltag +27 -27
  45. package/dependencies/lmdb-data-v1/libraries/liblmdb/CHANGES +266 -0
  46. package/dependencies/lmdb-data-v1/libraries/liblmdb/COPYRIGHT +20 -20
  47. package/dependencies/lmdb-data-v1/libraries/liblmdb/Doxyfile +1631 -1631
  48. package/dependencies/lmdb-data-v1/libraries/liblmdb/LICENSE +47 -47
  49. package/dependencies/lmdb-data-v1/libraries/liblmdb/intro.doc +192 -192
  50. package/dependencies/lmdb-data-v1/libraries/liblmdb/lmdb.h +1608 -1653
  51. package/dependencies/lmdb-data-v1/libraries/liblmdb/mdb.c +10320 -11359
  52. package/dependencies/lmdb-data-v1/libraries/liblmdb/mdb_copy.1 +55 -61
  53. package/dependencies/lmdb-data-v1/libraries/liblmdb/mdb_copy.c +82 -84
  54. package/dependencies/lmdb-data-v1/libraries/liblmdb/mdb_dump.1 +75 -81
  55. package/dependencies/lmdb-data-v1/libraries/liblmdb/mdb_dump.c +319 -319
  56. package/dependencies/lmdb-data-v1/libraries/liblmdb/mdb_load.1 +84 -84
  57. package/dependencies/lmdb-data-v1/libraries/liblmdb/mdb_load.c +496 -492
  58. package/dependencies/lmdb-data-v1/libraries/liblmdb/mdb_stat.1 +64 -70
  59. package/dependencies/lmdb-data-v1/libraries/liblmdb/mdb_stat.c +263 -264
  60. package/dependencies/lmdb-data-v1/libraries/liblmdb/midl.c +359 -421
  61. package/dependencies/lmdb-data-v1/libraries/liblmdb/midl.h +186 -200
  62. package/dependencies/lmdb-data-v1/libraries/liblmdb/mtest.c +177 -177
  63. package/dependencies/lmdb-data-v1/libraries/liblmdb/mtest2.c +124 -124
  64. package/dependencies/lmdb-data-v1/libraries/liblmdb/mtest3.c +133 -133
  65. package/dependencies/lmdb-data-v1/libraries/liblmdb/mtest4.c +168 -168
  66. package/dependencies/lmdb-data-v1/libraries/liblmdb/mtest5.c +135 -135
  67. package/dependencies/lmdb-data-v1/libraries/liblmdb/mtest6.c +141 -141
  68. package/dependencies/lmdb-data-v1/libraries/liblmdb/sample-bdb.txt +73 -73
  69. package/dependencies/lmdb-data-v1/libraries/liblmdb/sample-mdb.txt +62 -62
  70. package/dependencies/lmdb-data-v1/libraries/liblmdb/tooltag +22 -27
  71. package/dependencies/lz4/LICENSE +11 -11
  72. package/dependencies/lz4/lib/LICENSE +24 -24
  73. package/dependencies/lz4/lib/Makefile +225 -0
  74. package/dependencies/lz4/lib/README.md +169 -137
  75. package/dependencies/lz4/lib/dll/example/Makefile +63 -0
  76. package/dependencies/lz4/lib/dll/example/README.md +69 -69
  77. package/dependencies/lz4/lib/dll/liblz4.def +62 -62
  78. package/dependencies/lz4/lib/liblz4-dll.rc.in +35 -35
  79. package/dependencies/lz4/lib/liblz4.pc.in +14 -14
  80. package/dependencies/lz4/lib/lz4.c +2722 -2495
  81. package/dependencies/lz4/lib/lz4.h +842 -774
  82. package/dependencies/lz4/lib/lz4file.c +311 -0
  83. package/dependencies/lz4/lib/lz4file.h +93 -0
  84. package/dependencies/lz4/lib/lz4frame.c +2078 -1899
  85. package/dependencies/lz4/lib/lz4frame.h +692 -623
  86. package/dependencies/lz4/lib/lz4frame_static.h +47 -47
  87. package/dependencies/lz4/lib/lz4hc.c +1631 -1615
  88. package/dependencies/lz4/lib/lz4hc.h +413 -413
  89. package/dependencies/lz4/lib/xxhash.c +1030 -1030
  90. package/dependencies/lz4/lib/xxhash.h +328 -328
  91. package/dependencies/v8/v8-fast-api-calls-v16.h +790 -790
  92. package/dependencies/v8/v8-fast-api-calls.h +784 -888
  93. package/dist/index.cjs +1050 -584
  94. package/dist/index.cjs.map +1 -1
  95. package/index.d.cts +420 -0
  96. package/index.d.ts +450 -385
  97. package/index.js +38 -37
  98. package/keys.js +116 -102
  99. package/level.js +26 -26
  100. package/native.js +8 -5
  101. package/node-index.js +21 -17
  102. package/open.js +51 -17
  103. package/package.json +55 -21
  104. package/read.js +338 -95
  105. package/src/compression.cpp +12 -9
  106. package/src/cursor.cpp +82 -31
  107. package/src/dbi.cpp +78 -40
  108. package/src/env.cpp +336 -68
  109. package/src/lmdb-js.cpp +64 -64
  110. package/src/lmdb-js.h +83 -49
  111. package/src/misc.cpp +297 -28
  112. package/src/ordered-binary.cpp +46 -46
  113. package/src/txn.cpp +210 -257
  114. package/src/v8-functions.cpp +170 -166
  115. package/src/windows.c +34 -34
  116. package/src/writer.cpp +169 -84
  117. package/util/RangeIterable.js +112 -17
  118. package/util/set-optional-deps.cjs +14 -10
  119. package/util/when.js +8 -8
  120. package/write.js +145 -121
  121. package/dependencies/lmdb-data-v1/libraries/liblmdb/Makefile +0 -118
  122. package/dependencies/lmdb-data-v1/libraries/liblmdb/mdb_drop.1 +0 -40
  123. package/dependencies/lmdb-data-v1/libraries/liblmdb/mdb_drop.c +0 -135
  124. package/rollup.config.js +0 -12
package/read.js CHANGED
@@ -1,44 +1,60 @@
1
1
  import { RangeIterable } from './util/RangeIterable.js';
2
- import { getAddress, Cursor, Txn, orderedBinary, lmdbError, getByBinary, detachBuffer, setGlobalBuffer, prefetch, iterate, position as doPosition, resetTxn, getCurrentValue, getCurrentShared, getStringByBinary, getSharedByBinary } from './native.js';
2
+ import { getAddress, Cursor, Txn, orderedBinary, lmdbError, getByBinary, setGlobalBuffer, prefetch, iterate, position as doPosition, resetTxn, getCurrentValue, getCurrentShared, getStringByBinary, globalBuffer, getSharedBuffer, startRead, setReadCallback } from './native.js';
3
3
  import { saveKey } from './keys.js';
4
+ const IF_EXISTS = 3.542694326329068e-103;
4
5
  const ITERATOR_DONE = { done: true, value: undefined };
5
6
  const Uint8ArraySlice = Uint8Array.prototype.slice;
6
- const Uint8A = typeof Buffer != 'undefined' ? Buffer.allocUnsafeSlow : Uint8Array
7
- let getValueBytes = makeReusableBuffer(0);
7
+ let getValueBytes = globalBuffer;
8
+ if (!getValueBytes.maxLength) {
9
+ getValueBytes.maxLength = getValueBytes.length;
10
+ getValueBytes.isGlobal = true;
11
+ Object.defineProperty(getValueBytes, 'length', { value: getValueBytes.length, writable: true, configurable: true });
12
+ }
8
13
  const START_ADDRESS_POSITION = 4064;
9
14
  const NEW_BUFFER_THRESHOLD = 0x8000;
15
+ const SOURCE_SYMBOL = Symbol.for('source');
16
+ export const UNMODIFIED = {};
17
+ let mmaps = [];
10
18
 
11
19
  export function addReadMethods(LMDBStore, {
12
- maxKeySize, env, keyBytes, keyBytesView, getLastVersion
20
+ maxKeySize, env, keyBytes, keyBytesView, getLastVersion, getLastTxnId
13
21
  }) {
14
22
  let readTxn, readTxnRenewed, asSafeBuffer = false;
15
23
  let renewId = 1;
24
+ let outstandingReads = 0;
16
25
  Object.assign(LMDBStore.prototype, {
17
- getString(id) {
18
- (env.writeTxn || (readTxnRenewed ? readTxn : renewReadTxn(this)));
19
- let string = getStringByBinary(this.dbAddress, this.writeKey(id, keyBytes, 0));
26
+ getString(id, options) {
27
+ let txn = env.writeTxn || (options && options.transaction) || (readTxnRenewed ? readTxn : renewReadTxn(this));
28
+ let string = getStringByBinary(this.dbAddress, this.writeKey(id, keyBytes, 0), txn.address || 0);
20
29
  if (typeof string === 'number') { // indicates the buffer wasn't large enough
21
30
  this._allocateGetBuffer(string);
22
31
  // and then try again
23
- string = getStringByBinary(this.dbAddress, this.writeKey(id, keyBytes, 0));
32
+ string = getStringByBinary(this.dbAddress, this.writeKey(id, keyBytes, 0), txn.address || 0);
24
33
  }
25
34
  if (string)
26
35
  this.lastSize = string.length;
27
36
  return string;
28
37
  },
29
- getBinaryFast(id) {
30
- (env.writeTxn || (readTxnRenewed ? readTxn : renewReadTxn(this)));
31
- let rc = this.lastSize = getByBinary(this.dbAddress, this.writeKey(id, keyBytes, 0));
38
+ getBinaryFast(id, options) {
39
+ let rc;
40
+ let txn = env.writeTxn || (options && options.transaction) || (readTxnRenewed ? readTxn : renewReadTxn(this));
41
+ rc = this.lastSize = getByBinary(this.dbAddress, this.writeKey(id, keyBytes, 0), (options && options.ifNotTxnId) || 0, txn.address || 0);
32
42
  if (rc < 0) {
33
43
  if (rc == -30798) // MDB_NOTFOUND
34
44
  return; // undefined
45
+ if (rc == -30004) // txn id matched
46
+ return UNMODIFIED;
35
47
  if (rc == -30781 /*MDB_BAD_VALSIZE*/ && this.writeKey(id, keyBytes, 0) == 0)
36
48
  throw new Error(id === undefined ?
37
49
  'A key is required for get, but is undefined' :
38
50
  'Zero length key is not allowed in LMDB');
39
51
  if (rc == -30000) // int32 overflow, read uint32
40
52
  rc = this.lastSize = keyBytesView.getUint32(0, true);
41
- else
53
+ else if (rc == -30001) {// shared buffer
54
+ this.lastSize = keyBytesView.getUint32(0, true);
55
+ let bufferId = keyBytesView.getUint32(4, true);
56
+ return getMMapBuffer(bufferId, this.lastSize);
57
+ } else
42
58
  throw lmdbError(rc);
43
59
  }
44
60
  let compression = this.compression;
@@ -46,13 +62,89 @@ export function addReadMethods(LMDBStore, {
46
62
  if (rc > bytes.maxLength) {
47
63
  // this means the target buffer wasn't big enough, so the get failed to copy all the data from the database, need to either grow or use special buffer
48
64
  return this._returnLargeBuffer(
49
- () => getByBinary(this.dbAddress, this.writeKey(id, keyBytes, 0)),
50
- () => getSharedByBinary(this.dbAddress, this.writeKey(id, keyBytes, 0)));
65
+ () => getByBinary(this.dbAddress, this.writeKey(id, keyBytes, 0), 0, txn.address || 0));
51
66
  }
52
67
  bytes.length = this.lastSize;
53
68
  return bytes;
54
69
  },
55
- _returnLargeBuffer(getFast, getShared) {
70
+ getBFAsync(id, options, callback) {
71
+ let txn = env.writeTxn || (options && options.transaction) || (readTxnRenewed ? readTxn : renewReadTxn(this));
72
+ txn.refCount = (txn.refCount || 0) + 1;
73
+ outstandingReads++;
74
+ let address = recordReadInstruction(txn.address, this.db.dbi, id, this.writeKey, maxKeySize, ( rc, bufferId, offset, size ) => {
75
+ if (rc && rc !== 1)
76
+ callback(lmdbError(rc));
77
+ outstandingReads--;
78
+ let buffer = mmaps[bufferId];
79
+ if (!buffer) {
80
+ buffer = mmaps[bufferId] = getSharedBuffer(bufferId, env.address);
81
+ }
82
+ //console.log({bufferId, offset, size})
83
+ if (buffer.isSharedMap) {
84
+ // using LMDB shared memory
85
+ // TODO: We may want explicit support for clearing aborting the transaction on the next event turn,
86
+ // but for now we are relying on the GC to cleanup transaction for larger blocks of memory
87
+ let bytes = new Uint8Array(buffer, offset, size);
88
+ bytes.txn = txn;
89
+ callback(bytes, 0, size);
90
+ } else {
91
+ // using copied memory
92
+ txn.done(); // decrement and possibly abort
93
+ callback(buffer, offset, size);
94
+ }
95
+ });
96
+ if (address) {
97
+ startRead(address, () => {
98
+ resolveReads();
99
+ });
100
+ }
101
+ },
102
+ getAsync(id, options, callback) {
103
+ let promise;
104
+ if (!callback)
105
+ promise = new Promise(resolve => callback = resolve);
106
+ this.getBFAsync(id, options, (buffer, offset, size) => {
107
+ if (this.useVersions) {
108
+ // TODO: And get the version
109
+ offset += 8;
110
+ size -= 8;
111
+ }
112
+ let bytes = new Uint8Array(buffer, offset, size);
113
+ let value;
114
+ if (this.decoder) {
115
+ // the decoder potentially uses the data from the buffer in the future and needs a stable buffer
116
+ value = bytes && this.decoder.decode(bytes);
117
+ } else if (this.encoding == 'binary') {
118
+ value = bytes;
119
+ } else {
120
+ value = Buffer.prototype.utf8Slice.call(bytes, 0, size);
121
+ if (this.encoding == 'json' && value)
122
+ value = JSON.parse(value);
123
+ }
124
+ callback(value);
125
+ });
126
+ return promise;
127
+ },
128
+ retain(data, options) {
129
+ if (!data)
130
+ return
131
+ let source = data[SOURCE_SYMBOL];
132
+ let buffer = source ? source.bytes : data;
133
+ if (!buffer.isGlobal && !env.writeTxn) {
134
+ let txn = options?.transaction || (readTxnRenewed ? readTxn : renewReadTxn(this));
135
+ buffer.txn = txn;
136
+ txn.refCount = (txn.refCount || 0) + 1;
137
+ return data;
138
+ } else {
139
+ buffer = Uint8ArraySlice.call(buffer, 0, this.lastSize);
140
+ if (source) {
141
+ source.bytes = buffer;
142
+ return data;
143
+ } else
144
+ return buffer;
145
+ }
146
+ },
147
+ _returnLargeBuffer(getFast) {
56
148
  let bytes;
57
149
  let compression = this.compression;
58
150
  if (asSafeBuffer && this.lastSize > NEW_BUFFER_THRESHOLD) {
@@ -64,7 +156,7 @@ export function addReadMethods(LMDBStore, {
64
156
  let dictionary = compression.dictionary || [];
65
157
  let dictLength = (dictionary.length >> 3) << 3;// make sure it is word-aligned
66
158
  bytes = makeReusableBuffer(this.lastSize);
67
- compression.setBuffer(bytes, this.lastSize, dictionary, dictLength);
159
+ compression.setBuffer(bytes.buffer, bytes.byteOffset, this.lastSize, dictionary, dictLength);
68
160
  compression.getValueBytes = bytes;
69
161
  } else {
70
162
  bytesToRestore = getValueBytes;
@@ -74,7 +166,7 @@ export function addReadMethods(LMDBStore, {
74
166
  } finally {
75
167
  if (compression) {
76
168
  let dictLength = (compression.dictionary.length >> 3) << 3;
77
- compression.setBuffer(bytesToRestore, bytesToRestore.maxLength, compression.dictionary, dictLength);
169
+ compression.setBuffer(bytesToRestore.buffer, bytesToRestore.byteOffset, bytesToRestore.maxLength, compression.dictionary, dictLength);
78
170
  compression.getValueBytes = bytesToRestore;
79
171
  } else {
80
172
  setGlobalBuffer(bytesToRestore);
@@ -83,12 +175,6 @@ export function addReadMethods(LMDBStore, {
83
175
  }
84
176
  return bytes;
85
177
  }
86
- if (this.lastSize > NEW_BUFFER_THRESHOLD && !compression) {
87
- // for large binary objects, cheaper to make a buffer that directly points at the shared LMDB memory to avoid copying a large amount of memory, but only for large data since there is significant overhead to instantiating the buffer
88
- if (globalThis.__lmdb_last_shared__ && detachBuffer) // we have to detach the last one, or else could crash due to two buffers pointing at same location
89
- detachBuffer(globalThis.__lmdb_last_shared__.buffer);
90
- return globalThis.__lmdb_last_shared__ = getShared();
91
- }
92
178
  // grow our shared/static buffer to accomodate the size of the data
93
179
  bytes = this._allocateGetBuffer(this.lastSize);
94
180
  // and try again
@@ -100,13 +186,13 @@ export function addReadMethods(LMDBStore, {
100
186
  let newLength = Math.min(Math.max(lastSize * 2, 0x1000), 0xfffffff8);
101
187
  let bytes;
102
188
  if (this.compression) {
103
- let dictionary = this.compression.dictionary || new Uint8A(0);
189
+ let dictionary = this.compression.dictionary || Buffer.allocUnsafeSlow(0);
104
190
  let dictLength = (dictionary.length >> 3) << 3;// make sure it is word-aligned
105
- bytes = new Uint8A(newLength + dictLength);
191
+ bytes = Buffer.allocUnsafeSlow(newLength + dictLength);
106
192
  bytes.set(dictionary) // copy dictionary into start
107
193
  // the section after the dictionary is the target area for get values
108
194
  bytes = bytes.subarray(dictLength);
109
- this.compression.setBuffer(bytes, newLength, dictionary, dictLength);
195
+ this.compression.setBuffer(bytes.buffer, bytes.byteOffset, newLength, dictionary, dictLength);
110
196
  bytes.maxLength = newLength;
111
197
  Object.defineProperty(bytes, 'length', { value: newLength, writable: true, configurable: true });
112
198
  this.compression.getValueBytes = bytes;
@@ -114,41 +200,51 @@ export function addReadMethods(LMDBStore, {
114
200
  bytes = makeReusableBuffer(newLength);
115
201
  setGlobalBuffer(getValueBytes = bytes);
116
202
  }
117
- bytes.isShared = true;
203
+ bytes.isGlobal = true;
118
204
  return bytes;
119
205
  },
120
- getBinary(id) {
206
+ getBinary(id, options) {
121
207
  try {
122
208
  asSafeBuffer = true;
123
- let fastBuffer = this.getBinaryFast(id);
124
- return fastBuffer && (fastBuffer.isShared ? Uint8ArraySlice.call(fastBuffer, 0, this.lastSize) : fastBuffer);
209
+ let fastBuffer = this.getBinaryFast(id, options);
210
+ return fastBuffer && (fastBuffer.isGlobal ? Uint8ArraySlice.call(fastBuffer, 0, this.lastSize) : fastBuffer);
125
211
  } finally {
126
212
  asSafeBuffer = false;
127
213
  }
128
214
  },
129
- get(id) {
215
+ getSharedBinary(id, options) {
216
+ let fastBuffer = this.getBinaryFast(id, options);
217
+ if (fastBuffer) {
218
+ if (fastBuffer.isGlobal || writeTxn)
219
+ return Uint8ArraySlice.call(fastBuffer, 0, this.lastSize)
220
+ fastBuffer.txn = (options && options.transaction);
221
+ options.transaction.refCount = (options.transaction.refCount || 0) + 1;
222
+ return fastBuffer;
223
+ }
224
+ },
225
+ get(id, options) {
130
226
  if (this.decoderCopies) {
131
227
  // the decoder copies any data, so we can use the fast binary retrieval that overwrites the same buffer space
132
- let bytes = this.getBinaryFast(id);
133
- return bytes && this.decoder.decode(bytes);
228
+ let bytes = this.getBinaryFast(id, options);
229
+ return bytes && (bytes == UNMODIFIED ? UNMODIFIED : this.decoder.decode(bytes, options));
134
230
  }
135
231
  if (this.encoding == 'binary')
136
- return this.getBinary(id);
232
+ return this.getBinary(id, options);
137
233
  if (this.decoder) {
138
234
  // the decoder potentially uses the data from the buffer in the future and needs a stable buffer
139
- let bytes = this.getBinary(id);
140
- return bytes && this.decoder.decode(bytes);
235
+ let bytes = this.getBinary(id, options);
236
+ return bytes && (bytes == UNMODIFIED ? UNMODIFIED : this.decoder.decode(bytes));
141
237
  }
142
238
 
143
- let result = this.getString(id);
239
+ let result = this.getString(id, options);
144
240
  if (result) {
145
241
  if (this.encoding == 'json')
146
242
  return JSON.parse(result);
147
243
  }
148
244
  return result;
149
245
  },
150
- getEntry(id) {
151
- let value = this.get(id);
246
+ getEntry(id, options) {
247
+ let value = this.get(id, options);
152
248
  if (value !== undefined) {
153
249
  if (this.useVersions)
154
250
  return {
@@ -167,8 +263,11 @@ export function addReadMethods(LMDBStore, {
167
263
  resetReadTxn();
168
264
  },
169
265
  _commitReadTxn() {
170
- if (readTxn)
266
+ if (readTxn) {
267
+ readTxn.isCommitted = true
171
268
  readTxn.commit();
269
+ }
270
+ lastReadTxnRef = null;
172
271
  readTxnRenewed = null;
173
272
  readTxn = null;
174
273
  },
@@ -177,15 +276,12 @@ export function addReadMethods(LMDBStore, {
177
276
  renewReadTxn(this);
178
277
  },
179
278
  doesExist(key, versionOrValue) {
180
- if (!env.writeTxn)
181
- readTxnRenewed ? readTxn : renewReadTxn(this);
182
279
  if (versionOrValue == null) {
183
280
  // undefined means the entry exists, null is used specifically to check for the entry *not* existing
184
281
  return (this.getBinaryFast(key) === undefined) == (versionOrValue === null);
185
282
  }
186
283
  else if (this.useVersions) {
187
- this.getBinaryFast(key);
188
- return this.getBinaryFast(key) !== undefined && getLastVersion() === versionOrValue;
284
+ return this.getBinaryFast(key) !== undefined && (versionOrValue === IF_EXISTS || getLastVersion() === versionOrValue);
189
285
  }
190
286
  else {
191
287
  if (versionOrValue && versionOrValue['\x10binary-data\x02'])
@@ -251,28 +347,39 @@ export function addReadMethods(LMDBStore, {
251
347
  let cursor, cursorRenewId, cursorAddress;
252
348
  let txn;
253
349
  let flags = (includeValues ? 0x100 : 0) | (reverse ? 0x400 : 0) |
254
- (valuesForKey ? 0x800 : 0) | (options.exactMatch ? 0x4000 : 0);
350
+ (valuesForKey ? 0x800 : 0) | (options.exactMatch ? 0x4000 : 0) |
351
+ (options.inclusiveEnd ? 0x8000 : 0) |
352
+ (options.exclusiveStart ? 0x10000 : 0);
255
353
  let store = this;
256
354
  function resetCursor() {
257
355
  try {
258
356
  if (cursor)
259
357
  finishCursor();
260
- let writeTxn = env.writeTxn;
261
- if (writeTxn)
262
- snapshot = false;
263
- txn = writeTxn || (readTxnRenewed ? readTxn : renewReadTxn(store));
264
- cursor = !writeTxn && db.availableCursor;
358
+ let txnAddress;
359
+ txn = options.transaction
360
+ if (txn) {
361
+ if (txn.isDone) throw new Error('Can not iterate on range with transaction that is already' +
362
+ ' done');
363
+ txnAddress = txn.address;
364
+ cursor = null;
365
+ } else {
366
+ let writeTxn = env.writeTxn;
367
+ if (writeTxn)
368
+ snapshot = false;
369
+ txn = env.writeTxn || options.transaction || (readTxnRenewed ? readTxn : renewReadTxn(store));
370
+ cursor = !writeTxn && db.availableCursor;
371
+ }
265
372
  if (cursor) {
266
373
  db.availableCursor = null;
267
374
  flags |= 0x2000;
268
375
  } else {
269
- cursor = new Cursor(db);
376
+ cursor = new Cursor(db, txnAddress || 0);
270
377
  }
271
378
  cursorAddress = cursor.address;
272
- txn.cursorCount = (txn.cursorCount || 0) + 1; // track transaction so we always use the same one
379
+ txn.refCount = (txn.refCount || 0) + 1; // track transaction so we always use the same one
273
380
  if (snapshot === false) {
274
381
  cursorRenewId = renewId; // use shared read transaction
275
- txn.renewingCursorCount = (txn.renewingCursorCount || 0) + 1; // need to know how many are renewing cursors
382
+ txn.renewingRefCount = (txn.renewingRefCount || 0) + 1; // need to know how many are renewing cursors
276
383
  }
277
384
  } catch(error) {
278
385
  if (cursor) {
@@ -293,6 +400,9 @@ export function addReadMethods(LMDBStore, {
293
400
  return count;
294
401
  }
295
402
  function position(offset) {
403
+ if (!env.address) {
404
+ throw new Error('Can not iterate on a closed database');
405
+ }
296
406
  let keySize = currentKey === undefined ? 0 : store.writeKey(currentKey, keyBytes, 0);
297
407
  let endAddress;
298
408
  if (valuesForKey) {
@@ -311,7 +421,7 @@ export function addReadMethods(LMDBStore, {
311
421
  } else {
312
422
  throw new Error('Only key-based encoding is supported for start/end values');
313
423
  let encoded = store.encoder.encode(options.start);
314
- let bufferAddress = encoded.buffer.address || (encoded.buffer.address = getAddress(encoded) - encoded.byteOffset);
424
+ let bufferAddress = encoded.buffer.address || (encoded.buffer.address = getAddress(encoded.buffer) - encoded.byteOffset);
315
425
  startAddress = bufferAddress + encoded.byteOffset;
316
426
  }
317
427
  }
@@ -323,9 +433,11 @@ export function addReadMethods(LMDBStore, {
323
433
  function finishCursor() {
324
434
  if (txn.isDone)
325
435
  return;
436
+ if (iterable.onDone)
437
+ iterable.onDone()
326
438
  if (cursorRenewId)
327
- txn.renewingCursorCount--;
328
- if (--txn.cursorCount <= 0 && txn.onlyCursor) {
439
+ txn.renewingRefCount--;
440
+ if (--txn.refCount <= 0 && txn.notCurrent) {
329
441
  cursor.close();
330
442
  txn.abort(); // this is no longer main read txn, abort it now that we are done
331
443
  txn.isDone = true;
@@ -352,7 +464,7 @@ export function addReadMethods(LMDBStore, {
352
464
  if (keySize <= 0 ||
353
465
  (count++ >= limit)) {
354
466
  if (count < 0)
355
- lmdbError(count);
467
+ lmdbError(count);
356
468
  finishCursor();
357
469
  return ITERATOR_DONE;
358
470
  }
@@ -367,24 +479,27 @@ export function addReadMethods(LMDBStore, {
367
479
  if (includeValues) {
368
480
  let value;
369
481
  lastSize = keyBytesView.getUint32(0, true);
370
- let bytes = compression ? compression.getValueBytes : getValueBytes;
371
- if (lastSize > bytes.maxLength) {
372
- store.lastSize = lastSize;
373
- asSafeBuffer = store.encoding == 'binary';
374
- try {
375
- bytes = store._returnLargeBuffer(
376
- () => getCurrentValue(cursorAddress),
377
- () => getCurrentShared(cursorAddress)
378
- );
379
- } finally {
380
- asSafeBuffer = false;
381
- }
382
- } else
383
- bytes.length = lastSize;
482
+ let bufferId = keyBytesView.getUint32(4, true);
483
+ let bytes;
484
+ if (bufferId) {
485
+ bytes = getMMapBuffer(bufferId, lastSize);
486
+ } else {
487
+ bytes = compression ? compression.getValueBytes : getValueBytes;
488
+ if (lastSize > bytes.maxLength) {
489
+ store.lastSize = lastSize;
490
+ asSafeBuffer = store.encoding == 'binary';
491
+ try {
492
+ bytes = store._returnLargeBuffer(() => getCurrentValue(cursorAddress));
493
+ } finally {
494
+ asSafeBuffer = false;
495
+ }
496
+ } else
497
+ bytes.length = lastSize;
498
+ }
384
499
  if (store.decoder) {
385
500
  value = store.decoder.decode(bytes, lastSize);
386
501
  } else if (store.encoding == 'binary')
387
- value = bytes.isShared ? Uint8ArraySlice.call(bytes, 0, lastSize) : bytes;
502
+ value = bytes.isGlobal ? Uint8ArraySlice.call(bytes, 0, lastSize) : bytes;
388
503
  else {
389
504
  value = bytes.toString('utf8', 0, lastSize);
390
505
  if (store.encoding == 'json' && value)
@@ -450,8 +565,8 @@ export function addReadMethods(LMDBStore, {
450
565
  });
451
566
  return promise;
452
567
  },
453
- getSharedBufferForGet(id) {
454
- let txn = (env.writeTxn || (readTxnRenewed ? readTxn : renewReadTxn(this)));
568
+ getSharedBufferForGet(id, options) {
569
+ let txn = env.writeTxn || (options && options.transaction) || (readTxnRenewed ? readTxn : renewReadTxn(this));
455
570
  this.lastSize = this.keyIsCompatibility ? txn.getBinaryShared(id) : this.db.get(this.writeKey(id, keyBytes, 0));
456
571
  if (this.lastSize === -30798) { // not found code
457
572
  return; //undefined
@@ -489,7 +604,13 @@ export function addReadMethods(LMDBStore, {
489
604
  let bufferHolder = {};
490
605
  let lastBuffer;
491
606
  for (let key of keys) {
492
- let position = saveKey(key, this.writeKey, bufferHolder, maxKeySize);
607
+ let position
608
+ if (key && key.key !== undefined && key.value !== undefined) {
609
+ position = saveKey(key.value, this.writeKey, bufferHolder, maxKeySize, 0x80000000);
610
+ saveKey(key.key, this.writeKey, bufferHolder, maxKeySize);
611
+ } else {
612
+ position = saveKey(key, this.writeKey, bufferHolder, maxKeySize);
613
+ }
493
614
  if (!startPosition)
494
615
  startPosition = position;
495
616
  if (bufferHolder.saveBuffer != lastBuffer) {
@@ -499,7 +620,9 @@ export function addReadMethods(LMDBStore, {
499
620
  }
500
621
  }
501
622
  saveKey(undefined, this.writeKey, bufferHolder, maxKeySize);
623
+ outstandingReads++;
502
624
  prefetch(this.dbAddress, startPosition, (error) => {
625
+ outstandingReads--;
503
626
  if (error)
504
627
  console.error('Error with prefetch', buffers, bufferHolder); // partly exists to keep the buffers pinned in memory
505
628
  else
@@ -508,27 +631,44 @@ export function addReadMethods(LMDBStore, {
508
631
  if (!callback)
509
632
  return new Promise(resolve => callback = resolve);
510
633
  },
634
+ useReadTransaction() {
635
+ let txn = readTxnRenewed ? readTxn : renewReadTxn(this);
636
+ if (!txn.use) {
637
+ throw new Error('Can not use read transaction from a closed database');
638
+ }
639
+ txn.use();
640
+ return txn;
641
+ },
511
642
  close(callback) {
512
643
  this.status = 'closing';
644
+ let txnPromise;
513
645
  if (this.isRoot) {
514
- if (readTxn) {
515
- try {
516
- readTxn.abort();
517
- } catch(error) {}
518
- }
519
- readTxn = {
520
- renew() {
646
+ // if it is root, we need to abort and/or wait for transactions to finish
647
+ if (readTxn) readTxn.abort();
648
+ else readTxn = {};
649
+ readTxn.isDone = true;
650
+ Object.defineProperty(readTxn,'renew', {
651
+ value: () => {
521
652
  throw new Error('Can not read from a closed database');
522
- }
523
- };
653
+ }, configurable: true
654
+ });
655
+ Object.defineProperty(readTxn,'use', {
656
+ value: () => {
657
+ throw new Error('Can not read from a closed database');
658
+ }, configurable: true
659
+ });
524
660
  readTxnRenewed = null;
661
+ txnPromise = this._endWrites && this._endWrites();
525
662
  }
526
- let txnPromise = this._endWrites();
527
663
  const doClose = () => {
528
- this.db.close();
529
664
  if (this.isRoot) {
665
+ if (outstandingReads > 0) {
666
+ return new Promise(resolve => setTimeout(() => resolve(doClose()), 1));
667
+ }
668
+ env.address = 0;
530
669
  env.close();
531
- }
670
+ } else
671
+ this.db.close();
532
672
  this.status = 'closed';
533
673
  if (callback)
534
674
  callback();
@@ -541,18 +681,40 @@ export function addReadMethods(LMDBStore, {
541
681
  }
542
682
  },
543
683
  getStats() {
544
- readTxnRenewed ? readTxn : renewReadTxn(this);
545
- return this.db.stat();
546
- }
684
+ let txn = env.writeTxn || (readTxnRenewed ? readTxn : renewReadTxn(this));
685
+ let dbStats = this.db.stat();
686
+ dbStats.root = env.stat();
687
+ Object.assign(dbStats, env.info());
688
+ dbStats.free = env.freeStat();
689
+ return dbStats;
690
+ },
547
691
  });
548
692
  let get = LMDBStore.prototype.get;
693
+ let lastReadTxnRef;
694
+ function getMMapBuffer(bufferId, size) {
695
+ let buffer = mmaps[bufferId];
696
+ if (!buffer) {
697
+ buffer = mmaps[bufferId] = getSharedBuffer(bufferId, env.address);
698
+ }
699
+ let offset = keyBytesView.getUint32(8, true);
700
+ return new Uint8Array(buffer, offset, size);
701
+ }
549
702
  function renewReadTxn(store) {
703
+ if (!env.address) {
704
+ throw new Error('Can not renew a transaction from a closed database');
705
+ }
550
706
  if (!readTxn) {
551
707
  let retries = 0;
552
708
  let waitArray;
553
709
  do {
554
710
  try {
555
- readTxn = new Txn(env, 0x20000);
711
+ let lastReadTxn = lastReadTxnRef && lastReadTxnRef.deref();
712
+ readTxn = new Txn(env, 0x20000, lastReadTxn && !lastReadTxn.isDone && lastReadTxn);
713
+ if (readTxn.address == 0) {
714
+ readTxn = lastReadTxn;
715
+ if (readTxn.notCurrent)
716
+ readTxn.notCurrent = false;
717
+ }
556
718
  break;
557
719
  } catch (error) {
558
720
  if (error.message.includes('temporarily')) {
@@ -570,15 +732,20 @@ export function addReadMethods(LMDBStore, {
570
732
  store.emit('begin-transaction');
571
733
  return readTxn;
572
734
  }
573
- function resetReadTxn(hardReset) {
735
+ function resetReadTxn() {
574
736
  renewId++;
575
737
  if (readTxnRenewed) {
576
738
  readTxnRenewed = null;
577
- if (readTxn.cursorCount - (readTxn.renewingCursorCount || 0) > 0) {
578
- readTxn.onlyCursor = true;
739
+ if (readTxn.refCount - (readTxn.renewingRefCount || 0) > 0) {
740
+ readTxn.notCurrent = true;
741
+ lastReadTxnRef = new WeakRef(readTxn);
579
742
  readTxn = null;
580
- } else
743
+ } else if (readTxn.address && !readTxn.isDone) {
581
744
  resetTxn(readTxn.address);
745
+ } else {
746
+ console.warn('Attempt to reset an invalid read txn', readTxn);
747
+ throw new Error('Attempt to reset an invalid read txn');
748
+ }
582
749
  }
583
750
  }
584
751
  }
@@ -588,3 +755,79 @@ export function makeReusableBuffer(size) {
588
755
  Object.defineProperty(bytes, 'length', { value: size, writable: true, configurable: true });
589
756
  return bytes;
590
757
  }
758
+
759
+ Txn.prototype.done = function() {
760
+ this.refCount--;
761
+ if (this.refCount === 0 && this.notCurrent) {
762
+ this.abort();
763
+ this.isDone = true;
764
+ }
765
+ }
766
+ Txn.prototype.use = function() {
767
+ this.refCount = (this.refCount || 0) + 1;
768
+ }
769
+
770
+
771
+ let readInstructions, readCallbacks = new Map(), uint32Instructions, instructionsDataView = { setFloat64() {}, setUint32() {} }, instructionsAddress;
772
+ let savePosition = 8000;
773
+ let DYNAMIC_KEY_BUFFER_SIZE = 8192;
774
+ function allocateInstructionsBuffer() {
775
+ readInstructions = typeof Buffer != 'undefined' ? Buffer.alloc(DYNAMIC_KEY_BUFFER_SIZE) : new Uint8Array(DYNAMIC_KEY_BUFFER_SIZE);
776
+ uint32Instructions = new Int32Array(readInstructions.buffer, 0, readInstructions.buffer.byteLength >> 2);
777
+ uint32Instructions[2] = 0xf0000000; // indicates a new read task must be started
778
+ instructionsAddress = readInstructions.buffer.address = getAddress(readInstructions.buffer);
779
+ readInstructions.dataView = instructionsDataView = new DataView(readInstructions.buffer, readInstructions.byteOffset, readInstructions.byteLength);
780
+ savePosition = 0;
781
+ }
782
+ export function recordReadInstruction(txnAddress, dbi, key, writeKey, maxKeySize, callback) {
783
+ if (savePosition > 7800) {
784
+ allocateInstructionsBuffer();
785
+ }
786
+ let start = savePosition;
787
+ let keyPosition = savePosition + 16;
788
+ try {
789
+ savePosition = key === undefined ? keyPosition :
790
+ writeKey(key, readInstructions, keyPosition);
791
+ } catch (error) {
792
+ if (error.name == 'RangeError') {
793
+ if (8180 - start < maxKeySize) {
794
+ allocateInstructionsBuffer(); // try again:
795
+ return recordReadInstruction(txnAddress, dbi, key, writeKey, maxKeySize, callback);
796
+ }
797
+ throw new Error('Key was too large, max key size is ' + maxKeySize);
798
+ } else
799
+ throw error;
800
+ }
801
+ let length = savePosition - keyPosition;
802
+ if (length > maxKeySize) {
803
+ savePosition = start;
804
+ throw new Error('Key of size ' + length + ' was too large, max key size is ' + maxKeySize);
805
+ }
806
+ uint32Instructions[(start >> 2) + 3] = length; // save the length
807
+ uint32Instructions[(start >> 2) + 2] = dbi;
808
+ savePosition = (savePosition + 12) & 0xfffffc;
809
+ instructionsDataView.setFloat64(start, txnAddress, true);
810
+ let callbackId = addReadCallback(() => {
811
+ let position = start >> 2;
812
+ let rc = thisInstructions[position];
813
+ callback(rc, thisInstructions[position + 1], thisInstructions[position + 2], thisInstructions[position + 3]);
814
+ });
815
+ let thisInstructions = uint32Instructions;
816
+ //if (start === 0)
817
+ return startRead(instructionsAddress + start, callbackId, {}, 'read');
818
+ //else
819
+ //nextRead(start);
820
+ }
821
+ let nextCallbackId = 0;
822
+ let addReadCallback = globalThis.__lmdb_read_callback;
823
+ if (!addReadCallback) {
824
+ addReadCallback = globalThis.__lmdb_read_callback = function(callback) {
825
+ let callbackId = nextCallbackId++;
826
+ readCallbacks.set(callbackId, callback);
827
+ return callbackId;
828
+ };
829
+ setReadCallback(function(callbackId) {
830
+ readCallbacks.get(callbackId)();
831
+ readCallbacks.delete(callbackId);
832
+ })
833
+ }