@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/README.md +33 -6
- package/index.d.ts +38 -3
- package/index.js +3 -1
- package/lib/database.js +88 -63
- package/lib/ffi.js +13 -1
- package/lib/iterator.js +111 -0
- package/lib/statement.js +155 -107
- package/lib/transaction.js +95 -64
- package/package.json +6 -6
- package/src/stoolap.c +993 -1441
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
|
-
|
|
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
|
-
/*
|
|
179
|
-
|
|
180
|
-
|
|
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
|
-
|
|
205
|
-
|
|
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
|
-
|
|
232
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
403
|
-
|
|
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
|
|
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
|
|
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 (
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
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
|
-
|
|
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
|
-
*
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
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
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
1084
|
+
typedef struct {
|
|
1085
|
+
napi_async_work work;
|
|
1086
|
+
napi_deferred deferred;
|
|
1087
|
+
char* error;
|
|
1088
|
+
} AsyncBase;
|
|
825
1089
|
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
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
|
-
|
|
839
|
-
|
|
1096
|
+
typedef struct {
|
|
1097
|
+
AsyncBase base;
|
|
1098
|
+
StoolapDB* db;
|
|
1099
|
+
} AsyncDbCloseTask;
|
|
840
1100
|
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
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
|
-
|
|
864
|
-
|
|
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
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
1120
|
+
typedef enum {
|
|
1121
|
+
ASYNC_QUERY_DB,
|
|
1122
|
+
ASYNC_QUERY_TX,
|
|
1123
|
+
ASYNC_QUERY_STMT,
|
|
1124
|
+
} AsyncQueryKind;
|
|
871
1125
|
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
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
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
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
|
-
/*
|
|
1820
|
-
static napi_value
|
|
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
|
-
|
|
1826
|
-
|
|
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
|
-
|
|
1831
|
-
|
|
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
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
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
|
-
/*
|
|
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
|
-
|
|
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,
|
|
2150
|
-
|
|
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
|
-
/*
|
|
2161
|
-
static napi_value
|
|
2162
|
-
size_t argc =
|
|
2163
|
-
napi_value argv[
|
|
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
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
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
|
-
|
|
2441
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2496
|
-
int32_t rc = S.
|
|
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
|
-
|
|
2521
|
-
int32_t rc = S.
|
|
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
|
-
|
|
2616
|
-
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
|
|
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)
|
|
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)
|
|
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
|
-
|
|
3008
|
-
|
|
3009
|
-
|
|
3010
|
-
|
|
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, ¶m_count);
|
|
3020
2388
|
|
|
3021
2389
|
StoolapValue* vals = calloc(param_count, sizeof(StoolapValue));
|
|
3022
|
-
|
|
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, ¶ms_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],
|
|
3036
|
-
|
|
3037
|
-
free(vals);
|
|
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
|
-
|
|
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
|
-
|
|
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);
|
|
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, ¶m_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, ¶ms_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
|
-
|
|
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 =
|
|
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 +
|
|
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
|
-
|
|
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
|
-
|
|
3231
|
-
if (
|
|
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) {
|
|
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
|
-
|
|
3244
|
-
|
|
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
|
-
|
|
2570
|
+
S.stmt_finalize(stmt);
|
|
2571
|
+
BATCH_VALS_RELEASE();
|
|
3252
2572
|
rc = S.tx_commit(tx);
|
|
3253
|
-
if (rc != STOOLAP_OK) {
|
|
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 +
|
|
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
|
-
|
|
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
|
-
|
|
3288
|
-
|
|
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, ©, &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, "
|
|
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
|
|