@stoolap/node 0.4.1 → 0.5.0-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.
package/lib/statement.js CHANGED
@@ -1,6 +1,5 @@
1
1
  "use strict";
2
2
 
3
- const { processParams } = require("./ffi");
4
3
  const {
5
4
  parseRows,
6
5
  parseRaw,
@@ -8,6 +7,15 @@ const {
8
7
  makeRowFactory,
9
8
  parseRowsCached,
10
9
  } = require("./parse");
10
+ const { RowCursor } = require("./iterator");
11
+
12
+ function sameHeader(bytes, header) {
13
+ if (!header || bytes.length < header.length) return false;
14
+ for (let i = 0; i < header.length; i++) {
15
+ if (bytes[i] !== header[i]) return false;
16
+ }
17
+ return true;
18
+ }
11
19
 
12
20
  /* Call stmtExecNums with direct numeric args (no array wrapping).
13
21
  * Manual unpack for 1-3 params avoids spread overhead. */
@@ -84,8 +92,9 @@ let _pbuf = Buffer.allocUnsafe(16384);
84
92
  function encodeBatch(paramsArray) {
85
93
  const rowCount = paramsArray.length;
86
94
  const ppr = paramsArray[0].length;
95
+ if (ppr > 65535) throw new RangeError("Too many parameters per row");
87
96
  let buf = _pbuf;
88
- const estSize = 5 + rowCount * ppr * 14;
97
+ const estSize = 6 + rowCount * ppr * 14;
89
98
  if (estSize > buf.length) {
90
99
  buf = Buffer.allocUnsafe(estSize + 4096);
91
100
  _pbuf = buf;
@@ -93,7 +102,8 @@ function encodeBatch(paramsArray) {
93
102
  let off = 0;
94
103
  buf.writeUInt32LE(rowCount, off);
95
104
  off += 4;
96
- buf[off++] = ppr;
105
+ buf.writeUInt16LE(ppr, off);
106
+ off += 2;
97
107
  for (let r = 0; r < rowCount; r++) {
98
108
  const row = paramsArray[r];
99
109
  for (let j = 0; j < ppr; j++) {
@@ -134,8 +144,44 @@ function encodeBatch(paramsArray) {
134
144
  buf[off++] = 5;
135
145
  buf.writeBigInt64LE(v, off);
136
146
  off += 8;
147
+ } else if (v instanceof Date) {
148
+ const ms = v.getTime();
149
+ if (ms !== ms) {
150
+ buf[off++] = 0;
151
+ } else {
152
+ buf[off++] = 6;
153
+ buf.writeBigInt64LE(BigInt(Math.trunc(ms)) * 1000000n, off);
154
+ off += 8;
155
+ }
156
+ } else if (v instanceof Float32Array || v instanceof Uint8Array) {
157
+ /* Float32Array binds as a vector; Buffer and other Uint8Array as text bytes */
158
+ const bytes =
159
+ v instanceof Float32Array
160
+ ? new Uint8Array(v.buffer, v.byteOffset, v.byteLength)
161
+ : v;
162
+ const need = bytes.length + 6;
163
+ if (off + need > buf.length) {
164
+ const nb = Buffer.allocUnsafe(Math.max(buf.length * 2, off + need + 4096));
165
+ buf.copy(nb, 0, 0, off);
166
+ buf = nb;
167
+ _pbuf = nb;
168
+ }
169
+ if (v instanceof Float32Array) {
170
+ buf[off++] = 7;
171
+ buf.writeUInt32LE(bytes.length, off);
172
+ off += 4;
173
+ buf.set(bytes, off);
174
+ off += bytes.length;
175
+ } else {
176
+ buf[off++] = 3;
177
+ buf.writeUInt32LE(bytes.length, off);
178
+ off += 4;
179
+ buf.set(bytes, off);
180
+ off += bytes.length;
181
+ buf[off++] = 0;
182
+ }
137
183
  } else if (typeof v === "object") {
138
- const s = v instanceof Date ? v.toISOString() : JSON.stringify(v);
184
+ const s = JSON.stringify(v);
139
185
  const maxBytes = s.length * 3 + 6;
140
186
  if (off + maxBytes > buf.length) {
141
187
  const nb = Buffer.allocUnsafe(
@@ -158,7 +204,8 @@ function encodeBatch(paramsArray) {
158
204
  }
159
205
 
160
206
  class PreparedStatement {
161
- #ptr = null;
207
+ #ptr = null; /* null once finalized or finalize is pending */
208
+ #native = null; /* the handle, until it is actually released */
162
209
  #stoolap = null;
163
210
  #q1i = null;
164
211
  #factory = null;
@@ -166,24 +213,18 @@ class PreparedStatement {
166
213
  #colCount = 0;
167
214
  #owner = null;
168
215
  #sql = "";
169
- #preparedSql = "";
170
216
  #paramNames = null;
217
+ #header = null;
218
+ #pending = 0;
219
+ #finalizeWanted = false;
171
220
 
172
- constructor(
173
- ptr,
174
- stoolap,
175
- queryOneIntBound,
176
- owner,
177
- sql,
178
- preparedSql,
179
- paramNames,
180
- ) {
221
+ constructor(ptr, stoolap, queryOneIntBound, owner, sql, paramNames) {
181
222
  this.#ptr = ptr;
223
+ this.#native = ptr;
182
224
  this.#stoolap = stoolap;
183
225
  this.#q1i = queryOneIntBound || null;
184
226
  this.#owner = owner;
185
227
  this.#sql = sql;
186
- this.#preparedSql = preparedSql || sql;
187
228
  this.#paramNames = paramNames || null;
188
229
  }
189
230
 
@@ -191,48 +232,49 @@ class PreparedStatement {
191
232
  if (!this.#ptr) throw new Error("PreparedStatement is finalized");
192
233
  }
193
234
 
235
+ /* Parse a result buffer with the row factory cached from the first result,
236
+ * as long as the packed header still matches (a schema change after
237
+ * prepare gives the statement new columns). */
194
238
  #parseResult(abuf) {
195
- if (this.#factory) {
196
- return parseRowsCached(
197
- abuf,
198
- this.#headerOff,
199
- this.#factory,
200
- this.#colCount,
201
- );
239
+ const bytes = new Uint8Array(abuf);
240
+ if (this.#factory && sameHeader(bytes, this.#header)) {
241
+ return parseRowsCached(abuf, this.#headerOff, this.#factory, this.#colCount);
202
242
  }
203
243
  const meta = parseHeader(abuf);
204
244
  this.#headerOff = meta.headerOff;
205
245
  this.#colCount = meta.colCount;
206
- const f = makeRowFactory(meta.colNames);
207
- if (f) {
208
- this.#factory = f;
209
- return parseRowsCached(abuf, meta.headerOff, f, meta.colCount);
210
- }
211
- return parseRows(abuf);
246
+ this.#header = bytes.slice(0, meta.headerOff);
247
+ this.#factory = makeRowFactory(meta.colNames);
248
+ return this.#factory
249
+ ? parseRowsCached(abuf, meta.headerOff, this.#factory, meta.colCount)
250
+ : parseRows(abuf);
212
251
  }
213
252
 
214
- #normalizeParams(params) {
215
- if (!params) {
216
- return { sql: this.#preparedSql, values: null };
217
- }
218
- if (Array.isArray(params)) {
219
- return {
220
- sql: this.#preparedSql,
221
- values: params.length === 0 ? null : params,
222
- };
253
+ /* Run an async native call; finalize() waits for the last one to land. */
254
+ async #inflight(promise) {
255
+ this.#pending += 1;
256
+ try {
257
+ return await promise;
258
+ } finally {
259
+ this.#pending -= 1;
260
+ if (this.#finalizeWanted && this.#pending === 0) this.#release();
223
261
  }
224
- if (this.#paramNames) {
225
- const values = new Array(this.#paramNames.length);
226
- for (let i = 0; i < this.#paramNames.length; i++) {
227
- values[i] = params[this.#paramNames[i]];
228
- }
229
- return { sql: this.#preparedSql, values };
230
- }
231
- const rewritten = processParams(this.#sql, params);
232
- return {
233
- sql: rewritten.sql,
234
- values: rewritten.values,
235
- };
262
+ }
263
+
264
+ _hasPending() {
265
+ return this.#pending > 0;
266
+ }
267
+
268
+ /* Bind params positionally: an object's values are taken in the order of
269
+ * the :name placeholders the statement was prepared with. */
270
+ #positional(params) {
271
+ if (!params) return null;
272
+ if (Array.isArray(params)) return params.length === 0 ? null : params;
273
+ const names = this.#paramNames;
274
+ if (!names) return null;
275
+ const values = new Array(names.length);
276
+ for (let i = 0; i < names.length; i++) values[i] = params[names[i]];
277
+ return values;
236
278
  }
237
279
 
238
280
  get sql() {
@@ -244,47 +286,38 @@ class PreparedStatement {
244
286
 
245
287
  execute(params) {
246
288
  this.#checkOpen();
247
- const normalized = this.#normalizeParams(params);
248
- return this.#owner._stmtExecAsync(
249
- this.#ptr,
250
- normalized.values ? normalized.values : undefined,
251
- );
289
+ const values = this.#positional(params);
290
+ return this.#inflight(this.#owner._stmtExecAsync(this.#ptr, values || undefined));
252
291
  }
253
292
 
254
293
  executeSync(params) {
255
294
  this.#checkOpen();
256
- const normalized = this.#normalizeParams(params);
257
- if (!normalized.values) return this.#stoolap.stmtExec(this.#ptr);
258
- if (normalized.values.length <= 16 && isAllNums(normalized.values)) {
259
- return callExecNums(this.#stoolap, this.#ptr, normalized.values);
295
+ const values = this.#positional(params);
296
+ if (!values) return this.#stoolap.stmtExec(this.#ptr);
297
+ if (values.length <= 16 && isAllNums(values)) {
298
+ return callExecNums(this.#stoolap, this.#ptr, values);
260
299
  }
261
- return this.#stoolap.stmtExec(this.#ptr, normalized.values);
300
+ return this.#stoolap.stmtExec(this.#ptr, values);
262
301
  }
263
302
 
264
303
  // -- Query --
265
304
 
266
305
  async query(params) {
267
306
  this.#checkOpen();
268
- const normalized = this.#normalizeParams(params);
269
- const buf = await this.#owner._stmtQueryBufAsync(
270
- this.#ptr,
271
- normalized.values ? normalized.values : undefined,
307
+ const values = this.#positional(params);
308
+ const buf = await this.#inflight(
309
+ this.#owner._stmtQueryBufAsync(this.#ptr, values || undefined),
272
310
  );
273
311
  return this.#parseResult(buf);
274
312
  }
275
313
 
276
314
  querySync(params) {
277
315
  this.#checkOpen();
278
- const normalized = this.#normalizeParams(params);
279
- if (!normalized.values) {
280
- const result = this.#stoolap.stmtQuery(this.#ptr);
281
- return Array.isArray(result) ? result : this.#parseResult(result);
282
- }
283
- if (normalized.values.length <= 16 && isAllNums(normalized.values)) {
284
- const result = callQueryNums(this.#stoolap, this.#ptr, normalized.values);
285
- return Array.isArray(result) ? result : this.#parseResult(result);
286
- }
287
- const result = this.#stoolap.stmtQuery(this.#ptr, normalized.values);
316
+ const values = this.#positional(params);
317
+ const result =
318
+ values && values.length <= 16 && isAllNums(values)
319
+ ? callQueryNums(this.#stoolap, this.#ptr, values)
320
+ : this.#stoolap.stmtQueryAll(this.#ptr, values, 0);
288
321
  return Array.isArray(result) ? result : this.#parseResult(result);
289
322
  }
290
323
 
@@ -297,39 +330,47 @@ class PreparedStatement {
297
330
 
298
331
  queryOneSync(params) {
299
332
  this.#checkOpen();
300
- const normalized = this.#normalizeParams(params);
301
- if (!normalized.values) return this.#stoolap.stmtQueryOne(this.#ptr);
302
- if (
303
- normalized.values.length === 1 &&
304
- typeof normalized.values[0] === "number"
305
- ) {
306
- return this.#q1i(normalized.values[0]);
333
+ const values = this.#positional(params);
334
+ let result;
335
+ if (!values) {
336
+ result = this.#stoolap.stmtQueryAll(this.#ptr, undefined, 1);
337
+ } else if (values.length === 1 && typeof values[0] === "number") {
338
+ result = this.#q1i(values[0]);
339
+ } else if (values.length <= 16 && isAllNums(values)) {
340
+ result = callQueryOneNums(this.#stoolap, this.#ptr, values);
341
+ } else {
342
+ result = this.#stoolap.stmtQueryAll(this.#ptr, values, 1);
307
343
  }
308
- if (normalized.values.length <= 16 && isAllNums(normalized.values)) {
309
- return callQueryOneNums(this.#stoolap, this.#ptr, normalized.values);
344
+ if (result instanceof ArrayBuffer) {
345
+ const rows = this.#parseResult(result);
346
+ return rows.length === 0 ? null : rows[0];
310
347
  }
311
- return this.#stoolap.stmtQueryOne(this.#ptr, normalized.values);
348
+ return result;
312
349
  }
313
350
 
314
351
  // -- QueryRaw --
315
352
 
316
353
  async queryRaw(params) {
317
354
  this.#checkOpen();
318
- const normalized = this.#normalizeParams(params);
319
- const buf = await this.#owner._stmtQueryBufAsync(
320
- this.#ptr,
321
- normalized.values ? normalized.values : undefined,
355
+ const values = this.#positional(params);
356
+ const buf = await this.#inflight(
357
+ this.#owner._stmtQueryBufAsync(this.#ptr, values || undefined),
322
358
  );
323
359
  return parseRaw(buf);
324
360
  }
325
361
 
326
362
  queryRawSync(params) {
327
363
  this.#checkOpen();
328
- const normalized = this.#normalizeParams(params);
329
- if (!normalized.values) {
330
- return parseRaw(this.#stoolap.stmtQueryBuf(this.#ptr));
331
- }
332
- return parseRaw(this.#stoolap.stmtQueryBuf(this.#ptr, normalized.values));
364
+ return parseRaw(this.#stoolap.stmtQueryAll(this.#ptr, this.#positional(params), 2));
365
+ }
366
+
367
+ // -- Iterate --
368
+
369
+ /** Open a cursor over the statement's rows; iterate with `for...of` or `for await...of`. */
370
+ iterate(params) {
371
+ this.#checkOpen();
372
+ const ptr = this.#stoolap.rowsOpenStmt(this.#ptr, this.#positional(params));
373
+ return new RowCursor(this.#stoolap, ptr, this.#owner);
333
374
  }
334
375
 
335
376
  // -- Batch --
@@ -337,34 +378,41 @@ class PreparedStatement {
337
378
  executeBatchSync(paramsArray) {
338
379
  this.#checkOpen();
339
380
  if (paramsArray.length === 0) return { changes: 0 };
340
- if (paramsArray.length > 0 && Array.isArray(paramsArray[0])) {
381
+ if (Array.isArray(paramsArray[0])) {
341
382
  const batchBuf = encodeBatch(paramsArray);
342
383
  return this.#stoolap.stmtExecBatchBuf(this.#ptr, batchBuf);
343
384
  }
344
385
  let totalChanges = 0;
345
386
  for (const params of paramsArray) {
346
- const normalized = this.#normalizeParams(params);
347
- const result = normalized.values
348
- ? this.#stoolap.stmtExec(this.#ptr, normalized.values)
349
- : this.#stoolap.stmtExec(this.#ptr);
350
- totalChanges += result.changes;
387
+ totalChanges += this.executeSync(params).changes;
351
388
  }
352
389
  return { changes: totalChanges };
353
390
  }
354
391
 
355
- _invalidate() {
356
- if (this.#ptr) {
357
- this.#stoolap.stmtFinalize(this.#ptr);
358
- this.#ptr = null;
392
+ #release() {
393
+ if (this.#native) {
394
+ this.#stoolap.stmtFinalize(this.#native);
395
+ this.#native = null;
359
396
  }
397
+ this.#ptr = null;
398
+ this.#finalizeWanted = false;
399
+ if (this.#owner) this.#owner._untrackChild(this);
400
+ }
401
+
402
+ /* Called by the owner when it closes; it has already waited for async work. */
403
+ _invalidate() {
404
+ if (this.#pending > 0) this.#finalizeWanted = true;
405
+ else this.#release();
360
406
  }
361
407
 
408
+ /** Release the statement. Async calls still in flight finish first. */
362
409
  finalize() {
363
- if (this.#ptr) {
364
- this.#stoolap.stmtFinalize(this.#ptr);
365
- this.#ptr = null;
366
- if (this.#owner) this.#owner._untrackChild(this);
410
+ if (this.#pending > 0) {
411
+ this.#finalizeWanted = true;
412
+ this.#ptr = null; /* no new calls; the handle is freed when the last one lands */
413
+ return;
367
414
  }
415
+ this.#release();
368
416
  }
369
417
  }
370
418
 
@@ -1,7 +1,8 @@
1
1
  "use strict";
2
2
 
3
- const { processParams } = require("./ffi");
3
+ const { processParams, splitNamed } = require("./ffi");
4
4
  const { parseRows, parseRaw } = require("./parse");
5
+ const { RowCursor } = require("./iterator");
5
6
 
6
7
  class Transaction {
7
8
  #ptr = null;
@@ -11,6 +12,7 @@ class Transaction {
11
12
  #tail = Promise.resolve();
12
13
  #closingPromise = null;
13
14
  #owner = null;
15
+ #cursors = new Set(); /* WeakRef<RowCursor> */
14
16
 
15
17
  constructor(ptr, stoolap, owner, dbPtr) {
16
18
  this.#ptr = ptr;
@@ -19,8 +21,66 @@ class Transaction {
19
21
  this.#dbPtr = dbPtr || null;
20
22
  }
21
23
 
24
+ /* Called by the owner when it closes, after async work has settled:
25
+ * an open transaction is rolled back so its handle is released. */
22
26
  _invalidate() {
23
- this.#ptr = null;
27
+ this.#closeCursors();
28
+ if (this.#ptr) {
29
+ const ptr = this.#ptr;
30
+ this.#ptr = null;
31
+ try {
32
+ this.#stoolap.txRollback(ptr);
33
+ } catch {}
34
+ }
35
+ }
36
+
37
+ _settle() {
38
+ return this.#closingPromise || this.#tail;
39
+ }
40
+
41
+ _hasPending() {
42
+ return this.#pendingAsync > 0 || this.#closingPromise !== null;
43
+ }
44
+
45
+ _trackCursor(ref) {
46
+ this.#cursors.add(ref);
47
+ }
48
+
49
+ _untrackCursor(ref) {
50
+ this.#cursors.delete(ref);
51
+ }
52
+
53
+ #closeCursors() {
54
+ for (const ref of this.#cursors) {
55
+ const cursor = ref.deref();
56
+ if (cursor) cursor._invalidate();
57
+ }
58
+ this.#cursors.clear();
59
+ }
60
+
61
+ _rowsChunk(rowsPtr, maxRows, buf) {
62
+ this.#checkSyncReady();
63
+ return this.#stoolap.rowsChunk(rowsPtr, maxRows, buf);
64
+ }
65
+
66
+ _rowsChunkAsync(rowsPtr, maxRows) {
67
+ return this.#enqueueAsync(() => this.#stoolap.rowsChunkAsync(rowsPtr, maxRows));
68
+ }
69
+
70
+ /**
71
+ * Open a cursor over the query's rows inside the transaction. Open
72
+ * cursors are closed by commit and rollback.
73
+ */
74
+ iterate(sql, params) {
75
+ this.#checkSyncReady();
76
+ let ptr;
77
+ if (params && !Array.isArray(params)) {
78
+ const [names, values] = splitNamed(params);
79
+ ptr = this.#stoolap.rowsOpenNamed(this.#dbPtr, this.#ptr, sql, names, values);
80
+ } else {
81
+ ptr = this.#stoolap.rowsOpen(this.#dbPtr, this.#ptr, sql, params);
82
+ }
83
+ return new RowCursor(this.#stoolap, ptr, this);
24
84
  }
25
85
 
26
86
  #checkOpen() {
@@ -71,19 +131,13 @@ class Transaction {
71
131
 
72
132
  executeSync(sql, params) {
73
133
  this.#checkSyncReady();
74
- if (!params) return this.#stoolap.txExec(this.#ptr, sql);
75
- if (Array.isArray(params)) {
76
- if (params.length === 0) return this.#stoolap.txExec(this.#ptr, sql);
77
- return this.#stoolap.txExec(this.#ptr, sql, params);
134
+ if (params && !Array.isArray(params)) {
135
+ const [names, values] = splitNamed(params);
136
+ return this.#stoolap.txExecNamed(this.#ptr, sql, names, values);
78
137
  }
79
- const p = processParams(sql, params);
80
- return p.values
81
- ? this.#stoolap.txExec(this.#ptr, p.sql, p.values)
82
- : this.#stoolap.txExec(this.#ptr, sql);
138
+ return this.#stoolap.txExec(this.#ptr, sql, params);
83
139
  }
84
140
 
85
- // -- Query --
86
-
87
141
  async query(sql, params) {
88
142
  const buf = await this.#enqueueAsync((ptr) => {
89
143
  if (!params) return this.#stoolap.txQueryBufAsync(ptr, sql);
@@ -99,27 +153,21 @@ class Transaction {
99
153
  return parseRows(buf);
100
154
  }
101
155
 
156
+ /* One FFI call inside the transaction. mode: 0 = rows, 1 = first row, 2 = raw buffer */
157
+ #queryAll(sql, params, mode) {
158
+ if (params && !Array.isArray(params)) {
159
+ const [names, values] = splitNamed(params);
160
+ return this.#stoolap.dbQueryNamedAll(this.#dbPtr, this.#ptr, sql, names, values, mode);
161
+ }
162
+ return this.#stoolap.dbQueryAll(this.#dbPtr, this.#ptr, sql, params, mode);
163
+ }
164
+
102
165
  querySync(sql, params) {
103
166
  this.#checkSyncReady();
104
- let result;
105
- if (!params) {
106
- result = this.#stoolap.txQuery(this.#ptr, sql);
107
- } else if (Array.isArray(params)) {
108
- result =
109
- params.length === 0
110
- ? this.#stoolap.txQuery(this.#ptr, sql)
111
- : this.#stoolap.txQuery(this.#ptr, sql, params);
112
- } else {
113
- const p = processParams(sql, params);
114
- result = p.values
115
- ? this.#stoolap.txQuery(this.#ptr, p.sql, p.values)
116
- : this.#stoolap.txQuery(this.#ptr, sql);
117
- }
167
+ const result = this.#queryAll(sql, params, 0);
118
168
  return Array.isArray(result) ? result : parseRows(result);
119
169
  }
120
170
 
121
- // -- QueryOne --
122
-
123
171
  async queryOne(sql, params) {
124
172
  const rows = await this.query(sql, params);
125
173
  return rows.length === 0 ? null : rows[0];
@@ -127,19 +175,14 @@ class Transaction {
127
175
 
128
176
  queryOneSync(sql, params) {
129
177
  this.#checkSyncReady();
130
- if (!params) return this.#stoolap.txQueryOne(this.#ptr, sql);
131
- if (Array.isArray(params)) {
132
- if (params.length === 0) return this.#stoolap.txQueryOne(this.#ptr, sql);
133
- return this.#stoolap.txQueryOne(this.#ptr, sql, params);
178
+ const result = this.#queryAll(sql, params, 1);
179
+ if (result instanceof ArrayBuffer) {
180
+ const rows = parseRows(result);
181
+ return rows.length === 0 ? null : rows[0];
134
182
  }
135
- const p = processParams(sql, params);
136
- return p.values
137
- ? this.#stoolap.txQueryOne(this.#ptr, p.sql, p.values)
138
- : this.#stoolap.txQueryOne(this.#ptr, sql);
183
+ return result;
139
184
  }
140
185
 
141
- // -- QueryRaw --
142
-
143
186
  async queryRaw(sql, params) {
144
187
  const buf = await this.#enqueueAsync((ptr) => {
145
188
  if (!params) return this.#stoolap.txQueryBufAsync(ptr, sql);
@@ -157,25 +200,9 @@ class Transaction {
157
200
 
158
201
  queryRawSync(sql, params) {
159
202
  this.#checkSyncReady();
160
- let buf;
161
- if (!params) {
162
- buf = this.#stoolap.txQueryBuf(this.#ptr, sql);
163
- } else if (Array.isArray(params)) {
164
- buf =
165
- params.length === 0
166
- ? this.#stoolap.txQueryBuf(this.#ptr, sql)
167
- : this.#stoolap.txQueryBuf(this.#ptr, sql, params);
168
- } else {
169
- const p = processParams(sql, params);
170
- buf = p.values
171
- ? this.#stoolap.txQueryBuf(this.#ptr, p.sql, p.values)
172
- : this.#stoolap.txQueryBuf(this.#ptr, sql);
173
- }
174
- return parseRaw(buf);
203
+ return parseRaw(this.#queryAll(sql, params, 2));
175
204
  }
176
205
 
177
- // -- Batch --
178
-
179
206
  executeBatchSync(sql, paramsArray) {
180
207
  this.#checkSyncReady();
181
208
  if (paramsArray.length > 0 && Array.isArray(paramsArray[0])) {
@@ -188,11 +215,7 @@ class Transaction {
188
215
  }
189
216
  let totalChanges = 0;
190
217
  for (const params of paramsArray) {
191
- const p = processParams(sql, params);
192
- const result = p.values
193
- ? this.#stoolap.txExec(this.#ptr, p.sql, p.values)
194
- : this.#stoolap.txExec(this.#ptr, sql);
195
- totalChanges += result.changes;
218
+ totalChanges += this.executeSync(sql, params).changes;
196
219
  }
197
220
  return { changes: totalChanges };
198
221
  }
@@ -205,7 +228,10 @@ class Transaction {
205
228
 
206
229
  const ptr = this.#ptr;
207
230
  this.#closingPromise = this.#tail
208
- .then(() => this.#stoolap.txCommitAsync(ptr))
231
+ .then(() => {
232
+ this.#closeCursors();
233
+ return this.#stoolap.txCommitAsync(ptr);
234
+ })
209
235
  .finally(() => {
210
236
  this.#ptr = null;
211
237
  this.#closingPromise = null;
@@ -218,10 +244,11 @@ class Transaction {
218
244
 
219
245
  commitSync() {
220
246
  this.#checkSyncReady();
247
+ this.#closeCursors();
221
248
  const ptr = this.#ptr;
222
249
  this.#ptr = null;
223
- this.#stoolap.txCommit(ptr);
224
250
  if (this.#owner) this.#owner._untrackChild(this);
251
+ this.#stoolap.txCommit(ptr);
225
252
  }
226
253
 
227
254
  async rollback() {
@@ -230,7 +257,10 @@ class Transaction {
230
257
 
231
258
  const ptr = this.#ptr;
232
259
  this.#closingPromise = this.#tail
233
- .then(() => this.#stoolap.txRollbackAsync(ptr))
260
+ .then(() => {
261
+ this.#closeCursors();
262
+ return this.#stoolap.txRollbackAsync(ptr);
263
+ })
234
264
  .finally(() => {
235
265
  this.#ptr = null;
236
266
  this.#closingPromise = null;
@@ -243,10 +273,11 @@ class Transaction {
243
273
 
244
274
  rollbackSync() {
245
275
  this.#checkSyncReady();
276
+ this.#closeCursors();
246
277
  const ptr = this.#ptr;
247
278
  this.#ptr = null;
248
- this.#stoolap.txRollback(ptr);
249
279
  if (this.#owner) this.#owner._untrackChild(this);
280
+ this.#stoolap.txRollback(ptr);
250
281
  }
251
282
  }
252
283
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stoolap/node",
3
- "version": "0.4.1",
3
+ "version": "0.5.0-beta.1",
4
4
  "description": "High-performance Node.js driver for Stoolap embedded SQL database",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
@@ -18,11 +18,11 @@
18
18
  "bench": "node benchmark.mjs"
19
19
  },
20
20
  "optionalDependencies": {
21
- "@stoolap/lib-darwin-arm64": "0.4.1",
22
- "@stoolap/lib-darwin-x64": "0.4.1",
23
- "@stoolap/lib-linux-arm64-gnu": "0.4.1",
24
- "@stoolap/lib-linux-x64-gnu": "0.4.1",
25
- "@stoolap/lib-win32-x64-msvc": "0.4.1"
21
+ "@stoolap/lib-darwin-arm64": "0.5.0-beta.1",
22
+ "@stoolap/lib-darwin-x64": "0.5.0-beta.1",
23
+ "@stoolap/lib-linux-arm64-gnu": "0.5.0-beta.1",
24
+ "@stoolap/lib-linux-x64-gnu": "0.5.0-beta.1",
25
+ "@stoolap/lib-win32-x64-msvc": "0.5.0-beta.1"
26
26
  },
27
27
  "devDependencies": {
28
28
  "better-sqlite3": "^12.6.2",