@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/src/stoolap.c CHANGED
@@ -65,6 +65,14 @@ typedef struct {
65
65
  } v;
66
66
  } StoolapValue;
67
67
 
68
+ /* Named parameter (:name) — name is not NUL-terminated, length in name_len */
69
+ typedef struct {
70
+ const char* name;
71
+ int32_t name_len;
72
+ int32_t _padding;
73
+ StoolapValue value;
74
+ } StoolapNamedParam;
75
+
68
76
  /* ---- Function pointer types ---- */
69
77
 
70
78
  typedef const char* (*fn_version)(void);
@@ -89,25 +97,21 @@ typedef int32_t (*fn_tx_exec_p)(StoolapTx*, const char*, const StoolapValue*, in
89
97
  typedef int32_t (*fn_tx_query)(StoolapTx*, const char*, StoolapRows**);
90
98
  typedef int32_t (*fn_tx_query_p)(StoolapTx*, const char*, const StoolapValue*, int32_t, StoolapRows**);
91
99
  typedef int32_t (*fn_tx_stmt_exec)(StoolapTx*, StoolapStmt*, const StoolapValue*, int32_t, int64_t*);
92
- typedef int32_t (*fn_tx_stmt_query)(StoolapTx*, StoolapStmt*, const StoolapValue*, int32_t, StoolapRows**);
93
100
  typedef int32_t (*fn_tx_commit)(StoolapTx*);
94
101
  typedef int32_t (*fn_tx_rollback)(StoolapTx*);
95
102
  typedef const char* (*fn_tx_errmsg)(const StoolapTx*);
96
- typedef int32_t (*fn_rows_next)(StoolapRows*);
97
- typedef int32_t (*fn_rows_col_count)(const StoolapRows*);
98
- typedef const char* (*fn_rows_col_name)(const StoolapRows*, int32_t);
99
- typedef int32_t (*fn_rows_col_type)(const StoolapRows*, int32_t);
100
- typedef int64_t (*fn_rows_col_int)(const StoolapRows*, int32_t);
101
- typedef double (*fn_rows_col_dbl)(const StoolapRows*, int32_t);
102
- typedef const char* (*fn_rows_col_text)(StoolapRows*, int32_t, int64_t*);
103
- typedef int32_t (*fn_rows_col_bool)(const StoolapRows*, int32_t);
104
- typedef int64_t (*fn_rows_col_ts)(const StoolapRows*, int32_t);
105
- typedef const uint8_t* (*fn_rows_col_blob)(const StoolapRows*, int32_t, int64_t*);
106
- typedef int32_t (*fn_rows_col_null)(const StoolapRows*, int32_t);
107
- typedef int64_t (*fn_rows_affected)(const StoolapRows*);
108
103
  typedef void (*fn_rows_close)(StoolapRows*);
109
104
  typedef int32_t (*fn_rows_fetch_all)(StoolapRows*, uint8_t**, int64_t*);
110
105
  typedef void (*fn_buffer_free)(uint8_t*, int64_t);
106
+ typedef const char* (*fn_rows_errmsg)(const StoolapRows*);
107
+ typedef int32_t (*fn_rows_fetch_chunk)(StoolapRows*, int32_t, const uint8_t**, int64_t*, int32_t*);
108
+ typedef int32_t (*fn_exec_named)(StoolapDB*, const char*, const StoolapNamedParam*, int32_t, int64_t*);
109
+ typedef int32_t (*fn_query_named)(StoolapDB*, const char*, const StoolapNamedParam*, int32_t, StoolapRows**);
110
+ typedef int32_t (*fn_tx_exec_named)(StoolapTx*, const char*, const StoolapNamedParam*, int32_t, int64_t*);
111
+ typedef int32_t (*fn_tx_query_named)(StoolapTx*, const char*, const StoolapNamedParam*, int32_t, StoolapRows**);
112
+ typedef int32_t (*fn_stmt_query_all)(StoolapStmt*, StoolapTx*, const StoolapValue*, int32_t, const uint8_t**, int64_t*);
113
+ typedef int32_t (*fn_query_params_all)(StoolapDB*, StoolapTx*, const char*, const StoolapValue*, int32_t, const uint8_t**, int64_t*);
114
+ typedef int32_t (*fn_query_named_all)(StoolapDB*, StoolapTx*, const char*, const StoolapNamedParam*, int32_t, const uint8_t**, int64_t*);
111
115
 
112
116
  /* ---- Loaded function pointers ---- */
113
117
 
@@ -134,25 +138,21 @@ static struct {
134
138
  fn_tx_query tx_query;
135
139
  fn_tx_query_p tx_query_p;
136
140
  fn_tx_stmt_exec tx_stmt_exec;
137
- fn_tx_stmt_query tx_stmt_query;
138
141
  fn_tx_commit tx_commit;
139
142
  fn_tx_rollback tx_rollback;
140
143
  fn_tx_errmsg tx_errmsg;
141
- fn_rows_next rows_next;
142
- fn_rows_col_count rows_col_count;
143
- fn_rows_col_name rows_col_name;
144
- fn_rows_col_type rows_col_type;
145
- fn_rows_col_int rows_col_int;
146
- fn_rows_col_dbl rows_col_dbl;
147
- fn_rows_col_text rows_col_text;
148
- fn_rows_col_bool rows_col_bool;
149
- fn_rows_col_ts rows_col_ts;
150
- fn_rows_col_blob rows_col_blob;
151
- fn_rows_col_null rows_col_null;
152
- fn_rows_affected rows_affected;
153
144
  fn_rows_close rows_close;
154
145
  fn_rows_fetch_all rows_fetch_all;
155
146
  fn_buffer_free buffer_free;
147
+ fn_rows_errmsg rows_errmsg;
148
+ fn_rows_fetch_chunk rows_fetch_chunk;
149
+ fn_exec_named exec_named;
150
+ fn_query_named query_named;
151
+ fn_tx_exec_named tx_exec_named;
152
+ fn_tx_query_named tx_query_named;
153
+ fn_stmt_query_all stmt_query_all;
154
+ fn_query_params_all query_params_all;
155
+ fn_query_named_all query_named_all;
156
156
  } S;
157
157
 
158
158
  static LibHandle lib_handle = NULL;
@@ -162,7 +162,12 @@ static LibHandle lib_handle = NULL;
162
162
  #define THROW(env, msg) do { napi_throw_error(env, NULL, msg); return NULL; } while(0)
163
163
  #define ERR_SAVE(msg, fallback) \
164
164
  char _eb[512]; snprintf(_eb, sizeof(_eb), "%s", (msg) ? (msg) : (fallback))
165
- #define CHECK(env, status) if ((status) != napi_ok) return NULL
165
+
166
+ /* True when d is an integer the engine can hold exactly; false for NaN,
167
+ * infinities and anything outside 2^53 (casting those is undefined). */
168
+ static inline int is_safe_integer(double d) {
169
+ return d == d && d >= -9007199254740992.0 && d <= 9007199254740992.0 && (double)(int64_t)d == d;
170
+ }
166
171
 
167
172
  static napi_value make_changes(napi_env env, int64_t n) {
168
173
  napi_value obj, val;
@@ -175,9 +180,71 @@ static napi_value make_changes(napi_env env, int64_t n) {
175
180
  return obj;
176
181
  }
177
182
 
178
- /* Convert a JS value at index `i` in `args` array to StoolapValue. */
179
- static int js_to_value(napi_env env, napi_value jsval, StoolapValue* out,
180
- char** text_bufs, int* text_buf_count) {
183
+ /* ---- Text arena: strings bound for one call live on the stack; only strings
184
+ * that do not fit are malloc'd and freed with the arena. ---- */
185
+ /* A string copied with N-API "fits" only when there was room for one more
186
+ * UTF-8 sequence: V8 never splits a character, so a copy short of the
187
+ * buffer by less than that may still be truncated. */
188
+ #define UTF8_SLACK 4
189
+
190
+ #define PARAM_ARENA_SIZE 2048
191
+ typedef struct {
192
+ char buf[PARAM_ARENA_SIZE];
193
+ size_t used;
194
+ char** heap;
195
+ int heap_count, heap_cap;
196
+ } TextArena;
197
+ #define ARENA_INIT(a) do { (a).used = 0; (a).heap = NULL; (a).heap_count = 0; (a).heap_cap = 0; } while (0)
198
+
199
+ static char* arena_alloc(TextArena* a, size_t len) {
200
+ if (a->used + len <= PARAM_ARENA_SIZE) {
201
+ char* p = a->buf + a->used;
202
+ a->used += len;
203
+ return p;
204
+ }
205
+ char* p = malloc(len);
206
+ if (!p) return NULL;
207
+ if (a->heap_count == a->heap_cap) {
208
+ int cap = a->heap_cap ? a->heap_cap * 2 : 8;
209
+ char** grown = realloc(a->heap, (size_t)cap * sizeof(char*));
210
+ if (!grown) { free(p); return NULL; }
211
+ a->heap = grown;
212
+ a->heap_cap = cap;
213
+ }
214
+ a->heap[a->heap_count++] = p;
215
+ return p;
216
+ }
217
+
218
+ static void arena_reset(TextArena* a) {
219
+ for (int i = 0; i < a->heap_count; i++) free(a->heap[i]);
220
+ free(a->heap);
221
+ ARENA_INIT(*a);
222
+ }
223
+
224
+ /* Copy a JS string into the arena, NUL-terminated. Writes straight into the
225
+ * arena's free space when it fits, so a short string is copied once. */
226
+ static char* arena_string(napi_env env, napi_value jsval, TextArena* a, size_t* out_len) {
227
+ size_t avail = PARAM_ARENA_SIZE - a->used;
228
+ size_t len = 0;
229
+ if (avail > UTF8_SLACK) {
230
+ char* dst = a->buf + a->used;
231
+ if (napi_get_value_string_utf8(env, jsval, dst, avail, &len) != napi_ok) return NULL;
232
+ if (len + UTF8_SLACK < avail) {
233
+ a->used += len + 1;
234
+ *out_len = len;
235
+ return dst;
236
+ }
237
+ }
238
+ if (napi_get_value_string_utf8(env, jsval, NULL, 0, &len) != napi_ok) return NULL;
239
+ char* dst = arena_alloc(a, len + 1);
240
+ if (!dst) return NULL;
241
+ napi_get_value_string_utf8(env, jsval, dst, len + 1, &len);
242
+ *out_len = len;
243
+ return dst;
244
+ }
245
+
246
+ /* Convert a JS value to a StoolapValue; text is copied into `ta`. */
247
+ static int js_to_value(napi_env env, napi_value jsval, StoolapValue* out, TextArena* ta) {
181
248
  napi_valuetype vt;
182
249
  napi_typeof(env, jsval, &vt);
183
250
 
@@ -201,8 +268,8 @@ static int js_to_value(napi_env env, napi_value jsval, StoolapValue* out,
201
268
  double d;
202
269
  napi_get_value_double(env, jsval, &d);
203
270
  /* Check if integer */
204
- int64_t i64 = (int64_t)d;
205
- if ((double)i64 == d && d >= -9007199254740992.0 && d <= 9007199254740992.0) {
271
+ if (is_safe_integer(d)) {
272
+ int64_t i64 = (int64_t)d;
206
273
  out->value_type = T_INTEGER;
207
274
  out->_padding = 0;
208
275
  out->v.integer = i64;
@@ -218,6 +285,7 @@ static int js_to_value(napi_env env, napi_value jsval, StoolapValue* out,
218
285
  int64_t i64;
219
286
  bool lossless;
220
287
  napi_get_value_bigint_int64(env, jsval, &i64, &lossless);
288
+ if (!lossless) return -1; /* outside int64 */
221
289
  out->value_type = T_INTEGER;
222
290
  out->_padding = 0;
223
291
  out->v.integer = i64;
@@ -225,23 +293,9 @@ static int js_to_value(napi_env env, napi_value jsval, StoolapValue* out,
225
293
  }
226
294
 
227
295
  if (vt == napi_string) {
228
- /* Single-copy for short strings: try stack buffer first, avoid probe call */
229
- char tmp[256];
230
296
  size_t len;
231
- napi_get_value_string_utf8(env, jsval, tmp, sizeof(tmp), &len);
232
- char* buf;
233
- if (len < sizeof(tmp) - 1) {
234
- buf = malloc(len + 1);
235
- if (!buf) return -1;
236
- memcpy(buf, tmp, len + 1);
237
- } else {
238
- napi_get_value_string_utf8(env, jsval, NULL, 0, &len);
239
- buf = malloc(len + 1);
240
- if (!buf) return -1;
241
- napi_get_value_string_utf8(env, jsval, buf, len + 1, &len);
242
- }
243
- text_bufs[*text_buf_count] = buf;
244
- (*text_buf_count)++;
297
+ char* buf = arena_string(env, jsval, ta, &len);
298
+ if (!buf) return -1;
245
299
  out->value_type = T_TEXT;
246
300
  out->_padding = 0;
247
301
  out->v.text.ptr = buf;
@@ -256,6 +310,7 @@ static int js_to_value(napi_env env, napi_value jsval, StoolapValue* out,
256
310
  if (is_date) {
257
311
  double ms;
258
312
  napi_get_date_value(env, jsval, &ms);
313
+ if (ms != ms) { out->value_type = T_NULL; out->_padding = 0; out->v.integer = 0; return 0; }
259
314
  out->value_type = T_TIMESTAMP;
260
315
  out->_padding = 0;
261
316
  out->v.timestamp_nanos = (int64_t)(ms * 1000000.0);
@@ -286,12 +341,10 @@ static int js_to_value(napi_env env, napi_value jsval, StoolapValue* out,
286
341
  void* data;
287
342
  size_t len;
288
343
  napi_get_buffer_info(env, jsval, &data, &len);
289
- char* buf = malloc(len + 1);
344
+ char* buf = arena_alloc(ta, len + 1);
290
345
  if (!buf) return -1;
291
346
  memcpy(buf, data, len);
292
347
  buf[len] = 0;
293
- text_bufs[*text_buf_count] = buf;
294
- (*text_buf_count)++;
295
348
  out->value_type = T_TEXT;
296
349
  out->_padding = 0;
297
350
  out->v.text.ptr = buf;
@@ -307,12 +360,8 @@ static int js_to_value(napi_env env, napi_value jsval, StoolapValue* out,
307
360
  napi_call_function(env, json_obj, stringify_fn, 1, &jsval, &json_str);
308
361
 
309
362
  size_t len;
310
- napi_get_value_string_utf8(env, json_str, NULL, 0, &len);
311
- char* buf = malloc(len + 1);
363
+ char* buf = arena_string(env, json_str, ta, &len);
312
364
  if (!buf) return -1;
313
- napi_get_value_string_utf8(env, json_str, buf, len + 1, &len);
314
- text_bufs[*text_buf_count] = buf;
315
- (*text_buf_count)++;
316
365
  out->value_type = T_JSON;
317
366
  out->_padding = 0;
318
367
  out->v.text.ptr = buf;
@@ -326,55 +375,8 @@ static int js_to_value(napi_env env, napi_value jsval, StoolapValue* out,
326
375
  return 0;
327
376
  }
328
377
 
329
- /* Stack-based fast param conversion for ≤16 params.
330
- * Returns param count, or -1 on error. Caller must free text bufs only. */
331
378
  #define MAX_STACK_PARAMS 16
332
379
 
333
- /* Convert JS params (Array or null) to StoolapValue array. Caller frees. */
334
- static int convert_params(napi_env env, napi_value js_params,
335
- StoolapValue** out_values, int32_t* out_count,
336
- char*** out_text_bufs, int* out_text_buf_count) {
337
- *out_values = NULL;
338
- *out_count = 0;
339
- *out_text_bufs = NULL;
340
- *out_text_buf_count = 0;
341
-
342
- if (!js_params) return 0;
343
-
344
- napi_valuetype vt;
345
- napi_typeof(env, js_params, &vt);
346
- if (vt == napi_undefined || vt == napi_null) return 0;
347
-
348
- bool is_array;
349
- napi_is_array(env, js_params, &is_array);
350
- if (!is_array) return 0;
351
-
352
- uint32_t len;
353
- napi_get_array_length(env, js_params, &len);
354
- if (len == 0) return 0;
355
-
356
- StoolapValue* vals = calloc(len, sizeof(StoolapValue));
357
- char** tbufs = calloc(len * 2, sizeof(char*)); /* generous allocation */
358
- int tbuf_count = 0;
359
-
360
- for (uint32_t i = 0; i < len; i++) {
361
- napi_value elem;
362
- napi_get_element(env, js_params, i, &elem);
363
- if (js_to_value(env, elem, &vals[i], tbufs, &tbuf_count) != 0) {
364
- free(vals);
365
- for (int j = 0; j < tbuf_count; j++) free(tbufs[j]);
366
- free(tbufs);
367
- return -1;
368
- }
369
- }
370
-
371
- *out_values = vals;
372
- *out_count = (int32_t)len;
373
- *out_text_bufs = tbufs;
374
- *out_text_buf_count = tbuf_count;
375
- return 0;
376
- }
377
-
378
380
  static int js_to_value_copy(napi_env env, napi_value jsval, StoolapValue* out,
379
381
  char** heap_bufs, int* heap_buf_count) {
380
382
  napi_valuetype vt;
@@ -399,8 +401,8 @@ static int js_to_value_copy(napi_env env, napi_value jsval, StoolapValue* out,
399
401
  if (vt == napi_number) {
400
402
  double d;
401
403
  napi_get_value_double(env, jsval, &d);
402
- int64_t i64 = (int64_t)d;
403
- if ((double)i64 == d && d >= -9007199254740992.0 && d <= 9007199254740992.0) {
404
+ if (is_safe_integer(d)) {
405
+ int64_t i64 = (int64_t)d;
404
406
  out->value_type = T_INTEGER;
405
407
  out->_padding = 0;
406
408
  out->v.integer = i64;
@@ -416,6 +418,7 @@ static int js_to_value_copy(napi_env env, napi_value jsval, StoolapValue* out,
416
418
  int64_t i64;
417
419
  bool lossless;
418
420
  napi_get_value_bigint_int64(env, jsval, &i64, &lossless);
421
+ if (!lossless) return -1; /* outside int64 */
419
422
  out->value_type = T_INTEGER;
420
423
  out->_padding = 0;
421
424
  out->v.integer = i64;
@@ -443,6 +446,7 @@ static int js_to_value_copy(napi_env env, napi_value jsval, StoolapValue* out,
443
446
  if (is_date) {
444
447
  double ms;
445
448
  napi_get_date_value(env, jsval, &ms);
449
+ if (ms != ms) { out->value_type = T_NULL; out->_padding = 0; out->v.integer = 0; return 0; }
446
450
  out->value_type = T_TIMESTAMP;
447
451
  out->_padding = 0;
448
452
  out->v.timestamp_nanos = (int64_t)(ms * 1000000.0);
@@ -571,23 +575,34 @@ static void free_params(StoolapValue* vals, char** tbufs, int tbuf_count) {
571
575
  /* ---- Constants for direct C parsing ---- */
572
576
 
573
577
  #define MAX_DIRECT_COLS 64
574
- #define DIRECT_ROW_LIMIT 5
578
+ #define DIRECT_ROW_LIMIT 16
575
579
 
576
580
  /* ---- Cached prepared statement wrapper ---- */
577
581
 
578
582
  typedef struct {
579
583
  StoolapStmt* stmt;
580
584
  int32_t col_count;
581
- uint32_t header_size; /* cached byte offset to first row data in binary buffer */
585
+ uint32_t header_size; /* cached byte offset of row_count in the packed buffer */
582
586
  char col_names[MAX_DIRECT_COLS][128];
583
587
  int has_cache;
588
+ /* the packed header the cache was built from, compared on every call so a
589
+ * schema change after prepare is noticed */
590
+ uint8_t* header;
591
+ /* JS row factory for this column list (built by the maker in lib/parse.js):
592
+ * one napi_call_function builds a row with a stable hidden class */
593
+ napi_ref factory;
584
594
  } CachedStmt;
585
595
 
596
+ /* makeRowFactory from lib/parse.js, handed over at load time */
597
+ static napi_ref row_factory_maker = NULL;
598
+
586
599
  static void finalize_cached_stmt(napi_env env, void* data, void* hint) {
587
600
  (void)env; (void)hint;
588
601
  CachedStmt* cs = (CachedStmt*)data;
589
602
  if (cs) {
590
603
  if (cs->stmt) S.stmt_finalize(cs->stmt);
604
+ if (cs->factory) napi_delete_reference(env, cs->factory);
605
+ free(cs->header);
591
606
  free(cs);
592
607
  }
593
608
  }
@@ -600,19 +615,148 @@ static void finalize_cached_stmt(napi_env env, void* data, void* hint) {
600
615
 
601
616
  #define SQL_STACK_SIZE 512
602
617
 
618
+ /* SQL longer than the stack buffer goes to a scratch buffer that is reused
619
+ * across calls. Only the synchronous wrappers use it, so it needs no lock. */
620
+ static char* sql_scratch = NULL;
621
+ static size_t sql_scratch_cap = 0;
622
+
623
+ static char* sql_scratch_get(size_t len) {
624
+ if (len + 1 > sql_scratch_cap) {
625
+ size_t cap = len + 1 + len / 2;
626
+ char* grown = realloc(sql_scratch, cap);
627
+ if (!grown) return NULL;
628
+ sql_scratch = grown;
629
+ sql_scratch_cap = cap;
630
+ }
631
+ return sql_scratch;
632
+ }
633
+
603
634
  #define GET_SQL_STRING(env, jsval, sql_var) \
604
635
  char _stack_sql[SQL_STACK_SIZE]; \
605
- size_t _sql_len; \
606
- napi_get_value_string_utf8(env, jsval, _stack_sql, SQL_STACK_SIZE, &_sql_len); \
636
+ size_t _sql_len = 0; \
607
637
  char* sql_var; \
608
- if (_sql_len < SQL_STACK_SIZE - 1) { \
638
+ if (napi_get_value_string_utf8(env, jsval, _stack_sql, SQL_STACK_SIZE, &_sql_len) != napi_ok) { \
639
+ napi_throw_type_error(env, NULL, "sql must be a string"); \
640
+ return NULL; \
641
+ } \
642
+ if (_sql_len + UTF8_SLACK < SQL_STACK_SIZE) { \
609
643
  sql_var = _stack_sql; \
610
644
  } else { \
611
645
  napi_get_value_string_utf8(env, jsval, NULL, 0, &_sql_len); \
612
- sql_var = alloca(_sql_len + 1); \
646
+ sql_var = sql_scratch_get(_sql_len); \
647
+ if (!sql_var) THROW(env, "Out of memory"); \
613
648
  napi_get_value_string_utf8(env, jsval, sql_var, _sql_len + 1, &_sql_len); \
614
649
  }
615
650
 
651
+ /* ---- Parameter binding ---- */
652
+ typedef struct {
653
+ StoolapValue stack_vals[MAX_STACK_PARAMS];
654
+ StoolapValue* vals;
655
+ int32_t count;
656
+ TextArena ta;
657
+ } ParamBind;
658
+
659
+ static void bind_release(ParamBind* pb) {
660
+ arena_reset(&pb->ta);
661
+ if (pb->vals != pb->stack_vals) free(pb->vals);
662
+ pb->vals = pb->stack_vals;
663
+ pb->count = 0;
664
+ }
665
+
666
+ /* Bind a JS array of positional params (undefined/null = none).
667
+ * Returns 0, or -1 after throwing. */
668
+ static int bind_positional(napi_env env, napi_value arr, ParamBind* pb) {
669
+ pb->vals = pb->stack_vals;
670
+ pb->count = 0;
671
+ ARENA_INIT(pb->ta);
672
+ napi_valuetype vt;
673
+ napi_typeof(env, arr, &vt);
674
+ if (vt == napi_undefined || vt == napi_null) return 0;
675
+ uint32_t len = 0;
676
+ if (napi_get_array_length(env, arr, &len) != napi_ok) {
677
+ napi_throw_type_error(env, NULL, "params must be an array");
678
+ return -1;
679
+ }
680
+ if (len == 0) return 0;
681
+ if (len > MAX_STACK_PARAMS) {
682
+ pb->vals = malloc(len * sizeof(StoolapValue));
683
+ if (!pb->vals) { pb->vals = pb->stack_vals; napi_throw_error(env, NULL, "Out of memory"); return -1; }
684
+ }
685
+ for (uint32_t i = 0; i < len; i++) {
686
+ napi_value elem;
687
+ napi_get_element(env, arr, i, &elem);
688
+ if (js_to_value(env, elem, &pb->vals[i], &pb->ta) != 0) {
689
+ bind_release(pb);
690
+ napi_throw_error(env, NULL, "Failed to convert parameters");
691
+ return -1;
692
+ }
693
+ }
694
+ pb->count = (int32_t)len;
695
+ return 0;
696
+ }
697
+
698
+ typedef struct {
699
+ StoolapNamedParam stack_np[MAX_STACK_PARAMS];
700
+ StoolapNamedParam* np;
701
+ int32_t count;
702
+ TextArena ta;
703
+ } NamedBind;
704
+
705
+ static void named_release(NamedBind* nb) {
706
+ arena_reset(&nb->ta);
707
+ if (nb->np != nb->stack_np) free(nb->np);
708
+ nb->np = nb->stack_np;
709
+ nb->count = 0;
710
+ }
711
+
712
+ /* Bind named params from parallel JS arrays: `names` (strings) and `values`.
713
+ * Returns 0, or -1 after throwing. */
714
+ static int bind_named(napi_env env, napi_value names, napi_value values, NamedBind* nb) {
715
+ nb->np = nb->stack_np;
716
+ nb->count = 0;
717
+ ARENA_INIT(nb->ta);
718
+ uint32_t len = 0;
719
+ if (napi_get_array_length(env, names, &len) != napi_ok) {
720
+ napi_throw_type_error(env, NULL, "params must be an object");
721
+ return -1;
722
+ }
723
+ if (len == 0) return 0;
724
+ if (len > MAX_STACK_PARAMS) {
725
+ nb->np = malloc(len * sizeof(StoolapNamedParam));
726
+ if (!nb->np) { nb->np = nb->stack_np; napi_throw_error(env, NULL, "Out of memory"); return -1; }
727
+ }
728
+ for (uint32_t i = 0; i < len; i++) {
729
+ StoolapNamedParam* np = &nb->np[i];
730
+ np->_padding = 0;
731
+ napi_value n;
732
+ napi_get_element(env, names, i, &n);
733
+ size_t nlen;
734
+ char* name = arena_string(env, n, &nb->ta, &nlen);
735
+ if (!name) { named_release(nb); napi_throw_error(env, NULL, "Out of memory"); return -1; }
736
+ np->name = name;
737
+ np->name_len = (int32_t)nlen;
738
+ napi_value v;
739
+ napi_get_element(env, values, i, &v);
740
+ if (js_to_value(env, v, &np->value, &nb->ta) != 0) {
741
+ named_release(nb);
742
+ napi_throw_error(env, NULL, "Failed to convert parameters");
743
+ return -1;
744
+ }
745
+ }
746
+ nb->count = (int32_t)len;
747
+ return 0;
748
+ }
749
+
750
+ /* Optional transaction argument: external or undefined/null */
751
+ static StoolapTx* tx_arg(napi_env env, napi_value v) {
752
+ napi_valuetype vt;
753
+ napi_typeof(env, v, &vt);
754
+ if (vt == napi_undefined || vt == napi_null) return NULL;
755
+ StoolapTx* tx = NULL;
756
+ napi_get_value_external(env, v, (void**)&tx);
757
+ return tx;
758
+ }
759
+
616
760
  /* ---- Result fetching ---- */
617
761
 
618
762
  typedef struct {
@@ -665,21 +809,183 @@ static napi_value make_arraybuffer_from_owned_buffer(napi_env env, uint8_t* buf,
665
809
  return arraybuffer;
666
810
  }
667
811
 
668
- /* Return raw ArrayBuffer — used for queryRaw only */
669
- static napi_value fetch_as_arraybuffer(napi_env env, StoolapRows* rows) {
670
- uint8_t* buf = NULL;
671
- int64_t buf_len = 0;
672
- int32_t rc = S.rows_fetch_all(rows, &buf, &buf_len);
673
- S.rows_close(rows);
812
+ static napi_value read_buf_val(napi_env env, const uint8_t* buf, uint32_t* poff);
674
813
 
675
- if (rc != STOOLAP_OK) {
676
- if (buf) S.buffer_free(buf, buf_len);
677
- napi_value null_val;
678
- napi_get_null(env, &null_val);
679
- return null_val;
814
+ /* Copy a handle-owned packed buffer into a fresh ArrayBuffer */
815
+ static napi_value arraybuffer_copy(napi_env env, const uint8_t* buf, int64_t len) {
816
+ void* data;
817
+ napi_value ab;
818
+ napi_create_arraybuffer(env, (size_t)len, &data, &ab);
819
+ if (len > 0) memcpy(data, buf, (size_t)len);
820
+ return ab;
821
+ }
822
+
823
+ /* Build one row object from the packed buffer at *off (advanced past the row).
824
+ * napi_define_properties sets every column in one N-API call. */
825
+ static napi_value packed_row(napi_env env, const uint8_t* buf, uint32_t* off,
826
+ char names[][128], int32_t col_count) {
827
+ napi_value row_obj;
828
+ napi_create_object(env, &row_obj);
829
+ napi_property_descriptor props[MAX_DIRECT_COLS];
830
+ for (int32_t c = 0; c < col_count; c++) {
831
+ props[c] = (napi_property_descriptor){
832
+ .utf8name = names[c],
833
+ .name = NULL, .method = NULL, .getter = NULL, .setter = NULL,
834
+ .value = read_buf_val(env, buf, off),
835
+ .attributes = napi_default_jsproperty, .data = NULL,
836
+ };
837
+ }
838
+ napi_define_properties(env, row_obj, (size_t)col_count, props);
839
+ return row_obj;
840
+ }
841
+
842
+ /* Ask lib/parse.js for a row factory for these column names; NULL when it
843
+ * declines (no columns, or more than it compiles for). */
844
+ static napi_value build_row_factory(napi_env env, char names[][128], int32_t col_count) {
845
+ if (!row_factory_maker) return NULL;
846
+ napi_value maker, names_arr, factory;
847
+ napi_get_reference_value(env, row_factory_maker, &maker);
848
+ napi_create_array_with_length(env, (size_t)col_count, &names_arr);
849
+ for (int32_t i = 0; i < col_count; i++) {
850
+ napi_value name;
851
+ napi_create_string_utf8(env, names[i], NAPI_AUTO_LENGTH, &name);
852
+ napi_set_element(env, names_arr, (uint32_t)i, name);
853
+ }
854
+ napi_valuetype ft = napi_undefined;
855
+ if (napi_call_function(env, maker, maker, 1, &names_arr, &factory) != napi_ok ||
856
+ napi_typeof(env, factory, &ft) != napi_ok || ft != napi_function) {
857
+ return NULL;
680
858
  }
859
+ return factory;
860
+ }
681
861
 
682
- return make_arraybuffer_from_owned_buffer(env, buf, buf_len);
862
+ /* Row factories for ad hoc statements, keyed by the packed header bytes.
863
+ * A handful of hot queries keep their factory; the rest round-robin. */
864
+ #define FACTORY_CACHE_SIZE 8
865
+ typedef struct {
866
+ uint8_t* header;
867
+ uint32_t len;
868
+ napi_ref factory;
869
+ } FactoryEntry;
870
+ static FactoryEntry factory_cache[FACTORY_CACHE_SIZE];
871
+ static int factory_cache_next = 0;
872
+
873
+ static napi_value adhoc_row_factory(napi_env env, const uint8_t* buf, uint32_t header_len,
874
+ char names[][128], int32_t col_count) {
875
+ for (int i = 0; i < FACTORY_CACHE_SIZE; i++) {
876
+ FactoryEntry* e = &factory_cache[i];
877
+ if (e->factory && e->len == header_len && memcmp(e->header, buf, header_len) == 0) {
878
+ napi_value f;
879
+ napi_get_reference_value(env, e->factory, &f);
880
+ return f;
881
+ }
882
+ }
883
+ napi_value f = build_row_factory(env, names, col_count);
884
+ if (!f) return NULL;
885
+ uint8_t* copy = malloc(header_len);
886
+ if (!copy) return f;
887
+ memcpy(copy, buf, header_len);
888
+ FactoryEntry* e = &factory_cache[factory_cache_next];
889
+ factory_cache_next = (factory_cache_next + 1) % FACTORY_CACHE_SIZE;
890
+ if (e->factory) napi_delete_reference(env, e->factory);
891
+ free(e->header);
892
+ e->header = copy;
893
+ e->len = header_len;
894
+ napi_create_reference(env, f, 1, &e->factory);
895
+ return f;
896
+ }
897
+
898
+ /* Build one row by calling the statement's JS row factory with the values. */
899
+ static napi_value factory_row(napi_env env, const uint8_t* buf, uint32_t* off,
900
+ napi_value factory, int32_t col_count) {
901
+ napi_value argv[MAX_DIRECT_COLS];
902
+ for (int32_t c = 0; c < col_count; c++) argv[c] = read_buf_val(env, buf, off);
903
+ napi_value row;
904
+ if (napi_call_function(env, factory, factory, (size_t)col_count, argv, &row) != napi_ok) {
905
+ napi_get_null(env, &row);
906
+ }
907
+ return row;
908
+ }
909
+
910
+ #define RESULT_ROWS 0 /* hybrid: objects for small results, ArrayBuffer for JS parsing otherwise */
911
+ #define RESULT_ONE 1 /* first row object, or null */
912
+ #define RESULT_RAW 2 /* ArrayBuffer */
913
+
914
+ /* Turn the packed result of a *_query_all call into the JS value for `mode`.
915
+ * `cs` caches the column header of a prepared statement across calls.
916
+ * Results with more than MAX_DIRECT_COLS columns always go to JS as an
917
+ * ArrayBuffer; for RESULT_ONE the JS side then takes the first row. */
918
+ static napi_value packed_result(napi_env env, const uint8_t* buf, int64_t len,
919
+ int mode, CachedStmt* cs) {
920
+ if (mode == RESULT_RAW) return arraybuffer_copy(env, buf, len);
921
+ uint32_t cc;
922
+ memcpy(&cc, buf, 4);
923
+ if (cc > MAX_DIRECT_COLS) return arraybuffer_copy(env, buf, len);
924
+ int32_t col_count = (int32_t)cc;
925
+ char local_names[MAX_DIRECT_COLS][128];
926
+ char (*names)[128];
927
+ uint32_t off;
928
+ if (cs && cs->has_cache && cs->col_count == col_count &&
929
+ (int64_t)cs->header_size <= len && memcmp(buf, cs->header, cs->header_size) == 0) {
930
+ names = cs->col_names;
931
+ off = cs->header_size;
932
+ } else {
933
+ names = cs ? cs->col_names : local_names;
934
+ off = 4;
935
+ for (int32_t i = 0; i < col_count; i++) {
936
+ uint16_t nlen;
937
+ memcpy(&nlen, buf + off, 2); off += 2;
938
+ /* a name longer than the C key buffer: let JS build the keys */
939
+ if (nlen > 127) return arraybuffer_copy(env, buf, len);
940
+ memcpy(names[i], buf + off, nlen);
941
+ names[i][nlen] = '\0';
942
+ off += nlen;
943
+ }
944
+ if (cs) {
945
+ uint8_t* copy = realloc(cs->header, off);
946
+ if (copy) {
947
+ memcpy(copy, buf, off);
948
+ cs->header = copy;
949
+ cs->col_count = col_count;
950
+ cs->header_size = off;
951
+ cs->has_cache = 1;
952
+ } else {
953
+ cs->has_cache = 0;
954
+ }
955
+ if (cs->factory) {
956
+ napi_delete_reference(env, cs->factory);
957
+ cs->factory = NULL;
958
+ }
959
+ napi_value f = build_row_factory(env, names, col_count);
960
+ if (f) napi_create_reference(env, f, 1, &cs->factory);
961
+ }
962
+ }
963
+ uint32_t row_count;
964
+ memcpy(&row_count, buf + off, 4); off += 4;
965
+ napi_value factory = NULL;
966
+ if (cs) {
967
+ if (cs->factory) napi_get_reference_value(env, cs->factory, &factory);
968
+ } else {
969
+ factory = adhoc_row_factory(env, buf, off, names, col_count);
970
+ }
971
+ if (mode == RESULT_ONE) {
972
+ if (row_count == 0) {
973
+ napi_value null_val;
974
+ napi_get_null(env, &null_val);
975
+ return null_val;
976
+ }
977
+ return factory ? factory_row(env, buf, &off, factory, col_count)
978
+ : packed_row(env, buf, &off, names, col_count);
979
+ }
980
+ if (row_count > DIRECT_ROW_LIMIT) return arraybuffer_copy(env, buf, len);
981
+ napi_value result;
982
+ napi_create_array_with_length(env, row_count, &result);
983
+ for (uint32_t r = 0; r < row_count; r++) {
984
+ napi_value row = factory ? factory_row(env, buf, &off, factory, col_count)
985
+ : packed_row(env, buf, &off, names, col_count);
986
+ napi_set_element(env, result, r, row);
987
+ }
988
+ return result;
683
989
  }
684
990
 
685
991
  /* ---- Direct C parsing of binary buffer → JS objects ---- */
@@ -744,438 +1050,97 @@ static napi_value read_buf_val(napi_env env, const uint8_t* buf, uint32_t* poff)
744
1050
  return val;
745
1051
  }
746
1052
 
747
- /*
748
- * Read a column value directly from StoolapRows (row-by-row API).
749
- * No serialization — reads straight from the Rust iterator.
750
- * Uses rows_col_type to detect NULL (saves separate rows_col_null FFI call).
751
- */
752
- static napi_value read_col_val(napi_env env, StoolapRows* rows, int32_t c) {
753
- napi_value val;
754
- int32_t type = S.rows_col_type(rows, c);
755
- switch (type) {
756
- case T_NULL:
757
- napi_get_null(env, &val);
758
- return val;
759
- case T_INTEGER: {
760
- int64_t v = S.rows_col_int(rows, c);
761
- if (v >= -2147483648LL && v <= 2147483647LL)
762
- napi_create_int32(env, (int32_t)v, &val);
763
- else
764
- napi_create_int64(env, v, &val);
765
- return val;
766
- }
767
- case T_FLOAT:
768
- napi_create_double(env, S.rows_col_dbl(rows, c), &val);
769
- return val;
770
- case T_TEXT:
771
- case T_JSON: {
772
- int64_t len;
773
- const char* txt = S.rows_col_text(rows, c, &len);
774
- napi_create_string_utf8(env, txt, (size_t)len, &val);
775
- return val;
776
- }
777
- case T_BOOLEAN:
778
- napi_get_boolean(env, S.rows_col_bool(rows, c) != 0, &val);
779
- return val;
780
- case T_TIMESTAMP: {
781
- double ms = (double)S.rows_col_ts(rows, c) / 1000000.0;
782
- napi_create_date(env, ms, &val);
783
- return val;
784
- }
785
- case T_BLOB: {
786
- int64_t blen;
787
- const uint8_t* bdata = S.rows_col_blob(rows, c, &blen);
788
- void* ab_data;
789
- napi_value ab;
790
- napi_create_arraybuffer(env, (size_t)blen, &ab_data, &ab);
791
- if (blen > 0) memcpy(ab_data, bdata, (size_t)blen);
792
- napi_create_typedarray(env, napi_float32_array, (size_t)(blen / 4), ab, 0, &val);
793
- return val;
794
- }
795
- default:
796
- napi_get_null(env, &val);
797
- return val;
1053
+ /* Bound function: queryOne with a single integer param, CachedStmt baked in.
1054
+ * Saves the napi_get_value_external of the generic path. */
1055
+ static napi_value wrap_stmt_query_one_int_bound(napi_env env, napi_callback_info info) {
1056
+ size_t argc = 1;
1057
+ napi_value argv[1];
1058
+ void* data;
1059
+ napi_get_cb_info(env, info, &argc, argv, NULL, &data);
1060
+ CachedStmt* cs = (CachedStmt*)data;
1061
+ double d;
1062
+ napi_get_value_double(env, argv[0], &d);
1063
+ StoolapValue val;
1064
+ val._padding = 0;
1065
+ if (is_safe_integer(d)) {
1066
+ val.value_type = T_INTEGER;
1067
+ val.v.integer = (int64_t)d;
1068
+ } else {
1069
+ val.value_type = T_FLOAT;
1070
+ val.v.float64 = d;
1071
+ }
1072
+ const uint8_t* buf = NULL;
1073
+ int64_t len = 0;
1074
+ int32_t rc = S.stmt_query_all(cs->stmt, NULL, &val, 1, &buf, &len);
1075
+ if (rc != STOOLAP_OK) {
1076
+ const char* msg = S.stmt_errmsg(cs->stmt);
1077
+ THROW(env, msg ? msg : "Statement query error");
798
1078
  }
1079
+ return packed_result(env, buf, len, RESULT_ONE, cs);
799
1080
  }
800
1081
 
801
- /*
802
- * Hybrid fetch: auto-selects based on result size.
803
- * - Small results (≤ DIRECT_ROW_LIMIT): parse buffer in C with
804
- * napi_set_named_property (no napi string key creation)
805
- * - Large results: return ArrayBuffer for JS parsing
806
- * Closes rows handle.
807
- */
808
- static napi_value fetch_result(napi_env env, StoolapRows* rows) {
809
- uint8_t* buf = NULL;
810
- int64_t buf_len = 0;
811
- int32_t rc = S.rows_fetch_all(rows, &buf, &buf_len);
812
- S.rows_close(rows);
813
-
814
- if (rc != STOOLAP_OK || !buf || buf_len == 0) {
815
- if (buf) S.buffer_free(buf, buf_len);
816
- napi_value empty;
817
- napi_create_array_with_length(env, 0, &empty);
818
- return empty;
819
- }
1082
+ /* ---- Async work helpers ---- */
820
1083
 
821
- /* Parse header */
822
- uint32_t off = 0;
823
- uint32_t col_count;
824
- memcpy(&col_count, buf, 4); off = 4;
1084
+ typedef struct {
1085
+ napi_async_work work;
1086
+ napi_deferred deferred;
1087
+ char* error;
1088
+ } AsyncBase;
825
1089
 
826
- /* Store null-terminated column names on stack (avoids napi string creation) */
827
- char col_names[MAX_DIRECT_COLS][128];
828
- for (uint32_t i = 0; i < col_count; i++) {
829
- uint16_t nlen;
830
- memcpy(&nlen, buf + off, 2); off += 2;
831
- if (i < MAX_DIRECT_COLS && nlen < 127) {
832
- memcpy(col_names[i], buf + off, nlen);
833
- col_names[i][nlen] = '\0';
834
- }
835
- off += nlen;
836
- }
1090
+ typedef struct {
1091
+ AsyncBase base;
1092
+ char* dsn;
1093
+ StoolapDB* db;
1094
+ } AsyncDbOpenTask;
837
1095
 
838
- uint32_t row_count;
839
- memcpy(&row_count, buf + off, 4); off += 4;
1096
+ typedef struct {
1097
+ AsyncBase base;
1098
+ StoolapDB* db;
1099
+ } AsyncDbCloseTask;
840
1100
 
841
- if (row_count <= DIRECT_ROW_LIMIT && col_count <= MAX_DIRECT_COLS) {
842
- /* Small result: parse in C, batch set via napi_define_properties */
843
- napi_value result;
844
- napi_create_array_with_length(env, row_count, &result);
845
-
846
- napi_property_descriptor props[MAX_DIRECT_COLS];
847
- for (uint32_t r = 0; r < row_count; r++) {
848
- napi_value row_obj;
849
- napi_create_object(env, &row_obj);
850
- for (uint32_t c = 0; c < col_count; c++) {
851
- props[c] = (napi_property_descriptor){
852
- .utf8name = col_names[c],
853
- .name = NULL, .method = NULL, .getter = NULL, .setter = NULL,
854
- .value = read_buf_val(env, buf, &off),
855
- .attributes = napi_default_jsproperty,
856
- .data = NULL,
857
- };
858
- }
859
- napi_define_properties(env, row_obj, col_count, props);
860
- napi_set_element(env, result, r, row_obj);
861
- }
1101
+ typedef enum {
1102
+ ASYNC_EXEC_DB,
1103
+ ASYNC_EXEC_DB_SIMPLE,
1104
+ ASYNC_EXEC_TX,
1105
+ ASYNC_EXEC_STMT,
1106
+ } AsyncExecKind;
862
1107
 
863
- S.buffer_free(buf, buf_len);
864
- return result;
865
- }
1108
+ typedef struct {
1109
+ AsyncBase base;
1110
+ AsyncExecKind kind;
1111
+ void* target;
1112
+ char* sql;
1113
+ StoolapValue* vals;
1114
+ int32_t val_count;
1115
+ char** heap_bufs;
1116
+ int heap_buf_count;
1117
+ int64_t affected;
1118
+ } AsyncExecTask;
866
1119
 
867
- /* Large result: return ArrayBuffer for JS parsing */
868
- RustBuffer* rb = malloc(sizeof(RustBuffer));
869
- rb->ptr = buf;
870
- rb->len = buf_len;
1120
+ typedef enum {
1121
+ ASYNC_QUERY_DB,
1122
+ ASYNC_QUERY_TX,
1123
+ ASYNC_QUERY_STMT,
1124
+ } AsyncQueryKind;
871
1125
 
872
- napi_value arraybuffer;
873
- napi_status status = napi_create_external_arraybuffer(
874
- env, buf, (size_t)buf_len, finalize_rust_buffer, rb, &arraybuffer);
1126
+ typedef struct {
1127
+ AsyncBase base;
1128
+ AsyncQueryKind kind;
1129
+ void* target;
1130
+ char* sql;
1131
+ StoolapValue* vals;
1132
+ int32_t val_count;
1133
+ char** heap_bufs;
1134
+ int heap_buf_count;
1135
+ uint8_t* buf;
1136
+ int64_t buf_len;
1137
+ } AsyncQueryTask;
875
1138
 
876
- if (status != napi_ok) {
877
- free(rb);
878
- void* ab_data;
879
- napi_create_arraybuffer(env, (size_t)buf_len, &ab_data, &arraybuffer);
880
- memcpy(ab_data, buf, (size_t)buf_len);
881
- S.buffer_free(buf, buf_len);
882
- }
883
-
884
- return arraybuffer;
885
- }
886
-
887
- /*
888
- * Fetch first row as JS object, or null if empty.
889
- * Uses DIRECT row iteration — skips rows_fetch_all serialization entirely.
890
- * Uses napi_define_properties batch — 1 N-API call sets all properties.
891
- * Optimal for point lookups.
892
- * Closes rows handle.
893
- */
894
- static napi_value fetch_one(napi_env env, StoolapRows* rows) {
895
- int32_t rc = S.rows_next(rows);
896
- if (rc != STOOLAP_ROW) {
897
- S.rows_close(rows);
898
- napi_value null_val;
899
- napi_get_null(env, &null_val);
900
- return null_val;
901
- }
902
-
903
- int32_t col_count = S.rows_col_count(rows);
904
- if (col_count > MAX_DIRECT_COLS) col_count = MAX_DIRECT_COLS;
905
-
906
- napi_value row_obj;
907
- napi_create_object(env, &row_obj);
908
-
909
- /* Read all values and set in one batch via napi_define_properties */
910
- napi_property_descriptor props[MAX_DIRECT_COLS];
911
- for (int32_t c = 0; c < col_count; c++) {
912
- props[c] = (napi_property_descriptor){
913
- .utf8name = S.rows_col_name(rows, c),
914
- .name = NULL,
915
- .method = NULL,
916
- .getter = NULL,
917
- .setter = NULL,
918
- .value = read_col_val(env, rows, c),
919
- .attributes = napi_default_jsproperty,
920
- .data = NULL,
921
- };
922
- }
923
- napi_define_properties(env, row_obj, (size_t)col_count, props);
924
-
925
- S.rows_close(rows);
926
- return row_obj;
927
- }
928
-
929
- /*
930
- * Direct row iteration for prepared statement queryOne.
931
- * Uses rows_next + read_col_val (direct column access) — no serialization.
932
- * Caches column names in CachedStmt to avoid rows_col_name calls on repeat.
933
- * Faster than fetch_one_cached (no rows_fetch_all serialize+deserialize roundtrip).
934
- */
935
- static napi_value fetch_one_direct_cached(napi_env env, StoolapRows* rows, CachedStmt* cs) {
936
- int32_t rc = S.rows_next(rows);
937
- if (rc != STOOLAP_ROW) {
938
- S.rows_close(rows);
939
- napi_value null_val;
940
- napi_get_null(env, &null_val);
941
- return null_val;
942
- }
943
-
944
- int32_t col_count;
945
-
946
- if (!cs->has_cache) {
947
- /* First call: cache column names from rows iterator */
948
- int32_t cc = S.rows_col_count(rows);
949
- col_count = (cc > MAX_DIRECT_COLS) ? MAX_DIRECT_COLS : cc;
950
- cs->col_count = col_count;
951
-
952
- for (int32_t i = 0; i < col_count; i++) {
953
- const char* name = S.rows_col_name(rows, i);
954
- size_t nlen = strlen(name);
955
- if (nlen > 127) nlen = 127;
956
- memcpy(cs->col_names[i], name, nlen);
957
- cs->col_names[i][nlen] = '\0';
958
- }
959
- cs->has_cache = 1;
960
- } else {
961
- col_count = cs->col_count;
962
- }
963
-
964
- napi_value row_obj;
965
- napi_create_object(env, &row_obj);
966
-
967
- napi_property_descriptor props[MAX_DIRECT_COLS];
968
- for (int32_t c = 0; c < col_count; c++) {
969
- props[c] = (napi_property_descriptor){
970
- .utf8name = cs->col_names[c],
971
- .name = NULL,
972
- .method = NULL,
973
- .getter = NULL,
974
- .setter = NULL,
975
- .value = read_col_val(env, rows, c),
976
- .attributes = napi_default_jsproperty,
977
- .data = NULL,
978
- };
979
- }
980
- napi_define_properties(env, row_obj, (size_t)col_count, props);
981
-
982
- S.rows_close(rows);
983
- return row_obj;
984
- }
985
-
986
- /*
987
- * Hybrid fetch for prepared statements with column name caching.
988
- * Same as fetch_result but uses CachedStmt to cache header info.
989
- * On repeat calls: skip header parsing, use cached column names.
990
- */
991
- static napi_value fetch_result_cached(napi_env env, StoolapRows* rows, CachedStmt* cs) {
992
- uint8_t* buf = NULL;
993
- int64_t buf_len = 0;
994
- int32_t rc = S.rows_fetch_all(rows, &buf, &buf_len);
995
- S.rows_close(rows);
996
-
997
- if (rc != STOOLAP_OK || !buf || buf_len == 0) {
998
- if (buf) S.buffer_free(buf, buf_len);
999
- napi_value empty;
1000
- napi_create_array_with_length(env, 0, &empty);
1001
- return empty;
1002
- }
1003
-
1004
- uint32_t off = 0;
1005
- int32_t col_count;
1006
-
1007
- if (!cs->has_cache || cs->header_size == 0) {
1008
- /* First call, or cache was set by direct path (no header_size) */
1009
- uint32_t cc;
1010
- memcpy(&cc, buf, 4); off = 4;
1011
- col_count = (cc > MAX_DIRECT_COLS) ? MAX_DIRECT_COLS : (int32_t)cc;
1012
- cs->col_count = col_count;
1013
-
1014
- for (int32_t i = 0; i < col_count; i++) {
1015
- uint16_t nlen;
1016
- memcpy(&nlen, buf + off, 2); off += 2;
1017
- uint16_t cplen = (nlen > 127) ? 127 : nlen;
1018
- memcpy(cs->col_names[i], buf + off, cplen);
1019
- cs->col_names[i][cplen] = '\0';
1020
- off += nlen;
1021
- }
1022
-
1023
- uint32_t row_count;
1024
- memcpy(&row_count, buf + off, 4); off += 4;
1025
- cs->header_size = off;
1026
- cs->has_cache = 1;
1027
-
1028
- if (row_count == 0) {
1029
- S.buffer_free(buf, buf_len);
1030
- napi_value empty;
1031
- napi_create_array_with_length(env, 0, &empty);
1032
- return empty;
1033
- }
1034
-
1035
- if (row_count <= DIRECT_ROW_LIMIT) {
1036
- /* Small result: parse in C */
1037
- napi_value result;
1038
- napi_create_array_with_length(env, row_count, &result);
1039
- napi_property_descriptor props[MAX_DIRECT_COLS];
1040
- for (uint32_t r = 0; r < row_count; r++) {
1041
- napi_value row_obj;
1042
- napi_create_object(env, &row_obj);
1043
- for (int32_t c = 0; c < col_count; c++) {
1044
- props[c] = (napi_property_descriptor){
1045
- .utf8name = cs->col_names[c],
1046
- .name = NULL, .method = NULL, .getter = NULL, .setter = NULL,
1047
- .value = read_buf_val(env, buf, &off),
1048
- .attributes = napi_default_jsproperty, .data = NULL,
1049
- };
1050
- }
1051
- napi_define_properties(env, row_obj, (size_t)col_count, props);
1052
- napi_set_element(env, result, r, row_obj);
1053
- }
1054
- S.buffer_free(buf, buf_len);
1055
- return result;
1056
- }
1057
- } else {
1058
- /* Cached: use cached column info */
1059
- col_count = cs->col_count;
1060
- off = cs->header_size;
1061
- uint32_t row_count;
1062
- memcpy(&row_count, buf + cs->header_size - 4, 4);
1063
-
1064
- if (row_count == 0) {
1065
- S.buffer_free(buf, buf_len);
1066
- napi_value empty;
1067
- napi_create_array_with_length(env, 0, &empty);
1068
- return empty;
1069
- }
1070
-
1071
- if (row_count <= DIRECT_ROW_LIMIT) {
1072
- napi_value result;
1073
- napi_create_array_with_length(env, row_count, &result);
1074
- napi_property_descriptor props[MAX_DIRECT_COLS];
1075
- for (uint32_t r = 0; r < row_count; r++) {
1076
- napi_value row_obj;
1077
- napi_create_object(env, &row_obj);
1078
- for (int32_t c = 0; c < col_count; c++) {
1079
- props[c] = (napi_property_descriptor){
1080
- .utf8name = cs->col_names[c],
1081
- .name = NULL, .method = NULL, .getter = NULL, .setter = NULL,
1082
- .value = read_buf_val(env, buf, &off),
1083
- .attributes = napi_default_jsproperty, .data = NULL,
1084
- };
1085
- }
1086
- napi_define_properties(env, row_obj, (size_t)col_count, props);
1087
- napi_set_element(env, result, r, row_obj);
1088
- }
1089
- S.buffer_free(buf, buf_len);
1090
- return result;
1091
- }
1092
- }
1093
-
1094
- /* Large result: return ArrayBuffer for JS parsing */
1095
- RustBuffer* rb = malloc(sizeof(RustBuffer));
1096
- rb->ptr = buf;
1097
- rb->len = buf_len;
1098
-
1099
- napi_value arraybuffer;
1100
- napi_status status = napi_create_external_arraybuffer(
1101
- env, buf, (size_t)buf_len, finalize_rust_buffer, rb, &arraybuffer);
1102
-
1103
- if (status != napi_ok) {
1104
- free(rb);
1105
- void* ab_data;
1106
- napi_create_arraybuffer(env, (size_t)buf_len, &ab_data, &arraybuffer);
1107
- memcpy(ab_data, buf, (size_t)buf_len);
1108
- S.buffer_free(buf, buf_len);
1109
- }
1110
-
1111
- return arraybuffer;
1112
- }
1113
-
1114
- /* Forward declaration for bound function used in dbPrepare */
1115
- static napi_value wrap_stmt_query_one_int_bound(napi_env env, napi_callback_info info);
1116
-
1117
- /* ---- Async work helpers ---- */
1118
-
1119
- typedef struct {
1120
- napi_async_work work;
1121
- napi_deferred deferred;
1122
- char* error;
1123
- } AsyncBase;
1124
-
1125
- typedef struct {
1126
- AsyncBase base;
1127
- char* dsn;
1128
- StoolapDB* db;
1129
- } AsyncDbOpenTask;
1130
-
1131
- typedef struct {
1132
- AsyncBase base;
1133
- StoolapDB* db;
1134
- } AsyncDbCloseTask;
1135
-
1136
- typedef enum {
1137
- ASYNC_EXEC_DB,
1138
- ASYNC_EXEC_DB_SIMPLE,
1139
- ASYNC_EXEC_TX,
1140
- ASYNC_EXEC_STMT,
1141
- } AsyncExecKind;
1142
-
1143
- typedef struct {
1144
- AsyncBase base;
1145
- AsyncExecKind kind;
1146
- void* target;
1147
- char* sql;
1148
- StoolapValue* vals;
1149
- int32_t val_count;
1150
- char** heap_bufs;
1151
- int heap_buf_count;
1152
- int64_t affected;
1153
- } AsyncExecTask;
1154
-
1155
- typedef enum {
1156
- ASYNC_QUERY_DB,
1157
- ASYNC_QUERY_TX,
1158
- ASYNC_QUERY_STMT,
1159
- } AsyncQueryKind;
1160
-
1161
- typedef struct {
1162
- AsyncBase base;
1163
- AsyncQueryKind kind;
1164
- void* target;
1165
- char* sql;
1166
- StoolapValue* vals;
1167
- int32_t val_count;
1168
- char** heap_bufs;
1169
- int heap_buf_count;
1170
- uint8_t* buf;
1171
- int64_t buf_len;
1172
- } AsyncQueryTask;
1173
-
1174
- typedef struct {
1175
- AsyncBase base;
1176
- StoolapDB* db;
1177
- StoolapTx* tx;
1178
- } AsyncTxBeginTask;
1139
+ typedef struct {
1140
+ AsyncBase base;
1141
+ StoolapDB* db;
1142
+ StoolapTx* tx;
1143
+ } AsyncTxBeginTask;
1179
1144
 
1180
1145
  typedef enum {
1181
1146
  ASYNC_TX_COMMIT,
@@ -1385,9 +1350,12 @@ static void execute_async_query(napi_env env, void* data) {
1385
1350
  }
1386
1351
 
1387
1352
  rc = S.rows_fetch_all(rows, &task->buf, &task->buf_len);
1353
+ if (rc != STOOLAP_OK) {
1354
+ /* a runtime error while the rows were produced, e.g. an invalid REGEXP */
1355
+ task->base.error = dup_cstr(S.rows_errmsg(rows), "Failed to fetch query results");
1356
+ }
1388
1357
  S.rows_close(rows);
1389
1358
  if (rc != STOOLAP_OK) {
1390
- task->base.error = dup_cstr(NULL, "Failed to fetch query results");
1391
1359
  if (task->buf) {
1392
1360
  S.buffer_free(task->buf, task->buf_len);
1393
1361
  task->buf = NULL;
@@ -1459,7 +1427,9 @@ static void execute_tx_finish_async(napi_env env, void* data) {
1459
1427
  : S.tx_rollback(task->tx);
1460
1428
 
1461
1429
  if (rc != STOOLAP_OK) {
1462
- task->base.error = dup_cstr(S.tx_errmsg(task->tx),
1430
+ /* the tx handle is consumed by commit/rollback; the message is in the
1431
+ * thread-local error of this worker */
1432
+ task->base.error = dup_cstr(S.errmsg(NULL),
1463
1433
  task->kind == ASYNC_TX_COMMIT
1464
1434
  ? "Failed to commit transaction"
1465
1435
  : "Failed to rollback transaction");
@@ -1534,28 +1504,23 @@ static napi_value fn_load_library(napi_env env, napi_callback_info info) {
1534
1504
  LOAD(tx_exec_p, "stoolap_tx_exec_params");
1535
1505
  LOAD(tx_query, "stoolap_tx_query");
1536
1506
  LOAD(tx_query_p, "stoolap_tx_query_params");
1537
- /* Optional symbols — not in all engine versions */
1538
- S.tx_stmt_exec = (fn_tx_stmt_exec) DLSYM(lib_handle, "stoolap_tx_stmt_exec");
1539
- S.tx_stmt_query = (fn_tx_stmt_query)DLSYM(lib_handle, "stoolap_tx_stmt_query");
1507
+ LOAD(tx_stmt_exec, "stoolap_tx_stmt_exec");
1540
1508
 
1541
1509
  LOAD(tx_commit, "stoolap_tx_commit");
1542
1510
  LOAD(tx_rollback, "stoolap_tx_rollback");
1543
1511
  LOAD(tx_errmsg, "stoolap_tx_errmsg");
1544
- LOAD(rows_next, "stoolap_rows_next");
1545
- LOAD(rows_col_count,"stoolap_rows_column_count");
1546
- LOAD(rows_col_name, "stoolap_rows_column_name");
1547
- LOAD(rows_col_type, "stoolap_rows_column_type");
1548
- LOAD(rows_col_int, "stoolap_rows_column_int64");
1549
- LOAD(rows_col_dbl, "stoolap_rows_column_double");
1550
- LOAD(rows_col_text, "stoolap_rows_column_text");
1551
- LOAD(rows_col_bool, "stoolap_rows_column_bool");
1552
- LOAD(rows_col_ts, "stoolap_rows_column_timestamp");
1553
- LOAD(rows_col_blob, "stoolap_rows_column_blob");
1554
- LOAD(rows_col_null, "stoolap_rows_column_is_null");
1555
- LOAD(rows_affected, "stoolap_rows_affected");
1556
1512
  LOAD(rows_close, "stoolap_rows_close");
1557
1513
  LOAD(rows_fetch_all,"stoolap_rows_fetch_all");
1558
1514
  LOAD(buffer_free, "stoolap_buffer_free");
1515
+ LOAD(rows_errmsg, "stoolap_rows_errmsg");
1516
+ LOAD(rows_fetch_chunk, "stoolap_rows_fetch_chunk");
1517
+ LOAD(exec_named, "stoolap_exec_named");
1518
+ LOAD(query_named, "stoolap_query_named");
1519
+ LOAD(tx_exec_named, "stoolap_tx_exec_named");
1520
+ LOAD(tx_query_named,"stoolap_tx_query_named");
1521
+ LOAD(stmt_query_all, "stoolap_stmt_query_all");
1522
+ LOAD(query_params_all, "stoolap_query_params_all");
1523
+ LOAD(query_named_all, "stoolap_query_named_all");
1559
1524
 
1560
1525
  #undef LOAD
1561
1526
 
@@ -1564,6 +1529,18 @@ static napi_value fn_load_library(napi_env env, napi_callback_info info) {
1564
1529
  return undef;
1565
1530
  }
1566
1531
 
1532
+ /* setRowFactoryMaker(fn): keeps lib/parse.js makeRowFactory for packed_result */
1533
+ static napi_value fn_set_row_factory_maker(napi_env env, napi_callback_info info) {
1534
+ size_t argc = 1;
1535
+ napi_value argv[1];
1536
+ napi_get_cb_info(env, info, &argc, argv, NULL, NULL);
1537
+ if (row_factory_maker) napi_delete_reference(env, row_factory_maker);
1538
+ napi_create_reference(env, argv[0], 1, &row_factory_maker);
1539
+ napi_value undef;
1540
+ napi_get_undefined(env, &undef);
1541
+ return undef;
1542
+ }
1543
+
1567
1544
  /* dbOpen(dsn: string): external */
1568
1545
  static napi_value fn_db_open(napi_env env, napi_callback_info info) {
1569
1546
  size_t argc = 1;
@@ -1676,61 +1653,92 @@ static napi_value fn_db_exec(napi_env env, napi_callback_info info) {
1676
1653
  size_t argc = 3;
1677
1654
  napi_value argv[3];
1678
1655
  napi_get_cb_info(env, info, &argc, argv, NULL, NULL);
1679
-
1680
1656
  StoolapDB* db;
1681
1657
  napi_get_value_external(env, argv[0], (void**)&db);
1682
-
1683
1658
  GET_SQL_STRING(env, argv[1], sql);
1684
-
1659
+ ParamBind pb;
1660
+ if (bind_positional(env, argv[2], &pb) != 0) return NULL;
1685
1661
  int64_t affected = 0;
1686
- int32_t rc;
1687
-
1688
- if (argc > 2) {
1689
- napi_valuetype vt;
1690
- napi_typeof(env, argv[2], &vt);
1691
- if (vt == napi_undefined || vt == napi_null) {
1692
- rc = S.exec(db, sql, &affected);
1693
- } else {
1694
- uint32_t len;
1695
- napi_get_array_length(env, argv[2], &len);
1696
- if (len == 0) {
1697
- rc = S.exec(db, sql, &affected);
1698
- } else if (len <= MAX_STACK_PARAMS) {
1699
- /* Stack-allocated fast path */
1700
- StoolapValue vals[MAX_STACK_PARAMS];
1701
- char* tbufs[MAX_STACK_PARAMS * 2];
1702
- int tbuf_cnt = 0;
1703
- for (uint32_t i = 0; i < len; i++) {
1704
- napi_value elem;
1705
- napi_get_element(env, argv[2], i, &elem);
1706
- if (js_to_value(env, elem, &vals[i], tbufs, &tbuf_cnt) != 0) {
1707
- for (int k = 0; k < tbuf_cnt; k++) free(tbufs[k]);
1708
- THROW(env, "Failed to convert parameters");
1709
- }
1710
- }
1711
- rc = S.exec_p(db, sql, vals, (int32_t)len, &affected);
1712
- for (int k = 0; k < tbuf_cnt; k++) free(tbufs[k]);
1713
- } else {
1714
- StoolapValue* vals; int32_t cnt;
1715
- char** tbufs; int tbuf_cnt;
1716
- if (convert_params(env, argv[2], &vals, &cnt, &tbufs, &tbuf_cnt) != 0)
1717
- THROW(env, "Failed to convert parameters");
1718
- rc = S.exec_p(db, sql, vals, cnt, &affected);
1719
- free_params(vals, tbufs, tbuf_cnt);
1720
- }
1721
- }
1722
- } else {
1723
- rc = S.exec(db, sql, &affected);
1662
+ int32_t rc = pb.count > 0
1663
+ ? S.exec_p(db, sql, pb.vals, pb.count, &affected)
1664
+ : S.exec(db, sql, &affected);
1665
+ bind_release(&pb);
1666
+ if (rc != STOOLAP_OK) {
1667
+ const char* msg = S.errmsg(db);
1668
+ THROW(env, msg ? msg : "Exec error");
1724
1669
  }
1670
+ return make_changes(env, affected);
1671
+ }
1725
1672
 
1673
+ /* dbExecNamed(external, sql, names, values): { changes } */
1674
+ static napi_value fn_db_exec_named(napi_env env, napi_callback_info info) {
1675
+ size_t argc = 4;
1676
+ napi_value argv[4];
1677
+ napi_get_cb_info(env, info, &argc, argv, NULL, NULL);
1678
+ StoolapDB* db;
1679
+ napi_get_value_external(env, argv[0], (void**)&db);
1680
+ GET_SQL_STRING(env, argv[1], sql);
1681
+ NamedBind nb;
1682
+ if (bind_named(env, argv[2], argv[3], &nb) != 0) return NULL;
1683
+ int64_t affected = 0;
1684
+ int32_t rc = S.exec_named(db, sql, nb.np, nb.count, &affected);
1685
+ named_release(&nb);
1726
1686
  if (rc != STOOLAP_OK) {
1727
1687
  const char* msg = S.errmsg(db);
1728
1688
  THROW(env, msg ? msg : "Exec error");
1729
1689
  }
1730
-
1731
1690
  return make_changes(env, affected);
1732
1691
  }
1733
1692
 
1693
+ /* dbQueryAll(external, tx?, sql, params?, mode): Array | Object | null | ArrayBuffer
1694
+ * One FFI call: no rows handle, the buffer belongs to the database handle. */
1695
+ static napi_value fn_db_query_all(napi_env env, napi_callback_info info) {
1696
+ size_t argc = 5;
1697
+ napi_value argv[5];
1698
+ napi_get_cb_info(env, info, &argc, argv, NULL, NULL);
1699
+ StoolapDB* db;
1700
+ napi_get_value_external(env, argv[0], (void**)&db);
1701
+ StoolapTx* tx = tx_arg(env, argv[1]);
1702
+ GET_SQL_STRING(env, argv[2], sql);
1703
+ int32_t mode;
1704
+ napi_get_value_int32(env, argv[4], &mode);
1705
+ ParamBind pb;
1706
+ if (bind_positional(env, argv[3], &pb) != 0) return NULL;
1707
+ const uint8_t* buf = NULL;
1708
+ int64_t len = 0;
1709
+ int32_t rc = S.query_params_all(db, tx, sql, pb.vals, pb.count, &buf, &len);
1710
+ bind_release(&pb);
1711
+ if (rc != STOOLAP_OK) {
1712
+ const char* msg = S.errmsg(db);
1713
+ THROW(env, msg ? msg : "Query error");
1714
+ }
1715
+ return packed_result(env, buf, len, mode, NULL);
1716
+ }
1717
+
1718
+ /* dbQueryNamedAll(external, tx?, sql, names, values, mode) */
1719
+ static napi_value fn_db_query_named_all(napi_env env, napi_callback_info info) {
1720
+ size_t argc = 6;
1721
+ napi_value argv[6];
1722
+ napi_get_cb_info(env, info, &argc, argv, NULL, NULL);
1723
+ StoolapDB* db;
1724
+ napi_get_value_external(env, argv[0], (void**)&db);
1725
+ StoolapTx* tx = tx_arg(env, argv[1]);
1726
+ GET_SQL_STRING(env, argv[2], sql);
1727
+ int32_t mode;
1728
+ napi_get_value_int32(env, argv[5], &mode);
1729
+ NamedBind nb;
1730
+ if (bind_named(env, argv[3], argv[4], &nb) != 0) return NULL;
1731
+ const uint8_t* buf = NULL;
1732
+ int64_t len = 0;
1733
+ int32_t rc = S.query_named_all(db, tx, sql, nb.np, nb.count, &buf, &len);
1734
+ named_release(&nb);
1735
+ if (rc != STOOLAP_OK) {
1736
+ const char* msg = S.errmsg(db);
1737
+ THROW(env, msg ? msg : "Query error");
1738
+ }
1739
+ return packed_result(env, buf, len, mode, NULL);
1740
+ }
1741
+
1734
1742
  /* dbExecAsync(external, sql, params?): Promise<{ changes }> */
1735
1743
  static napi_value fn_db_exec_async(napi_env env, napi_callback_info info) {
1736
1744
  size_t argc = 3;
@@ -1816,87 +1824,28 @@ static napi_value fn_db_exec_simple_async(napi_env env, napi_callback_info info)
1816
1824
  return promise;
1817
1825
  }
1818
1826
 
1819
- /* dbQueryBuf(external, sql, params?): ArrayBuffer | null */
1820
- static napi_value fn_db_query_buf(napi_env env, napi_callback_info info) {
1827
+ /* dbQueryBufAsync(external, sql, params?): Promise<ArrayBuffer | null> */
1828
+ static napi_value fn_db_query_buf_async(napi_env env, napi_callback_info info) {
1821
1829
  size_t argc = 3;
1822
1830
  napi_value argv[3];
1823
1831
  napi_get_cb_info(env, info, &argc, argv, NULL, NULL);
1824
1832
 
1825
- StoolapDB* db;
1826
- napi_get_value_external(env, argv[0], (void**)&db);
1827
-
1828
- GET_SQL_STRING(env, argv[1], sql);
1833
+ AsyncQueryTask* task = calloc(1, sizeof(AsyncQueryTask));
1834
+ if (!task) THROW(env, "Out of memory");
1829
1835
 
1830
- StoolapRows* rows = NULL;
1831
- int32_t rc;
1836
+ task->kind = ASYNC_QUERY_DB;
1837
+ napi_get_value_external(env, argv[0], &task->target);
1838
+ if (copy_napi_string(env, argv[1], &task->sql) != 0) {
1839
+ free(task);
1840
+ THROW(env, "Out of memory");
1841
+ }
1832
1842
 
1833
1843
  if (argc > 2) {
1834
- napi_valuetype vt;
1835
- napi_typeof(env, argv[2], &vt);
1836
- if (vt == napi_undefined || vt == napi_null) {
1837
- rc = S.query(db, sql, &rows);
1838
- } else {
1839
- uint32_t len;
1840
- napi_get_array_length(env, argv[2], &len);
1841
- if (len == 0) {
1842
- rc = S.query(db, sql, &rows);
1843
- } else if (len <= MAX_STACK_PARAMS) {
1844
- StoolapValue vals[MAX_STACK_PARAMS];
1845
- char* tbufs[MAX_STACK_PARAMS * 2];
1846
- int tbuf_cnt = 0;
1847
- for (uint32_t i = 0; i < len; i++) {
1848
- napi_value elem;
1849
- napi_get_element(env, argv[2], i, &elem);
1850
- if (js_to_value(env, elem, &vals[i], tbufs, &tbuf_cnt) != 0) {
1851
- for (int k = 0; k < tbuf_cnt; k++) free(tbufs[k]);
1852
- THROW(env, "Failed to convert parameters");
1853
- }
1854
- }
1855
- rc = S.query_p(db, sql, vals, (int32_t)len, &rows);
1856
- for (int k = 0; k < tbuf_cnt; k++) free(tbufs[k]);
1857
- } else {
1858
- StoolapValue* vals; int32_t cnt;
1859
- char** tbufs; int tbuf_cnt;
1860
- if (convert_params(env, argv[2], &vals, &cnt, &tbufs, &tbuf_cnt) != 0)
1861
- THROW(env, "Failed to convert parameters");
1862
- rc = S.query_p(db, sql, vals, cnt, &rows);
1863
- free_params(vals, tbufs, tbuf_cnt);
1864
- }
1865
- }
1866
- } else {
1867
- rc = S.query(db, sql, &rows);
1868
- }
1869
-
1870
- if (rc != STOOLAP_OK) {
1871
- const char* msg = S.errmsg(db);
1872
- THROW(env, msg ? msg : "Query error");
1873
- }
1874
-
1875
- return fetch_as_arraybuffer(env, rows);
1876
- }
1877
-
1878
- /* dbQueryBufAsync(external, sql, params?): Promise<ArrayBuffer | null> */
1879
- static napi_value fn_db_query_buf_async(napi_env env, napi_callback_info info) {
1880
- size_t argc = 3;
1881
- napi_value argv[3];
1882
- napi_get_cb_info(env, info, &argc, argv, NULL, NULL);
1883
-
1884
- AsyncQueryTask* task = calloc(1, sizeof(AsyncQueryTask));
1885
- if (!task) THROW(env, "Out of memory");
1886
-
1887
- task->kind = ASYNC_QUERY_DB;
1888
- napi_get_value_external(env, argv[0], &task->target);
1889
- if (copy_napi_string(env, argv[1], &task->sql) != 0) {
1890
- free(task);
1891
- THROW(env, "Out of memory");
1892
- }
1893
-
1894
- if (argc > 2) {
1895
- if (convert_params_copy(env, argv[2], &task->vals, &task->val_count,
1896
- &task->heap_bufs, &task->heap_buf_count) != 0) {
1897
- free(task->sql);
1898
- free(task);
1899
- THROW(env, "Failed to convert parameters");
1844
+ if (convert_params_copy(env, argv[2], &task->vals, &task->val_count,
1845
+ &task->heap_bufs, &task->heap_buf_count) != 0) {
1846
+ free(task->sql);
1847
+ free(task);
1848
+ THROW(env, "Failed to convert parameters");
1900
1849
  }
1901
1850
  }
1902
1851
 
@@ -1911,125 +1860,7 @@ static napi_value fn_db_query_buf_async(napi_env env, napi_callback_info info) {
1911
1860
  return promise;
1912
1861
  }
1913
1862
 
1914
- /* dbQuery(external, sql, params?): Array | ArrayBuffer */
1915
- static napi_value fn_db_query(napi_env env, napi_callback_info info) {
1916
- size_t argc = 3;
1917
- napi_value argv[3];
1918
- napi_get_cb_info(env, info, &argc, argv, NULL, NULL);
1919
-
1920
- StoolapDB* db;
1921
- napi_get_value_external(env, argv[0], (void**)&db);
1922
-
1923
- GET_SQL_STRING(env, argv[1], sql);
1924
-
1925
- StoolapRows* rows = NULL;
1926
- int32_t rc;
1927
-
1928
- if (argc > 2) {
1929
- napi_valuetype vt;
1930
- napi_typeof(env, argv[2], &vt);
1931
- if (vt == napi_undefined || vt == napi_null) {
1932
- rc = S.query(db, sql, &rows);
1933
- } else {
1934
- uint32_t len;
1935
- napi_get_array_length(env, argv[2], &len);
1936
- if (len == 0) {
1937
- rc = S.query(db, sql, &rows);
1938
- } else if (len <= MAX_STACK_PARAMS) {
1939
- StoolapValue vals[MAX_STACK_PARAMS];
1940
- char* tbufs[MAX_STACK_PARAMS * 2];
1941
- int tbuf_cnt = 0;
1942
- for (uint32_t i = 0; i < len; i++) {
1943
- napi_value elem;
1944
- napi_get_element(env, argv[2], i, &elem);
1945
- if (js_to_value(env, elem, &vals[i], tbufs, &tbuf_cnt) != 0) {
1946
- for (int k = 0; k < tbuf_cnt; k++) free(tbufs[k]);
1947
- THROW(env, "Failed to convert parameters");
1948
- }
1949
- }
1950
- rc = S.query_p(db, sql, vals, (int32_t)len, &rows);
1951
- for (int k = 0; k < tbuf_cnt; k++) free(tbufs[k]);
1952
- } else {
1953
- StoolapValue* vals; int32_t cnt;
1954
- char** tbufs; int tbuf_cnt;
1955
- if (convert_params(env, argv[2], &vals, &cnt, &tbufs, &tbuf_cnt) != 0)
1956
- THROW(env, "Failed to convert parameters");
1957
- rc = S.query_p(db, sql, vals, cnt, &rows);
1958
- free_params(vals, tbufs, tbuf_cnt);
1959
- }
1960
- }
1961
- } else {
1962
- rc = S.query(db, sql, &rows);
1963
- }
1964
-
1965
- if (rc != STOOLAP_OK) {
1966
- const char* msg = S.errmsg(db);
1967
- THROW(env, msg ? msg : "Query error");
1968
- }
1969
-
1970
- return fetch_result(env, rows);
1971
- }
1972
-
1973
- /* dbQueryOne(external, sql, params?): Object | null */
1974
- static napi_value fn_db_query_one(napi_env env, napi_callback_info info) {
1975
- size_t argc = 3;
1976
- napi_value argv[3];
1977
- napi_get_cb_info(env, info, &argc, argv, NULL, NULL);
1978
-
1979
- StoolapDB* db;
1980
- napi_get_value_external(env, argv[0], (void**)&db);
1981
-
1982
- GET_SQL_STRING(env, argv[1], sql);
1983
-
1984
- StoolapRows* rows = NULL;
1985
- int32_t rc;
1986
-
1987
- if (argc > 2) {
1988
- napi_valuetype vt;
1989
- napi_typeof(env, argv[2], &vt);
1990
- if (vt == napi_undefined || vt == napi_null) {
1991
- rc = S.query(db, sql, &rows);
1992
- } else {
1993
- uint32_t len;
1994
- napi_get_array_length(env, argv[2], &len);
1995
- if (len == 0) {
1996
- rc = S.query(db, sql, &rows);
1997
- } else if (len <= MAX_STACK_PARAMS) {
1998
- StoolapValue vals[MAX_STACK_PARAMS];
1999
- char* tbufs[MAX_STACK_PARAMS * 2];
2000
- int tbuf_cnt = 0;
2001
- for (uint32_t i = 0; i < len; i++) {
2002
- napi_value elem;
2003
- napi_get_element(env, argv[2], i, &elem);
2004
- if (js_to_value(env, elem, &vals[i], tbufs, &tbuf_cnt) != 0) {
2005
- for (int k = 0; k < tbuf_cnt; k++) free(tbufs[k]);
2006
- THROW(env, "Failed to convert parameters");
2007
- }
2008
- }
2009
- rc = S.query_p(db, sql, vals, (int32_t)len, &rows);
2010
- for (int k = 0; k < tbuf_cnt; k++) free(tbufs[k]);
2011
- } else {
2012
- StoolapValue* vals; int32_t cnt;
2013
- char** tbufs; int tbuf_cnt;
2014
- if (convert_params(env, argv[2], &vals, &cnt, &tbufs, &tbuf_cnt) != 0)
2015
- THROW(env, "Failed to convert parameters");
2016
- rc = S.query_p(db, sql, vals, cnt, &rows);
2017
- free_params(vals, tbufs, tbuf_cnt);
2018
- }
2019
- }
2020
- } else {
2021
- rc = S.query(db, sql, &rows);
2022
- }
2023
-
2024
- if (rc != STOOLAP_OK) {
2025
- const char* msg = S.errmsg(db);
2026
- THROW(env, msg ? msg : "Query error");
2027
- }
2028
-
2029
- return fetch_one(env, rows);
2030
- }
2031
-
2032
- /* dbPrepare(external, sql): external (returns CachedStmt wrapper) */
1863
+ /* dbPrepare(external, sql): [external, boundQueryOneInt] */
2033
1864
  static napi_value fn_db_prepare(napi_env env, napi_callback_info info) {
2034
1865
  size_t argc = 2;
2035
1866
  napi_value argv[2];
@@ -2050,7 +1881,6 @@ static napi_value fn_db_prepare(napi_env env, napi_callback_info info) {
2050
1881
  CachedStmt* cs = calloc(1, sizeof(CachedStmt));
2051
1882
  cs->stmt = stmt;
2052
1883
  cs->has_cache = 0;
2053
-
2054
1884
  napi_value ext;
2055
1885
  napi_create_external(env, cs, finalize_cached_stmt, NULL, &ext);
2056
1886
 
@@ -2067,151 +1897,43 @@ static napi_value fn_db_prepare(napi_env env, napi_callback_info info) {
2067
1897
  return result;
2068
1898
  }
2069
1899
 
2070
- /* stmtExec(external, params?): { changes }
2071
- * Uses stack allocation for ≤16 params (avoids calloc/free overhead). */
1900
+ /* stmtExec(external, params?): { changes } */
2072
1901
  static napi_value wrap_stmt_exec(napi_env env, napi_callback_info info) {
2073
1902
  size_t argc = 2;
2074
1903
  napi_value argv[2];
2075
1904
  napi_get_cb_info(env, info, &argc, argv, NULL, NULL);
2076
-
2077
1905
  GET_STMT(env, argv[0], cs);
2078
- StoolapStmt* stmt = cs->stmt;
2079
-
2080
- if (argc <= 1) {
2081
- int64_t affected = 0;
2082
- int32_t rc = S.stmt_exec(stmt, NULL, 0, &affected);
2083
- if (rc != STOOLAP_OK) {
2084
- const char* msg = S.stmt_errmsg(stmt);
2085
- THROW(env, msg ? msg : "Statement exec error");
2086
- }
2087
- return make_changes(env, affected);
2088
- }
2089
-
2090
- /* Check if params is null/undefined */
2091
- napi_valuetype vt;
2092
- napi_typeof(env, argv[1], &vt);
2093
- if (vt == napi_undefined || vt == napi_null) {
2094
- int64_t affected = 0;
2095
- int32_t rc = S.stmt_exec(stmt, NULL, 0, &affected);
2096
- if (rc != STOOLAP_OK) {
2097
- const char* msg = S.stmt_errmsg(stmt);
2098
- THROW(env, msg ? msg : "Statement exec error");
2099
- }
2100
- return make_changes(env, affected);
2101
- }
2102
-
2103
- uint32_t len;
2104
- napi_get_array_length(env, argv[1], &len);
2105
-
2106
- if (len == 0) {
2107
- int64_t affected = 0;
2108
- int32_t rc = S.stmt_exec(stmt, NULL, 0, &affected);
2109
- if (rc != STOOLAP_OK) {
2110
- const char* msg = S.stmt_errmsg(stmt);
2111
- THROW(env, msg ? msg : "Statement exec error");
2112
- }
2113
- return make_changes(env, affected);
2114
- }
2115
-
2116
- if (len <= MAX_STACK_PARAMS) {
2117
- /* Stack-allocated fast path — no calloc/free for vals and tbufs */
2118
- StoolapValue vals[MAX_STACK_PARAMS];
2119
- char* tbufs[MAX_STACK_PARAMS * 2];
2120
- int tbuf_cnt = 0;
2121
-
2122
- for (uint32_t i = 0; i < len; i++) {
2123
- napi_value elem;
2124
- napi_get_element(env, argv[1], i, &elem);
2125
- if (js_to_value(env, elem, &vals[i], tbufs, &tbuf_cnt) != 0) {
2126
- for (int k = 0; k < tbuf_cnt; k++) free(tbufs[k]);
2127
- THROW(env, "Failed to convert parameters");
2128
- }
2129
- }
2130
-
2131
- int64_t affected = 0;
2132
- int32_t rc = S.stmt_exec(stmt, vals, (int32_t)len, &affected);
2133
- for (int k = 0; k < tbuf_cnt; k++) free(tbufs[k]);
2134
-
2135
- if (rc != STOOLAP_OK) {
2136
- const char* msg = S.stmt_errmsg(stmt);
2137
- THROW(env, msg ? msg : "Statement exec error");
2138
- }
2139
- return make_changes(env, affected);
2140
- }
2141
-
2142
- /* Large param count — heap allocated */
2143
- StoolapValue* vals = NULL; int32_t cnt = 0;
2144
- char** tbufs = NULL; int tbuf_cnt = 0;
2145
- if (convert_params(env, argv[1], &vals, &cnt, &tbufs, &tbuf_cnt) != 0)
2146
- THROW(env, "Failed to convert parameters");
2147
-
1906
+ ParamBind pb;
1907
+ if (bind_positional(env, argv[1], &pb) != 0) return NULL;
2148
1908
  int64_t affected = 0;
2149
- int32_t rc = S.stmt_exec(stmt, vals, cnt, &affected);
2150
- free_params(vals, tbufs, tbuf_cnt);
2151
-
1909
+ int32_t rc = S.stmt_exec(cs->stmt, pb.vals, pb.count, &affected);
1910
+ bind_release(&pb);
2152
1911
  if (rc != STOOLAP_OK) {
2153
- const char* msg = S.stmt_errmsg(stmt);
1912
+ const char* msg = S.stmt_errmsg(cs->stmt);
2154
1913
  THROW(env, msg ? msg : "Statement exec error");
2155
1914
  }
2156
-
2157
1915
  return make_changes(env, affected);
2158
1916
  }
2159
1917
 
2160
- /* stmtQueryBuf(external, params?): ArrayBuffer | null */
2161
- static napi_value wrap_stmt_query_buf(napi_env env, napi_callback_info info) {
2162
- size_t argc = 2;
2163
- napi_value argv[2];
1918
+ /* stmtQueryAll(external, params?, mode): Array | Object | null | ArrayBuffer */
1919
+ static napi_value wrap_stmt_query_all(napi_env env, napi_callback_info info) {
1920
+ size_t argc = 3;
1921
+ napi_value argv[3];
2164
1922
  napi_get_cb_info(env, info, &argc, argv, NULL, NULL);
2165
-
2166
1923
  GET_STMT(env, argv[0], cs);
2167
- StoolapStmt* stmt = cs->stmt;
2168
-
2169
- StoolapRows* rows = NULL;
2170
- int32_t rc;
2171
-
2172
- if (argc <= 1) {
2173
- rc = S.stmt_query(stmt, NULL, 0, &rows);
2174
- } else {
2175
- napi_valuetype vt;
2176
- napi_typeof(env, argv[1], &vt);
2177
- if (vt == napi_undefined || vt == napi_null) {
2178
- rc = S.stmt_query(stmt, NULL, 0, &rows);
2179
- } else {
2180
- uint32_t len;
2181
- napi_get_array_length(env, argv[1], &len);
2182
- if (len == 0) {
2183
- rc = S.stmt_query(stmt, NULL, 0, &rows);
2184
- } else if (len <= MAX_STACK_PARAMS) {
2185
- StoolapValue vals[MAX_STACK_PARAMS];
2186
- char* tbufs[MAX_STACK_PARAMS * 2];
2187
- int tbuf_cnt = 0;
2188
- for (uint32_t i = 0; i < len; i++) {
2189
- napi_value elem;
2190
- napi_get_element(env, argv[1], i, &elem);
2191
- if (js_to_value(env, elem, &vals[i], tbufs, &tbuf_cnt) != 0) {
2192
- for (int k = 0; k < tbuf_cnt; k++) free(tbufs[k]);
2193
- THROW(env, "Failed to convert parameters");
2194
- }
2195
- }
2196
- rc = S.stmt_query(stmt, vals, (int32_t)len, &rows);
2197
- for (int k = 0; k < tbuf_cnt; k++) free(tbufs[k]);
2198
- } else {
2199
- StoolapValue* vals = NULL; int32_t cnt = 0;
2200
- char** tbufs = NULL; int tbuf_cnt = 0;
2201
- if (convert_params(env, argv[1], &vals, &cnt, &tbufs, &tbuf_cnt) != 0)
2202
- THROW(env, "Failed to convert parameters");
2203
- rc = S.stmt_query(stmt, vals, cnt, &rows);
2204
- free_params(vals, tbufs, tbuf_cnt);
2205
- }
2206
- }
2207
- }
2208
-
1924
+ int32_t mode;
1925
+ napi_get_value_int32(env, argv[2], &mode);
1926
+ ParamBind pb;
1927
+ if (bind_positional(env, argv[1], &pb) != 0) return NULL;
1928
+ const uint8_t* buf = NULL;
1929
+ int64_t len = 0;
1930
+ int32_t rc = S.stmt_query_all(cs->stmt, NULL, pb.vals, pb.count, &buf, &len);
1931
+ bind_release(&pb);
2209
1932
  if (rc != STOOLAP_OK) {
2210
- const char* msg = S.stmt_errmsg(stmt);
1933
+ const char* msg = S.stmt_errmsg(cs->stmt);
2211
1934
  THROW(env, msg ? msg : "Statement query error");
2212
1935
  }
2213
-
2214
- return fetch_as_arraybuffer(env, rows);
1936
+ return packed_result(env, buf, len, mode, cs);
2215
1937
  }
2216
1938
 
2217
1939
  /* stmtExecAsync(external, params?): Promise<{ changes }> */
@@ -2288,157 +2010,13 @@ static napi_value wrap_stmt_query_buf_async(napi_env env, napi_callback_info inf
2288
2010
  return promise;
2289
2011
  }
2290
2012
 
2291
- /* stmtQuery(external, params?): Array | ArrayBuffer
2292
- * Uses stack allocation for ≤16 params. */
2293
- static napi_value wrap_stmt_query(napi_env env, napi_callback_info info) {
2294
- size_t argc = 2;
2295
- napi_value argv[2];
2296
- napi_get_cb_info(env, info, &argc, argv, NULL, NULL);
2297
-
2298
- GET_STMT(env, argv[0], cs);
2299
- StoolapStmt* stmt = cs->stmt;
2300
-
2301
- StoolapRows* rows = NULL;
2302
- int32_t rc;
2303
-
2304
- if (argc <= 1) {
2305
- rc = S.stmt_query(stmt, NULL, 0, &rows);
2306
- } else {
2307
- napi_valuetype vt;
2308
- napi_typeof(env, argv[1], &vt);
2309
- if (vt == napi_undefined || vt == napi_null) {
2310
- rc = S.stmt_query(stmt, NULL, 0, &rows);
2311
- } else {
2312
- uint32_t len;
2313
- napi_get_array_length(env, argv[1], &len);
2314
- if (len == 0) {
2315
- rc = S.stmt_query(stmt, NULL, 0, &rows);
2316
- } else if (len <= MAX_STACK_PARAMS) {
2317
- StoolapValue vals[MAX_STACK_PARAMS];
2318
- char* tbufs[MAX_STACK_PARAMS * 2];
2319
- int tbuf_cnt = 0;
2320
- for (uint32_t i = 0; i < len; i++) {
2321
- napi_value elem;
2322
- napi_get_element(env, argv[1], i, &elem);
2323
- if (js_to_value(env, elem, &vals[i], tbufs, &tbuf_cnt) != 0) {
2324
- for (int k = 0; k < tbuf_cnt; k++) free(tbufs[k]);
2325
- THROW(env, "Failed to convert parameters");
2326
- }
2327
- }
2328
- rc = S.stmt_query(stmt, vals, (int32_t)len, &rows);
2329
- for (int k = 0; k < tbuf_cnt; k++) free(tbufs[k]);
2330
- } else {
2331
- StoolapValue* vals = NULL; int32_t cnt = 0;
2332
- char** tbufs = NULL; int tbuf_cnt = 0;
2333
- if (convert_params(env, argv[1], &vals, &cnt, &tbufs, &tbuf_cnt) != 0)
2334
- THROW(env, "Failed to convert parameters");
2335
- rc = S.stmt_query(stmt, vals, cnt, &rows);
2336
- free_params(vals, tbufs, tbuf_cnt);
2337
- }
2338
- }
2339
- }
2340
-
2341
- if (rc != STOOLAP_OK) {
2342
- const char* msg = S.stmt_errmsg(stmt);
2343
- THROW(env, msg ? msg : "Statement query error");
2344
- }
2345
-
2346
- return fetch_result_cached(env, rows, cs);
2347
- }
2348
-
2349
- /* stmtQueryOne(external, params?): Object | null
2350
- * Uses stack allocation for ≤16 params. */
2351
- static napi_value wrap_stmt_query_one(napi_env env, napi_callback_info info) {
2352
- size_t argc = 2;
2353
- napi_value argv[2];
2354
- napi_get_cb_info(env, info, &argc, argv, NULL, NULL);
2355
-
2356
- GET_STMT(env, argv[0], cs);
2357
- StoolapStmt* stmt = cs->stmt;
2358
-
2359
- StoolapRows* rows = NULL;
2360
- int32_t rc;
2361
-
2362
- if (argc <= 1) {
2363
- rc = S.stmt_query(stmt, NULL, 0, &rows);
2364
- } else {
2365
- napi_valuetype vt;
2366
- napi_typeof(env, argv[1], &vt);
2367
- if (vt == napi_undefined || vt == napi_null) {
2368
- rc = S.stmt_query(stmt, NULL, 0, &rows);
2369
- } else {
2370
- uint32_t len;
2371
- napi_get_array_length(env, argv[1], &len);
2372
- if (len == 0) {
2373
- rc = S.stmt_query(stmt, NULL, 0, &rows);
2374
- } else if (len <= MAX_STACK_PARAMS) {
2375
- StoolapValue vals[MAX_STACK_PARAMS];
2376
- char* tbufs[MAX_STACK_PARAMS * 2];
2377
- int tbuf_cnt = 0;
2378
- for (uint32_t i = 0; i < len; i++) {
2379
- napi_value elem;
2380
- napi_get_element(env, argv[1], i, &elem);
2381
- if (js_to_value(env, elem, &vals[i], tbufs, &tbuf_cnt) != 0) {
2382
- for (int k = 0; k < tbuf_cnt; k++) free(tbufs[k]);
2383
- THROW(env, "Failed to convert parameters");
2384
- }
2385
- }
2386
- rc = S.stmt_query(stmt, vals, (int32_t)len, &rows);
2387
- for (int k = 0; k < tbuf_cnt; k++) free(tbufs[k]);
2388
- } else {
2389
- StoolapValue* vals = NULL; int32_t cnt = 0;
2390
- char** tbufs = NULL; int tbuf_cnt = 0;
2391
- if (convert_params(env, argv[1], &vals, &cnt, &tbufs, &tbuf_cnt) != 0)
2392
- THROW(env, "Failed to convert parameters");
2393
- rc = S.stmt_query(stmt, vals, cnt, &rows);
2394
- free_params(vals, tbufs, tbuf_cnt);
2395
- }
2396
- }
2397
- }
2398
-
2399
- if (rc != STOOLAP_OK) {
2400
- const char* msg = S.stmt_errmsg(stmt);
2401
- THROW(env, msg ? msg : "Statement query error");
2402
- }
2403
-
2404
- return fetch_one_direct_cached(env, rows, cs);
2405
- }
2406
-
2407
- /* stmtQueryOneIntBound(intValue): Object | null
2408
- * Bound version — CachedStmt baked into function data.
2409
- * Eliminates napi_get_value_external call (~35ns savings per call). */
2410
- static napi_value wrap_stmt_query_one_int_bound(napi_env env, napi_callback_info info) {
2411
- size_t argc = 1;
2412
- napi_value argv[1];
2413
- void* data;
2414
- napi_get_cb_info(env, info, &argc, argv, NULL, &data);
2415
- CachedStmt* cs = (CachedStmt*)data;
2416
-
2417
- double d;
2418
- napi_get_value_double(env, argv[0], &d);
2419
- StoolapValue val;
2420
- val.value_type = T_INTEGER;
2421
- val._padding = 0;
2422
- val.v.integer = (int64_t)d;
2423
-
2424
- StoolapRows* rows = NULL;
2425
- int32_t rc = S.stmt_query(cs->stmt, &val, 1, &rows);
2426
-
2427
- if (rc != STOOLAP_OK) {
2428
- const char* msg = S.stmt_errmsg(cs->stmt);
2429
- THROW(env, msg ? msg : "Statement query error");
2430
- }
2431
-
2432
- return fetch_one_direct_cached(env, rows, cs);
2433
- }
2434
-
2435
2013
  /* Helper: read numeric args from argv[start..start+count] into StoolapValue array */
2436
2014
  static void read_num_args(napi_env env, napi_value* argv, uint32_t start, uint32_t count, StoolapValue* vals) {
2437
2015
  for (uint32_t i = 0; i < count; i++) {
2438
2016
  double d;
2439
2017
  napi_get_value_double(env, argv[start + i], &d);
2440
- int64_t i64 = (int64_t)d;
2441
- if ((double)i64 == d && d >= -9007199254740992.0 && d <= 9007199254740992.0) {
2018
+ if (is_safe_integer(d)) {
2019
+ int64_t i64 = (int64_t)d;
2442
2020
  vals[i].value_type = T_INTEGER;
2443
2021
  vals[i]._padding = 0;
2444
2022
  vals[i].v.integer = i64;
@@ -2474,7 +2052,10 @@ static napi_value wrap_stmt_exec_nums(napi_env env, napi_callback_info info) {
2474
2052
  }
2475
2053
 
2476
2054
  napi_value result;
2477
- napi_create_int32(env, (int32_t)affected, &result);
2055
+ if (affected >= -2147483648LL && affected <= 2147483647LL)
2056
+ napi_create_int32(env, (int32_t)affected, &result);
2057
+ else
2058
+ napi_create_int64(env, affected, &result);
2478
2059
  return result;
2479
2060
  }
2480
2061
 
@@ -2484,61 +2065,39 @@ static napi_value wrap_stmt_query_nums(napi_env env, napi_callback_info info) {
2484
2065
  size_t argc = 17;
2485
2066
  napi_value argv[17];
2486
2067
  napi_get_cb_info(env, info, &argc, argv, NULL, NULL);
2487
-
2488
2068
  GET_STMT(env, argv[0], cs);
2489
2069
  uint32_t len = (uint32_t)(argc - 1);
2490
2070
  if (len > 16) len = 16;
2491
-
2492
2071
  StoolapValue vals[16];
2493
2072
  read_num_args(env, argv, 1, len, vals);
2494
-
2495
- StoolapRows* rows = NULL;
2496
- int32_t rc = S.stmt_query(cs->stmt, vals, (int32_t)len, &rows);
2497
-
2073
+ const uint8_t* buf = NULL;
2074
+ int64_t blen = 0;
2075
+ int32_t rc = S.stmt_query_all(cs->stmt, NULL, vals, (int32_t)len, &buf, &blen);
2498
2076
  if (rc != STOOLAP_OK) {
2499
2077
  const char* msg = S.stmt_errmsg(cs->stmt);
2500
2078
  THROW(env, msg ? msg : "Statement query error");
2501
2079
  }
2502
-
2503
- return fetch_result_cached(env, rows, cs);
2080
+ return packed_result(env, buf, blen, RESULT_ROWS, cs);
2504
2081
  }
2505
2082
 
2506
- /* stmtQueryOneNums(external, num1, num2, ...): Object | null
2507
- * Direct numeric args — no array unwrapping overhead. */
2083
+ /* stmtQueryOneNums(external, num1, num2, ...): Object | null */
2508
2084
  static napi_value wrap_stmt_query_one_nums(napi_env env, napi_callback_info info) {
2509
2085
  size_t argc = 17;
2510
2086
  napi_value argv[17];
2511
2087
  napi_get_cb_info(env, info, &argc, argv, NULL, NULL);
2512
-
2513
2088
  GET_STMT(env, argv[0], cs);
2514
2089
  uint32_t len = (uint32_t)(argc - 1);
2515
2090
  if (len > 16) len = 16;
2516
-
2517
2091
  StoolapValue vals[16];
2518
2092
  read_num_args(env, argv, 1, len, vals);
2519
-
2520
- StoolapRows* rows = NULL;
2521
- int32_t rc = S.stmt_query(cs->stmt, vals, (int32_t)len, &rows);
2522
-
2093
+ const uint8_t* buf = NULL;
2094
+ int64_t blen = 0;
2095
+ int32_t rc = S.stmt_query_all(cs->stmt, NULL, vals, (int32_t)len, &buf, &blen);
2523
2096
  if (rc != STOOLAP_OK) {
2524
2097
  const char* msg = S.stmt_errmsg(cs->stmt);
2525
2098
  THROW(env, msg ? msg : "Statement query error");
2526
2099
  }
2527
-
2528
- return fetch_one_direct_cached(env, rows, cs);
2529
- }
2530
-
2531
- /* stmtSql(external): string */
2532
- static napi_value wrap_stmt_sql(napi_env env, napi_callback_info info) {
2533
- size_t argc = 1;
2534
- napi_value argv[1];
2535
- napi_get_cb_info(env, info, &argc, argv, NULL, NULL);
2536
-
2537
- GET_STMT(env, argv[0], cs);
2538
- const char* sql = S.stmt_sql(cs->stmt);
2539
- napi_value val;
2540
- napi_create_string_utf8(env, sql ? sql : "", NAPI_AUTO_LENGTH, &val);
2541
- return val;
2100
+ return packed_result(env, buf, blen, RESULT_ONE, cs);
2542
2101
  }
2543
2102
 
2544
2103
  /* stmtFinalize(external): void */
@@ -2603,57 +2162,40 @@ static napi_value wrap_tx_exec(napi_env env, napi_callback_info info) {
2603
2162
  size_t argc = 3;
2604
2163
  napi_value argv[3];
2605
2164
  napi_get_cb_info(env, info, &argc, argv, NULL, NULL);
2606
-
2607
2165
  StoolapTx* tx;
2608
2166
  napi_get_value_external(env, argv[0], (void**)&tx);
2609
-
2610
2167
  GET_SQL_STRING(env, argv[1], sql);
2611
-
2168
+ ParamBind pb;
2169
+ if (bind_positional(env, argv[2], &pb) != 0) return NULL;
2612
2170
  int64_t affected = 0;
2613
- int32_t rc;
2614
-
2615
- if (argc > 2) {
2616
- napi_valuetype vt;
2617
- napi_typeof(env, argv[2], &vt);
2618
- if (vt == napi_undefined || vt == napi_null) {
2619
- rc = S.tx_exec(tx, sql, &affected);
2620
- } else {
2621
- uint32_t len;
2622
- napi_get_array_length(env, argv[2], &len);
2623
- if (len == 0) {
2624
- rc = S.tx_exec(tx, sql, &affected);
2625
- } else if (len <= MAX_STACK_PARAMS) {
2626
- StoolapValue vals[MAX_STACK_PARAMS];
2627
- char* tbufs[MAX_STACK_PARAMS * 2];
2628
- int tbuf_cnt = 0;
2629
- for (uint32_t i = 0; i < len; i++) {
2630
- napi_value elem;
2631
- napi_get_element(env, argv[2], i, &elem);
2632
- if (js_to_value(env, elem, &vals[i], tbufs, &tbuf_cnt) != 0) {
2633
- for (int k = 0; k < tbuf_cnt; k++) free(tbufs[k]);
2634
- THROW(env, "Failed to convert parameters");
2635
- }
2636
- }
2637
- rc = S.tx_exec_p(tx, sql, vals, (int32_t)len, &affected);
2638
- for (int k = 0; k < tbuf_cnt; k++) free(tbufs[k]);
2639
- } else {
2640
- StoolapValue* vals; int32_t cnt;
2641
- char** tbufs; int tbuf_cnt;
2642
- if (convert_params(env, argv[2], &vals, &cnt, &tbufs, &tbuf_cnt) != 0)
2643
- THROW(env, "Failed to convert parameters");
2644
- rc = S.tx_exec_p(tx, sql, vals, cnt, &affected);
2645
- free_params(vals, tbufs, tbuf_cnt);
2646
- }
2647
- }
2648
- } else {
2649
- rc = S.tx_exec(tx, sql, &affected);
2171
+ int32_t rc = pb.count > 0
2172
+ ? S.tx_exec_p(tx, sql, pb.vals, pb.count, &affected)
2173
+ : S.tx_exec(tx, sql, &affected);
2174
+ bind_release(&pb);
2175
+ if (rc != STOOLAP_OK) {
2176
+ const char* msg = S.tx_errmsg(tx);
2177
+ THROW(env, msg ? msg : "Transaction exec error");
2650
2178
  }
2179
+ return make_changes(env, affected);
2180
+ }
2651
2181
 
2182
+ /* txExecNamed(external, sql, names, values): { changes } */
2183
+ static napi_value wrap_tx_exec_named(napi_env env, napi_callback_info info) {
2184
+ size_t argc = 4;
2185
+ napi_value argv[4];
2186
+ napi_get_cb_info(env, info, &argc, argv, NULL, NULL);
2187
+ StoolapTx* tx;
2188
+ napi_get_value_external(env, argv[0], (void**)&tx);
2189
+ GET_SQL_STRING(env, argv[1], sql);
2190
+ NamedBind nb;
2191
+ if (bind_named(env, argv[2], argv[3], &nb) != 0) return NULL;
2192
+ int64_t affected = 0;
2193
+ int32_t rc = S.tx_exec_named(tx, sql, nb.np, nb.count, &affected);
2194
+ named_release(&nb);
2652
2195
  if (rc != STOOLAP_OK) {
2653
2196
  const char* msg = S.tx_errmsg(tx);
2654
2197
  THROW(env, msg ? msg : "Transaction exec error");
2655
2198
  }
2656
-
2657
2199
  return make_changes(env, affected);
2658
2200
  }
2659
2201
 
@@ -2693,65 +2235,6 @@ static napi_value wrap_tx_exec_async(napi_env env, napi_callback_info info) {
2693
2235
  return promise;
2694
2236
  }
2695
2237
 
2696
- /* txQueryBuf(external, sql, params?): ArrayBuffer | null */
2697
- static napi_value wrap_tx_query_buf(napi_env env, napi_callback_info info) {
2698
- size_t argc = 3;
2699
- napi_value argv[3];
2700
- napi_get_cb_info(env, info, &argc, argv, NULL, NULL);
2701
-
2702
- StoolapTx* tx;
2703
- napi_get_value_external(env, argv[0], (void**)&tx);
2704
-
2705
- GET_SQL_STRING(env, argv[1], sql);
2706
-
2707
- StoolapRows* rows = NULL;
2708
- int32_t rc;
2709
-
2710
- if (argc > 2) {
2711
- napi_valuetype vt;
2712
- napi_typeof(env, argv[2], &vt);
2713
- if (vt == napi_undefined || vt == napi_null) {
2714
- rc = S.tx_query(tx, sql, &rows);
2715
- } else {
2716
- uint32_t len;
2717
- napi_get_array_length(env, argv[2], &len);
2718
- if (len == 0) {
2719
- rc = S.tx_query(tx, sql, &rows);
2720
- } else if (len <= MAX_STACK_PARAMS) {
2721
- StoolapValue vals[MAX_STACK_PARAMS];
2722
- char* tbufs[MAX_STACK_PARAMS * 2];
2723
- int tbuf_cnt = 0;
2724
- for (uint32_t i = 0; i < len; i++) {
2725
- napi_value elem;
2726
- napi_get_element(env, argv[2], i, &elem);
2727
- if (js_to_value(env, elem, &vals[i], tbufs, &tbuf_cnt) != 0) {
2728
- for (int k = 0; k < tbuf_cnt; k++) free(tbufs[k]);
2729
- THROW(env, "Failed to convert parameters");
2730
- }
2731
- }
2732
- rc = S.tx_query_p(tx, sql, vals, (int32_t)len, &rows);
2733
- for (int k = 0; k < tbuf_cnt; k++) free(tbufs[k]);
2734
- } else {
2735
- StoolapValue* vals; int32_t cnt;
2736
- char** tbufs; int tbuf_cnt;
2737
- if (convert_params(env, argv[2], &vals, &cnt, &tbufs, &tbuf_cnt) != 0)
2738
- THROW(env, "Failed to convert parameters");
2739
- rc = S.tx_query_p(tx, sql, vals, cnt, &rows);
2740
- free_params(vals, tbufs, tbuf_cnt);
2741
- }
2742
- }
2743
- } else {
2744
- rc = S.tx_query(tx, sql, &rows);
2745
- }
2746
-
2747
- if (rc != STOOLAP_OK) {
2748
- const char* msg = S.tx_errmsg(tx);
2749
- THROW(env, msg ? msg : "Transaction query error");
2750
- }
2751
-
2752
- return fetch_as_arraybuffer(env, rows);
2753
- }
2754
-
2755
2238
  /* txQueryBufAsync(external, sql, params?): Promise<ArrayBuffer | null> */
2756
2239
  static napi_value wrap_tx_query_buf_async(napi_env env, napi_callback_info info) {
2757
2240
  size_t argc = 3;
@@ -2788,124 +2271,6 @@ static napi_value wrap_tx_query_buf_async(napi_env env, napi_callback_info info)
2788
2271
  return promise;
2789
2272
  }
2790
2273
 
2791
- /* txQuery(external, sql, params?): Array | ArrayBuffer */
2792
- static napi_value wrap_tx_query(napi_env env, napi_callback_info info) {
2793
- size_t argc = 3;
2794
- napi_value argv[3];
2795
- napi_get_cb_info(env, info, &argc, argv, NULL, NULL);
2796
-
2797
- StoolapTx* tx;
2798
- napi_get_value_external(env, argv[0], (void**)&tx);
2799
-
2800
- GET_SQL_STRING(env, argv[1], sql);
2801
-
2802
- StoolapRows* rows = NULL;
2803
- int32_t rc;
2804
-
2805
- if (argc > 2) {
2806
- napi_valuetype vt;
2807
- napi_typeof(env, argv[2], &vt);
2808
- if (vt == napi_undefined || vt == napi_null) {
2809
- rc = S.tx_query(tx, sql, &rows);
2810
- } else {
2811
- uint32_t len;
2812
- napi_get_array_length(env, argv[2], &len);
2813
- if (len == 0) {
2814
- rc = S.tx_query(tx, sql, &rows);
2815
- } else if (len <= MAX_STACK_PARAMS) {
2816
- StoolapValue vals[MAX_STACK_PARAMS];
2817
- char* tbufs[MAX_STACK_PARAMS * 2];
2818
- int tbuf_cnt = 0;
2819
- for (uint32_t i = 0; i < len; i++) {
2820
- napi_value elem;
2821
- napi_get_element(env, argv[2], i, &elem);
2822
- if (js_to_value(env, elem, &vals[i], tbufs, &tbuf_cnt) != 0) {
2823
- for (int k = 0; k < tbuf_cnt; k++) free(tbufs[k]);
2824
- THROW(env, "Failed to convert parameters");
2825
- }
2826
- }
2827
- rc = S.tx_query_p(tx, sql, vals, (int32_t)len, &rows);
2828
- for (int k = 0; k < tbuf_cnt; k++) free(tbufs[k]);
2829
- } else {
2830
- StoolapValue* vals; int32_t cnt;
2831
- char** tbufs; int tbuf_cnt;
2832
- if (convert_params(env, argv[2], &vals, &cnt, &tbufs, &tbuf_cnt) != 0)
2833
- THROW(env, "Failed to convert parameters");
2834
- rc = S.tx_query_p(tx, sql, vals, cnt, &rows);
2835
- free_params(vals, tbufs, tbuf_cnt);
2836
- }
2837
- }
2838
- } else {
2839
- rc = S.tx_query(tx, sql, &rows);
2840
- }
2841
-
2842
- if (rc != STOOLAP_OK) {
2843
- const char* msg = S.tx_errmsg(tx);
2844
- THROW(env, msg ? msg : "Transaction query error");
2845
- }
2846
-
2847
- return fetch_result(env, rows);
2848
- }
2849
-
2850
- /* txQueryOne(external, sql, params?): Object | null */
2851
- static napi_value wrap_tx_query_one(napi_env env, napi_callback_info info) {
2852
- size_t argc = 3;
2853
- napi_value argv[3];
2854
- napi_get_cb_info(env, info, &argc, argv, NULL, NULL);
2855
-
2856
- StoolapTx* tx;
2857
- napi_get_value_external(env, argv[0], (void**)&tx);
2858
-
2859
- GET_SQL_STRING(env, argv[1], sql);
2860
-
2861
- StoolapRows* rows = NULL;
2862
- int32_t rc;
2863
-
2864
- if (argc > 2) {
2865
- napi_valuetype vt;
2866
- napi_typeof(env, argv[2], &vt);
2867
- if (vt == napi_undefined || vt == napi_null) {
2868
- rc = S.tx_query(tx, sql, &rows);
2869
- } else {
2870
- uint32_t len;
2871
- napi_get_array_length(env, argv[2], &len);
2872
- if (len == 0) {
2873
- rc = S.tx_query(tx, sql, &rows);
2874
- } else if (len <= MAX_STACK_PARAMS) {
2875
- StoolapValue vals[MAX_STACK_PARAMS];
2876
- char* tbufs[MAX_STACK_PARAMS * 2];
2877
- int tbuf_cnt = 0;
2878
- for (uint32_t i = 0; i < len; i++) {
2879
- napi_value elem;
2880
- napi_get_element(env, argv[2], i, &elem);
2881
- if (js_to_value(env, elem, &vals[i], tbufs, &tbuf_cnt) != 0) {
2882
- for (int k = 0; k < tbuf_cnt; k++) free(tbufs[k]);
2883
- THROW(env, "Failed to convert parameters");
2884
- }
2885
- }
2886
- rc = S.tx_query_p(tx, sql, vals, (int32_t)len, &rows);
2887
- for (int k = 0; k < tbuf_cnt; k++) free(tbufs[k]);
2888
- } else {
2889
- StoolapValue* vals; int32_t cnt;
2890
- char** tbufs; int tbuf_cnt;
2891
- if (convert_params(env, argv[2], &vals, &cnt, &tbufs, &tbuf_cnt) != 0)
2892
- THROW(env, "Failed to convert parameters");
2893
- rc = S.tx_query_p(tx, sql, vals, cnt, &rows);
2894
- free_params(vals, tbufs, tbuf_cnt);
2895
- }
2896
- }
2897
- } else {
2898
- rc = S.tx_query(tx, sql, &rows);
2899
- }
2900
-
2901
- if (rc != STOOLAP_OK) {
2902
- const char* msg = S.tx_errmsg(tx);
2903
- THROW(env, msg ? msg : "Transaction query error");
2904
- }
2905
-
2906
- return fetch_one(env, rows);
2907
- }
2908
-
2909
2274
  /* txCommit(external): void */
2910
2275
  static napi_value wrap_tx_commit(napi_env env, napi_callback_info info) {
2911
2276
  size_t argc = 1;
@@ -2916,7 +2281,10 @@ static napi_value wrap_tx_commit(napi_env env, napi_callback_info info) {
2916
2281
  napi_get_value_external(env, argv[0], (void**)&tx);
2917
2282
 
2918
2283
  int32_t rc = S.tx_commit(tx);
2919
- if (rc != STOOLAP_OK) THROW(env, "Failed to commit transaction");
2284
+ if (rc != STOOLAP_OK) {
2285
+ const char* msg = S.errmsg(NULL);
2286
+ THROW(env, msg && msg[0] ? msg : "Failed to commit transaction");
2287
+ }
2920
2288
 
2921
2289
  napi_value undef;
2922
2290
  napi_get_undefined(env, &undef);
@@ -2953,7 +2321,10 @@ static napi_value wrap_tx_rollback(napi_env env, napi_callback_info info) {
2953
2321
  napi_get_value_external(env, argv[0], (void**)&tx);
2954
2322
 
2955
2323
  int32_t rc = S.tx_rollback(tx);
2956
- if (rc != STOOLAP_OK) THROW(env, "Failed to rollback transaction");
2324
+ if (rc != STOOLAP_OK) {
2325
+ const char* msg = S.errmsg(NULL);
2326
+ THROW(env, msg && msg[0] ? msg : "Failed to rollback transaction");
2327
+ }
2957
2328
 
2958
2329
  napi_value undef;
2959
2330
  napi_get_undefined(env, &undef);
@@ -3002,14 +2373,11 @@ static napi_value wrap_tx_exec_batch(napi_env env, napi_callback_info info) {
3002
2373
 
3003
2374
  /* Prepare statement once if tx_stmt_exec is available */
3004
2375
  StoolapStmt* stmt = NULL;
3005
- int use_stmt = (S.tx_stmt_exec != NULL);
3006
2376
  int32_t rc;
3007
- if (use_stmt) {
3008
- rc = S.prepare(db, sql, &stmt);
3009
- if (rc != STOOLAP_OK) {
3010
- const char* msg = S.errmsg(db);
3011
- THROW(env, msg ? msg : "Prepare error");
3012
- }
2377
+ rc = S.prepare(db, sql, &stmt);
2378
+ if (rc != STOOLAP_OK) {
2379
+ const char* msg = S.errmsg(db);
2380
+ THROW(env, msg ? msg : "Prepare error");
3013
2381
  }
3014
2382
 
3015
2383
  /* Get param count from first element — pre-allocate ONCE */
@@ -3019,7 +2387,8 @@ static napi_value wrap_tx_exec_batch(napi_env env, napi_callback_info info) {
3019
2387
  napi_get_array_length(env, first_arr, &param_count);
3020
2388
 
3021
2389
  StoolapValue* vals = calloc(param_count, sizeof(StoolapValue));
3022
- char** tbufs = calloc(param_count * 2, sizeof(char*));
2390
+ TextArena ta;
2391
+ ARENA_INIT(ta);
3023
2392
 
3024
2393
  int64_t total_affected = 0;
3025
2394
 
@@ -3028,31 +2397,25 @@ static napi_value wrap_tx_exec_batch(napi_env env, napi_callback_info info) {
3028
2397
  napi_get_element(env, argv[3], i, &params_arr);
3029
2398
 
3030
2399
  /* Convert params into pre-allocated buffers */
3031
- int tbuf_cnt = 0;
3032
2400
  for (uint32_t j = 0; j < param_count; j++) {
3033
2401
  napi_value elem;
3034
2402
  napi_get_element(env, params_arr, j, &elem);
3035
- if (js_to_value(env, elem, &vals[j], tbufs, &tbuf_cnt) != 0) {
3036
- for (int k = 0; k < tbuf_cnt; k++) free(tbufs[k]);
3037
- free(vals); free(tbufs);
2403
+ if (js_to_value(env, elem, &vals[j], &ta) != 0) {
2404
+ arena_reset(&ta);
2405
+ free(vals);
3038
2406
  if (stmt) S.stmt_finalize(stmt);
3039
2407
  THROW(env, "Failed to convert parameters");
3040
2408
  }
3041
2409
  }
3042
2410
 
3043
2411
  int64_t affected = 0;
3044
- if (use_stmt) {
3045
- rc = S.tx_stmt_exec(tx, stmt, vals, (int32_t)param_count, &affected);
3046
- } else {
3047
- rc = S.tx_exec_p(tx, sql, vals, (int32_t)param_count, &affected);
3048
- }
2412
+ rc = S.tx_stmt_exec(tx, stmt, vals, (int32_t)param_count, &affected);
3049
2413
 
3050
- /* Free only text buffers, keep vals/tbufs arrays for reuse */
3051
- for (int k = 0; k < tbuf_cnt; k++) { free(tbufs[k]); tbufs[k] = NULL; }
2414
+ arena_reset(&ta);
3052
2415
 
3053
2416
  if (rc != STOOLAP_OK) {
3054
2417
  ERR_SAVE(S.tx_errmsg(tx), "Transaction batch exec error");
3055
- free(vals); free(tbufs);
2418
+ free(vals);
3056
2419
  if (stmt) S.stmt_finalize(stmt);
3057
2420
  THROW(env, _eb);
3058
2421
  }
@@ -3061,70 +2424,10 @@ static napi_value wrap_tx_exec_batch(napi_env env, napi_callback_info info) {
3061
2424
  }
3062
2425
 
3063
2426
  free(vals);
3064
- free(tbufs);
3065
2427
  if (stmt) S.stmt_finalize(stmt);
3066
2428
  return make_changes(env, total_affected);
3067
2429
  }
3068
2430
 
3069
- /* stmtExecBatch(external, paramsArrayOfArrays): { changes }
3070
- * Batch exec via prepared statement — pre-allocated buffers, loops in C. */
3071
- static napi_value wrap_stmt_exec_batch(napi_env env, napi_callback_info info) {
3072
- size_t argc = 2;
3073
- napi_value argv[2];
3074
- napi_get_cb_info(env, info, &argc, argv, NULL, NULL);
3075
-
3076
- GET_STMT(env, argv[0], cs);
3077
- StoolapStmt* stmt = cs->stmt;
3078
-
3079
- uint32_t batch_len;
3080
- napi_get_array_length(env, argv[1], &batch_len);
3081
- if (batch_len == 0) return make_changes(env, 0);
3082
-
3083
- /* Get param count from first element — pre-allocate ONCE */
3084
- napi_value first_arr;
3085
- napi_get_element(env, argv[1], 0, &first_arr);
3086
- uint32_t param_count;
3087
- napi_get_array_length(env, first_arr, &param_count);
3088
-
3089
- StoolapValue* vals = calloc(param_count, sizeof(StoolapValue));
3090
- char** tbufs = calloc(param_count * 2, sizeof(char*));
3091
-
3092
- int64_t total_affected = 0;
3093
-
3094
- for (uint32_t i = 0; i < batch_len; i++) {
3095
- napi_value params_arr;
3096
- napi_get_element(env, argv[1], i, &params_arr);
3097
-
3098
- int tbuf_cnt = 0;
3099
- for (uint32_t j = 0; j < param_count; j++) {
3100
- napi_value elem;
3101
- napi_get_element(env, params_arr, j, &elem);
3102
- if (js_to_value(env, elem, &vals[j], tbufs, &tbuf_cnt) != 0) {
3103
- for (int k = 0; k < tbuf_cnt; k++) free(tbufs[k]);
3104
- free(vals); free(tbufs);
3105
- THROW(env, "Failed to convert parameters");
3106
- }
3107
- }
3108
-
3109
- int64_t affected = 0;
3110
- int32_t rc = S.stmt_exec(stmt, vals, (int32_t)param_count, &affected);
3111
-
3112
- for (int k = 0; k < tbuf_cnt; k++) { free(tbufs[k]); tbufs[k] = NULL; }
3113
-
3114
- if (rc != STOOLAP_OK) {
3115
- free(vals); free(tbufs);
3116
- const char* msg = S.stmt_errmsg(stmt);
3117
- THROW(env, msg ? msg : "Statement batch exec error");
3118
- }
3119
-
3120
- total_affected += affected;
3121
- }
3122
-
3123
- free(vals);
3124
- free(tbufs);
3125
- return make_changes(env, total_affected);
3126
- }
3127
-
3128
2431
  /* ---- Binary param buffer protocol ---- */
3129
2432
  /* JS encodes params to a Buffer, C reads directly — eliminates per-param N-API calls.
3130
2433
  * Format per param: [uint8 tag][value bytes]
@@ -3136,6 +2439,8 @@ static napi_value wrap_stmt_exec_batch(napi_env env, napi_callback_info info) {
3136
2439
  #define PBUF_TEXT 3
3137
2440
  #define PBUF_BOOL 4
3138
2441
  #define PBUF_I64 5
2442
+ #define PBUF_TS 6 /* i64 nanoseconds since the epoch */
2443
+ #define PBUF_BLOB 7 /* u32 len + bytes */
3139
2444
 
3140
2445
  static int decode_params_buf(const uint8_t* buf, size_t buf_len,
3141
2446
  StoolapValue* vals, int max_params,
@@ -3161,11 +2466,10 @@ static int decode_params_buf(const uint8_t* buf, size_t buf_len,
3161
2466
  case PBUF_F64: {
3162
2467
  if (ptr + 8 > end) return -1;
3163
2468
  double d; memcpy(&d, ptr, 8); ptr += 8;
3164
- int64_t i64 = (int64_t)d;
3165
- if ((double)i64 == d && d >= -9007199254740992.0 && d <= 9007199254740992.0) {
2469
+ if (is_safe_integer(d)) {
3166
2470
  vals[count].value_type = T_INTEGER;
3167
2471
  vals[count]._padding = 0;
3168
- vals[count].v.integer = i64;
2472
+ vals[count].v.integer = (int64_t)d;
3169
2473
  } else {
3170
2474
  vals[count].value_type = T_FLOAT;
3171
2475
  vals[count]._padding = 0;
@@ -3198,6 +2502,25 @@ static int decode_params_buf(const uint8_t* buf, size_t buf_len,
3198
2502
  vals[count].v.integer = i64;
3199
2503
  break;
3200
2504
  }
2505
+ case PBUF_TS: {
2506
+ if (ptr + 8 > end) return -1;
2507
+ int64_t nanos; memcpy(&nanos, ptr, 8); ptr += 8;
2508
+ vals[count].value_type = T_TIMESTAMP;
2509
+ vals[count]._padding = 0;
2510
+ vals[count].v.timestamp_nanos = nanos;
2511
+ break;
2512
+ }
2513
+ case PBUF_BLOB: {
2514
+ if (ptr + 4 > end) return -1;
2515
+ uint32_t len; memcpy(&len, ptr, 4); ptr += 4;
2516
+ if (ptr + len > end) return -1;
2517
+ vals[count].value_type = T_BLOB;
2518
+ vals[count]._padding = 0;
2519
+ vals[count].v.blob.ptr = ptr;
2520
+ vals[count].v.blob.len = (int64_t)len;
2521
+ ptr += len;
2522
+ break;
2523
+ }
3201
2524
  default: return -1;
3202
2525
  }
3203
2526
  count++;
@@ -3219,39 +2542,40 @@ static napi_value wrap_db_exec_batch_buf(napi_env env, napi_callback_info info)
3219
2542
  napi_get_buffer_info(env, argv[2], &buf_data, &buf_len);
3220
2543
  const uint8_t* ptr = (const uint8_t*)buf_data;
3221
2544
  const uint8_t* end = ptr + buf_len;
3222
- if (ptr + 5 > end) THROW(env, "Batch buffer too short");
2545
+ if (ptr + 6 > end) THROW(env, "Batch buffer too short");
3223
2546
  uint32_t row_count; memcpy(&row_count, ptr, 4); ptr += 4;
3224
- uint8_t ppr = *ptr++;
2547
+ uint16_t ppr; memcpy(&ppr, ptr, 2); ptr += 2;
3225
2548
  if (row_count == 0) return make_changes(env, 0);
2549
+ StoolapValue stack_vals[MAX_STACK_PARAMS];
2550
+ StoolapValue* vals = ppr <= MAX_STACK_PARAMS ? stack_vals : malloc(ppr * sizeof(StoolapValue));
2551
+ if (!vals) THROW(env, "Out of memory");
2552
+ #define BATCH_VALS_RELEASE() do { if (vals != stack_vals) free(vals); } while (0)
3226
2553
  StoolapTx* tx = NULL;
3227
2554
  int32_t rc = S.begin(db, &tx);
3228
- if (rc != STOOLAP_OK) { const char* msg = S.errmsg(db); THROW(env, msg ? msg : "Begin error"); }
2555
+ if (rc != STOOLAP_OK) { BATCH_VALS_RELEASE(); const char* msg = S.errmsg(db); THROW(env, msg ? msg : "Begin error"); }
3229
2556
  StoolapStmt* stmt = NULL;
3230
- int use_stmt = (S.tx_stmt_exec != NULL);
3231
- if (use_stmt) {
3232
- rc = S.prepare(db, sql, &stmt);
3233
- if (rc != STOOLAP_OK) { ERR_SAVE(S.errmsg(db), "Prepare error"); S.tx_rollback(tx); THROW(env, _eb); }
3234
- }
2557
+ rc = S.prepare(db, sql, &stmt);
2558
+ if (rc != STOOLAP_OK) { ERR_SAVE(S.errmsg(db), "Prepare error"); S.tx_rollback(tx); BATCH_VALS_RELEASE(); THROW(env, _eb); }
3235
2559
  int64_t total = 0;
3236
2560
  for (uint32_t i = 0; i < row_count; i++) {
3237
- StoolapValue vals[MAX_STACK_PARAMS];
3238
2561
  size_t consumed = 0;
3239
2562
  int decoded = decode_params_buf(ptr, (size_t)(end - ptr), vals, ppr, &consumed);
3240
- if (decoded != ppr) { if (stmt) S.stmt_finalize(stmt); S.tx_rollback(tx); THROW(env, "Batch param decode error"); }
2563
+ if (decoded != ppr) { S.stmt_finalize(stmt); S.tx_rollback(tx); BATCH_VALS_RELEASE(); THROW(env, "Batch param decode error"); }
3241
2564
  ptr += consumed;
3242
2565
  int64_t affected = 0;
3243
- if (use_stmt) {
3244
- rc = S.tx_stmt_exec(tx, stmt, vals, decoded, &affected);
3245
- } else {
3246
- rc = S.tx_exec_p(tx, sql, vals, decoded, &affected);
3247
- }
3248
- if (rc != STOOLAP_OK) { ERR_SAVE(S.tx_errmsg(tx), "Batch exec error"); if (stmt) S.stmt_finalize(stmt); S.tx_rollback(tx); THROW(env, _eb); }
2566
+ rc = S.tx_stmt_exec(tx, stmt, vals, decoded, &affected);
2567
+ if (rc != STOOLAP_OK) { ERR_SAVE(S.tx_errmsg(tx), "Batch exec error"); S.stmt_finalize(stmt); S.tx_rollback(tx); BATCH_VALS_RELEASE(); THROW(env, _eb); }
3249
2568
  total += affected;
3250
2569
  }
3251
- if (stmt) S.stmt_finalize(stmt);
2570
+ S.stmt_finalize(stmt);
2571
+ BATCH_VALS_RELEASE();
3252
2572
  rc = S.tx_commit(tx);
3253
- if (rc != STOOLAP_OK) { THROW(env, "Commit error"); }
2573
+ if (rc != STOOLAP_OK) {
2574
+ const char* msg = S.errmsg(NULL);
2575
+ THROW(env, msg && msg[0] ? msg : "Commit error");
2576
+ }
3254
2577
  return make_changes(env, total);
2578
+ #undef BATCH_VALS_RELEASE
3255
2579
  }
3256
2580
 
3257
2581
  /* stmtExecBatchBuf(external, buffer): { changes }
@@ -3269,29 +2593,256 @@ static napi_value wrap_stmt_exec_batch_buf(napi_env env, napi_callback_info info
3269
2593
  const uint8_t* ptr = (const uint8_t*)buf_data;
3270
2594
  const uint8_t* end = ptr + buf_len;
3271
2595
 
3272
- if (ptr + 5 > end) THROW(env, "Batch buffer too short");
2596
+ if (ptr + 6 > end) THROW(env, "Batch buffer too short");
3273
2597
  uint32_t row_count; memcpy(&row_count, ptr, 4); ptr += 4;
3274
- uint8_t ppr = *ptr++;
2598
+ uint16_t ppr; memcpy(&ppr, ptr, 2); ptr += 2;
3275
2599
  if (row_count == 0) return make_changes(env, 0);
2600
+ StoolapValue stack_vals[MAX_STACK_PARAMS];
2601
+ StoolapValue* vals = ppr <= MAX_STACK_PARAMS ? stack_vals : malloc(ppr * sizeof(StoolapValue));
2602
+ if (!vals) THROW(env, "Out of memory");
3276
2603
 
3277
2604
  int64_t total = 0;
3278
2605
  for (uint32_t i = 0; i < row_count; i++) {
3279
- StoolapValue vals[MAX_STACK_PARAMS];
3280
2606
  size_t consumed = 0;
3281
2607
  int decoded = decode_params_buf(ptr, (size_t)(end - ptr), vals, ppr, &consumed);
3282
- if (decoded != ppr) THROW(env, "Batch param decode error");
2608
+ if (decoded != ppr) { if (vals != stack_vals) free(vals); THROW(env, "Batch param decode error"); }
3283
2609
  ptr += consumed;
3284
2610
  int64_t affected = 0;
3285
2611
  int32_t rc = S.stmt_exec(stmt, vals, decoded, &affected);
3286
2612
  if (rc != STOOLAP_OK) {
3287
- const char* msg = S.stmt_errmsg(stmt);
3288
- THROW(env, msg ? msg : "Statement batch exec error");
2613
+ ERR_SAVE(S.stmt_errmsg(stmt), "Statement batch exec error");
2614
+ if (vals != stack_vals) free(vals);
2615
+ THROW(env, _eb);
3289
2616
  }
3290
2617
  total += affected;
3291
2618
  }
2619
+ if (vals != stack_vals) free(vals);
3292
2620
  return make_changes(env, total);
3293
2621
  }
3294
2622
 
2623
+ /* ---- Row cursor: a StoolapRows handle read in packed chunks ---- */
2624
+ /* The external's payload: rows is NULL once the cursor was closed, so the
2625
+ * finalizer does not close it a second time. */
2626
+ typedef struct {
2627
+ StoolapRows* rows;
2628
+ } RowsHandle;
2629
+
2630
+ static void finalize_rows_handle(napi_env env, void* data, void* hint) {
2631
+ (void)env; (void)hint;
2632
+ RowsHandle* h = (RowsHandle*)data;
2633
+ if (h->rows) S.rows_close(h->rows);
2634
+ free(h);
2635
+ }
2636
+
2637
+ static napi_value rows_external(napi_env env, StoolapRows* rows) {
2638
+ RowsHandle* h = malloc(sizeof(RowsHandle));
2639
+ if (!h) { S.rows_close(rows); THROW(env, "Out of memory"); }
2640
+ h->rows = rows;
2641
+ napi_value ext;
2642
+ napi_create_external(env, h, finalize_rows_handle, NULL, &ext);
2643
+ return ext;
2644
+ }
2645
+
2646
+ /* rowsOpen(db, tx?, sql, params?): external */
2647
+ static napi_value fn_rows_open(napi_env env, napi_callback_info info) {
2648
+ size_t argc = 4;
2649
+ napi_value argv[4];
2650
+ napi_get_cb_info(env, info, &argc, argv, NULL, NULL);
2651
+ StoolapDB* db;
2652
+ napi_get_value_external(env, argv[0], (void**)&db);
2653
+ StoolapTx* tx = tx_arg(env, argv[1]);
2654
+ GET_SQL_STRING(env, argv[2], sql);
2655
+ ParamBind pb;
2656
+ if (bind_positional(env, argv[3], &pb) != 0) return NULL;
2657
+ StoolapRows* rows = NULL;
2658
+ int32_t rc;
2659
+ if (tx) {
2660
+ rc = pb.count > 0 ? S.tx_query_p(tx, sql, pb.vals, pb.count, &rows) : S.tx_query(tx, sql, &rows);
2661
+ } else {
2662
+ rc = pb.count > 0 ? S.query_p(db, sql, pb.vals, pb.count, &rows) : S.query(db, sql, &rows);
2663
+ }
2664
+ bind_release(&pb);
2665
+ if (rc != STOOLAP_OK) {
2666
+ const char* msg = tx ? S.tx_errmsg(tx) : S.errmsg(db);
2667
+ THROW(env, msg ? msg : "Query error");
2668
+ }
2669
+ return rows_external(env, rows);
2670
+ }
2671
+
2672
+ /* rowsOpenNamed(db, tx?, sql, names, values): external */
2673
+ static napi_value fn_rows_open_named(napi_env env, napi_callback_info info) {
2674
+ size_t argc = 5;
2675
+ napi_value argv[5];
2676
+ napi_get_cb_info(env, info, &argc, argv, NULL, NULL);
2677
+ StoolapDB* db;
2678
+ napi_get_value_external(env, argv[0], (void**)&db);
2679
+ StoolapTx* tx = tx_arg(env, argv[1]);
2680
+ GET_SQL_STRING(env, argv[2], sql);
2681
+ NamedBind nb;
2682
+ if (bind_named(env, argv[3], argv[4], &nb) != 0) return NULL;
2683
+ StoolapRows* rows = NULL;
2684
+ int32_t rc = tx
2685
+ ? S.tx_query_named(tx, sql, nb.np, nb.count, &rows)
2686
+ : S.query_named(db, sql, nb.np, nb.count, &rows);
2687
+ named_release(&nb);
2688
+ if (rc != STOOLAP_OK) {
2689
+ const char* msg = tx ? S.tx_errmsg(tx) : S.errmsg(db);
2690
+ THROW(env, msg ? msg : "Query error");
2691
+ }
2692
+ return rows_external(env, rows);
2693
+ }
2694
+
2695
+ /* rowsOpenStmt(external, params?): external */
2696
+ static napi_value wrap_rows_open_stmt(napi_env env, napi_callback_info info) {
2697
+ size_t argc = 2;
2698
+ napi_value argv[2];
2699
+ napi_get_cb_info(env, info, &argc, argv, NULL, NULL);
2700
+ GET_STMT(env, argv[0], cs);
2701
+ ParamBind pb;
2702
+ if (bind_positional(env, argv[1], &pb) != 0) return NULL;
2703
+ StoolapRows* rows = NULL;
2704
+ int32_t rc = S.stmt_query(cs->stmt, pb.vals, pb.count, &rows);
2705
+ bind_release(&pb);
2706
+ if (rc != STOOLAP_OK) {
2707
+ const char* msg = S.stmt_errmsg(cs->stmt);
2708
+ THROW(env, msg ? msg : "Statement query error");
2709
+ }
2710
+ return rows_external(env, rows);
2711
+ }
2712
+
2713
+ /* rowsChunk(external, maxRows, buffer?): ArrayBuffer | null
2714
+ * Packs up to maxRows rows into `buffer` when it is large enough, else into
2715
+ * a new ArrayBuffer sized to grow; the caller keeps the returned buffer for
2716
+ * the next call. null once the result is exhausted. */
2717
+ static napi_value fn_rows_chunk(napi_env env, napi_callback_info info) {
2718
+ size_t argc = 3;
2719
+ napi_value argv[3];
2720
+ napi_get_cb_info(env, info, &argc, argv, NULL, NULL);
2721
+ RowsHandle* h;
2722
+ napi_get_value_external(env, argv[0], (void**)&h);
2723
+ StoolapRows* rows = h->rows;
2724
+ int32_t max_rows;
2725
+ napi_get_value_int32(env, argv[1], &max_rows);
2726
+ const uint8_t* buf = NULL;
2727
+ int64_t len = 0;
2728
+ int32_t n = 0;
2729
+ if (rows && S.rows_fetch_chunk(rows, max_rows, &buf, &len, &n) != STOOLAP_OK) {
2730
+ const char* msg = S.rows_errmsg(rows);
2731
+ THROW(env, msg ? msg : "Row fetch error");
2732
+ }
2733
+ if (n == 0) {
2734
+ napi_value null_val;
2735
+ napi_get_null(env, &null_val);
2736
+ return null_val;
2737
+ }
2738
+ napi_value ab = argv[2];
2739
+ void* data = NULL;
2740
+ size_t cap = 0;
2741
+ bool is_ab = false;
2742
+ napi_is_arraybuffer(env, ab, &is_ab);
2743
+ if (is_ab) napi_get_arraybuffer_info(env, ab, &data, &cap);
2744
+ if (!is_ab || cap < (size_t)len) {
2745
+ cap = (size_t)len < 16384 ? 16384 : (size_t)len * 2;
2746
+ napi_create_arraybuffer(env, cap, &data, &ab);
2747
+ }
2748
+ memcpy(data, buf, (size_t)len);
2749
+ return ab;
2750
+ }
2751
+
2752
+ typedef struct {
2753
+ AsyncBase base;
2754
+ RowsHandle* h;
2755
+ int32_t max_rows;
2756
+ uint8_t* buf;
2757
+ int64_t len;
2758
+ } AsyncChunkTask;
2759
+
2760
+ static void execute_rows_chunk_async(napi_env env, void* data) {
2761
+ (void)env;
2762
+ AsyncChunkTask* task = (AsyncChunkTask*)data;
2763
+ const uint8_t* buf = NULL;
2764
+ int64_t len = 0;
2765
+ int32_t n = 0;
2766
+ StoolapRows* rows = task->h->rows;
2767
+ if (!rows) return;
2768
+ if (S.rows_fetch_chunk(rows, task->max_rows, &buf, &len, &n) != STOOLAP_OK) {
2769
+ task->base.error = dup_cstr(S.rows_errmsg(rows), "Row fetch error");
2770
+ return;
2771
+ }
2772
+ if (n == 0) return;
2773
+ task->buf = malloc((size_t)len);
2774
+ if (!task->buf) { task->base.error = dup_cstr(NULL, "Out of memory"); return; }
2775
+ memcpy(task->buf, buf, (size_t)len);
2776
+ task->len = len;
2777
+ }
2778
+
2779
+ static void finalize_malloc_buffer(napi_env env, void* data, void* hint) {
2780
+ (void)env; (void)hint;
2781
+ free(data);
2782
+ }
2783
+
2784
+ static void complete_rows_chunk_async(napi_env env, napi_status status, void* data) {
2785
+ AsyncChunkTask* task = (AsyncChunkTask*)data;
2786
+ if (status != napi_ok && !task->base.error) {
2787
+ task->base.error = dup_cstr(NULL, "Row fetch cancelled");
2788
+ }
2789
+ if (task->base.error) {
2790
+ free(task->buf);
2791
+ reject_async_error(env, &task->base, "Row fetch error");
2792
+ } else if (!task->buf) {
2793
+ napi_value null_val;
2794
+ napi_get_null(env, &null_val);
2795
+ napi_resolve_deferred(env, task->base.deferred, null_val);
2796
+ } else {
2797
+ napi_value ab;
2798
+ if (napi_create_external_arraybuffer(env, task->buf, (size_t)task->len,
2799
+ finalize_malloc_buffer, NULL, &ab) != napi_ok) {
2800
+ void* copy;
2801
+ napi_create_arraybuffer(env, (size_t)task->len, &copy, &ab);
2802
+ memcpy(copy, task->buf, (size_t)task->len);
2803
+ free(task->buf);
2804
+ }
2805
+ napi_resolve_deferred(env, task->base.deferred, ab);
2806
+ }
2807
+ napi_delete_async_work(env, task->base.work);
2808
+ free(task->base.error);
2809
+ free(task);
2810
+ }
2811
+
2812
+ /* rowsChunkAsync(external, maxRows): Promise<ArrayBuffer | null> */
2813
+ static napi_value fn_rows_chunk_async(napi_env env, napi_callback_info info) {
2814
+ size_t argc = 2;
2815
+ napi_value argv[2];
2816
+ napi_get_cb_info(env, info, &argc, argv, NULL, NULL);
2817
+ AsyncChunkTask* task = calloc(1, sizeof(AsyncChunkTask));
2818
+ if (!task) THROW(env, "Out of memory");
2819
+ napi_get_value_external(env, argv[0], (void**)&task->h);
2820
+ napi_get_value_int32(env, argv[1], &task->max_rows);
2821
+ napi_value promise = queue_async_work(
2822
+ env, task, &task->base, "rowsChunkAsync", execute_rows_chunk_async, complete_rows_chunk_async);
2823
+ if (!promise) {
2824
+ free(task);
2825
+ THROW(env, "Failed to queue rowsChunkAsync");
2826
+ }
2827
+ return promise;
2828
+ }
2829
+
2830
+ /* rowsClose(external): closes the rows handle; later calls on it are no-ops */
2831
+ static napi_value fn_rows_cursor_close(napi_env env, napi_callback_info info) {
2832
+ size_t argc = 1;
2833
+ napi_value argv[1];
2834
+ napi_get_cb_info(env, info, &argc, argv, NULL, NULL);
2835
+ RowsHandle* h;
2836
+ napi_get_value_external(env, argv[0], (void**)&h);
2837
+ if (h->rows) {
2838
+ S.rows_close(h->rows);
2839
+ h->rows = NULL;
2840
+ }
2841
+ napi_value undef;
2842
+ napi_get_undefined(env, &undef);
2843
+ return undef;
2844
+ }
2845
+
3295
2846
  /* ============================================================
3296
2847
  * Module initialization
3297
2848
  * ============================================================ */
@@ -3304,6 +2855,7 @@ static napi_value wrap_stmt_exec_batch_buf(napi_env env, napi_callback_info info
3304
2855
 
3305
2856
  static napi_value Init(napi_env env, napi_value exports) {
3306
2857
  EXPORT_FN(env, exports, "loadLibrary", fn_load_library);
2858
+ EXPORT_FN(env, exports, "setRowFactoryMaker", fn_set_row_factory_maker);
3307
2859
  EXPORT_FN(env, exports, "dbOpen", fn_db_open);
3308
2860
  EXPORT_FN(env, exports, "dbOpenAsync", fn_db_open_async);
3309
2861
  EXPORT_FN(env, exports, "dbClone", fn_db_clone);
@@ -3311,24 +2863,20 @@ static napi_value Init(napi_env env, napi_value exports) {
3311
2863
  EXPORT_FN(env, exports, "dbCloseAsync", fn_db_close_async);
3312
2864
  EXPORT_FN(env, exports, "dbExec", fn_db_exec);
3313
2865
  EXPORT_FN(env, exports, "dbExecAsync", fn_db_exec_async);
2866
+ EXPORT_FN(env, exports, "dbExecNamed", fn_db_exec_named);
2867
+ EXPORT_FN(env, exports, "dbQueryAll", fn_db_query_all);
2868
+ EXPORT_FN(env, exports, "dbQueryNamedAll", fn_db_query_named_all);
3314
2869
  EXPORT_FN(env, exports, "dbExecSimple", fn_db_exec_simple);
3315
2870
  EXPORT_FN(env, exports, "dbExecSimpleAsync", fn_db_exec_simple_async);
3316
- EXPORT_FN(env, exports, "dbQueryBuf", fn_db_query_buf);
3317
2871
  EXPORT_FN(env, exports, "dbQueryBufAsync", fn_db_query_buf_async);
3318
- EXPORT_FN(env, exports, "dbQuery", fn_db_query);
3319
- EXPORT_FN(env, exports, "dbQueryOne", fn_db_query_one);
3320
2872
  EXPORT_FN(env, exports, "dbPrepare", fn_db_prepare);
3321
2873
  EXPORT_FN(env, exports, "stmtExec", wrap_stmt_exec);
3322
2874
  EXPORT_FN(env, exports, "stmtExecAsync", wrap_stmt_exec_async);
3323
- EXPORT_FN(env, exports, "stmtQueryBuf", wrap_stmt_query_buf);
2875
+ EXPORT_FN(env, exports, "stmtQueryAll", wrap_stmt_query_all);
3324
2876
  EXPORT_FN(env, exports, "stmtQueryBufAsync", wrap_stmt_query_buf_async);
3325
- EXPORT_FN(env, exports, "stmtQuery", wrap_stmt_query);
3326
- EXPORT_FN(env, exports, "stmtQueryOne", wrap_stmt_query_one);
3327
- EXPORT_FN(env, exports, "stmtSql", wrap_stmt_sql);
3328
2877
  EXPORT_FN(env, exports, "stmtExecNums", wrap_stmt_exec_nums);
3329
2878
  EXPORT_FN(env, exports, "stmtQueryNums", wrap_stmt_query_nums);
3330
2879
  EXPORT_FN(env, exports, "stmtQueryOneNums", wrap_stmt_query_one_nums);
3331
- EXPORT_FN(env, exports, "stmtExecBatch", wrap_stmt_exec_batch);
3332
2880
  EXPORT_FN(env, exports, "stmtExecBatchBuf", wrap_stmt_exec_batch_buf);
3333
2881
  EXPORT_FN(env, exports, "stmtFinalize", wrap_stmt_finalize);
3334
2882
  EXPORT_FN(env, exports, "dbExecBatchBuf", wrap_db_exec_batch_buf);
@@ -3336,15 +2884,19 @@ static napi_value Init(napi_env env, napi_value exports) {
3336
2884
  EXPORT_FN(env, exports, "txBeginAsync", fn_tx_begin_async);
3337
2885
  EXPORT_FN(env, exports, "txExec", wrap_tx_exec);
3338
2886
  EXPORT_FN(env, exports, "txExecAsync", wrap_tx_exec_async);
2887
+ EXPORT_FN(env, exports, "txExecNamed", wrap_tx_exec_named);
3339
2888
  EXPORT_FN(env, exports, "txExecBatch", wrap_tx_exec_batch);
3340
- EXPORT_FN(env, exports, "txQueryBuf", wrap_tx_query_buf);
3341
2889
  EXPORT_FN(env, exports, "txQueryBufAsync", wrap_tx_query_buf_async);
3342
- EXPORT_FN(env, exports, "txQuery", wrap_tx_query);
3343
- EXPORT_FN(env, exports, "txQueryOne", wrap_tx_query_one);
3344
2890
  EXPORT_FN(env, exports, "txCommit", wrap_tx_commit);
3345
2891
  EXPORT_FN(env, exports, "txCommitAsync", wrap_tx_commit_async);
3346
2892
  EXPORT_FN(env, exports, "txRollback", wrap_tx_rollback);
3347
2893
  EXPORT_FN(env, exports, "txRollbackAsync", wrap_tx_rollback_async);
2894
+ EXPORT_FN(env, exports, "rowsOpen", fn_rows_open);
2895
+ EXPORT_FN(env, exports, "rowsOpenNamed", fn_rows_open_named);
2896
+ EXPORT_FN(env, exports, "rowsOpenStmt", wrap_rows_open_stmt);
2897
+ EXPORT_FN(env, exports, "rowsChunk", fn_rows_chunk);
2898
+ EXPORT_FN(env, exports, "rowsChunkAsync", fn_rows_chunk_async);
2899
+ EXPORT_FN(env, exports, "rowsClose", fn_rows_cursor_close);
3348
2900
  return exports;
3349
2901
  }
3350
2902